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');
}
}