diff --git a/FEATURES.md b/FEATURES.md index 45d2b442cf1..ec1987b0f14 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -32,16 +32,6 @@ for a detailed explanation. Add `{{@foo}}` syntax to access named arguments in component templates per [RFC](https://github.com/emberjs/rfcs/pull/276). -* `ember-glimmer-remove-application-template-wrapper` - - Remove the `
` wrapper around the application template per - [RFC](https://github.com/emberjs/rfcs/pull/280). - -* `ember-glimmer-template-only-components` - - Use Glimmer Components semantics for template-only components per - [RFC](https://github.com/emberjs/rfcs/pull/278). - * `ember-metal-es5-getters` Define ES5 getters for computed properties, eliminating the need to access them diff --git a/features.json b/features.json index 865f4ffdd39..215861fb5f1 100644 --- a/features.json +++ b/features.json @@ -4,8 +4,6 @@ "ember-libraries-isregistered": null, "ember-improved-instrumentation": null, "ember-glimmer-named-arguments": true, - "ember-glimmer-remove-application-template-wrapper": null, - "ember-glimmer-template-only-components": null, "ember-metal-es5-getters": true, "ember-routing-router-service": true, "ember-engines-mount-params": true, diff --git a/packages/ember-environment/lib/index.d.ts b/packages/ember-environment/lib/index.d.ts index 78b0d804287..18012c11ef6 100644 --- a/packages/ember-environment/lib/index.d.ts +++ b/packages/ember-environment/lib/index.d.ts @@ -9,5 +9,7 @@ export const environment: { } export const ENV: { + _APPLICATION_TEMPLATE_WRAPPER: boolean; _ENABLE_RENDER_SUPPORT: boolean; + _TEMPLATE_ONLY_GLIMMER_COMPONENTS: boolean; }; diff --git a/packages/ember-environment/lib/index.js b/packages/ember-environment/lib/index.js index f2ce2143dbc..8edb121c91f 100644 --- a/packages/ember-environment/lib/index.js +++ b/packages/ember-environment/lib/index.js @@ -78,9 +78,38 @@ ENV.LOG_VERSION = defaultTrue(ENV.LOG_VERSION); */ ENV.LOG_BINDINGS = defaultFalse(ENV.LOG_BINDINGS); - ENV.RAISE_ON_DEPRECATION = defaultFalse(ENV.RAISE_ON_DEPRECATION); +/** + Whether to insert a `
` wrapper around the + application template. See RFC #280. + + This is not intended to be set directly, as the implementation may change in + the future. Use `@ember/optional-features` instead. + + @property _APPLICATION_TEMPLATE_WRAPPER + @for EmberENV + @type Boolean + @default true + @private +*/ +ENV._APPLICATION_TEMPLATE_WRAPPER = defaultTrue(ENV._APPLICATION_TEMPLATE_WRAPPER); + +/** + Whether to use Glimmer Component semantics (as opposed to the classic "Curly" + components semantics) for template-only components. See RFC #278. + + This is not intended to be set directly, as the implementation may change in + the future. Use `@ember/optional-features` instead. + + @property _TEMPLATE_ONLY_GLIMMER_COMPONENTS + @for EmberENV + @type Boolean + @default false + @private +*/ +ENV._TEMPLATE_ONLY_GLIMMER_COMPONENTS = defaultFalse(ENV._TEMPLATE_ONLY_GLIMMER_COMPONENTS); + // check if window exists and actually is the global const hasDOM = typeof window !== 'undefined' && window === global && window.document && window.document.createElement && diff --git a/packages/ember-glimmer/externs.d.ts b/packages/ember-glimmer/externs.d.ts index 7d0f1b1521d..f6f86f405c0 100644 --- a/packages/ember-glimmer/externs.d.ts +++ b/packages/ember-glimmer/externs.d.ts @@ -4,8 +4,6 @@ declare module 'ember/features' { export const GLIMMER_CUSTOM_COMPONENT_MANAGER: boolean | null; export const EMBER_ENGINES_MOUNT_PARAMS: boolean | null; export const EMBER_GLIMMER_DETECT_BACKTRACKING_RERENDER: boolean | null; - export const EMBER_GLIMMER_REMOVE_APPLICATION_TEMPLATE_WRAPPER: boolean | null; - export const EMBER_GLIMMER_TEMPLATE_ONLY_COMPONENTS: boolean | null; export const MANDATORY_SETTER: boolean | null; } diff --git a/packages/ember-glimmer/lib/component-managers/outlet.ts b/packages/ember-glimmer/lib/component-managers/outlet.ts index 4de49b46ea9..77949935afb 100644 --- a/packages/ember-glimmer/lib/component-managers/outlet.ts +++ b/packages/ember-glimmer/lib/component-managers/outlet.ts @@ -14,12 +14,10 @@ import { } from '@glimmer/runtime'; import { Destroyable } from '@glimmer/util'; import { DEBUG } from 'ember-env-flags'; +import { ENV } from 'ember-environment'; import { _instrumentStart } from 'ember-metal'; import { assign, guidFor } from 'ember-utils'; import { OwnedTemplateMeta } from 'ember-views'; -import { - EMBER_GLIMMER_REMOVE_APPLICATION_TEMPLATE_WRAPPER, -} from 'ember/features'; import { DynamicScope } from '../renderer'; import RuntimeResolver from '../resolver'; import { @@ -127,49 +125,45 @@ export class OutletComponentDefinition implements ComponentDefinition OutletComponentDefinition; - -if (EMBER_GLIMMER_REMOVE_APPLICATION_TEMPLATE_WRAPPER) { - createRootOutlet = (outletView: OutletView) => new OutletComponentDefinition(outletView.state); -} else { - const WRAPPED_CAPABILITIES = assign({}, CAPABILITIES, { - dynamicTag: true, - elementHook: true, - }); +export function createRootOutlet(outletView: OutletView): OutletComponentDefinition { + if (ENV._APPLICATION_TEMPLATE_WRAPPER) { + const WRAPPED_CAPABILITIES = assign({}, CAPABILITIES, { + dynamicTag: true, + elementHook: true, + }); - const WrappedOutletComponentManager = class extends OutletComponentManager + const WrappedOutletComponentManager = class extends OutletComponentManager implements WithDynamicTagName { - getTagName(_component: OutletInstanceState) { - return 'div'; - } - - getLayout(state: OutletDefinitionState, resolver: RuntimeResolver): Invocation { - // The router has already resolved the template - const template = state.template; - const layout = resolver.getWrappedLayout(template, WRAPPED_CAPABILITIES); - return { - handle: layout.compile(), - symbolTable: layout.symbolTable - }; - } - - getCapabilities(): ComponentCapabilities { - return WRAPPED_CAPABILITIES; - } - - didCreateElement(component: OutletInstanceState, element: Element, _operations: ElementOperations): void { - // to add GUID id and class - element.setAttribute('class', 'ember-view'); - element.setAttribute('id', guidFor(component)); - } - }; + getTagName(_component: OutletInstanceState) { + return 'div'; + } + + getLayout(state: OutletDefinitionState, resolver: RuntimeResolver): Invocation { + // The router has already resolved the template + const template = state.template; + const layout = resolver.getWrappedLayout(template, WRAPPED_CAPABILITIES); + return { + handle: layout.compile(), + symbolTable: layout.symbolTable + }; + } + + getCapabilities(): ComponentCapabilities { + return WRAPPED_CAPABILITIES; + } + + didCreateElement(component: OutletInstanceState, element: Element, _operations: ElementOperations): void { + // to add GUID id and class + element.setAttribute('class', 'ember-view'); + element.setAttribute('id', guidFor(component)); + } + }; - const WRAPPED_OUTLET_MANAGER = new WrappedOutletComponentManager(); + const WRAPPED_OUTLET_MANAGER = new WrappedOutletComponentManager(); - createRootOutlet = (outletView: OutletView) => { return new OutletComponentDefinition(outletView.state, WRAPPED_OUTLET_MANAGER); - }; + } else { + return new OutletComponentDefinition(outletView.state); + } } - -export { createRootOutlet }; diff --git a/packages/ember-glimmer/lib/environment.ts b/packages/ember-glimmer/lib/environment.ts index 47a8fe4b641..78abd10c54d 100644 --- a/packages/ember-glimmer/lib/environment.ts +++ b/packages/ember-glimmer/lib/environment.ts @@ -30,7 +30,6 @@ import installPlatformSpecificProtocolForURL from './protocol-for-url'; import { EMBER_MODULE_UNIFICATION, - // EMBER_GLIMMER_TEMPLATE_ONLY_COMPONENTS, // GLIMMER_CUSTOM_COMPONENT_MANAGER, } from 'ember/features'; import { OwnedTemplate } from './template'; @@ -75,7 +74,7 @@ export default class Environment extends GlimmerEnvironment { // this._definitionCache = new Cache(2000, ({ name, source, owner }) => { // let { component: componentFactory, layout } = lookupComponent(owner, name, { source }); // let customManager: any; - // if (EMBER_GLIMMER_TEMPLATE_ONLY_COMPONENTS && layout && !componentFactory) { + // if (ENV._TEMPLATE_ONLY_GLIMMER_COMPONENTS && layout && !componentFactory) { // return new TemplateOnlyComponentDefinition(name, layout); // } else if (componentFactory || layout) { // if (GLIMMER_CUSTOM_COMPONENT_MANAGER) { diff --git a/packages/ember-glimmer/lib/renderer.ts b/packages/ember-glimmer/lib/renderer.ts index 9bd683c02ec..f8b65055a38 100644 --- a/packages/ember-glimmer/lib/renderer.ts +++ b/packages/ember-glimmer/lib/renderer.ts @@ -444,6 +444,9 @@ export abstract class Renderer { } finally { if (!completedWithoutError) { this._lastRevision = CURRENT_TAG.value(); + if (this._env.inTransaction === true) { + this._env.commit(); + } } this._isRenderingRoots = false; } diff --git a/packages/ember-glimmer/lib/resolver.ts b/packages/ember-glimmer/lib/resolver.ts index a80ed51f69e..bbcedb81da7 100644 --- a/packages/ember-glimmer/lib/resolver.ts +++ b/packages/ember-glimmer/lib/resolver.ts @@ -16,6 +16,7 @@ import { } from '@glimmer/runtime'; import { privatize as P } from 'container'; import { assert } from 'ember-debug'; +import { ENV } from 'ember-environment'; import { _instrumentStart } from 'ember-metal'; import { assign, LookupOptions, Owner, setOwner } from 'ember-utils'; import { @@ -23,7 +24,6 @@ import { lookupPartial, OwnedTemplateMeta, } from 'ember-views'; -import { EMBER_GLIMMER_TEMPLATE_ONLY_COMPONENTS } from 'ember/features'; import CompileTimeLookup from './compile-time-lookup'; import { CurlyComponentDefinition } from './component-managers/curly'; import { TemplateOnlyComponentDefinition } from './component-managers/template-only'; @@ -297,7 +297,7 @@ export default class RuntimeResolver implements IRuntimeResolver { let { layout, component } = lookupComponent(meta.owner, name, makeOptions(meta.moduleName)); - if (layout && !component && EMBER_GLIMMER_TEMPLATE_ONLY_COMPONENTS) { + if (layout && !component && ENV._TEMPLATE_ONLY_GLIMMER_COMPONENTS) { return new TemplateOnlyComponentDefinition(layout); } diff --git a/packages/ember-glimmer/lib/setup-registry.ts b/packages/ember-glimmer/lib/setup-registry.ts index cfe0e9123e8..02f46806cba 100644 --- a/packages/ember-glimmer/lib/setup-registry.ts +++ b/packages/ember-glimmer/lib/setup-registry.ts @@ -1,6 +1,5 @@ import { privatize as P } from 'container'; -import { environment } from 'ember-environment'; -import { EMBER_GLIMMER_TEMPLATE_ONLY_COMPONENTS } from 'ember/features'; +import { ENV, environment } from 'ember-environment'; import Component from './component'; import Checkbox from './components/checkbox'; import LinkToComponent from './components/link-to'; @@ -79,7 +78,7 @@ export function setupEngineRegistry(registry: Registry) { registry.register('component:-checkbox', Checkbox); registry.register('component:link-to', LinkToComponent); - if (!EMBER_GLIMMER_TEMPLATE_ONLY_COMPONENTS) { + if (!ENV._TEMPLATE_ONLY_GLIMMER_COMPONENTS) { registry.register(P`component:-default`, Component); } } diff --git a/packages/ember-glimmer/tests/integration/application/rendering-test.js b/packages/ember-glimmer/tests/integration/application/rendering-test.js index 4b55bf496c9..f7faeb6978d 100644 --- a/packages/ember-glimmer/tests/integration/application/rendering-test.js +++ b/packages/ember-glimmer/tests/integration/application/rendering-test.js @@ -1,3 +1,4 @@ +import { ENV } from 'ember-environment'; import { Controller } from 'ember-runtime'; import { moduleFor, ApplicationTest } from '../../utils/test-case'; import { strip } from '../../utils/abstract-test-case'; @@ -5,8 +6,19 @@ import { Route } from 'ember-routing'; import { Component } from 'ember-glimmer'; moduleFor('Application test: rendering', class extends ApplicationTest { + constructor() { + super(); + this._APPLICATION_TEMPLATE_WRAPPER = ENV._APPLICATION_TEMPLATE_WRAPPER; + } + + teardown() { + super.teardown(); + ENV._APPLICATION_TEMPLATE_WRAPPER = this._APPLICATION_TEMPLATE_WRAPPER; + } + + ['@test it can render the application template with a wrapper']() { + ENV._APPLICATION_TEMPLATE_WRAPPER = true; - ['@feature(!ember-glimmer-remove-application-template-wrapper) it can render the application template']() { this.addTemplate('application', 'Hello world!'); return this.visit('/').then(() => { @@ -14,7 +26,9 @@ moduleFor('Application test: rendering', class extends ApplicationTest { }); } - ['@feature(ember-glimmer-remove-application-template-wrapper) it can render the application template']() { + ['@test it can render the application template without a wrapper']() { + ENV._APPLICATION_TEMPLATE_WRAPPER = false; + this.addTemplate('application', 'Hello world!'); return this.visit('/').then(() => { diff --git a/packages/ember-glimmer/tests/integration/components/error-handling-test.js b/packages/ember-glimmer/tests/integration/components/error-handling-test.js new file mode 100644 index 00000000000..9614b74cca6 --- /dev/null +++ b/packages/ember-glimmer/tests/integration/components/error-handling-test.js @@ -0,0 +1,72 @@ +import { set } from 'ember-metal'; +import { Component } from '../../utils/helpers'; +import { moduleFor, RenderingTest } from '../../utils/test-case'; + +moduleFor('Errors thrown during render', class extends RenderingTest { + ['@test it can recover resets the transaction when an error is thrown during initial render'](assert) { + let shouldThrow = true; + let FooBarComponent = Component.extend({ + init() { + this._super(...arguments); + if (shouldThrow) { + throw new Error('silly mistake in init!'); + } + } + }); + + this.registerComponent('foo-bar', { ComponentClass: FooBarComponent, template: 'hello' }); + + assert.throws(() => { + this.render('{{#if switch}}{{#foo-bar}}{{foo-bar}}{{/foo-bar}}{{/if}}', { switch: true }); + }, /silly mistake in init/); + + assert.equal(this.env.inTransaction, false, 'should not be in a transaction even though an error was thrown'); + + this.assertText(''); + + this.runTask(() => set(this.context, 'switch', false)); + + shouldThrow = false; + + this.runTask(() => set(this.context, 'switch', true)); + + this.assertText('hello'); + } + + ['@test it can recover resets the transaction when an error is thrown during rerender'](assert) { + let shouldThrow = false; + let FooBarComponent = Component.extend({ + init() { + this._super(...arguments); + if (shouldThrow) { + throw new Error('silly mistake in init!'); + } + } + }); + + this.registerComponent('foo-bar', { ComponentClass: FooBarComponent, template: 'hello' }); + + this.render('{{#if switch}}{{#foo-bar}}{{foo-bar}}{{/foo-bar}}{{/if}}', { switch: true }); + + this.assertText('hello'); + + this.runTask(() => set(this.context, 'switch', false)); + + shouldThrow = true; + + assert.throws(() => { + this.runTask(() => set(this.context, 'switch', true)); + }, /silly mistake in init/); + + assert.equal(this.env.inTransaction, false, 'should not be in a transaction even though an error was thrown'); + + this.assertText(''); + + this.runTask(() => set(this.context, 'switch', false)); + shouldThrow = false; + + this.runTask(() => set(this.context, 'switch', true)); + + this.assertText('hello'); + } +}); diff --git a/packages/ember-glimmer/tests/integration/components/template-only-components-test.js b/packages/ember-glimmer/tests/integration/components/template-only-components-test.js index 90e4bb8221e..fea801384c6 100644 --- a/packages/ember-glimmer/tests/integration/components/template-only-components-test.js +++ b/packages/ember-glimmer/tests/integration/components/template-only-components-test.js @@ -1,6 +1,6 @@ import { moduleFor, RenderingTest } from '../../utils/test-case'; import { classes } from '../../utils/test-helpers'; -import { EMBER_GLIMMER_TEMPLATE_ONLY_COMPONENTS } from 'ember/features'; +import { ENV } from 'ember-environment'; class TemplateOnlyComponentsTest extends RenderingTest { registerComponent(name, template) { @@ -8,189 +8,209 @@ class TemplateOnlyComponentsTest extends RenderingTest { } } -if (EMBER_GLIMMER_TEMPLATE_ONLY_COMPONENTS) { - moduleFor('Components test: template-only components (glimmer components)', class extends TemplateOnlyComponentsTest { - ['@test it can render a template-only component']() { - this.registerComponent('foo-bar', 'hello'); +moduleFor('Components test: template-only components (glimmer components)', class extends TemplateOnlyComponentsTest { + constructor() { + super(); + this._TEMPLATE_ONLY_GLIMMER_COMPONENTS = ENV._TEMPLATE_ONLY_GLIMMER_COMPONENTS; + ENV._TEMPLATE_ONLY_GLIMMER_COMPONENTS = true; + } + + teardown() { + super.teardown(); + ENV._TEMPLATE_ONLY_GLIMMER_COMPONENTS = this._TEMPLATE_ONLY_GLIMMER_COMPONENTS; + } - this.render('{{foo-bar}}'); + ['@test it can render a template-only component']() { + this.registerComponent('foo-bar', 'hello'); - this.assertInnerHTML('hello'); + this.render('{{foo-bar}}'); - this.assertStableRerender(); - } + this.assertInnerHTML('hello'); + + this.assertStableRerender(); + } - ['@feature(ember-glimmer-named-arguments) it can render named arguments']() { - this.registerComponent('foo-bar', '|{{@foo}}|{{@bar}}|'); + ['@feature(ember-glimmer-named-arguments) it can render named arguments']() { + this.registerComponent('foo-bar', '|{{@foo}}|{{@bar}}|'); - this.render('{{foo-bar foo=foo bar=bar}}', { - foo: 'foo', bar: 'bar' - }); + this.render('{{foo-bar foo=foo bar=bar}}', { + foo: 'foo', bar: 'bar' + }); - this.assertInnerHTML('|foo|bar|'); + this.assertInnerHTML('|foo|bar|'); - this.assertStableRerender(); + this.assertStableRerender(); - this.runTask(() => this.context.set('foo', 'FOO')); + this.runTask(() => this.context.set('foo', 'FOO')); - this.assertInnerHTML('|FOO|bar|'); + this.assertInnerHTML('|FOO|bar|'); - this.runTask(() => this.context.set('bar', 'BAR')); + this.runTask(() => this.context.set('bar', 'BAR')); + + this.assertInnerHTML('|FOO|BAR|'); + + this.runTask(() => this.context.setProperties({ foo: 'foo', bar: 'bar' })); + + this.assertInnerHTML('|foo|bar|'); + } - this.assertInnerHTML('|FOO|BAR|'); + ['@test it does not reflected arguments as properties']() { + this.registerComponent('foo-bar', '|{{foo}}|{{this.bar}}|'); - this.runTask(() => this.context.setProperties({ foo: 'foo', bar: 'bar' })); + this.render('{{foo-bar foo=foo bar=bar}}', { + foo: 'foo', bar: 'bar' + }); - this.assertInnerHTML('|foo|bar|'); - } + this.assertInnerHTML('|||'); - ['@test it does not reflected arguments as properties']() { - this.registerComponent('foo-bar', '|{{foo}}|{{this.bar}}|'); + this.assertStableRerender(); - this.render('{{foo-bar foo=foo bar=bar}}', { - foo: 'foo', bar: 'bar' - }); + this.runTask(() => this.context.set('foo', 'FOO')); - this.assertInnerHTML('|||'); + this.assertInnerHTML('|||'); - this.assertStableRerender(); + this.runTask(() => this.context.set('bar', null)); - this.runTask(() => this.context.set('foo', 'FOO')); + this.assertInnerHTML('|||'); - this.assertInnerHTML('|||'); + this.runTask(() => this.context.setProperties({ foo: 'foo', bar: 'bar' })); - this.runTask(() => this.context.set('bar', null)); + this.assertInnerHTML('|||'); + } - this.assertInnerHTML('|||'); + ['@test it does not have curly component features']() { + this.registerComponent('foo-bar', 'hello'); - this.runTask(() => this.context.setProperties({ foo: 'foo', bar: 'bar' })); + this.render('{{foo-bar tagName="p" class=class}}', { + class: 'foo bar' + }); - this.assertInnerHTML('|||'); - } + this.assertInnerHTML('hello'); - ['@test it does not have curly component features']() { - this.registerComponent('foo-bar', 'hello'); - this.render('{{foo-bar tagName="p" class=class}}', { - class: 'foo bar' - }); + this.assertStableRerender(); - this.assertInnerHTML('hello'); + this.runTask(() => this.context.set('class', 'foo')); + this.assertInnerHTML('hello'); - this.assertStableRerender(); + this.runTask(() => this.context.set('class', null)); - this.runTask(() => this.context.set('class', 'foo')); + this.assertInnerHTML('hello'); - this.assertInnerHTML('hello'); + this.runTask(() => this.context.set('class', 'foo bar')); - this.runTask(() => this.context.set('class', null)); + this.assertInnerHTML('hello'); + } +}); - this.assertInnerHTML('hello'); +moduleFor('Components test: template-only components (curly components)', class extends TemplateOnlyComponentsTest { + constructor() { + super(); + this._TEMPLATE_ONLY_GLIMMER_COMPONENTS = ENV._TEMPLATE_ONLY_GLIMMER_COMPONENTS; + ENV._TEMPLATE_ONLY_GLIMMER_COMPONENTS = false; + } - this.runTask(() => this.context.set('class', 'foo bar')); + teardown() { + super.teardown(); + ENV._TEMPLATE_ONLY_GLIMMER_COMPONENTS = this._TEMPLATE_ONLY_GLIMMER_COMPONENTS; + } - this.assertInnerHTML('hello'); - } - }); -} else { - moduleFor('Components test: template-only components (curly components)', class extends TemplateOnlyComponentsTest { - ['@test it can render a template-only component']() { - this.registerComponent('foo-bar', 'hello'); + ['@test it can render a template-only component']() { + this.registerComponent('foo-bar', 'hello'); - this.render('{{foo-bar}}'); + this.render('{{foo-bar}}'); - this.assertComponentElement(this.firstChild, { content: 'hello' }); + this.assertComponentElement(this.firstChild, { content: 'hello' }); - this.assertStableRerender(); - } + this.assertStableRerender(); + } - ['@feature(ember-glimmer-named-arguments) it can render named arguments']() { - this.registerComponent('foo-bar', '|{{@foo}}|{{@bar}}|'); + ['@feature(ember-glimmer-named-arguments) it can render named arguments']() { + this.registerComponent('foo-bar', '|{{@foo}}|{{@bar}}|'); - this.render('{{foo-bar foo=foo bar=bar}}', { - foo: 'foo', bar: 'bar' - }); + this.render('{{foo-bar foo=foo bar=bar}}', { + foo: 'foo', bar: 'bar' + }); - this.assertComponentElement(this.firstChild, { content: '|foo|bar|' }); + this.assertComponentElement(this.firstChild, { content: '|foo|bar|' }); - this.assertStableRerender(); + this.assertStableRerender(); - this.runTask(() => this.context.set('foo', 'FOO')); + this.runTask(() => this.context.set('foo', 'FOO')); - this.assertComponentElement(this.firstChild, { content: '|FOO|bar|' }); + this.assertComponentElement(this.firstChild, { content: '|FOO|bar|' }); - this.runTask(() => this.context.set('bar', 'BAR')); + this.runTask(() => this.context.set('bar', 'BAR')); - this.assertComponentElement(this.firstChild, { content: '|FOO|BAR|' }); + this.assertComponentElement(this.firstChild, { content: '|FOO|BAR|' }); - this.runTask(() => this.context.setProperties({ foo: 'foo', bar: 'bar' })); + this.runTask(() => this.context.setProperties({ foo: 'foo', bar: 'bar' })); - this.assertComponentElement(this.firstChild, { content: '|foo|bar|' }); - } + this.assertComponentElement(this.firstChild, { content: '|foo|bar|' }); + } - ['@test it renders named arguments as reflected properties']() { - this.registerComponent('foo-bar', '|{{foo}}|{{this.bar}}|'); + ['@test it renders named arguments as reflected properties']() { + this.registerComponent('foo-bar', '|{{foo}}|{{this.bar}}|'); - this.render('{{foo-bar foo=foo bar=bar}}', { - foo: 'foo', bar: 'bar' - }); + this.render('{{foo-bar foo=foo bar=bar}}', { + foo: 'foo', bar: 'bar' + }); - this.assertComponentElement(this.firstChild, { content: '|foo|bar|' }); + this.assertComponentElement(this.firstChild, { content: '|foo|bar|' }); - this.assertStableRerender(); + this.assertStableRerender(); - this.runTask(() => this.context.set('foo', 'FOO')); + this.runTask(() => this.context.set('foo', 'FOO')); - this.assertComponentElement(this.firstChild, { content: '|FOO|bar|' }); + this.assertComponentElement(this.firstChild, { content: '|FOO|bar|' }); - this.runTask(() => this.context.set('bar', null)); + this.runTask(() => this.context.set('bar', null)); - this.assertComponentElement(this.firstChild, { content: '|FOO||' }); + this.assertComponentElement(this.firstChild, { content: '|FOO||' }); - this.runTask(() => this.context.setProperties({ foo: 'foo', bar: 'bar' })); + this.runTask(() => this.context.setProperties({ foo: 'foo', bar: 'bar' })); - this.assertComponentElement(this.firstChild, { content: '|foo|bar|' }); - } + this.assertComponentElement(this.firstChild, { content: '|foo|bar|' }); + } - ['@test it has curly component features']() { - this.registerComponent('foo-bar', 'hello'); + ['@test it has curly component features']() { + this.registerComponent('foo-bar', 'hello'); - this.render('{{foo-bar tagName="p" class=class}}', { - class: 'foo bar' - }); + this.render('{{foo-bar tagName="p" class=class}}', { + class: 'foo bar' + }); - this.assertComponentElement(this.firstChild, { - tagName: 'p', - attrs: { class: classes('foo bar ember-view') }, - content: 'hello' - }); + this.assertComponentElement(this.firstChild, { + tagName: 'p', + attrs: { class: classes('foo bar ember-view') }, + content: 'hello' + }); - this.assertStableRerender(); + this.assertStableRerender(); - this.runTask(() => this.context.set('class', 'foo')); + this.runTask(() => this.context.set('class', 'foo')); - this.assertComponentElement(this.firstChild, { - tagName: 'p', - attrs: { class: classes('foo ember-view') }, - content: 'hello' - }); + this.assertComponentElement(this.firstChild, { + tagName: 'p', + attrs: { class: classes('foo ember-view') }, + content: 'hello' + }); - this.runTask(() => this.context.set('class', null)); + this.runTask(() => this.context.set('class', null)); - this.assertComponentElement(this.firstChild, { - tagName: 'p', - attrs: { class: classes('ember-view') }, - content: 'hello' - }); + this.assertComponentElement(this.firstChild, { + tagName: 'p', + attrs: { class: classes('ember-view') }, + content: 'hello' + }); - this.runTask(() => this.context.set('class', 'foo bar')); + this.runTask(() => this.context.set('class', 'foo bar')); - this.assertComponentElement(this.firstChild, { - tagName: 'p', - attrs: { class: classes('foo bar ember-view') }, - content: 'hello' - }); - } - }); -} + this.assertComponentElement(this.firstChild, { + tagName: 'p', + attrs: { class: classes('foo bar ember-view') }, + content: 'hello' + }); + } +}); diff --git a/packages/ember-views/lib/utils/lookup-component.js b/packages/ember-views/lib/utils/lookup-component.js index a0798d1c558..5b142edf0e7 100644 --- a/packages/ember-views/lib/utils/lookup-component.js +++ b/packages/ember-views/lib/utils/lookup-component.js @@ -1,7 +1,7 @@ import { privatize as P } from 'container'; +import { ENV } from 'ember-environment'; import { EMBER_MODULE_UNIFICATION, - EMBER_GLIMMER_TEMPLATE_ONLY_COMPONENTS } from 'ember/features'; function lookupModuleUnificationComponentPair(componentLookup, owner, name, options) { @@ -24,7 +24,7 @@ function lookupModuleUnificationComponentPair(componentLookup, owner, name, opti let defaultComponentFactory = null; - if (!EMBER_GLIMMER_TEMPLATE_ONLY_COMPONENTS) { + if (!ENV._TEMPLATE_ONLY_GLIMMER_COMPONENTS) { defaultComponentFactory = owner.factoryFor(P`component:-default`); } @@ -46,7 +46,7 @@ function lookupComponentPair(componentLookup, owner, name, options) { let result = { layout, component }; - if (!EMBER_GLIMMER_TEMPLATE_ONLY_COMPONENTS && layout && !component) { + if (!ENV._TEMPLATE_ONLY_GLIMMER_COMPONENTS && layout && !component) { result.component = owner.factoryFor(P`component:-default`); } diff --git a/packages/ember/tests/routing/basic_test.js b/packages/ember/tests/routing/basic_test.js index d27d4668ceb..0fed702cfcf 100644 --- a/packages/ember/tests/routing/basic_test.js +++ b/packages/ember/tests/routing/basic_test.js @@ -16,7 +16,6 @@ import { run, get, set, - computed, Mixin, observer, addObserver @@ -61,16 +60,6 @@ function handleURLAborts(assert, path) { }); } -function handleURLRejectsWith(assert, path, expectedReason) { - run(() => { - router.handleURL(path).then(function() { - assert.ok(false, 'expected handleURLing: `' + path + '` to fail'); - }, function(reason) { - assert.equal(reason, expectedReason); - }); - }); -} - QUnit.module('Basic Routing', { beforeEach() { run(() => { @@ -117,422 +106,6 @@ QUnit.module('Basic Routing', { } }); -QUnit.test('The route controller specified via controllerName is used in render', function(assert) { - Router.map(function() { - this.route('home', { path: '/' }); - }); - - setTemplate('alternative_home', compile( - '

alternative home: {{myValue}}

' - )); - - App.HomeRoute = Route.extend({ - controllerName: 'myController', - renderTemplate() { - this.render('alternative_home'); - } - }); - - registry.register('controller:myController', Controller.extend({ - myValue: 'foo' - })); - - bootApplication(); - - assert.deepEqual(container.lookup('route:home').controller, container.lookup('controller:myController'), 'route controller is set by controllerName'); - assert.equal(jQuery('p', '#qunit-fixture').text(), 'alternative home: foo', 'The homepage template was rendered with data from the custom controller'); -}); - -QUnit.test('The route controller specified via controllerName is used in render even when a controller with the routeName is available', function(assert) { - Router.map(function() { - this.route('home', { path: '/' }); - }); - - setTemplate('home', compile( - '

home: {{myValue}}

' - )); - - App.HomeRoute = Route.extend({ - controllerName: 'myController' - }); - - registry.register('controller:home', Controller.extend({ - myValue: 'home' - })); - - registry.register('controller:myController', Controller.extend({ - myValue: 'myController' - })); - - bootApplication(); - - assert.deepEqual(container.lookup('route:home').controller, container.lookup('controller:myController'), 'route controller is set by controllerName'); - assert.equal(jQuery('p', '#qunit-fixture').text(), 'home: myController', 'The homepage template was rendered with data from the custom controller'); -}); - -QUnit.test('The Homepage with a `setupController` hook modifying other controllers', function(assert) { - Router.map(function() { - this.route('home', { path: '/' }); - }); - - App.HomeRoute = Route.extend({ - setupController(/* controller */) { - set(this.controllerFor('home'), 'hours', emberA([ - 'Monday through Friday: 9am to 5pm', - 'Saturday: Noon to Midnight', - 'Sunday: Noon to 6pm' - ])); - } - }); - - setTemplate('home', compile( - '
    {{#each hours as |entry|}}
  • {{entry}}
  • {{/each}}
' - )); - - bootApplication(); - - assert.equal(document.querySelectorAll('#qunit-fixture ul li')[2].textContent, 'Sunday: Noon to 6pm', 'The template was rendered with the hours context'); -}); - -QUnit.test('The Homepage with a computed context that does not get overridden', function(assert) { - Router.map(function() { - this.route('home', { path: '/' }); - }); - - App.HomeController = Controller.extend({ - model: computed(function() { - return emberA([ - 'Monday through Friday: 9am to 5pm', - 'Saturday: Noon to Midnight', - 'Sunday: Noon to 6pm' - ]); - }) - }); - - setTemplate('home', compile( - '
    {{#each model as |passage|}}
  • {{passage}}
  • {{/each}}
' - )); - - bootApplication(); - - assert.equal(document.querySelectorAll('#qunit-fixture ul li')[2].textContent, 'Sunday: Noon to 6pm', 'The template was rendered with the context intact'); -}); - -QUnit.test('The Homepage getting its controller context via model', function(assert) { - Router.map(function() { - this.route('home', { path: '/' }); - }); - - App.HomeRoute = Route.extend({ - model() { - return emberA([ - 'Monday through Friday: 9am to 5pm', - 'Saturday: Noon to Midnight', - 'Sunday: Noon to 6pm' - ]); - }, - - setupController(controller, model) { - assert.equal(this.controllerFor('home'), controller); - - set(this.controllerFor('home'), 'hours', model); - } - }); - - setTemplate('home', compile( - '
    {{#each hours as |entry|}}
  • {{entry}}
  • {{/each}}
' - )); - - bootApplication(); - - assert.equal(document.querySelectorAll('#qunit-fixture ul li', '#qunit-fixture')[2].textContent, 'Sunday: Noon to 6pm', 'The template was rendered with the hours context'); -}); - -QUnit.test('The Specials Page getting its controller context by deserializing the params hash', function(assert) { - Router.map(function() { - this.route('home', { path: '/' }); - this.route('special', { path: '/specials/:menu_item_id' }); - }); - - App.SpecialRoute = Route.extend({ - model(params) { - return EmberObject.create({ - menuItemId: params.menu_item_id - }); - }, - - setupController(controller, model) { - set(controller, 'model', model); - } - }); - - setTemplate('special', compile( - '

{{model.menuItemId}}

' - )); - - bootApplication(); - - registry.register('controller:special', Controller.extend()); - - handleURL(assert, '/specials/1'); - - assert.equal(jQuery('p', '#qunit-fixture').text(), '1', 'The model was used to render the template'); -}); - -QUnit.test('The Specials Page defaults to looking models up via `find`', function(assert) { - Router.map(function() { - this.route('home', { path: '/' }); - this.route('special', { path: '/specials/:menu_item_id' }); - }); - - App.MenuItem = EmberObject.extend(); - App.MenuItem.reopenClass({ - find(id) { - return App.MenuItem.create({ - id: id - }); - } - }); - - App.SpecialRoute = Route.extend({ - setupController(controller, model) { - set(controller, 'model', model); - } - }); - - setTemplate('special', compile( - '

{{model.id}}

' - )); - - bootApplication(); - - registry.register('controller:special', Controller.extend()); - - handleURL(assert, '/specials/1'); - - assert.equal(jQuery('p', '#qunit-fixture').text(), '1', 'The model was used to render the template'); -}); - -QUnit.test('The Special Page returning a promise puts the app into a loading state until the promise is resolved', function(assert) { - Router.map(function() { - this.route('home', { path: '/' }); - this.route('special', { path: '/specials/:menu_item_id' }); - }); - - let menuItem, resolve; - - App.MenuItem = EmberObject.extend(); - App.MenuItem.reopenClass({ - find(id) { - menuItem = App.MenuItem.create({ id: id }); - - return new RSVP.Promise(function(res) { - resolve = res; - }); - } - }); - - App.LoadingRoute = Route.extend({ - - }); - - App.SpecialRoute = Route.extend({ - setupController(controller, model) { - set(controller, 'model', model); - } - }); - - setTemplate('special', compile( - '

{{model.id}}

' - )); - - setTemplate('loading', compile( - '

LOADING!

' - )); - - bootApplication(); - - registry.register('controller:special', Controller.extend()); - - handleURL(assert, '/specials/1'); - - assert.equal(jQuery('p', '#qunit-fixture').text(), 'LOADING!', 'The app is in the loading state'); - - run(() => resolve(menuItem)); - - assert.equal(jQuery('p', '#qunit-fixture').text(), '1', 'The app is now in the specials state'); -}); - -QUnit.test('The loading state doesn\'t get entered for promises that resolve on the same run loop', function(assert) { - Router.map(function() { - this.route('home', { path: '/' }); - this.route('special', { path: '/specials/:menu_item_id' }); - }); - - App.MenuItem = EmberObject.extend(); - App.MenuItem.reopenClass({ - find(id) { - return { id: id }; - } - }); - - App.LoadingRoute = Route.extend({ - enter() { - assert.ok(false, 'LoadingRoute shouldn\'t have been entered.'); - } - }); - - App.SpecialRoute = Route.extend({ - setupController(controller, model) { - set(controller, 'model', model); - } - }); - - setTemplate('special', compile( - '

{{model.id}}

' - )); - - setTemplate('loading', compile( - '

LOADING!

' - )); - - bootApplication(); - - registry.register('controller:special', Controller.extend()); - - handleURL(assert, '/specials/1'); - - assert.equal(jQuery('p', '#qunit-fixture').text(), '1', 'The app is now in the specials state'); -}); - -QUnit.test('The Special page returning an error invokes SpecialRoute\'s error handler', function(assert) { - Router.map(function() { - this.route('home', { path: '/' }); - this.route('special', { path: '/specials/:menu_item_id' }); - }); - - let menuItem, promise, resolve; - - App.MenuItem = EmberObject.extend(); - App.MenuItem.reopenClass({ - find(id) { - menuItem = App.MenuItem.create({ id: id }); - promise = new RSVP.Promise(function(res) { - resolve = res; - }); - - return promise; - } - }); - - App.SpecialRoute = Route.extend({ - setup() { - throw 'Setup error'; - }, - actions: { - error(reason) { - assert.equal(reason, 'Setup error', 'SpecialRoute#error received the error thrown from setup'); - return true; - } - } - }); - - bootApplication(); - - handleURLRejectsWith(assert, '/specials/1', 'Setup error'); - - run(() => resolve(menuItem)); -}); - -QUnit.test('ApplicationRoute\'s default error handler can be overridden', function(assert) { - assert.expect(2); - - Router.map(function() { - this.route('home', { path: '/' }); - this.route('special', { path: '/specials/:menu_item_id' }); - }); - - let menuItem, resolve; - - App.MenuItem = EmberObject.extend(); - App.MenuItem.reopenClass({ - find(id) { - menuItem = App.MenuItem.create({ id: id }); - return new RSVP.Promise(function(res) { - resolve = res; - }); - } - }); - - App.ApplicationRoute = Route.extend({ - actions: { - error(reason) { - assert.equal(reason, 'Setup error', 'error was correctly passed to custom ApplicationRoute handler'); - return true; - } - } - }); - - - App.SpecialRoute = Route.extend({ - setup() { - throw 'Setup error'; - } - }); - - bootApplication(); - - handleURLRejectsWith(assert, '/specials/1', 'Setup error'); - - run(() => resolve(menuItem)); -}); - -QUnit.test('Moving from one page to another triggers the correct callbacks', function(assert) { - assert.expect(3); - let done = assert.async(); - - Router.map(function() { - this.route('home', { path: '/' }); - this.route('special', { path: '/specials/:menu_item_id' }); - }); - - App.MenuItem = EmberObject.extend(); - - App.SpecialRoute = Route.extend({ - setupController(controller, model) { - set(controller, 'model', model); - } - }); - - setTemplate('home', compile( - '

Home

' - )); - - setTemplate('special', compile( - '

{{model.id}}

' - )); - - bootApplication(); - - registry.register('controller:special', Controller.extend()); - - let transition = handleURL(assert, '/'); - - run(() => { - transition.then(function() { - assert.equal(jQuery('h3', '#qunit-fixture').text(), 'Home', 'The app is now in the initial state'); - - let promiseContext = App.MenuItem.create({ id: 1 }); - run.later(() => RSVP.resolve(promiseContext), 1); - - return router.transitionTo('special', promiseContext); - }).then(function() { - assert.deepEqual(router.location.path, '/specials/1'); - done(); - }); - }); -}); - QUnit.test('Nested callbacks are not exited when moving to siblings', function(assert) { let done = assert.async(); Router.map(function() { diff --git a/packages/ember/tests/routing/decoupled_basic_test.js b/packages/ember/tests/routing/decoupled_basic_test.js index 67ebe360b2a..489dde99953 100644 --- a/packages/ember/tests/routing/decoupled_basic_test.js +++ b/packages/ember/tests/routing/decoupled_basic_test.js @@ -1,6 +1,8 @@ +import RSVP from 'rsvp'; import { Route } from 'ember-routing'; -import { Controller, A as emberA } from 'ember-runtime'; +import { Controller, Object as EmberObject } from 'ember-runtime'; import { moduleFor, ApplicationTestCase } from 'internal-test-helpers'; +import { computed, run } from 'ember-metal'; moduleFor('Basic Routing - Decoupled from global resovler', class extends ApplicationTestCase { @@ -23,6 +25,19 @@ class extends ApplicationTestCase { return this.getController('application').get('currentPath'); } + get currentURL() { + return this.appRouter.get('currentURL'); + } + + handleURLRejectsWith(context, assert, path, expectedReason) { + return context.visit(path).then(() => { + assert.ok(false, 'expected handleURLing: `' + path + '` to fail'); + }).catch(reason => { + assert.equal(reason, expectedReason); + }); + } + + ['@test warn on URLs not included in the route set']() { return this.visit('/').then(() => { expectAssertion(() => { @@ -268,11 +283,11 @@ class extends ApplicationTestCase { this.add('route:home', Route.extend({ setupController(controller) { - controller.set('hours', emberA([ + controller.set('hours', [ 'Monday through Friday: 9am to 5pm', 'Saturday: Noon to Midnight', 'Sunday: Noon to 6pm' - ])); + ]); } })); return this.visit('/').then(() => { @@ -318,7 +333,7 @@ class extends ApplicationTestCase { let myController = this.applicationInstance.lookup('controller:myController'); let text = this.$('p').text(); - assert.deepEqual(homeRoute.controller, myController, + assert.equal(homeRoute.controller, myController, 'route controller is set by controllerName' ); assert.equal(text, 'foo', @@ -326,4 +341,373 @@ class extends ApplicationTestCase { ); }); } + + [`@test The route controller specified via controllerName is used in render`](assert) { + this.router.map(function() { + this.route('home', { path: '/' }); + }); + + this.add('route:home', Route.extend({ + controllerName: 'myController', + renderTemplate() { + this.render('alternative_home'); + } + })); + + this.add('controller:myController', Controller.extend({ + myValue: 'foo' + })); + + this.addTemplate('alternative_home', '

alternative home: {{myValue}}

'); + + + return this.visit('/').then(() => { + let homeRoute = this.applicationInstance.lookup('route:home'); + let myController = this.applicationInstance.lookup('controller:myController'); + let text = this.$('p').text(); + + assert.equal(homeRoute.controller, myController, + 'route controller is set by controllerName'); + + assert.equal(text, 'alternative home: foo', + 'The homepage template was rendered with data from the custom controller'); + }); + + } + + [`@test The route controller specified via controllerName is used in render even when a controller with the routeName is available`](assert) { + this.router.map(function() { + this.route('home', { path: '/' }); + }); + + this.addTemplate('home', '

home: {{myValue}}

'); + + this.add('route:home', Route.extend({ + controllerName: 'myController' + })); + + this.add('controller:home', Controller.extend({ + myValue: 'home' + })); + + this.add('controller:myController', Controller.extend({ + myValue: 'myController' + })); + + return this.visit('/').then(() => { + let homeRoute = this.applicationInstance.lookup('route:home'); + let myController = this.applicationInstance.lookup('controller:myController'); + let text = this.$('p').text(); + + assert.equal(homeRoute.controller, myController, + 'route controller is set by controllerName'); + + assert.equal(text, 'home: myController', + 'The homepage template was rendered with data from the custom controller'); + }); + } + + [`@test The Homepage with a 'setupController' hook modifying other controllers`](assert) { + this.router.map(function() { + this.route('home', { path: '/' }); + }); + + this.add('route:home', Route.extend({ + setupController(/* controller */) { + this.controllerFor('home').set('hours', [ + 'Monday through Friday: 9am to 5pm', + 'Saturday: Noon to Midnight', + 'Sunday: Noon to 6pm' + ]); + } + })); + + this.addTemplate('home', '
    {{#each hours as |entry|}}
  • {{entry}}
  • {{/each}}
'); + + return this.visit('/').then(() => { + let text = this.$('ul li:nth-child(3)').text(); + + assert.equal(text, 'Sunday: Noon to 6pm', + 'The template was rendered with the hours context'); + }); + } + + [`@test The Homepage with a computed model that does not get overridden`](assert) { + this.router.map(function() { + this.route('home', { path: '/' }); + }); + + this.add('controller:home', Controller.extend({ + model: computed(function() { + return [ + 'Monday through Friday: 9am to 5pm', + 'Saturday: Noon to Midnight', + 'Sunday: Noon to 6pm' + ]; + }) + })); + + this.addTemplate('home', '
    {{#each model as |passage|}}
  • {{passage}}
  • {{/each}}
'); + + return this.visit('/').then(() => { + let text = this.$('ul li:nth-child(3)').text(); + + assert.equal(text, 'Sunday: Noon to 6pm', + 'The template was rendered with the context intact'); + }); + } + + [`@test The Homepage getting its controller context via model`](assert) { + this.router.map(function() { + this.route('home', { path: '/' }); + }); + + this.add('route:home', Route.extend({ + model() { + return [ + 'Monday through Friday: 9am to 5pm', + 'Saturday: Noon to Midnight', + 'Sunday: Noon to 6pm' + ]; + }, + + setupController(controller, model) { + assert.equal(this.controllerFor('home'), controller); + + this.controllerFor('home').set('hours', model); + } + })); + + this.addTemplate('home', '
    {{#each hours as |entry|}}
  • {{entry}}
  • {{/each}}
'); + + return this.visit('/').then(() => { + let text = this.$('ul li:nth-child(3)').text(); + + assert.equal(text, 'Sunday: Noon to 6pm', + 'The template was rendered with the hours context'); + }); + + } + + [`@test The Specials Page getting its controller context by deserializing the params hash`](assert) { + this.router.map(function() { + this.route('home', { path: '/' }); + this.route('special', { path: '/specials/:menu_item_id' }); + }); + + this.add('route:special', Route.extend({ + model(params) { + return EmberObject.create({ + menuItemId: params.menu_item_id + }); + } + })); + + this.addTemplate('special', '

{{model.menuItemId}}

'); + + return this.visit('/specials/1').then(() => { + let text = this.$('p').text(); + + assert.equal(text, '1', + 'The model was used to render the template'); + }); + } + + + ['@test The Specials Page defaults to looking models up via `find`']() { + let MenuItem = EmberObject.extend(); + MenuItem.reopenClass({ + find(id) { + return MenuItem.create({id}); + } + }); + this.add('model:menu_item', MenuItem); + + this.router.map(function() { + this.route('home', { path: '/' }); + this.route('special', { path: '/specials/:menu_item_id' }); + }); + + this.addTemplate('special', '{{model.id}}'); + + return this.visit('/specials/1').then(() => { + this.assertText('1', 'The model was used to render the template'); + }); + } + + ['@test The Special Page returning a promise puts the app into a loading state until the promise is resolved']() { + this.router.map(function() { + this.route('home', { path: '/' }); + this.route('special', { path: '/specials/:menu_item_id' }); + }); + + let menuItem, resolve; + + let MenuItem = EmberObject.extend(); + MenuItem.reopenClass({ + find(id) { + menuItem = MenuItem.create({ id: id }); + + return new RSVP.Promise(function(res) { + resolve = res; + }); + } + }); + + this.add('model:menu_item', MenuItem); + + this.addTemplate('special', '

{{model.id}}

'); + this.addTemplate('loading', '

LOADING!

'); + + let visited = this.visit('/specials/1'); + this.assertText('LOADING!', 'The app is in the loading state'); + + resolve(menuItem); + + return visited.then(() => { + this.assertText('1', 'The app is now in the specials state'); + }); + } + + [`@test The loading state doesn't get entered for promises that resolve on the same run loop`](assert) { + this.router.map(function() { + this.route('home', { path: '/' }); + this.route('special', { path: '/specials/:menu_item_id' }); + }); + + let MenuItem = EmberObject.extend(); + MenuItem.reopenClass({ + find(id) { + return { id: id }; + } + }); + + this.add('model:menu_item', MenuItem); + + + this.add('route:loading', Route.extend({ + enter() { + assert.ok(false, 'LoadingRoute shouldn\'t have been entered.'); + } + })); + + this.addTemplate('special', '

{{model.id}}

'); + this.addTemplate('loading', '

LOADING!

'); + + return this.visit('/specials/1').then(() => { + let text = this.$('p').text(); + + assert.equal(text, '1', 'The app is now in the specials state'); + }); + } + + ['@test The Special page returning an error invokes SpecialRoute\'s error handler'](assert) { + this.router.map(function() { + this.route('home', { path: '/' }); + this.route('special', { path: '/specials/:menu_item_id' }); + }); + + let menuItem, promise, resolve; + + let MenuItem = EmberObject.extend(); + MenuItem.reopenClass({ + find(id) { + menuItem = MenuItem.create({ id: id }); + promise = new RSVP.Promise(res => resolve = res); + + return promise; + } + }); + + this.add('model:menu_item', MenuItem); + + + this.add('route:special', Route.extend({ + setup() { + throw 'Setup error'; + }, + actions: { + error(reason) { + assert.equal(reason, 'Setup error', 'SpecialRoute#error received the error thrown from setup'); + return true; + } + } + })); + + this.handleURLRejectsWith(this, assert, 'specials/1', 'Setup error'); + + run(() => resolve(menuItem)); + } + + ['@test ApplicationRoute\'s default error handler can be overridden'](assert) { + assert.expect(2); + + this.router.map(function() { + this.route('home', { path: '/' }); + this.route('special', { path: '/specials/:menu_item_id' }); + }); + + let menuItem, resolve; + + let MenuItem = EmberObject.extend(); + + MenuItem.reopenClass({ + find(id) { + menuItem = MenuItem.create({ id: id }); + return new RSVP.Promise(res => resolve = res); + } + }); + this.add('model:menu_item', MenuItem); + + this.add('route:application', Route.extend({ + actions: { + error(reason) { + assert.equal(reason, 'Setup error', 'error was correctly passed to custom ApplicationRoute handler'); + return true; + } + } + })); + + this.add('route:special', Route.extend({ + setup() { + throw 'Setup error'; + } + })); + + this.handleURLRejectsWith(this, assert, '/specials/1', 'Setup error'); + + run(() => resolve(menuItem)); + } + + ['@test Moving from one page to another triggers the correct callbacks'](assert) { + assert.expect(3); + + this.router.map(function() { + this.route('home', { path: '/' }); + this.route('special', { path: '/specials/:menu_item_id' }); + }); + + let MenuItem = EmberObject.extend(); + MenuItem.reopenClass({ + find(id) { + return MenuItem.create({ id: id }); + } + }); + this.add('model:menu_item', MenuItem); + + this.addTemplate('home', '

Home

'); + this.addTemplate('special', '

{{model.id}}

'); + + return this.visit('/').then(() => { + this.assertText('Home', 'The app is now in the initial state'); + + let promiseContext = MenuItem.create({ id: 1 }); + + return this.visit('/specials/1', promiseContext); + }).then(() => { + assert.equal(this.currentURL, '/specials/1'); + this.assertText('1', 'The app is now transitioned'); + }); + } + }); diff --git a/packages/ember/tests/routing/toplevel_dom_test.js b/packages/ember/tests/routing/toplevel_dom_test.js index d004cbbd5e1..e22e9172dfb 100644 --- a/packages/ember/tests/routing/toplevel_dom_test.js +++ b/packages/ember/tests/routing/toplevel_dom_test.js @@ -1,7 +1,20 @@ +import { ENV } from 'ember-environment'; import { moduleFor, ApplicationTestCase } from 'internal-test-helpers'; moduleFor('Top Level DOM Structure', class extends ApplicationTestCase { - ['@feature(!ember-glimmer-remove-application-template-wrapper) Topmost template always get an element']() { + constructor() { + super(); + this._APPLICATION_TEMPLATE_WRAPPER = ENV._APPLICATION_TEMPLATE_WRAPPER; + } + + teardown() { + super.teardown(); + ENV._APPLICATION_TEMPLATE_WRAPPER = this._APPLICATION_TEMPLATE_WRAPPER; + } + + ['@test topmost template with wrapper']() { + ENV._APPLICATION_TEMPLATE_WRAPPER = true; + this.addTemplate('application', 'hello world'); return this.visit('/').then(() => { @@ -9,7 +22,9 @@ moduleFor('Top Level DOM Structure', class extends ApplicationTestCase { }); } - ['@feature(ember-glimmer-remove-application-template-wrapper) Topmost template does not get an element']() { + ['@test topmost template without wrapper']() { + ENV._APPLICATION_TEMPLATE_WRAPPER = false; + this.addTemplate('application', 'hello world'); return this.visit('/').then(() => { diff --git a/packages/internal-test-helpers/lib/test-cases/abstract-application.js b/packages/internal-test-helpers/lib/test-cases/abstract-application.js index 4acbbb632e4..3005e6fc6fa 100644 --- a/packages/internal-test-helpers/lib/test-cases/abstract-application.js +++ b/packages/internal-test-helpers/lib/test-cases/abstract-application.js @@ -1,5 +1,5 @@ import { compile } from 'ember-template-compiler'; -import { EMBER_GLIMMER_REMOVE_APPLICATION_TEMPLATE_WRAPPER } from 'ember/features'; +import { ENV } from 'ember-environment'; import AbstractTestCase from './abstract'; import { runDestroy } from '../run'; @@ -30,10 +30,10 @@ export default class AbstractApplicationTestCase extends AbstractTestCase { get element() { if (this._element) { return this._element; - } else if (EMBER_GLIMMER_REMOVE_APPLICATION_TEMPLATE_WRAPPER) { - return this._element = document.querySelector('#qunit-fixture'); - } else { + } else if (ENV._APPLICATION_TEMPLATE_WRAPPER) { return this._element = document.querySelector('#qunit-fixture > div.ember-view'); + } else { + return this._element = document.querySelector('#qunit-fixture'); } }