Howdy,
I wrote a blog post about how to add a custom waiter in a component that uses an async DOM function to be able to test it properly.
Here is the component:
import Component from '@glimmer/component';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';
export default class AvatarComponent extends Component {
@tracked isShowingInitials = false;
get initials() {
const [first, ...rest] = this.args.name.split(/\s+/);
const last = rest.pop();
return [first, last].map((name) => name[0]).join('');
}
@action
showInitials() {
this.isShowingInitials = true;
}
}
And its template:
<div class="avatar">
{{#if this.isShowingInitials}}
<div class="initials">{{this.initials}}</div>
{{else}}
<img
src={{@url}}
onError={{this.showInitials}}
alt={{concat @name "'s avatar"}}
/>
{{/if}}
<div class="name">{{@name}}</div>
</div>
Here is the test:
module('Integration | Component | avatar', function (hooks) {
setupRenderingTest(hooks);
test('It falls back to initials when the image cannot be loaded', async function (assert) {
await render(
hbs`<Avatar @name="Marquis de Carabas" @url="/images/non-existent.webp" />`,
);
assert.dom('.initials').hasText('MC');
});
});
If the waiter is not there, Ember doesn’t wait for the onError
callback to finish before moving on to the assertion in the test, and thus the test fails. With the waiter properly set up, it works great.
However, I also noticed that if there is no custom waiter, but I add an await settled()
after the await render()
line in the test, the test seems to pass reliably:
import { module, test } from 'qunit';
import { setupRenderingTest } from 'image-onerror/tests/helpers';
import { render, settled } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
module('Integration | Component | avatar', function (hooks) {
setupRenderingTest(hooks);
test('It falls back to initials when the image cannot be loaded', async function (assert) {
await render(
hbs`<Avatar @name="Marquis de Carabas" @url="/images/non-existent.webp" />`,
);
await settled();
assert.dom('.initials').hasText('MC');
});
});
And this is what I’m quite baffled about. Adding the call to settled
shouldn’t matter at all, it even prompts a linting error that says that I shouldn’t add a settled when the helper (the render
) has it as its return value and yet with this addition the test seems to always pass.
So why does settled work here?
Is it that calling settled
takes a minuscule amount of time but enough for the onError
callback to run? Is it something else?
Thank you!