Mostly documenting this for future travelers…here goes.
So in this commit:
The internal ember-glimmer package changed from using Ember.run
to using Ember.run.join
. If you aren’t familiar with the difference the former creates a nested “runloop” and the latter will either join an existing “runloop” or if one is not available will create one.
99% of the time when people use Ember.run
they could be better served by using Ember.run.join
. This is because when you call Ember.run
you ALWAYS nest a whole runloop which carries with it a lot of unnecessary book keeping and work.
When I updated my project from 3.1 to 3.2 this change broke 9 tests. Just to be clear; each of these tests were incorrectly written. But the general gist was something like this (pay specific note the when you click .find-by-something
it triggers a closure action):
this.render(hbs`
{{something}}
`);
await $('.find-by-something').click();
assert.ok(/* something happens here as a result of a call to an action */);
The solution is to rewrite like this:
this.render(hbs`
{{something}}
`);
$('.find-by-something').click();
await settled().then(function() {
assert.ok(/* something happens here as a result of a call to an action */);
});
First of all, the first example likely never should have worked. But let’s talk about what is happening.
When you await
a jQuery click you aren’t really doing anything that Ember knows or cares about because $(...).click()
doesn’t return a promise. So don’t think too hard about that line.
It does however trigger a runloop, since Ember wraps events in a runloop to handle side effects, and to be generally helpful. Previously, before the commit linked above, this would create a runloop and then the action would trigger which would create a nested runloop.
The nested runloop would completely flush before the outer or parent runloop (instantiated by Ember’s click handler). Which means you’d end up with a guarantee that Ember’s action and all of it’s associated side effects would be handled before the click runloop (the outermost runloop) finished. Which sorta simulates synchronous behavior.
This is BAD. Because it means that we are blocking rendering while those actions fire.
So the commit fixes this by scheduling the action into the current runloop with join
.
A simplified example of the difference:
This really would have been a lot easier with the new test helpers. Since helpers will handle the settling of runloops out of the box. See more details here about that: Testing Components - Testing - Ember Guides
Thanks to @rwjblue and @kpfefferle for helping me talk through what was happening in these cases.