/* globals EmberDev */ import { RenderingTest, moduleFor } from '../utils/test-case'; import { applyMixins } from '../utils/abstract-test-case'; import { set, computed } from '@ember/-internals/metal'; import { getDebugFunction, setDebugFunction } from '@ember/debug'; import { readOnly } from '@ember/object/computed'; import { Object as EmberObject, ObjectProxy } from '@ember/-internals/runtime'; import { classes } from '../utils/test-helpers'; import { constructStyleDeprecationMessage } from '@ember/-internals/views'; import { Component, SafeString, htmlSafe } from '../utils/helpers'; moduleFor( 'Static content tests', class extends RenderingTest { ['@test it can render a static text node']() { this.render('hello'); let text1 = this.assertTextNode(this.firstChild, 'hello'); this.runTask(() => this.rerender()); let text2 = this.assertTextNode(this.firstChild, 'hello'); this.assertSameNode(text1, text2); } ['@test it can render a static element']() { this.render('

hello

'); let p1 = this.assertElement(this.firstChild, { tagName: 'p' }); let text1 = this.assertTextNode(this.firstChild.firstChild, 'hello'); this.runTask(() => this.rerender()); let p2 = this.assertElement(this.firstChild, { tagName: 'p' }); let text2 = this.assertTextNode(this.firstChild.firstChild, 'hello'); this.assertSameNode(p1, p2); this.assertSameNode(text1, text2); } ['@test it can render a static template']() { let template = `

Welcome to Ember.js

Why you should use Ember.js?

  1. It's great
  2. It's awesome
  3. It's Ember.js
`; this.render(template); this.assertHTML(template); this.runTask(() => this.rerender()); this.assertHTML(template); } } ); class DynamicContentTest extends RenderingTest { /* abstract */ renderPath(/* path, context = {} */) { throw new Error('Not implemented: `renderValues`'); } assertIsEmpty() { this.assert.strictEqual(this.firstChild, null); } /* abstract */ assertContent(/* content */) { throw new Error('Not implemented: `assertContent`'); } ['@test it can render a dynamic path']() { this.renderPath('message', { message: 'hello' }); this.assertContent('hello'); this.assertStableRerender(); this.runTask(() => set(this.context, 'message', 'goodbye')); this.assertContent('goodbye'); this.assertInvariants(); this.runTask(() => set(this.context, 'message', 'hello')); this.assertContent('hello'); this.assertInvariants(); } ['@test resolves the string length properly']() { this.render('

{{foo.length}}

', { foo: undefined }); this.assertHTML('

'); this.assertStableRerender(); this.runTask(() => set(this.context, 'foo', 'foo')); this.assertHTML('

3

'); this.runTask(() => set(this.context, 'foo', '')); this.assertHTML('

0

'); this.runTask(() => set(this.context, 'foo', undefined)); this.assertHTML('

'); } ['@test resolves the array length properly']() { this.render('

{{foo.length}}

', { foo: undefined }); this.assertHTML('

'); this.assertStableRerender(); this.runTask(() => set(this.context, 'foo', [1, 2, 3])); this.assertHTML('

3

'); this.runTask(() => set(this.context, 'foo', [])); this.assertHTML('

0

'); this.runTask(() => set(this.context, 'foo', undefined)); this.assertHTML('

'); } ['@test it can render a capitalized path with no deprecation']() { expectNoDeprecation(); this.renderPath('CaptializedPath', { CaptializedPath: 'no deprecation' }); this.assertContent('no deprecation'); this.assertStableRerender(); this.runTask(() => set(this.context, 'CaptializedPath', 'still no deprecation')); this.assertContent('still no deprecation'); this.assertInvariants(); this.runTask(() => set(this.context, 'CaptializedPath', 'no deprecation')); this.assertContent('no deprecation'); this.assertInvariants(); } ['@test it can render undefined dynamic paths']() { this.renderPath('name', {}); this.assertIsEmpty(); this.assertStableRerender(); this.runTask(() => set(this.context, 'name', 'foo-bar')); this.assertContent('foo-bar'); this.runTask(() => set(this.context, 'name', undefined)); this.assertIsEmpty(); } ['@test it can render a deeply nested dynamic path']() { this.renderPath('a.b.c.d.e.f', { a: { b: { c: { d: { e: { f: 'hello' } } } } }, }); this.assertContent('hello'); this.assertStableRerender(); this.runTask(() => set(this.context, 'a.b.c.d.e.f', 'goodbye')); this.assertContent('goodbye'); this.assertInvariants(); this.runTask(() => set(this.context, 'a.b.c.d', { e: { f: 'aloha' } })); this.assertContent('aloha'); this.assertInvariants(); this.runTask(() => { set(this.context, 'a', { b: { c: { d: { e: { f: 'hello' } } } } }); }); this.assertContent('hello'); this.assertInvariants(); } ['@test it can render a computed property']() { let Formatter = EmberObject.extend({ formattedMessage: computed('message', function() { return this.get('message').toUpperCase(); }), }); let m = Formatter.create({ message: 'hello' }); this.renderPath('m.formattedMessage', { m }); this.assertContent('HELLO'); this.assertStableRerender(); this.runTask(() => set(m, 'message', 'goodbye')); this.assertContent('GOODBYE'); this.assertInvariants(); this.runTask(() => set(this.context, 'm', Formatter.create({ message: 'hello' }))); this.assertContent('HELLO'); this.assertInvariants(); } ['@test it can render a computed property with nested dependency']() { let Formatter = EmberObject.extend({ formattedMessage: computed('messenger.message', function() { return this.get('messenger.message').toUpperCase(); }), }); let m = Formatter.create({ messenger: { message: 'hello' } }); this.renderPath('m.formattedMessage', { m }); this.assertContent('HELLO'); this.assertStableRerender(); this.runTask(() => set(m, 'messenger.message', 'goodbye')); this.assertContent('GOODBYE'); this.assertInvariants(); this.runTask(() => set(this.context, 'm', Formatter.create({ messenger: { message: 'hello' } })) ); this.assertContent('HELLO'); this.assertInvariants(); } ['@test it can read from a proxy object']() { this.renderPath('proxy.name', { proxy: ObjectProxy.create({ content: { name: 'Tom Dale' } }), }); this.assertContent('Tom Dale'); this.assertStableRerender(); this.runTask(() => set(this.context, 'proxy.content.name', 'Yehuda Katz')); this.assertContent('Yehuda Katz'); this.assertInvariants(); this.runTask(() => set(this.context, 'proxy.content', { name: 'Godfrey Chan' })); this.assertContent('Godfrey Chan'); this.assertInvariants(); this.runTask(() => set(this.context, 'proxy.name', 'Stefan Penner')); this.assertContent('Stefan Penner'); this.assertInvariants(); this.runTask(() => set(this.context, 'proxy.content', null)); this.assertIsEmpty(); this.runTask(() => set(this.context, 'proxy', ObjectProxy.create({ content: { name: 'Tom Dale' } })) ); this.assertContent('Tom Dale'); this.assertInvariants(); } ['@test it can read from a nested path in a proxy object']() { this.renderPath('proxy.name.last', { proxy: ObjectProxy.create({ content: { name: { first: 'Tom', last: 'Dale' } }, }), }); this.assertContent('Dale'); this.assertStableRerender(); this.runTask(() => set(this.context, 'proxy.content.name.last', 'Cruise')); this.assertContent('Cruise'); this.assertInvariants(); this.runTask(() => set(this.context, 'proxy.content.name.first', 'Suri')); this.assertStableRerender(); this.runTask(() => set(this.context, 'proxy.content.name', { first: 'Yehuda', last: 'Katz' })); this.assertContent('Katz'); this.assertInvariants(); this.runTask(() => set(this.context, 'proxy.content', { name: { first: 'Godfrey', last: 'Chan' }, }) ); this.assertContent('Chan'); this.assertInvariants(); this.runTask(() => set(this.context, 'proxy.name', { first: 'Stefan', last: 'Penner' })); this.assertContent('Penner'); this.assertInvariants(); this.runTask(() => set(this.context, 'proxy', null)); this.assertIsEmpty(); this.runTask(() => set( this.context, 'proxy', ObjectProxy.create({ content: { name: { first: 'Tom', last: 'Dale' } }, }) ) ); this.assertContent('Dale'); this.assertInvariants(); } ['@test it can read from a path flipping between a proxy and a real object']() { this.renderPath('proxyOrObject.name.last', { proxyOrObject: ObjectProxy.create({ content: { name: { first: 'Tom', last: 'Dale' } }, }), }); this.assertContent('Dale'); this.assertStableRerender(); this.runTask(() => set(this.context, 'proxyOrObject', { name: { first: 'Tom', last: 'Dale' }, }) ); this.assertStableRerender(); this.runTask(() => set(this.context, 'proxyOrObject.name.last', 'Cruise')); this.assertContent('Cruise'); this.assertInvariants(); this.runTask(() => set(this.context, 'proxyOrObject.name.first', 'Suri')); this.assertStableRerender(); this.runTask(() => set(this.context, 'proxyOrObject', { name: { first: 'Yehuda', last: 'Katz' }, }) ); this.assertContent('Katz'); this.assertInvariants(); this.runTask(() => set( this.context, 'proxyOrObject', ObjectProxy.create({ content: { name: { first: 'Godfrey', last: 'Chan' } }, }) ) ); this.assertContent('Chan'); this.assertInvariants(); this.runTask(() => set(this.context, 'proxyOrObject.content.name', { first: 'Stefan', last: 'Penner', }) ); this.assertContent('Penner'); this.assertInvariants(); this.runTask(() => set(this.context, 'proxyOrObject', null)); this.assertIsEmpty(); this.runTask(() => set( this.context, 'proxyOrObject', ObjectProxy.create({ content: { name: { first: 'Tom', last: 'Dale' } }, }) ) ); this.assertContent('Dale'); this.assertInvariants(); } ['@test it can read from a path flipping between a real object and a proxy']() { this.renderPath('objectOrProxy.name.last', { objectOrProxy: { name: { first: 'Tom', last: 'Dale' } }, }); this.assertContent('Dale'); this.assertStableRerender(); this.runTask(() => set( this.context, 'objectOrProxy', ObjectProxy.create({ content: { name: { first: 'Tom', last: 'Dale' } }, }) ) ); this.assertStableRerender(); this.runTask(() => set(this.context, 'objectOrProxy.content.name.last', 'Cruise')); this.assertContent('Cruise'); this.assertInvariants(); this.runTask(() => set(this.context, 'objectOrProxy.content.name.first', 'Suri')); this.assertStableRerender(); this.runTask(() => set(this.context, 'objectOrProxy.content', { name: { first: 'Yehuda', last: 'Katz' }, }) ); this.assertContent('Katz'); this.assertInvariants(); this.runTask(() => set(this.context, 'objectOrProxy', { name: { first: 'Godfrey', last: 'Chan' }, }) ); this.assertContent('Chan'); this.assertInvariants(); this.runTask(() => set(this.context, 'objectOrProxy.name', { first: 'Stefan', last: 'Penner', }) ); this.assertContent('Penner'); this.assertInvariants(); this.runTask(() => set(this.context, 'objectOrProxy', null)); this.assertIsEmpty(); this.runTask(() => set(this.context, 'objectOrProxy', { name: { first: 'Tom', last: 'Dale' }, }) ); this.assertContent('Dale'); this.assertInvariants(); } ['@test it can read from a null object']() { let nullObject = Object.create(null); nullObject['message'] = 'hello'; this.renderPath('nullObject.message', { nullObject }); this.assertContent('hello'); this.assertStableRerender(); this.runTask(() => set(nullObject, 'message', 'goodbye')); this.assertContent('goodbye'); this.assertInvariants(); nullObject = Object.create(null); nullObject['message'] = 'hello'; this.runTask(() => set(this.context, 'nullObject', nullObject)); this.assertContent('hello'); this.assertInvariants(); } ['@test it can render a readOnly property of a path']() { let Messenger = EmberObject.extend({ message: readOnly('a.b.c'), }); let messenger = Messenger.create({ a: { b: { c: 'hello', }, }, }); this.renderPath('messenger.message', { messenger }); this.assertContent('hello'); this.assertStableRerender(); this.runTask(() => set(messenger, 'a.b.c', 'hi')); this.assertContent('hi'); this.assertInvariants(); this.runTask(() => set(this.context, 'messenger.a.b', { c: 'goodbye', }) ); this.assertContent('goodbye'); this.assertInvariants(); this.runTask(() => set(this.context, 'messenger', { message: 'hello', }) ); this.assertContent('hello'); this.assertInvariants(); } ['@test it can render a property on a function']() { let func = () => {}; func.aProp = 'this is a property on a function'; this.renderPath('func.aProp', { func }); this.assertContent('this is a property on a function'); this.assertStableRerender(); // this.runTask(() => set(func, 'aProp', 'still a property on a function')); // this.assertContent('still a property on a function'); // this.assertInvariants(); // func = () => {}; // func.aProp = 'a prop on a new function'; // this.runTask(() => set(this.context, 'func', func)); // this.assertContent('a prop on a new function'); // this.assertInvariants(); } } const EMPTY = {}; class ContentTestGenerator { constructor(cases, tag = '@test') { this.cases = cases; this.tag = tag; } generate([value, expected, label]) { let tag = this.tag; label = label || value; if (expected === EMPTY) { return { [`${tag} rendering ${label}`]() { this.renderPath('value', { value }); this.assertIsEmpty(); this.runTask(() => set(this.context, 'value', 'hello')); this.assertContent('hello'); this.runTask(() => set(this.context, 'value', value)); this.assertIsEmpty(); }, }; } else { return { [`${tag} rendering ${label}`]() { this.renderPath('value', { value }); this.assertContent(expected); this.assertStableRerender(); this.runTask(() => set(this.context, 'value', 'hello')); this.assertContent('hello'); this.assertInvariants(); this.runTask(() => set(this.context, 'value', value)); this.assertContent(expected); this.assertInvariants(); }, }; } } } const SharedContentTestCases = new ContentTestGenerator([ ['foo', 'foo'], [0, '0'], [-0, '0', '-0'], [1, '1'], [-1, '-1'], [0.0, '0', '0.0'], [0.5, '0.5'], [undefined, EMPTY], [null, EMPTY], [true, 'true'], [false, 'false'], [NaN, 'NaN'], [new Date(2000, 0, 1), String(new Date(2000, 0, 1)), 'a Date object'], [Infinity, 'Infinity'], [1 / -0, '-Infinity'], [{ foo: 'bar' }, '[object Object]', `{ foo: 'bar' }`], [ { toString() { return 'foo'; }, }, 'foo', 'an object with a custom toString function', ], [ { valueOf() { return 1; }, }, '[object Object]', 'an object with a custom valueOf function', ], // Escaping tests ['MaxJames', 'MaxJames'], ]); let GlimmerContentTestCases = new ContentTestGenerator([ [Object.create(null), EMPTY, 'an object with no toString'], ]); if (typeof Symbol !== 'undefined') { GlimmerContentTestCases.cases.push([Symbol('debug'), 'Symbol(debug)', 'a symbol']); } applyMixins(DynamicContentTest, SharedContentTestCases, GlimmerContentTestCases); moduleFor( 'Dynamic content tests (content position)', class extends DynamicContentTest { renderPath(path, context = {}) { this.render(`{{${path}}}`, context); } assertContent(content) { this.assert.strictEqual(this.nodesCount, 1, 'It should render exactly one text node'); this.assertTextNode(this.firstChild, content); // this.takeSnapshot(); } ['@test it can render empty safe strings [GH#16314]']() { this.render('before {{value}} after', { value: htmlSafe('hello') }); this.assertHTML('before hello after'); this.assertStableRerender(); this.runTask(() => set(this.context, 'value', htmlSafe(''))); this.assertHTML('before after'); this.runTask(() => set(this.context, 'value', htmlSafe('hello'))); this.assertHTML('before hello after'); } } ); moduleFor( 'Dynamic content tests (content concat)', class extends DynamicContentTest { renderPath(path, context = {}) { this.render(`{{concat "" ${path} ""}}`, context); } assertContent(content) { this.assert.strictEqual(this.nodesCount, 1, 'It should render exactly one text node'); this.assertTextNode(this.firstChild, content); } } ); moduleFor( 'Dynamic content tests (inside an element)', class extends DynamicContentTest { renderPath(path, context = {}) { this.render(`

{{${path}}}

`, context); } assertIsEmpty() { this.assert.strictEqual(this.nodesCount, 1, 'It should render exactly one

tag'); this.assertElement(this.firstChild, { tagName: 'p' }); this.assertText(''); } assertContent(content) { this.assert.strictEqual(this.nodesCount, 1, 'It should render exactly one

tag'); this.assertElement(this.firstChild, { tagName: 'p' }); this.assertText(content); } } ); moduleFor( 'Dynamic content tests (attribute position)', class extends DynamicContentTest { renderPath(path, context = {}) { this.render(`

`, context); } assertIsEmpty() { this.assert.strictEqual(this.nodesCount, 1, 'It should render exactly one
tag'); this.assertElement(this.firstChild, { tagName: 'div', content: '' }); } assertContent(content) { this.assert.strictEqual(this.nodesCount, 1, 'It should render exactly one
tag'); this.assertElement(this.firstChild, { tagName: 'div', attrs: { 'data-foo': content }, content: '', }); } } ); class TrustedContentTest extends DynamicContentTest { assertIsEmpty() { this.assert.strictEqual(this.firstChild, null); } assertContent(content) { this.assertHTML(content); } assertStableRerender() { this.takeSnapshot(); this.runTask(() => this.rerender()); super.assertInvariants(); } assertInvariants() { // If it's not stable, we will wipe out all the content and replace them, // so there are no invariants } } moduleFor( 'Dynamic content tests (trusted)', class extends TrustedContentTest { renderPath(path, context = {}) { this.render(`{{{${path}}}}`, context); } ['@test updating trusted curlies']() { this.render('{{{htmlContent}}}{{{nested.htmlContent}}}', { htmlContent: 'Max', nested: { htmlContent: 'James' }, }); this.assertContent('MaxJames'); this.runTask(() => this.rerender()); this.assertStableRerender(); this.runTask(() => set(this.context, 'htmlContent', 'Max')); this.assertContent('MaxJames'); this.runTask(() => set(this.context, 'nested.htmlContent', 'Jammie')); this.assertContent('MaxJammie'); this.runTask(() => { set(this.context, 'htmlContent', 'Max'); set(this.context, 'nested', { htmlContent: 'James' }); }); this.assertContent('MaxJames'); } ['@test empty content in trusted curlies [GH#14978]']() { this.render('before {{{value}}} after', { value: 'hello', }); this.assertContent('before hello after'); this.runTask(() => this.rerender()); this.assertStableRerender(); this.runTask(() => set(this.context, 'value', undefined)); this.assertContent('before after'); this.runTask(() => set(this.context, 'value', 'hello')); this.assertContent('before hello after'); this.runTask(() => set(this.context, 'value', null)); this.assertContent('before after'); this.runTask(() => set(this.context, 'value', 'hello')); this.assertContent('before hello after'); this.runTask(() => set(this.context, 'value', '')); this.assertContent('before after'); this.runTask(() => set(this.context, 'value', 'hello')); this.assertContent('before hello after'); this.runTask(() => set(this.context, 'value', htmlSafe(''))); this.assertContent('before after'); this.runTask(() => set(this.context, 'value', 'hello')); this.assertContent('before hello after'); } } ); moduleFor( 'Dynamic content tests (integration)', class extends RenderingTest { ['@test it can render a dynamic template']() { let template = `

Welcome to {{framework}}

Why you should use {{framework}}?

  1. It's great
  2. It's awesome
  3. It's {{framework}}
`; let ember = `

Welcome to Ember.js

Why you should use Ember.js?

  1. It's great
  2. It's awesome
  3. It's Ember.js
`; let react = `

Welcome to React

Why you should use React?

  1. It's great
  2. It's awesome
  3. It's React
`; this.render(template, { framework: 'Ember.js', }); this.assertHTML(ember); this.runTask(() => this.rerender()); this.assertHTML(ember); this.runTask(() => set(this.context, 'framework', 'React')); this.assertHTML(react); this.runTask(() => set(this.context, 'framework', 'Ember.js')); this.assertHTML(ember); } ['@test it should evaluate to nothing if part of the path is `undefined`']() { this.render('{{foo.bar.baz.bizz}}', { foo: {}, }); this.assertText(''); this.runTask(() => this.rerender()); this.assertText(''); this.runTask(() => set(this.context, 'foo', { bar: { baz: { bizz: 'Hey!' } }, }) ); this.assertText('Hey!'); this.runTask(() => set(this.context, 'foo', {})); this.assertText(''); this.runTask(() => set(this.context, 'foo', { bar: { baz: { bizz: 'Hello!' } }, }) ); this.assertText('Hello!'); this.runTask(() => set(this.context, 'foo', {})); this.assertText(''); } ['@test it should evaluate to nothing if part of the path is a primative']() { this.render('{{foo.bar.baz.bizz}}', { foo: { bar: true }, }); this.assertText(''); this.runTask(() => this.rerender()); this.assertText(''); this.runTask(() => set(this.context, 'foo', { bar: false, }) ); this.assertText(''); this.runTask(() => set(this.context, 'foo', { bar: 'Haha', }) ); this.assertText(''); this.runTask(() => set(this.context, 'foo', { bar: null, }) ); this.assertText(''); this.runTask(() => set(this.context, 'foo', { bar: undefined, }) ); this.assertText(''); this.runTask(() => set(this.context, 'foo', { bar: 1, }) ); this.assertText(''); this.runTask(() => set(this.context, 'foo', { bar: { baz: { bizz: 'Hello!' } }, }) ); this.assertText('Hello!'); this.runTask(() => set(this.context, 'foo', { bar: true, }) ); this.assertText(''); } ['@test can set dynamic href']() { this.render('Example', { model: { url: 'http://example.com', }, }); this.assertElement(this.firstChild, { tagName: 'a', content: 'Example', attrs: { href: 'http://example.com' }, }); this.runTask(() => this.rerender()); this.assertElement(this.firstChild, { tagName: 'a', content: 'Example', attrs: { href: 'http://example.com' }, }); this.runTask(() => set(this.context, 'model.url', 'http://linkedin.com')); this.assertElement(this.firstChild, { tagName: 'a', content: 'Example', attrs: { href: 'http://linkedin.com' }, }); this.runTask(() => set(this.context, 'model', { url: 'http://example.com' })); this.assertElement(this.firstChild, { tagName: 'a', content: 'Example', attrs: { href: 'http://example.com' }, }); } ['@test quoteless class attributes update correctly']() { this.render('
hello
', { fooBar: true, }); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { class: classes('foo-bar') }, }); this.runTask(() => this.rerender()); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { class: classes('foo-bar') }, }); this.runTask(() => set(this.context, 'fooBar', false)); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello' }); this.runTask(() => set(this.context, 'fooBar', true)); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { class: classes('foo-bar') }, }); } ['@test quoted class attributes update correctly'](assert) { this.render('
hello
', { fooBar: true, }); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { class: classes('foo-bar') }, }); this.runTask(() => this.rerender()); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { class: classes('foo-bar') }, }); this.runTask(() => set(this.context, 'fooBar', false)); assert.equal(this.firstChild.className, ''); this.runTask(() => set(this.context, 'fooBar', true)); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { class: classes('foo-bar') }, }); } ['@test unquoted class attribute can contain multiple classes']() { this.render('
hello
', { model: { classes: 'foo bar baz', }, }); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { class: classes('foo bar baz') }, }); this.runTask(() => this.rerender()); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { class: classes('foo bar baz') }, }); this.runTask(() => set(this.context, 'model.classes', 'fizz bizz')); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { class: classes('fizz bizz') }, }); this.runTask(() => set(this.context, 'model', { classes: 'foo bar baz' })); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { class: classes('foo bar baz') }, }); } ['@test unquoted class attribute']() { this.render('
hello
', { model: { foo: 'foo', }, }); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { class: classes('foo') }, }); this.runTask(() => this.rerender()); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { class: classes('foo') }, }); this.runTask(() => set(this.context, 'model.foo', 'fizz')); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { class: classes('fizz') }, }); this.runTask(() => set(this.context, 'model', { foo: 'foo' })); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { class: classes('foo') }, }); } ['@test quoted class attribute']() { this.render('
hello
', { model: { foo: 'foo', }, }); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { class: classes('foo') }, }); this.runTask(() => this.rerender()); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { class: classes('foo') }, }); this.runTask(() => set(this.context, 'model.foo', 'fizz')); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { class: classes('fizz') }, }); this.runTask(() => set(this.context, 'model', { foo: 'foo' })); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { class: classes('foo') }, }); } ['@test quoted class attribute can contain multiple classes']() { this.render('
hello
', { model: { classes: 'foo bar baz', }, }); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { class: classes('foo bar baz') }, }); this.runTask(() => this.rerender()); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { class: classes('foo bar baz') }, }); this.runTask(() => set(this.context, 'model.classes', 'fizz bizz')); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { class: classes('fizz bizz') }, }); this.runTask(() => set(this.context, 'model', { classes: 'foo bar baz' })); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { class: classes('foo bar baz') }, }); } ['@test class attribute concats bound values']() { this.render('
hello
', { model: { foo: 'foo', bar: 'bar', bizz: 'bizz', }, }); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { class: classes('foo bar bizz') }, }); this.runTask(() => this.rerender()); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { class: classes('foo bar bizz') }, }); this.runTask(() => set(this.context, 'model.foo', 'fizz')); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { class: classes('fizz bar bizz') }, }); this.runTask(() => set(this.context, 'model.bar', null)); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { class: classes('fizz bizz') }, }); this.runTask(() => set(this.context, 'model', { foo: 'foo', bar: 'bar', bizz: 'bizz', }) ); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { class: classes('foo bar bizz') }, }); } ['@test class attribute accepts nested helpers, and updates']() { this.render( `
hello
`, { model: { size: 'large', hasSize: true, hasShape: false, shape: 'round', }, } ); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { class: classes('large') }, }); this.runTask(() => this.rerender()); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { class: classes('large') }, }); this.runTask(() => set(this.context, 'model.hasShape', true)); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { class: classes('large round') }, }); this.runTask(() => set(this.context, 'model.hasSize', false)); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { class: classes('round') }, }); this.runTask(() => set(this.context, 'model', { size: 'large', hasSize: true, hasShape: false, shape: 'round', }) ); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { class: classes('large') }, }); } ['@test Multiple dynamic classes']() { this.render( '
hello
', { model: { foo: 'foo', bar: 'bar', fizz: 'fizz', baz: 'baz', }, } ); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { class: classes('foo bar fizz baz') }, }); this.runTask(() => this.rerender()); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { class: classes('foo bar fizz baz') }, }); this.runTask(() => { set(this.context, 'model.foo', null); set(this.context, 'model.fizz', null); }); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { class: classes('bar baz') }, }); this.runTask(() => { set(this.context, 'model', { foo: 'foo', bar: 'bar', fizz: 'fizz', baz: 'baz', }); }); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { class: classes('foo bar fizz baz') }, }); } ['@test classes are ordered: See issue #9912']() { this.render('
hello
', { model: { foo: 'foo', bar: 'bar', }, }); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { class: 'foo static bar' }, }); this.runTask(() => this.rerender()); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { class: 'foo static bar' }, }); this.runTask(() => { set(this.context, 'model.bar', null); }); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { class: 'foo static ' }, }); this.runTask(() => { set(this.context, 'model', { foo: 'foo', bar: 'bar', }); }); this.assertElement(this.firstChild, { tagName: 'div', content: 'hello', attrs: { class: 'foo static bar' }, }); } } ); let warnings, originalWarn; class StyleTest extends RenderingTest { constructor() { super(...arguments); warnings = []; originalWarn = getDebugFunction('warn'); setDebugFunction('warn', function(message, test) { if (!test) { warnings.push(message); } }); } teardown() { super.teardown(...arguments); setDebugFunction('warn', originalWarn); } assertStyleWarning(style) { this.assert.deepEqual(warnings, [constructStyleDeprecationMessage(style)]); } assertNoWarning() { this.assert.deepEqual(warnings, []); } } moduleFor( 'Inline style tests', class extends StyleTest { ['@test can set dynamic style']() { this.render('
', { model: { style: htmlSafe('width: 60px;'), }, }); this.assertElement(this.firstChild, { tagName: 'div', content: '', attrs: { style: 'width: 60px;' }, }); this.runTask(() => this.rerender()); this.assertElement(this.firstChild, { tagName: 'div', content: '', attrs: { style: 'width: 60px;' }, }); this.runTask(() => set(this.context, 'model.style', 'height: 60px;')); this.assertElement(this.firstChild, { tagName: 'div', content: '', attrs: { style: 'height: 60px;' }, }); this.runTask(() => set(this.context, 'model.style', null)); this.assertElement(this.firstChild, { tagName: 'div', content: '', attrs: {}, }); this.runTask(() => set(this.context, 'model', { style: 'width: 60px;' })); this.assertElement(this.firstChild, { tagName: 'div', content: '', attrs: { style: 'width: 60px;' }, }); } ['@test can set dynamic style with -html-safe']() { this.render('
', { model: { style: htmlSafe('width: 60px;'), }, }); this.assertElement(this.firstChild, { tagName: 'div', content: '', attrs: { style: 'width: 60px;' }, }); this.runTask(() => this.rerender()); this.assertElement(this.firstChild, { tagName: 'div', content: '', attrs: { style: 'width: 60px;' }, }); this.runTask(() => set(this.context, 'model.style', 'height: 60px;')); this.assertElement(this.firstChild, { tagName: 'div', content: '', attrs: { style: 'height: 60px;' }, }); this.runTask(() => set(this.context, 'model', { style: 'width: 60px;' })); this.assertElement(this.firstChild, { tagName: 'div', content: '', attrs: { style: 'width: 60px;' }, }); } } ); if (!EmberDev.runningProdBuild) { moduleFor( 'Inline style tests - warnings', class extends StyleTest { ['@test specifying
generates a warning']() { let userValue = 'width: 42px'; this.render('
', { userValue, }); this.assertStyleWarning(userValue); } ['@test specifying `attributeBindings: ["style"]` generates a warning']() { let FooBarComponent = Component.extend({ attributeBindings: ['style'], }); this.registerComponent('foo-bar', { ComponentClass: FooBarComponent, template: 'hello', }); let userValue = 'width: 42px'; this.render('{{foo-bar style=userValue}}', { userValue, }); this.assertStyleWarning(userValue); } ['@test specifying `
` works properly without a warning']() { this.render('
', { userValue: 'width: 42px', }); this.assertNoWarning(); } ['@test specifying `
` works properly with a SafeString']() { this.render('
', { userValue: new SafeString('width: 42px'), }); this.assertNoWarning(); } ['@test null value do not generate htmlsafe warning']() { this.render('
', { userValue: null, }); this.assertNoWarning(); } ['@test undefined value do not generate htmlsafe warning']() { this.render('
'); this.assertNoWarning(); } ['@test no warnings are triggered when using `-html-safe`']() { this.render('
', { userValue: 'width: 42px', }); this.assertNoWarning(); } ['@test no warnings are triggered when a safe string is quoted']() { this.render('
', { userValue: new SafeString('width: 42px'), }); this.assertNoWarning(); } ['@test binding warning is triggered when an unsafe string is quoted']() { let userValue = 'width: 42px'; this.render('
', { userValue, }); this.assertStyleWarning(userValue); } ['@test binding warning is triggered when a safe string for a complete property is concatenated in place']() { let userValue = 'width: 42px'; this.render('
', { userValue: new SafeString('width: 42px'), }); this.assertStyleWarning(`color: green; ${userValue}`); } ['@test binding warning is triggered when a safe string for a value is concatenated in place']() { let userValue = '42px'; this.render('
', { userValue: new SafeString(userValue), }); this.assertStyleWarning(`color: green; width: ${userValue}`); } ['@test binding warning is triggered when a safe string for a property name is concatenated in place']() { let userValue = 'width'; this.render('
', { userProperty: new SafeString(userValue), }); this.assertStyleWarning(`color: green; ${userValue}: 42px`); } } ); }