/* 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 = `{{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 onetag'); 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