Ember acceptance testing - async side effects error - ember.js

Attempting to run acceptance tests in Ember:
test('successful login', (assert) => {
Ember.run(() => {
visit('/signin');
fillIn('#email', 'validemail#server.com');
fillIn('#password', 'password');
click(':submit');
andThen(function() {
assert.equal(currentURL(), '/');
});
});
});
Occasionally (and seemingly randomly) yields the error:
"Global error: Error: Assertion Failed: You have turned on testing mode, which disabled the run-loop's autorun. You will need to wrap any code with asynchronous side-effects in a run..."
I was able to get a working version:
test('successful login', (assert) => {
const done = assert.async();
Ember.run(() => {
visit('/signin').then(() => {
fillIn('#email', 'isaac#silverorange.com').then(() => {
fillIn('#password', 'keen').then(() => {
click(':submit').then(() => {
assert.equal(currentURL(), '/');
done();
});
});
});
});
});
});
However, if I include a second test making use of the same route (for an unsuccessful login), one of them almost always ends up with the error listed above.
I am wondering what I am not understanding about the run-loop, Ember.run, and how to test with async behavior. Any help or pointers towards a good resource would be greatly appreciated!

According to the guide, your code should be like this:
test('successful login', (assert) => {
visit('/signin');
fillIn('#email', 'validemail#server.com');
fillIn('#password', 'password');
click(':submit');
andThen(function() {
assert.equal(currentURL(), '/');
});
});
You don't need to add an Ember.run to your cases.

Most commonly, this problem occurs when you're doing something (asynchronously) in your application that isn't properly wrapped for Ember (by wrapped I mean executed inside the Ember run loop).
Most common causes
You attached an event handler to the DOM, either directly or with jQuery without wrapping interaction with the Ember application in Ember.run()
You executed a XHR (asynchronous), either directly or with jQuery without wrapping interaction with the Ember application inside the callback in Ember.run()
Generic fix
When you cause code execution that interacts with your application outside the runloop (XHR callback or event handlers) wrap that code with Ember.run().
Events:
Ember.$('div').on('mouseover',function() {
Ember.run(function() {
// Interaction with application
});
});
XHR/Ajax:
Ember.$.ajax({
success: function() {
Ember.run(function() {
// Interaction with application
});
}
});
Best practices
When working with DOM events:
Use component event handling (https://guides.emberjs.com/v2.11.0/components/handling-events/)
Use template actions (https://guides.emberjs.com/v2.11.0/templates/actions/)
When you want to do AJAX/XHR use ember-ajax (https://github.com/ember-cli/ember-ajax)

Related

Ember 2.3 | Record is updated on save(), but Ember still says that it failed on dev ENV

Given the following code snippets, Ember.js keeps hitting the following alert everytime I press the update button on Edit.hbs while in development mode:
alert('failed to update user!')
However, what makes this issue different is that the record is persisted on the server side with a 200 (M'kay) reply, and when I am in production mode with my live website, Ember.js confirms that the user has been updated with the other alert()... Any thoughts on what could be the source of this issue?
I have seen quite a few examples around, but I am suspecting that most of them are no longer valid for the 2.0 ember versions...
Router
this.route('user', { path:'user/:user_id' }, function() {
this.route('edit');
});
Edit Controller
// Removed extra code that controls warning messages to model.user
// and comparison to model.users
update: function(user) {
// Extra Code that checks current_password and others omitted.
user.save().then(function() {
alert('User Updated!');
// transitionToRoute errors with undefined...
this.controller.transitionToRoute('user', user);
}, function() {
alert('failed to update user!');
});
}
Edit Route
export default Ember.Route.extend ({
model: function() {
return Ember.Object.create ({
user: this.modelFor('user'),
users: this.store.findAll('user')
});
}
});
Edit Template (Button Only)
<button class="button_type1" {{action "update" model.user}}>Update User</button>
Live Website
This will take you directly to User #2 edit page. At the moment you will have unrestricted access to the website as this will make it easier for bug hunting... Changing password is not working, but the other attributes should persist as discussed previously.
Source Code
EDIT #1
this.transitionToRoute('user', user); is now working as expected based on Deewendra's answer.
user.save().then(() => {
alert('User Updated!');
this.transitionToRoute('user', user);
}).catch(() => {
alert('User Not Updated!');
});
EDIT #2
By appending reason to the catch statement, I was able to narrow down the second issue that I was having:
user.save().then(() => {
// Not reaching
}).catch((reason) => {
console.log(reason); //Assertion Failed: normalizeResponse must return a valid JSON API document: * Top level of a JSON API document must be an object.
});
The server terminal kept telling me 200 OK, but the actual response was badly formatted on the Rails side. If you encounter a similar problem, then make the following changes on your Rails controller:
// Outputs Bad JSON
render json: user.update(user_params)
// Output proper JSON
user.update(user_params)
render json: user
The issue lies in the controller code. When you do user.save(), it returns a promise and for any function you call on it, the context will be the promise itself so in your then block 'this' points to the promise object and not the router instance so it cannot resolve this.controller and hence although it saves your model, the control goes to the catch/fail block. To solve this you can do :
1) bind context to the 'then' function
update: function(user) {
// Extra Code that checks current_password and others omitted.
user.save().then(function() {
alert('User Updated!');
// transitionToRoute errors with undefined...
this.controller.transitionToRoute('user', user);
//OR this.transitionTo('user', user);
}.bind(this), function() {
alert('failed to update user!');
});
}
2) store router instance in a variable outside 'then' so its accessible within the then block
update: function(user) {
// Extra Code that checks current_password and others omitted.
var context = this;
user.save().then(function() {
alert('User Updated!');
// transitionToRoute errors with undefined...
context.controller.transitionToRoute('user', user);
}, function() {
alert('failed to update user!');
});
}
3) Or use ES6 arrow function which will bind this context automagically. I have not tried these before, but it should work.
user.save().then(()=> {
alert('User Updated!');
this.controller.transitionToRoute('user', user);
}, () => {
alert('failed to update user!');
});
Hope this helps.

How to test a factory that has a method that returns a promise in Karma / Mocha?

I am new to unit testing angular code, and have been trying to figure this out. I am using Karma, Mocha and Chai. Asynchronous unit testing is supported in Mocha (Asynchronous code) via a mechanism that looks like this:
describe('User', function(){
describe('#save()', function(){
it('should save without error', function(done){
var user = new User('Luna');
user.save(done);
})
})
})
which I assume can be rewritten assuming savePromise is a promise implementation
describe('User', function(){
describe('#save()', function(){
it('should save without error', function(done){
var user = new User('Luna');
user.savePromise().then(function(){
done()
})
})
})
})
that would be cool, but inject from angular-mock doesn't push the done function into the "it" callback. I reviewed the code and even tried it (knowing it would fail). Essentially I believe I want something like this
describe('#save()', function(){
it('should save without error', inject(function (User, done) {
var user = new User('Luna');
user.savePromise().then(function(){
done()
})
})
})
How do I get angular-mock to work with Mocha?
Wait... is it as simple as performing your injections and before the test?
var myUser
beforeEach(inject(function (User) {
myUser = User
}))
describe('#save()', function(){
it('should save without error', function (done) {
var user = new myUser('Luna');
user.savePromise().then(function(){
done()
})
})
})
It still is not working in my case. I am trying to test a HTTP client which doesn't look to be making the calls. However the code looks like, if I make the change above, that it should work.

Handling network connectivity and promise rejections in Ember

I am trying to implement an application-level error handler to catch failed promises within my application. Most of the suggestions I've seen around this have to do with error logging, but I actually want to trigger a modal window to let my users know they're no longer connected to the internet + can only read data.
I'd like to trigger an action from within the RSVP.onerror handler. The only way I've managed to do this so far is with something like this:
Ember.RSVP.configure('onerror', function(e) {
window.App.__container__
.lookup('controller:application')
.send('showModal', 'disconnected');
});
but it seems a bit hacky to me.
Is there somewhere more appropriate to write this code? More generally, are there any best practices around notifying users of a lost internet connection?
Ember has a built in error route (docs here).
Depending on how you want to handle other kinds of errors in your app, you could add views/error.js and run a method on willInsertElement in that view that tests whether the user is offline and, if true, sends the showModal action.
Alternatively, failed promises can automatically be caught in the original promise and trigger some method that has been injected into the controller or whatever instance is making the promise:
Controller:
this.set('thing').then(function() {
// Success
}, this.offlineError()); // Failure
Initializer (note the syntax is for ember-cli):
export default {
name: 'offline-error',
initialize: function(container, app) {
var offlineError = function() {
// or something like this
container.lookup('controller:application').send('showModal', 'disconnected');
};
app.register('offlineError', offlineError, { instantiate: false });
// Inject into routes, controllers, and views.
Em.A(['route', 'controller', 'view']).forEach(function(place) {
app.inject(place, 'offlineError', 'offlineError:main');
});
}
};
The above code makes the offlineError method available in all routes, controllers, and views but the beauty of it is that the method has access to the container without using the hacky private property.
I ended up wrapping Offline.js in a service. I'm using ember-cli.
// services/connection.js
/* global Offline */
import Ember from 'ember';
// Configure Offline.js. In this case, we don't want to retry XHRs.
Offline.requests = false;
export default Ember.Object.extend({
setup: function() {
if (Offline.state === 'up') {
this._handleOnline();
} else {
this._handleOffline();
}
Offline.on('down', this._handleOffline, this);
Offline.on('up', this._handleOnline, this);
}.on('init'),
_handleOffline: function() {
this.set('isOffline', true);
this.set('isOnline', false);
},
_handleOnline: function() {
this.set('isOnline', true);
this.set('isOffline', false);
}
});
I injected the service into controllers and routes:
// initializers/connection.js
export default {
name: 'connection',
initialize: function(container, app) {
app.inject('controller', 'connection', 'service:connection');
app.inject('route', 'connection', 'service:connection');
}
};
Now in my templates, I can reference the service:
{{#if connection.isOffline}}
<span class="offline-status">
<span class="offline-status__indicator"></span>
Offline
</span>
{{/if}}
(Offline.js also has some packaged themes, but I went with something custom here).
Also, in my promise rejection handlers I can check if the app is offline, or if it was another unknown error with the back-end, and respond appropriately.
If anyone has any suggestions on this solution, chime in!

How to defer / advance in ember 1.3.1 for configuration based integration testing?

I have a basic QUnit integration test with ember.js 1.3.1
test("try to get html", function() {
App.Bootstrap.init();
visit("/").then(function() {
//do some basic asserts here
});
});
The trick is that my App.Bootstrap.init does some configuration setup (before the app boots).
App.Bootstrap = Ember.Object.create({
init: function() {
App.deferReadiness();
var configuration = ajaxPromise("/imaging_ui/configuration/", "GET");
Ember.run.next(function() {
Ember.RSVP.all([configuration]).then(function(result) {
//do something w/ the result like setup my configuration globally
App.advanceReadiness();
});
});
}
});
The problem is that since "lazy routing" was applied I can't defer /advance myself anymore because I get the error
"You cannot defer readiness since the ready() hook has already been
called"
Here is what my test helper does (basic ember-testing stuff here)
App.setupForTesting();
App.injectTestHelpers();
Note -this works fine in production as the defer / advance work like they would normally (and this init method is invoked inside the following initializer). The below is monkey patched in my test as it runs before I have the chance to mock any xhrs (and because this usually fires an xhr I decided to monkey patch and invoke the init myself before each test run).
App.initializer({
name: 'bootstrap',
initialize: function() {
App.Bootstrap.init();
}
});
After this PR, the visit helper will call App.advanceReadiness(); in the first call.
Make sure that you don't have an advanceReadiness in your test setup, like the following:
module('my module', {
setup: function() {
//...
// App.advanceReadiness(); can be removed
}
});
Otherwise you'll receive that error.

Getting timeout error when testing ajax behavior within ember component with sinon and mocha

I am trying to test an ember component with mocha and sinon. I wanted to test one of the actions of the component which makes an ajax call by using sinon's "useFakeXMLHttpRequest". But this test is causing time-out error. I am using mocha test adapter for ember taken from https://github.com/teddyzeenny/ember-mocha-adapter, I couldn't find the js file in cloud so I have pasted in whole code - so it might look bit messy in the jsbin.
Here is a jsbin link to the issue : http://jsbin.com/usajOhE/1/
The code for the component is :
AS.QuestionViewComponent = Ember.Component.extend({
templateName: "components/question-view",
actions: {
makeAjaxCall: function() {
jQuery.ajax({
url: "/todo/items",
success: function(data) {
//callback(null, data);
}
});
}
}
});
The handle bar associated with the component is :
<a {{action "makeAjaxCall"}} class="test-link">Make ajax call</a>
And my test script is:
describe("Testing", function() {
var xhr, requests;
before(function() {
xhr = sinon.useFakeXMLHttpRequest();
requests = [];
xhr.onCreate = function(req) {
requests.push(req);
};
});
after(function() {
xhr.restore();
});
beforeEach(function() {
AS.reset();
visit("/");
});
it("shoud make ajax call", function() {
//TIMESOUT HERE
click($("a.test-link:first")).then(function() {
console.log(requests);
expect(requests.length).to.be(1);
});
});
});
Your help will be much appreciated. Thanks
Most likely it is because you have not responded to the fake ajax request. The ember-testing package counts the pending ajax requests made by jQuery (see pendingAjaxRequests here). If this stays at 1, the ember-testing wait() helper never resolves.
The ember-testing package increments this counter via ajaxStart and ajaxStop filters.
To clarify what's happening here: When you use the click() helper, this sends a click message to the element, and then defers to the wait() helper (a promise). The same applies for other helpers such as fillIn(), keyEvent() etc. You can see from the comments in the source for wait() that it will not progress on with the rest of your specs:
// 1. If the router is loading
// 2. *If there are pending Ajax requests
// 3. If there are scheduled timers or we are inside of a run loop
The fix:
Unfortunately, if you never make it to the then block of your test, you cannot fake a response via requests[0].respond(...).
Instead, I've solved this by using sinon's fake server:
var server;
beforeEach(function () {
server = sinon.fakeServer.create();
server.autoRespond = true;
server.autoRespondAfter = 1; // ms
App.reset();
});
afterEach(function () {
server.restore();
});
it("should make ajax call", function() {
// set up the fake response
server.responses[0].response = [200, { "Content-Type": "application/json" }, '{ "todos": [] }'];
visit('/')
.click($("a.test-link:first"))
.then(function() {
// should make it to here now
});
});
This pattern works fine when you are expecting a single, or a deterministic order of ajax requests going into your fake server. If you expect lots of requests (with different paths), you can use server.respondWith([regex], ...) to match certain urls to specific responses.
Another thing to note is that it's generally good practice to put the success part of your ajax call into an Ember.run:
jQuery.ajax({
url: "/todo/items",
success: function(data) {
Ember.run(function () {
//callback(null, data);
})
}
});