I have a problem calling a function outside action functions from an actions function. As you can see from the code below, I have a selectClient action that calls two functions, createCompanyAccount and createPrivateAccount. But I always get a this.createPrivateAccount is undefined. I have tried using self, but to no avail. Weirdly, I thought I would have to use self.createCompanyAccount, but then I get a self.createCompanyAccount is not defined.
I use Ember 2.12 and Ember Data 2.16.3.
import Ember from 'ember';
export default Ember.Component.extend({
store: Ember.inject.service(),
tagName: '',
/**
* Actions
*/
actions: {
// Select from selectList
selectClient(element) {
let self = this;
if (element.company) {
this.get('store').query('account', { 'filter' : {'orgnumber': element.orgNumber}}).then(
(accounts) => {
/* Organisation exist already */
},
(error) => {
let code = Number(error.errors[0].status);
if (code === 404) {
// company does not exist, so lets create it, and an account.
this.createCompanyAccount(element).then(
(account) => {
/* Do stuff... */
}
);
}
}
);
} else {
this.createPrivateAccount(element).then(
(anonUser) => {
/* Do stuff... */
}
);
}
}
},
createCompanyAccount(company) {
let account = this.get('store').createRecord('account', {
type: 'company',
});
// Check if postal address is set on result
if (typeof company.addressObject !== 'undefined') {
let postAddress = this.get('store').createRecord('address', {
address: company.addressObject.streetName,
zip: company.addressObject.zip,
postal_address: company.addressObject.postalAddress
});
account.get('addresses').pushObject(postAddress);
}
this.get('store').createRecord('company', {
name: company.name,
org_number: Number(company.orgNumber),
account: account
}).save().then((new_company) => {
return new_company.get('account');
});
},
createPrivateAccount(person) {
let account = this.get('store').createRecord('account', {
type: 'anonuser'
});
// Check if postal address is set on result
if (typeof person.addressObject !== 'undefined') {
let postAddress = this.get('store').createRecord('address', {
address: person.addressObject.streetName,
zip: person.addressObject.zip,
postal_address: person.addressObject.postalAddress
});
account.get('addresses').pushObject(postAddress);
}
this.get('store').createRecord('anonUser', {
first_name: person.firstName,
sur_name: person.surName,
email: person.email,
phone: person.phone,
account: account,
}).save().then((new_person) => {
return new_person.get('account');
});
}
});
Can anyone see where I go wrong? I can note that there is a few other functions that I have removed for clarity.
Thank you,
Tommy
Your issue is not about this.createPrivateAccount and this.createCompanyAccount being undefined. I think both of them are being executed but they do return undefined but you are expecting a Promise. Therefore this.createPrivateAccount().then is undefined.
Related
I'm writing my first question here sorry for any ambiguity.
I write an integration test for update-pw component which simple render update-pw and then fill input field with fillIn and then click save button which trigger the action savePW in update-pw.js. I only pass email(for whom we want to change password) and new password.
savePW() function further has a function call self.store.updateSingleUserPw(email, newPw) which is written in service store.js.
updateSingleUserPw(email, newPw) returns a promise after server process on API call. On basis of fulfillment or rejection of promise I show a modal.
I just want to make that promise fulfill or rejected in my test instead of server response for promise.
// integration/component/update-pw-test.js
import { module, test } from 'qunit';
import EmberObject from '#ember/object';
import { setupRenderingTest } from 'ember-qunit';
import { render, fillIn, click } from '#ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import Service from '#ember/service';
module('Integration | Component | update-pw', function(hooks) {
setupRenderingTest(hooks);
const store = Service.extend({
savePW() {
self.store.updateSingleUserPw(email, newPw, function() {
console.log('this is function overriding', email, newPw);
return true;
})
.then(function() {
// Reset controller fields
self.set('password', '');
self.set('updateModal', false);
swal({
title: 'Das hat geklappt',
type: 'success'
});
}, function() {
self.set('updateModal', false);
swal({
title: 'problems with setting new pw.',
type: 'error'
});
})
.finally(function() {
self.set('changingPassword', false);
});
}
});
test('it renders', async function(assert) {
this.application.register('service:store', store);
this.application.inject.service('store', { as: 'store' });
assert.expect(2);
this.set('updateModal', true);
this.set('testing', true);
let currentUpdateAdmin = EmberObject.create({
username: 'steinauer',
email: 'lala#test.at'
});
this.set('currentUpdateAdmin', currentUpdateAdmin);
await render(hbs`{{update-pw updateModal=updateModal currentUpdateAdmin=currentUpdateAdmin testing=testing store=store}}`);
assert.equal(this.element.querySelector('h4').textContent.trim(), 'set new PW for steinauer');
await fillIn('#password', 'test123456');
await click('.save-button');
// Template block usage:
await render(hbs`
{{#update-pw}}
template block text
{{/update-pw}}
`);
// assert.equal(this.element.textContent.trim(), 'what is this');
});
});
// components/update-pw.js
import Component from '#ember/component';
export default Component.extend({
changingPassword: false,
actions: {
savePW() {
let self = this;
if (!self.get('currentUpdateAdmin.email'))
return;
let newPw = self.get('password');
let email = self.get('currentUpdateAdmin.email');
self.set('changingPassword', true);
if (!email)
return;
self.store.updateSingleUserPw(email, newPw)
.then(function() {
// Reset controller fields
self.set('password', '');
self.set('updateModal', false);
swal({
title: 'Das hat geklappt',
type: 'success'
});
}, function() {
self.set('updateModal', false);
swal({
title: 'problems with setting new pw',
type: 'error'
});
})
.finally(function() {
self.set('changingPassword', false);
});
}
}
});
function in Service/store.js :
updateSingleUserPw(email, newPw) {
let headers = this.get('headers');
return new Promise(function(resolve, reject) {
$.ajax({
type: 'POST',
url: ENV.api + '/accounts/updateSingleUserPw',
data: {
email: email,
pwNew: newPw
},
headers,
dataType: 'json'
}).then(function(success) {
if (success) {
resolve(newPw);
} else {
reject('password change failed');
}
}, function(xhr, status, error) {
reject(error);
});
});
}
Before trying to override function I got only rejected promise modal but after the try of overriding the function i'm getting:
Promise rejected during "it renders": Cannot read property register of undefined.
thanks for your question 🎉
Firstly can I thank you for providing your code samples, I would not have been able to solve your question had you not provided so much! I have actually simplified some of the things that you are trying to do and I think by simplifying things I have come to the solution.
Firstly I have renamed the Service that you keep using to be called password-store. Usually when an Ember developer sees a Service named store they tend to think of an ember-data store which I'm assuming you're not actually using here by the functionality that you are expecting.
I generated a very simple mock store that just had one function in it:
// app/services/password-store.js
import Service from '#ember/service';
export default Service.extend({
updateSingleUserPw(email, password) {
// TODO: do something with email & password
return Promise.resolve();
}
});
This just returns a promise so that it won't break any of the other code samples. I then updated your update-pw component to use the new password store:
// app/components/update-pw.js
import Component from '#ember/component';
import { inject as service } from '#ember/service';
function swal() {
// noop - not sure where this comes from
}
export default Component.extend({
passwordStore: service(),
changingPassword: false,
actions: {
savePW() {
if (!this.get('currentUpdateAdmin.email'))
return;
let newPw = this.get('password');
let email = this.get('currentUpdateAdmin.email');
this.set('changingPassword', true);
if (!email)
return;
this.passwordStore.updateSingleUserPw(email, newPw)
.then(() => {
// Reset controller fields
this.set('password', '');
this.set('updateModal', false);
swal({
title: 'Das hat geklappt',
type: 'success'
});
}, () => {
this.set('updateModal', false);
swal({
title: 'problems with setting new pw',
type: 'error'
});
})
.finally(() => {
this.set('changingPassword', false);
});
}
}
});
I also added a swal() function because I didn't quite know where that came from in your example. It seemed to be missing so I just ignored it.
Now lastly I have setup a template so that the test will actually pass:
// app/templates/components/update-pw.hbs
<h4>set new PW for steinauer</h4>
{{input id="password" value=password}}
<button type="button" name="button" class="save-button" {{action 'savePW'}}></button>
Now with the application fully setup here is the full example of a test that will do exactly what you were hoping to do:
// tests/integration/components/update-pw-test.js
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, fillIn, click } from '#ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import StoreService from 'your-app-name/services/password-store';
module('Integration | Component | update-pw', function(hooks) {
setupRenderingTest(hooks);
test('it renders', async function(assert) {
const passwordStore = StoreService.extend({
updateSingleUserPw(email, newPw) {
console.log('updateSingleUserPw override!!');
assert.equal(newPw, 'test123456');
return Promise.resolve();
}
});
this.owner.register('service:password-store', passwordStore);
assert.expect(2);
this.set('updateModal', true);
this.set('testing', true);
let currentUpdateAdmin = {
username: 'steinauer',
email: 'lala#test.at'
};
this.set('currentUpdateAdmin', currentUpdateAdmin);
await render(hbs`{{update-pw updateModal=updateModal currentUpdateAdmin=currentUpdateAdmin testing=testing store=store}}`);
assert.equal(this.element.querySelector('h4').textContent.trim(), 'set new PW for steinauer');
await fillIn('#password', 'test123456');
await click('.save-button');
// Template block usage:
await render(hbs`
{{#update-pw}}
template block text
{{/update-pw}}
`);
});
});
The first thing that you might notice is that we are not using this.application.register or this.application.inject. I can't remember exactly if this is how it used to be done a long time ago but this is not available for a few years in Ember.
What we end up doing is we import the StoreService from your-app-name/services/password-store (replacing your-app-name with whatever your modulePrefix is) and then we extend it while overriding the updateSingleUserPw() function. In your example it looked like you were trying to override a function called savePW() but that is actually the action name from the component and it might have been slightly confusing you.
I hope that helps, I have tested the example locally and it works perfectly well! You may also notice I added an assertion inside the service, this is quite a useful pattern to make sure that the service receives the right arguments from the component 👍
I'm having some sorting issues inside one of my components and cannot seem to figure it out. Currently it seems to be sorting correctly, but it's putting what should be the 2nd one sorted, at the bottom. Here is my component, hoping someone could give some insight here...Thanks.
import Component from '#ember/component';
import { inject as service } from '#ember/service';
import EmberObject, { computed, observer } from '#ember/object';
export default Component.extend({
googleMapsApi: service(),
geolocation: service(),
sortDefinition: ['distanceTo'],
sortedVineyards: Ember.computed.sort('model', 'sortDefinition'),
didInsertElement() {
this.send('distanceFrom');
},
actions: {
distanceFrom(){
let distanceFromLoading = this.get('distanceFromLoading');
let userLocation = this.get('userLocation');
var userLocationLat = userLocation[0];
var userLocationLon = userLocation[1];
let userLocationFormat = '' + userLocationLat + ',' + userLocationLon;
// console.log(userLocationFormat);
var self = this;
let model = this.get('model');
// console.log(model);
model.forEach(function(item) {
let endLocation = '' + item.get('location');
self._super(...arguments);
self.get('googleMapsApi.google').then((google) => {
var self = this;
let distanceMatrixService = new google.maps.DistanceMatrixService();
function calculateDistance() {
distanceMatrixService.getDistanceMatrix({
origins: [userLocationFormat],
destinations: [endLocation],
travelMode: google.maps.TravelMode.DRIVING,
unitSystem: google.maps.UnitSystem.IMPERIAL,
avoidHighways: false,
avoidTolls: false
}, callback);
}
function callback(response, status) {
if (status != google.maps.DistanceMatrixStatus.OK) {
} else {
if (response.rows[0].elements[0].status === "ZERO_RESULTS") {
} else {
var distance = response.rows[0].elements[0].distance;
var distance_text = distance.text;
item.set('distanceTo', distance_text);
}
}
}
calculateDistance();
});
});
}
}
});
Turns out in my example, distance_text (the sort definition) was a string. Given my small data set it looked like it was half sorting, when likely it wasn't sorting at all. Turned that number into a proper integer and everything worked nicely.
Although I have been writing Angular 2 for a while now, I am only just writing my first Jasmine tests and have run into a little difficulty. I am trying to test that the CanActivate method of service implementing CanActivate is behaving itself, and is returning true or false as expected.
My method looks like this:
canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot ): Observable<boolean> {
return this.store$
.map( ( store: StoreState ) => store.currentUser )
.first()
.map( ( user ) => {
if ( user.isAuthenticated ) {
return true;
}
// TODO: This needs refactoring. Need to provide RouterStateSnapshot in test,
// rather than ignoring it!
this.redirectUrl = state ? state.url : '';
this.injector.get( Router ).navigate( ['/login'] );
return false;
} );
}
An extract of my test looks like this:
service = TestBed.get( AuthGuardService );
it( 'should prevent navigation', () => {
service.canActivate(null, null).subscribe((res) => expect( res ).toBeTruthy() );
} );
How do I mock/stub/whatever the second parameter of my call to service.canActivate, rather than simply passing in null?
describe('AuthGuard', () => {
let mockSnapshot: RouterStateSnapshot;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
// so we can get the Router injected
RouterTestingModule,
// other imports as needed
],
// usual config here
});
// create a jasmine spy object, of the required type
// toString is because we have to mock at least one method
mockSnapshot = createSpyObj<RouterStateSnapshot>('RouterStateSnapshot', ['toString']);
});
it('should prevent non-authenticated access',
async(inject([AuthGuard, AuthService, Router], (guard: AuthGuard, auth: AuthService, router: Router) => {
// ensure we're logged out
auth.logout();
// set the url on our mock snapshot
mockSnapshot.url = '/protected';
// so we can spy on what's been called on the router object navigate method
spyOn(router, 'navigate');
expect(guard.canActivate(null, mockSnapshot)).toBeFalsy();
// check that our guard re-directed the user to another url
expect(router.navigate).toHaveBeenCalled();
})));
});
})
Here is my solution which I used for unit testing of Custom Router State Serializer
custom-serializer.ts
import { RouterStateSerializer } from '#ngrx/router-store';
import { RouterStateSnapshot, Params } from '#angular/router';
/**
* The RouterStateSerializer takes the current RouterStateSnapshot
* and returns any pertinent information needed. The snapshot contains
* all information about the state of the router at the given point in time.
* The entire snapshot is complex and not always needed. In this case, you only
* need the URL and query parameters from the snapshot in the store. Other items could be
* returned such as route parameters and static route data.
*/
export interface RouterStateUrl {
url: string;
params: Params;
queryParams: Params;
}
export class CustomRouterStateSerializer
implements RouterStateSerializer<RouterStateUrl> {
serialize(routerState: RouterStateSnapshot): RouterStateUrl {
let route = routerState.root;
while (route.firstChild) {
route = route.firstChild;
}
const { url, root: { queryParams } } = routerState;
const { params } = route;
// Only return an object including the URL, params and query params
// instead of the entire snapshot
return { url, params, queryParams };
}
}
custom-serializer.spec.ts
import { CustomRouterStateSerializer } from './utils';
import { RouterStateSnapshot } from '#angular/router';
describe('Utils CustomRouterStateSerializer', () => {
let mockSnapshot: RouterStateSnapshot;
let serializer: CustomRouterStateSerializer;
let mockSnapshotProxy;
beforeEach(() => {
mockSnapshot = jasmine.createSpyObj<RouterStateSnapshot>('RouterStateSnapshot', ['toString']);
serializer = new CustomRouterStateSerializer();
});
it('should serialize RouterStateSnapshot to subset of params', () => {
mockSnapshotProxy = new Proxy(mockSnapshot, {
get(target, prop) {
if (prop === 'root') {
return {
params: {
id: 100
},
queryParams: {
name: 'John'
}
};
} else if (prop === 'url') {
return '/orders';
}
},
});
const result = serializer.serialize(mockSnapshotProxy);
expect(result.url).toBe('/orders');
expect(result.params.id).toBe(100);
expect(result.queryParams.name).toBe('John');
});
});
I used jasmine.createSpyObj to create object with proper type and Proxy to pass in required properties
I keep getting the error "The specified email address is invalid" in my Ember-Firebase app, even if I created an account just to test log in.
I'm wondering if it has to do with the way I'm passing information as a string?
initializer/emberfire.js
import Ember from 'ember';
import Firebase from 'firebase';
var session = Ember.Object.extend({
ref : new Firebase("https://nutella.firebaseio.com"),
addFirebaseCallback: function() {
var session = this;
var isNewUser = true;
this.get("ref").onAuth(function(authData) {
if (authData) {
session.set("isAuthenticated", true);
} else if (authData && isNewUser) {
session.get("ref").child("users").child(authData.uid).set({
provider: authData.provider,
name: getName(authData)
});
} else {
session.set("isAuthenticated", false);
}
});
}.on("init"),
createUser: function() {
var session = this;
return new Ember.RSVP.Promise(function(resolve, reject) {
session.get('ref').createUser({
name: "",
email: "",
password: ""
},
function(error, userData) {
if (userData) {
resolve(userData.uid);
session.set("isNewUser", true);
} else {
reject(error);
}
});
});
},
application.js
import Ember from 'ember';
import Firebase from 'firebase';
var ref = new Firebase("https://nutella.firebaseio.com");
export default Ember.Route.extend({
actions: {
createUser: function() {
var controller = this;
controller.get('session').createUser().then(function(user) {
}, function() {
});
},
I'd really appreciate if you could point me in the right direction!
Passing a string shouldn't be a problem. Here are a couple suggestions:
(This may be obvious, but...) It seems like you're passing empty strings to createUser:
session.get('ref').createUser({
name: "",
email: "",
password: ""
},
Assuming that you removed the strings from the code on purpose, there is also the fact that you're passing Firebase.createUser() an extra argument, name.
In the .createUser() documentation, the arguments are listed as:
credentials Object
An object containing email and password attributes corresponding to the new user account.
onComplete Function
A callback function that will be called when the user account has been created. On failure, the first argument will be an Error object indicating the failure, with a machine-readable code attribute. On success, the first argument will be null, and the second argument will be an object containing attributes of the newly-created user, including the uid.
So, here is a revised version of your call to createUser():
session.get('ref').createUser({
email: "some#user.com",
password: "somepassword123"
},
If that doesn't do it, can you provide more info - where (line number) are you getting that error?
Hope that helps.
Undoubtedly this error is something easy for an ember expert to identify but thats not me so here it is
Ember-cli identifies blank space before this line as an unexpected token:
this.store = container.lookup('store:main');
/*global md5*/
import Ember from 'ember';
// Since I've defined my url in environment.js I can do this
import ENV from '../config/environment';
var ref = new window.Firebase(ENV.firebaseURL);
export default {
name: 'session',
// Run the initializer after the store is ready
after: 'store',
initialize: function(container, app) {
// session object is nested here as we need access to the container to get the store
var session = Ember.Object.extend({
// initial state
authed: false,
// get access to the ember data store
//Here is the offending line
this.store = container.lookup('store:main');
init: function() {
// on init try to login
ref.onAuth(function(authData) {
// Not authenticated
if (!authData) {
this.set('authed', false);
this.set('authData', null);
this.set('user', null);
return false;
}
// Authenticated
this.set('authed', true);
this.set('authData', authData);
this.afterAuthentication(authData.uid);
}.bind(this));
},
// Call this from your Ember templates
login: function(provider) {
this._loginWithPopup(provider);
},
// Call this from your Ember templates
logout: function() {
ref.unauth();
},
// Default login method
_loginWithPopup: function(provider) {
var _this = this;
// Ember.debug('logging in with popup');
ref.authWithOAuthPopup(provider, function(error, authData) {
if (error) {
if (error.code === "TRANSPORT_UNAVAILABLE") {
// fall-back to browser redirects, and pick up the session
// automatically when we come back to the origin page
_this._loginWithRedirect(provider);
}
} else if (authData) {
// we're good!
// this will automatically call the on ref.onAuth method inside init()
}
});
},
// Alternative login with redirect (needed for Chrome on iOS)
_loginWithRedirect: function(provider) {
ref.authWithOAuthRedirect(provider, function(error, authData) {
if (error) {
} else if (authData) {
// we're good!
// this will automatically call the on ref.onAuth method inside init()
}
});
},
// Runs after authentication
// It either sets a new or already exisiting user
afterAuthentication: function(userId) {
var _this = this;
// See if the user exists using native Firebase because of EmberFire problem with "id already in use"
ref.child('users').child(userId).once('value', function(snapshot) {
var exists = (snapshot.val() !== null);
userExistsCallback(userId, exists);
});
// Do the right thing depending on whether the user exists
function userExistsCallback(userId, exists) {
if (exists) {
_this.existingUser(userId);
} else {
_this.createUser(userId);
}
}
},
// Existing user
existingUser: function(userId) {
this.store.find('user', userId).then(function(user) {
_this.set('user', user);
}.bind(this));
},
// Create a new user
createUser: function(userId) {
var _this = this;
this.get('store').createRecord('user', {
id: userId,
provider: this.get('authData.provider'),
name: this.get('authData.facebook.displayName') || this.get('authData.google.displayName'),
email: this.get('authData.facebook.email') || this.get('authData.google.email'),
created: new Date().getTime()
}).save().then(function(user){
// Proceed with the newly create user
_this.set('user', user);
});
},
// This is the last step in a successful authentication
// Set the user (either new or existing)
afterUser: function(user) {
this.set('user', user);
}
});
// Register and inject the 'session' initializer into all controllers and routes
app.register('session:main', session);
app.inject('route', 'session', 'session:main');
app.inject('controller', 'session', 'session:main');
}
};
You're calling Ember.Object.extend with an Javascript Object literal what you are trying to do is invalid javascript syntax.
You'll probably want to stick that line in your init function.
init: function() {
//Here is the offending line
this.store = container.lookup('store:main');
...
When you get an invalid token error message you're writing something the javascript compiler doesn't understand.