calling set on destroyed object setInterval in acceptance tests - ember.js

I know that there are like 5 topics with the same name here, but they seems to be unrelated to my case: I'm getting this error in acceptance tests, and the reason for that is the countdown component built with Ember.run.later.
Maybe someone know how to cure that?

Just should stop timer upon element destruction. Ember, of course, doesn't handle that by himself.
startCountdown: function() {
let handler;
//...
handler = setInterval(...);
this.set('handler', handler);
}
teardown: function() {
clearInterval(this.get('handler'));
}.on('willDestroyElement')

Related

Ember concurrency timeout hanging in qunit

In Ember I have a component that starts a never-ending poll to keep some data up to date. Like so:
export default Component.extend({
pollTask: task(function * () {
while(true) {
yield timeout(this.get('pollRate'));
this.fetchSomeData();
}
}).on('init')
})
This causes a preexisting acceptance test to get stuck in this task and run forever, even though it should be run asynchronously. The test looks like this:
test('my test', async function(assert) {
mockFindRecord(make('fetched-model'));
await visit('/mypage'); // gets stuck here
await typeIn('input', 'abc123');
assert.ok(somethingAboutThePage);
});
I thought at first that I had mocked the request wrong and that the test was just timing out, but it was in fact correctly polling data. Removing this task makes the acceptance test finish normally.
Testing this manually seems to work fine, and nothing gets stuck. Why is this happening and what is the right way to address this?
Saw Unit testing ember-concurrency tasks and yields but it doesn't really help since it only deals with unit tests.
You're not doing anything wrong and this is a common gotcha with ember-concurrency. Ember-concurrency's timeout() function relies on Ember.run.later() to create the timeout and fortunately or unfortunately, Ember's test suite is aware of all timers created with Ember.run.later() and will wait for all timers to settle before letting the test continue. Since your code is using an infinite loop your timers will never settle so the test hangs. There's a nice little section about testing asynchronous code in the Ember guides here.
There's a section in the ember-concurrency docs about this exact problem here. I recommend you look through it to see their recommendations on how to tackle this although it seems as if there's no real elegant solution at the time.
The quickest and probably easiest way to get this to not hang would be to throw in a check to see if the app is being tested (gross, I know):
pollTask: task(function * () {
while(true) {
yield timeout(this.get('pollRate'));
this.fetchSomeData();
if (Ember.testing) return; // stop polling to prevent tests from hanging
}
}).on('init')
You can also try to throw in a call to Ember.run.cancelTimers() in your tests/helpers/start-app.js file (another suggestion in that section):
window._breakTimerLoopsId = Ember.run.later(() => {
Ember.run.cancelTimers();
}, 500);
But it doesn't seem to appear in the API docs so I personally wouldn't rely on it.

What is the proper way in ember to use scheduleOnce that is test friendly?

Often, we'll need to schedule an update after rendering completes in reaction to a property change. Here is an example:
page_changed: function() {
Ember.run.scheduleOnce('afterRender', this, this.scroll);
}.observes('current_page')
This doesn't work for testing since there is no runloop at the time scheduleOnce is called. We can simply wrap scheduleOnce in an Ember.run
page_changed: function() {
Ember.run(function() {
Ember.run.scheduleOnce('afterRender', that, that.scroll);
});
).observes('current_page')
..but I'm being told that's not the right way to go about it. I thought I'd reach out and get some other ideas.
For reference here is the issue that I opened up in ember.js#10536
Looks like this is the way to do it according to #StefanPenner's comment. Instead of modifying the app code itself just wrap the render call with an Ember.run
test('it renders', function() {
expect(2);
var component = this.subject();
var that = this;
equal(component._state, 'preRender');
// Wrapping render in Ember.run
Ember.run(function() {
that.render();
});
equal(component._state, 'inDOM');
});
the entry point into your application in the test needs the Ember.run. For example
test('foo', function() {
user.set('foo', 1); // may have side-affects
});
test('foo', function() {
Ember.run(function() {
user.set('foo', 1);
});
});
Why is this needed? Ember.run wraps the root of all call-stacks that are triggered by click/user-actions/ajax etc. Why? The run-loop is what allows ember to batch
When writing unit-tests, we are "faking" the user or network actions, it isn't obvious to ember what groups of changes you want to "Batch" together.
We can think of Ember.run as a way to create a batch or transactions of changes. Ultimately we use this, to batch DOM reads/writes to interact with the DOM in an ideal way.
Although maybe frustrating until you get the hang of it, it is a great way to create concise and deterministic tests. For example:
test('a series of grouped things', function() {
Ember.run(function() {
component.set('firstName', 'Stef');
component.set('lastName', 'Penner');
// DOM isn't updated yet
});
// DOM is updated;
// assert the DOM is updated
Ember.run(function() {
component.set('firstName', 'Kris');
component.set('lastName', 'Selden');
// DOM isn't updated yet, (its still Stef Penner)
});
// DOM is once again updated, its now Kris Selden.
});
Why is this cool? Ember gives us fine-grained control, which lets us not only very easily test aspects of our app in isolation, but also test sequences of what may be user or ajax pushed based actions, without needing to incorporate those aspects.
As time goes on, we hope to improve the test helpers and clarity around this.
..but I'm being told that's not the right way to go about it.
The reason is that you need the Ember.run call when the callback is executed, not when it's scheduled. Like this:
Ember.run.scheduleOnce('afterRender', function() {
Ember.run(function() {
that.scroll();
});
});
In your code, Ember.run is being called when the callback is scheduled. This is likely unnecessary as you're probably already in the run loop. My version ensures that when the callback is called, even if the run loop finished long ago, that another run loop is started and that.scroll() is called within the run loop. Does that makes sense?

Uncaught Error: Assertion Failed: calling set on destroyed object

working in ember-cli testing. After all tests passed it returns extra two test with errors.
Uncaught Error: Assertion Failed: calling set on destroyed object
Source : '../dist/assets/vendor.js:13269'
this is one unit test configuration
import Ember from "ember";
import { test,moduleFor } from 'ember-qunit';
import startApp from '../helpers/start-app';
var App;
module('An Integration test',{
setup:function(){
App=startApp();
},
teardown: function() {
Ember.run(App, 'destroy');
}
});
This is either because in the result of a promise or any other deferred code you do not check the destroy status of an object, or because you didn't teardown something that has been setup and interact with DOM events or anything external to the core of Ember.
I used to have this especially on some jQuery plugins which I mapped to Ember, and during the tests the plugins were destroying too slowly and I was then either not using a run loop, or not checking the destroyed status of the Ember object I was manipulating.
You can do so with:
if ( !(obj.get('isDestroyed') || obj.get('isDestroying')) ) {
// do your destroying code setting stuff
}
Also think about destroying any jQuery plugins that might have been initialised in the code of your views (anything setup in didInsertElement should be teardown in willDestroyElement for example).
Ok i struggled with similar thing. So basically when you have "this.set()" inside a promise, it might happen that the promise takes too long to resolve, and the user already clicked away from that page, in this case you are trying to set something, that is already destroyed. I found the simplest solution to be just a simple check in the beginning of the promise.
if (this.isDestroyed) {
return;
}
this.set('...');
...
Edit: alternatively you can use Ember.trySet.
The issue is related to a promise not completely resolving and another test getting run immediately after.
You should give Ember Concurrency a try.
import { task, timeout } from 'ember-concurrency';
myFunction: task(function * () {
// do somethinng
yield timeout(1000); // wait for x milliseconds
// do something else
}).drop(),
I had a similar issue in an integration test. To resolve, in the integration test, I waited before performing the next action.
import wait from 'ember-test-helpers/wait';
wait().then(() => {
// perform action (which previously used to cause an exception)
});

Catch app 'ready' event outside the app

I need to run some code after ember application got initialized. I don't want to invoke this code from App.ready to avoid tight coupling. It would be nice to have something like this:
App.on 'ready, -> console.log('do stuff')
But it won't work since Em.Application object is not subscribable and ready isn't really an event despite that docs said so
A simple way you can achieve this would be to extend your Application class with the Ember.Evented mixin:
App = Ember.Application.createWithMixins(Ember.Evented, {
ready: function() {
console.log('App ready');
this.trigger('appReady');
}
});
And you hook inside the ready event inside your app and trigger your custom event using this.trigger(...)
At this point you can use .on(...) to be notified when the event is triggered.
App.on('appReady', function() {
console.log('App already ready');
});
Example demo.
Hope it helps.
An other possibility may be to invoke your code from the resolve callback of the application.
App.then(function(app) {
console.log("App is resolved, so it's ready");
});
example stolen from #intuitivepixel ;) http://jsbin.com/AdOVala/66/edit
Edit/Note:
App.then() has been deprecated, see http://emberjs.com/deprecations/v1.x/#toc_code-then-code-on-ember-application:
As part of the Ember.DeferredMixin deprecation, using .then on an
Ember.Application instance itself has been deprecated.
You can use the ready hook or initializers to defer/advance readiness instead.

Handling several event listeners

Update: here's a fiddle of my problem. The tests pass once, and fail the next time:
http://jsfiddle.net/samselikoff/hhk6u/4/
The problem is departments has events.on("userSet:company"), so both variables respond to the event.
This is a general question about unit testing. In my app, a certain event is fired, and several other pieces of my app listen for this event. I'd like to unit test each piece separately, since they are performing different functions; but to do this, I have to fire off the event in each test.
This causes problems, since the first test must fire off the event, triggering the listeners in the other tests. How can I keep my tests atomic while still testing multiple event listeners?
(I am using QUnit, but I think this is a more general unit-testing question).
Answer:
Jeferson is correct. One easy way to solve this, is to use events.once instead of events.on. This way you clean up your events from each test.
All your calls to async methods should be tested using "asyncTest" methods and making sure you wrap your calls in other functions that calls QUnit.start() when the assertions data are ready to be collected and analyzed.
I updated your JSFiddle with working code: http://jsfiddle.net/hhk6u/8/
The new code is:
QUnit.config.autostart = false;
QUnit.config.testTimeOut = 1000;
asyncTest('Some test that needs companies.', function() {
function getCompanies() {
var companies = new Companies();
ok(1);
start();
}
setTimeout(getCompanies, 500);
});
asyncTest('Some other async test that triggers a listener in companies.', function() {
var companies = new Companies();
events.trigger("userSet:company", { name: "Acme", id: 1 });
stop();
events.on('fetched:departments', function(response) {
console.log(response);
deepEqual(response, [1, 2, 3]);
start();
});
});
See my answer in this other question for more details:
Test fails then succeeds
Hope this helps you!