diff --git a/src/application.ts b/src/application.ts index 3ae3a49..123fd9b 100644 --- a/src/application.ts +++ b/src/application.ts @@ -51,16 +51,18 @@ export default class Application implements Owner { private _registry: Registry; private _container: Container; private _renderResult: RenderResult; - /** Whether the initial render has completed. */ - private _rendered: boolean; + private _afterRender: Option<() => void>; /** Whether a re-render has been scheduled. */ - private _scheduled: boolean; + private _scheduled: Option> = null; private _initializers: Initializer[] = []; private _initialized = false; constructor(options: ApplicationOptions) { this.rootName = options.rootName; this.resolver = options.resolver; + this._scheduled = new Promise(resolve => { + this._afterRender = resolve; + }); } /** @hidden */ @@ -136,13 +138,22 @@ export default class Application implements Owner { this.env.commit(); - this._rendered = true; + let { _afterRender: afterRender } = this; + + this._afterRender = null; + this._scheduled = null; this._renderResult = result.value; + + afterRender(); } - renderComponent(component: string | ComponentDefinition, parent: Simple.Node, nextSibling: Option): void { + renderComponent( + component: string | ComponentDefinition, + parent: Simple.Node, + nextSibling: Option = null + ): Promise { this._roots.push({ id: this._rootsIndex++, component, parent, nextSibling }); - this.scheduleRerender(); + return this.scheduleRerender(); } /** @hidden */ @@ -153,13 +164,15 @@ export default class Application implements Owner { } /** @hidden */ - scheduleRerender(): void { - if (this._scheduled || !this._rendered) { return; } - - this._scheduled = true; - requestAnimationFrame(() => { - this._scheduled = false; - this.rerender(); + scheduleRerender(): Promise { + if (this._scheduled) return this._scheduled; + + return this._scheduled = new Promise(resolve => { + requestAnimationFrame(() => { + this._scheduled = null; + this.rerender(); + resolve(); + }); }); } diff --git a/test/render-component-test.ts b/test/render-component-test.ts new file mode 100644 index 0000000..c425bcb --- /dev/null +++ b/test/render-component-test.ts @@ -0,0 +1,122 @@ +import buildApp from './test-helpers/test-app'; + +const { module, test } = QUnit; + +module('renderComponent'); + +test('renders a component', function(assert) { + assert.expect(1); + + let containerElement = document.createElement('div'); + + let app = buildApp() + .template('hello-world', `

Hello Glimmer!

`) + .boot(); + + return app.renderComponent('hello-world', containerElement).then(() => { + assert.equal(containerElement.innerHTML, '

Hello Glimmer!

'); + }); +}); + +test('renders a component without affecting existing content', function(assert) { + assert.expect(2); + + let containerElement = document.createElement('div'); + let previousSibling = document.createElement('p'); + + previousSibling.appendChild(document.createTextNode('foo')); + containerElement.appendChild(previousSibling); + containerElement.appendChild(document.createTextNode('bar')); + + let app = buildApp() + .template('hello-world', `

Hello Glimmer!

`) + .boot(); + + assert.equal(containerElement.innerHTML, '

foo

bar'); + + return app.renderComponent('hello-world', containerElement).then(() => { + assert.equal(containerElement.innerHTML, '

foo

bar

Hello Glimmer!

'); + }); +}); + +test('renders a component before a given sibling', function(assert) { + assert.expect(2); + + let containerElement = document.createElement('div'); + let previousSibling = document.createElement('p'); + let nextSibling = document.createElement('aside'); + + containerElement.appendChild(previousSibling); + containerElement.appendChild(nextSibling); + + let app = buildApp() + .template('hello-world', `

Hello Glimmer!

`) + .boot(); + + assert.equal(containerElement.innerHTML, '

'); + + return app.renderComponent('hello-world', containerElement, nextSibling).then(() => { + assert.equal(containerElement.innerHTML, '

Hello Glimmer!

'); + }); +}); + +test('renders multiple components in different places', function(assert) { + assert.expect(2); + + let firstContainerElement = document.createElement('div'); + let secondContainerElement = document.createElement('div'); + + let app = buildApp() + .template('hello-world', `

Hello Glimmer!

`) + .template('hello-robbie', `

Hello Robbie!

`) + .boot(); + + return Promise.all([ + app.renderComponent('hello-world', firstContainerElement), + app.renderComponent('hello-robbie', secondContainerElement) + ]).then(() => { + assert.equal(firstContainerElement.innerHTML, '

Hello Glimmer!

'); + assert.equal(secondContainerElement.innerHTML, '

Hello Robbie!

'); + }); +}); + +test('renders multiple components in the same container', function(assert) { + assert.expect(1); + + let containerElement = document.createElement('div'); + + let app = buildApp() + .template('hello-world', `

Hello Glimmer!

`) + .template('hello-robbie', `

Hello Robbie!

`) + .boot(); + + return Promise.all([ + app.renderComponent('hello-world', containerElement), + app.renderComponent('hello-robbie', containerElement) + ]).then(() => { + assert.equal(containerElement.innerHTML, '

Hello Glimmer!

Hello Robbie!

'); + }); +}); + +test('renders multiple components in the same container in particular places', function(assert) { + assert.expect(2); + + let containerElement = document.createElement('div'); + let nextSibling = document.createElement('aside'); + + containerElement.appendChild(nextSibling); + + let app = buildApp() + .template('hello-world', `

Hello Glimmer!

`) + .template('hello-robbie', `

Hello Robbie!

`) + .boot(); + + assert.equal(containerElement.innerHTML, ''); + + return Promise.all([ + app.renderComponent('hello-world', containerElement), + app.renderComponent('hello-robbie', containerElement, nextSibling) + ]).then(() => { + assert.equal(containerElement.innerHTML, '

Hello Robbie!

Hello Glimmer!

'); + }); +}); diff --git a/test/test-helpers/test-app.ts b/test/test-helpers/test-app.ts index 0e96668..8b7512f 100644 --- a/test/test-helpers/test-app.ts +++ b/test/test-helpers/test-app.ts @@ -1,11 +1,10 @@ import Application from '../../src/application'; import Resolver, { BasicModuleRegistry } from '@glimmer/resolver'; -import { Factory } from '@glimmer/di'; import { TestComponent, TestComponentManager } from './components'; import { precompile } from './compiler'; -interface ComponentFactory { +export interface ComponentFactory { create(injections: object): TestComponent; } @@ -52,6 +51,7 @@ export class AppBuilder { constructor(name: string) { this.rootName = name; this.modules[`component-manager:/${this.rootName}/component-managers/main`] = TestComponentManager; + this.template('main', '
'); } template(name: string, template: string) {