Skip to content

Commit

Permalink
feat: add onLoad, didLoad, onError
Browse files Browse the repository at this point in the history
Closes #93.
  • Loading branch information
buschtoens committed Feb 22, 2020
1 parent 606f165 commit 608ca4e
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 32 deletions.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,21 @@ the engine, nothing is rendered.
{{lazy-mount engineName model=optionalDataForTheEngine}}
```

You can also pass three optional hooks:

```hbs
{{lazy-mount
this.engineName
onLoad=this.onLoad
didLoad=this.didLoad
onError=this.onError
}}
```

- **`onLoad()`** — Called when the engine starts loading
- **`didLoad()`** — Called when the engine loaded successfully
- **`onError(error: Error)`** — Called when the engine failed to load

#### Block Usage

While the engine is loading or if there was an error loading the engine, the
Expand All @@ -65,3 +80,5 @@ engine.
{{/if}}
{{/lazy-mount}}
```

You can also pass in the same hooks: `onLoad`, `didLoad`, `onError`
30 changes: 30 additions & 0 deletions addon/components/lazy-mount/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,33 @@ export default Component.extend({
*/
model: null,

/**
* Optional callback called when the engine starts loading.
*
* @property onLoad
* @type {(() => void)?}
* @public
*/
onLoad: null,

/**
* Optional callback called when the engine finished loading.
*
* @property didLoad
* @type {(() => void)?}
* @public
*/
didLoad: null,

/**
* Optional callback called when the engine filed to load.
*
* @property onLoad
* @type {((error: Error) => void)?}
* @public
*/
onError: null,

/**
* When the engine was loaded successfully, this will then be the name of the
* engine. Presence of this field therefore indicates that the engine was
Expand Down Expand Up @@ -163,12 +190,15 @@ export default Component.extend({
},

setLoading() {
this.onLoad && this.onLoad();
setProperties(this, { loadedName: null, error: null, isLoading: true });
},
setLoaded(loadedName) {
this.didLoad && this.didLoad();
setProperties(this, { loadedName, error: null, isLoading: false });
},
setError(error) {
this.onError && this.onError(error);
setProperties(this, { loadedName: null, error, isLoading: false });
},

Expand Down
88 changes: 56 additions & 32 deletions tests/integration/components/lazy-mount-test.js
Original file line number Diff line number Diff line change
@@ -1,56 +1,79 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, settled } from '@ember/test-helpers';
import { run } from '@ember/runloop';
import hbs from 'htmlbars-inline-precompile';
import Service from '@ember/service';
import { schedule } from '@ember/runloop';

module('Integration | Component | lazy-mount', function(hooks) {
setupRenderingTest(hooks);

hooks.beforeEach(function(assert) {
this.onError = () => assert.step('onError');
this.onLoad = () => assert.step('onLoad');
this.didLoad = () => assert.step('didLoad');
});

const TEMPLATE = hbs`
{{#lazy-mount
this.engineName
onError=this.onError
onLoad=this.onLoad
didLoad=this.didLoad
as |e|
}}
isLoading: {{e.isLoading}};
error: {{e.error}};
{{/lazy-mount}}
`;

test('it works', async function(assert) {
assert.expect(2);
assert.expect(7);

render(hbs`
{{#lazy-mount "an-engine" as |e|}}
isLoading: {{e.isLoading}};
error: {{e.error}};
{{/lazy-mount}}
`);
this.engineName = 'an-engine';

render(TEMPLATE);

// Using `await render(...)` instead is flaky, because sometimes the request
// starts before the render queue is flushed. This then causes the `await`
// to also wait for the request to finish, meaning that we never get to test
// the loading state.
schedule('afterRender', () =>
assert.dom().hasText('isLoading: true; error: ;', 'shows a loading state')
);
schedule('afterRender', () => {
assert.verifySteps(['onLoad']);
assert
.dom()
.hasText('isLoading: true; error: ;', 'shows a loading state');
});

// Wait for the engine to be loaded and the template to update.
await settled();

assert.verifySteps(['didLoad']);
assert
.dom()
.hasText('Hello from the other side.', 'shows the engine template');

assert.verifySteps([]);
});

test('it shows an error right away for a non-existing engine', async function(assert) {
await render(hbs`
{{#lazy-mount "not-an-engine" as |e|}}
isLoading: {{e.isLoading}};
error: {{e.error}};
{{/lazy-mount}}
`);
test('it throws an error right away for a non-existing engine', async function(assert) {
this.engineName = 'not-an-engine';

assert
.dom()
.hasText(
'isLoading: false; error: Error: Assertion Failed: No bundle with name "not-an-engine" exists in the asset manifest.;'
);
run(() => render(TEMPLATE));

schedule('afterRender', () => {
assert
.dom()
.hasText(
'isLoading: false; error: Error: Assertion Failed: No bundle with name "not-an-engine" exists in the asset manifest.;'
);
assert.verifySteps(['onLoad', 'onError']);
});
});

test('it shows a loading state and an error', async function(assert) {
assert.expect(2);
assert.expect(6);

class AssetLoaderService extends Service {
async loadBundle() {
Expand All @@ -61,25 +84,26 @@ module('Integration | Component | lazy-mount', function(hooks) {
this.owner.unregister('service:asset-loader');
this.owner.register('service:asset-loader', AssetLoaderService);

render(hbs`
{{#lazy-mount "an-engine" as |e|}}
isLoading: {{e.isLoading}};
error: {{e.error}};
{{/lazy-mount}}
`);
this.engineName = 'an-engine';

render(TEMPLATE);

// Using `await render(...)` instead would not work, since the timer in
// `loadBundle` is synchronously added to the queue in order to avoid
// blazing past the promise with the last assertion. The alternative would
// be storing the promise in the closure of the test and awaiting it further
// down.
schedule('afterRender', () =>
assert.dom().hasText('isLoading: true; error: ;', 'shows a loading state')
);
schedule('afterRender', () => {
assert.verifySteps(['onLoad']);
assert
.dom()
.hasText('isLoading: true; error: ;', 'shows a loading state');
});

// Waits for the promise in `loadBundle` to resolve and the template to re-render.
await settled();

assert.verifySteps(['onError']);
assert
.dom()
.hasText(
Expand Down

0 comments on commit 608ca4e

Please sign in to comment.