import { observer, set, computed } from 'ember-metal'; import Controller from '@ember/controller'; import { ENV } from 'ember-environment'; import { RenderingTest, moduleFor } from '../../utils/test-case'; moduleFor( 'Helpers test: {{render}}', class extends RenderingTest { constructor() { super(...arguments); this.originalRenderSupport = ENV._ENABLE_RENDER_SUPPORT; ENV._ENABLE_RENDER_SUPPORT = true; } teardown() { super.teardown(); ENV._ENABLE_RENDER_SUPPORT = this.originalRenderSupport; } ['@test should render given template']() { this.registerTemplate('home', '<p>BYE</p>'); expectDeprecation(() => { this.render(`<h1>HI</h1>{{render 'home'}}`); }, /Please refactor [\w\{\}"` ]+ to a component/); this.assertText('HIBYE'); } ['@test uses `controller:basic` as the basis for a generated controller when none exists for specified name']() { this.owner.register( 'controller:basic', Controller.extend({ isBasicController: true, }) ); this.registerTemplate('home', '{{isBasicController}}'); expectDeprecation(() => { this.render(`{{render 'home'}}`); }, /Please refactor [\w\{\}"` ]+ to a component/); this.assertText('true'); } ['@test generates a controller if none exists']() { this.registerTemplate('home', '<p>{{this}}</p>'); expectDeprecation(() => { this.render(`<h1>HI</h1>{{render 'home'}}`); }, /Please refactor [\w\{\}"` ]+ to a component/); this.assertText('HI(generated home controller)'); } ['@test should use controller with the same name as template if present']() { this.owner.register('controller:home', Controller.extend({ name: 'home' })); this.registerTemplate('home', '{{name}}<p>BYE</p>'); expectDeprecation(() => { this.render(`<h1>HI</h1>{{render 'home'}}`); }, /Please refactor [\w\{\}"` ]+ to a component/); this.assertText('HIhomeBYE'); } ['@test should render nested helpers']() { this.owner.register('controller:home', Controller.extend()); this.owner.register('controller:foo', Controller.extend()); this.owner.register('controller:bar', Controller.extend()); this.owner.register('controller:baz', Controller.extend()); this.registerTemplate('home', '<p>BYE</p>'); this.registerTemplate('baz', `<p>BAZ</p>`); expectDeprecation(() => { this.registerTemplate('foo', `<p>FOO</p>{{render 'bar'}}`); this.registerTemplate('bar', `<p>BAR</p>{{render 'baz'}}`); this.render("<h1>HI</h1>{{render 'foo'}}"); }, /Please refactor [\w\{\}"` ]+ to a component/); this.assertText('HIFOOBARBAZ'); } ['@test should have assertion if the template does not exist']() { this.owner.register('controller:oops', Controller.extend()); expectDeprecation(() => { expectAssertion(() => { this.render(`<h1>HI</h1>{{render 'oops'}}`); }, "You used `{{render 'oops'}}`, but 'oops' can not be found as a template."); }, /Please refactor [\w\{\}"` ]+ to a component/); } ['@test should render given template with the singleton controller as its context']() { this.owner.register( 'controller:post', Controller.extend({ init() { this.set('title', `It's Simple Made Easy`); }, }) ); this.registerTemplate('post', '<p>{{title}}</p>'); expectDeprecation(() => { this.render(`<h1>HI</h1>{{render 'post'}}`); }, /Please refactor [\w\{\}"` ]+ to a component/); this.assertText(`HIIt's Simple Made Easy`); this.runTask(() => this.rerender()); this.assertText(`HIIt's Simple Made Easy`); let controller = this.owner.lookup('controller:post'); this.runTask(() => set(controller, 'title', `Rails is omakase`)); this.assertText(`HIRails is omakase`); this.runTask(() => set(controller, 'title', `It's Simple Made Easy`)); this.assertText(`HIIt's Simple Made Easy`); } ['@test should not destroy the singleton controller on teardown'](assert) { let willDestroyFired = 0; this.owner.register( 'controller:post', Controller.extend({ init() { this.set('title', `It's Simple Made Easy`); }, willDestroy() { this._super(...arguments); willDestroyFired++; }, }) ); this.registerTemplate('post', '<p>{{title}}</p>'); expectDeprecation(() => { this.render(`{{#if showPost}}{{render 'post'}}{{else}}Nothing here{{/if}}`, { showPost: false, }); }, /Please refactor [\w\{\}"` ]+ to a component/); this.assertText(`Nothing here`); assert.strictEqual(willDestroyFired, 0, 'it did not destroy the controller'); this.runTask(() => this.rerender()); this.assertText(`Nothing here`); assert.strictEqual(willDestroyFired, 0, 'it did not destroy the controller'); this.runTask(() => set(this.context, 'showPost', true)); this.assertText(`It's Simple Made Easy`); assert.strictEqual(willDestroyFired, 0, 'it did not destroy the controller'); this.runTask(() => set(this.context, 'showPost', false)); this.assertText(`Nothing here`); assert.strictEqual(willDestroyFired, 0, 'it did not destroy the controller'); } ['@test should render given template with a supplied model']() { this.owner.register('controller:post', Controller.extend()); this.registerTemplate('post', '<p>{{model.title}}</p>'); expectDeprecation(() => { this.render(`<h1>HI</h1>{{render 'post' post}}`, { post: { title: `It's Simple Made Easy`, }, }); }, /Please refactor [\w\{\}"` ]+ to a component/); this.assertText(`HIIt's Simple Made Easy`); this.runTask(() => this.rerender()); this.assertText(`HIIt's Simple Made Easy`); this.runTask(() => set(this.context, 'post.title', `Rails is omakase`)); this.assertText(`HIRails is omakase`); this.runTask(() => set(this.context, 'post', { title: `It's Simple Made Easy` })); this.assertText(`HIIt's Simple Made Easy`); } ['@test should destroy the non-singleton controllers on teardown'](assert) { let willDestroyFired = 0; this.owner.register( 'controller:post', Controller.extend({ willDestroy() { this._super(...arguments); willDestroyFired++; }, }) ); this.registerTemplate('post', '<p>{{model.title}}</p>'); expectDeprecation(() => { this.render(`{{#if showPost}}{{render 'post' post}}{{else}}Nothing here{{/if}}`, { showPost: false, post: { title: `It's Simple Made Easy`, }, }); }, /Please refactor [\w\{\}"` ]+ to a component/); this.assertText(`Nothing here`); assert.strictEqual(willDestroyFired, 0, 'it did not destroy the controller'); this.runTask(() => this.rerender()); this.assertText(`Nothing here`); assert.strictEqual(willDestroyFired, 0, 'it did not destroy the controller'); this.runTask(() => set(this.context, 'showPost', true)); this.assertText(`It's Simple Made Easy`); assert.strictEqual(willDestroyFired, 0, 'it did not destroy the controller'); this.runTask(() => set(this.context, 'showPost', false)); this.assertText(`Nothing here`); assert.strictEqual(willDestroyFired, 1, 'it did destroy the controller'); this.runTask(() => set(this.context, 'showPost', true)); this.assertText(`It's Simple Made Easy`); assert.strictEqual(willDestroyFired, 1, 'it did not destroy the controller'); this.runTask(() => set(this.context, 'showPost', false)); this.assertText(`Nothing here`); assert.strictEqual(willDestroyFired, 2, 'it did destroy the controller'); } ['@test with a supplied model should not fire observers on the controller'](assert) { this.owner.register('controller:post', Controller.extend()); this.registerTemplate('post', '<p>{{model.title}}</p>'); let postDidChange = 0; expectDeprecation(() => { this.render(`<h1>HI</h1>{{render 'post' post}}`, { postDidChange: observer('post', function() { postDidChange++; }), post: { title: `It's Simple Made Easy`, }, }); }, /Please refactor [\w\{\}"` ]+ to a component/); this.assertText(`HIIt's Simple Made Easy`); this.runTask(() => this.rerender()); this.assertText(`HIIt's Simple Made Easy`); assert.equal(postDidChange, 0); } ['@test should raise an error when a given controller name does not resolve to a controller']() { this.registerTemplate('home', '<p>BYE</p>'); this.owner.register('controller:posts', Controller.extend()); expectDeprecation(() => { expectAssertion(() => { this.render(`<h1>HI</h1>{{render "home" controller="postss"}}`); }, /The controller name you supplied \'postss\' did not resolve to a controller./); }, /Please refactor [\w\{\}"` ]+ to a component/); } ['@test should render with given controller'](assert) { this.registerTemplate('home', '{{uniqueId}}'); let id = 0; let model = {}; this.owner.register( 'controller:posts', Controller.extend({ init() { this._super(...arguments); this.uniqueId = id++; this.set('model', model); }, }) ); expectDeprecation(() => { this.render('{{render "home" controller="posts"}}'); }, /Please refactor [\w\{\}"` ]+ to a component/); let renderedController = this.owner.lookup('controller:posts'); let uniqueId = renderedController.get('uniqueId'); let renderedModel = renderedController.get('model'); assert.equal(uniqueId, 0); assert.equal(renderedModel, model); this.assertText('0'); this.runTask(() => this.rerender()); assert.equal(uniqueId, 0); assert.equal(renderedModel, model); this.assertText('0'); } ['@test should render templates with models multiple times']() { this.owner.register('controller:post', Controller.extend()); this.registerTemplate('post', '<p>{{model.title}}</p>'); expectDeprecation(() => { this.render(`<h1>HI</h1> {{render 'post' post1}} {{render 'post' post2}}`, { post1: { title: 'Me First', }, post2: { title: 'Then me', }, }); }, /Please refactor [\w\{\}"` ]+ to a component/); this.assertText('HI Me First Then me'); this.runTask(() => this.rerender()); this.assertText('HI Me First Then me'); this.runTask(() => set(this.context, 'post1.title', 'I am new')); this.assertText('HI I am new Then me'); this.runTask(() => set(this.context, 'post1', { title: 'Me First' })); this.assertText('HI Me First Then me'); } ['@test should not treat invocations with falsy contexts as context-less'](assert) { this.registerTemplate('post', '<p>{{#unless model.zero}}NOTHING{{/unless}}</p>'); this.owner.register('controller:post', Controller.extend()); expectDeprecation(() => { this.render(`<h1>HI</h1> {{render 'post' zero}} {{render 'post' nonexistent}}`, { model: { zero: false, }, }); }, /Please refactor [\w\{\}"` ]+ to a component/); assert.ok( this.$() .text() .match(/^HI ?NOTHING ?NOTHING$/) ); } ['@test should render templates both with and without models'](assert) { this.registerTemplate('post', `<p>Title:{{model.title}}</p>`); this.owner.register('controller:post', Controller.extend()); let post = { title: 'Rails is omakase', }; expectDeprecation(() => { this.render(`<h1>HI</h1> {{render 'post'}} {{render 'post' post}}`, { post, }); }, /Please refactor [\w\{\}"` ]+ to a component/); assert.ok( this.$() .text() .match(/^HI ?Title: ?Title:Rails is omakase$/) ); this.runTask(() => this.rerender()); assert.ok( this.$() .text() .match(/^HI ?Title: ?Title:Rails is omakase$/) ); this.runTask(() => set(this.context, 'post.title', 'Simple Made Easy')); assert.ok( this.$() .text() .match(/^HI ?Title: ?Title:Simple Made Easy$/) ); this.runTask(() => set(this.context, 'post', { title: 'Rails is omakase' })); assert.ok( this.$() .text() .match(/^HI ?Title: ?Title:Rails is omakase$/) ); } ['@test works with dot notation']() { this.registerTemplate('blog.post', '{{uniqueId}}'); let id = 0; this.owner.register( 'controller:blog.post', Controller.extend({ init() { this._super(...arguments); this.uniqueId = id++; }, }) ); expectDeprecation(() => { this.render('{{render "blog.post"}}'); }, /Please refactor [\w\.{\}"` ]+ to a component/); this.assertText(`0`); } ['@test throws an assertion if called with an unquoted template name']() { this.registerTemplate('home', '<p>BYE</p>'); expectAssertion(() => { this.render('<h1>HI</h1>{{render home}}'); }, 'The first argument of {{render}} must be quoted, e.g. {{render "sidebar"}}.'); } ['@test throws an assertion if called with a literal for a model']() { this.registerTemplate('home', '<p>BYE</p>'); expectAssertion(() => { this.render('<h1>HI</h1>{{render "home" "model"}}', { model: { title: 'Simple Made Easy', }, }); }, 'The second argument of {{render}} must be a path, e.g. {{render "post" post}}.'); } ['@test should set router as target when action not found on parentController is not found']( assert ) { let postController; this.registerTemplate('post', 'post template'); this.owner.register( 'controller:post', Controller.extend({ init() { this._super(...arguments); postController = this; }, }) ); let routerStub = { send(actionName) { assert.equal(actionName, 'someAction'); assert.ok(true, 'routerStub#send called'); }, }; this.owner.register('router:main', routerStub, { instantiate: false }); expectDeprecation(() => { this.render(`{{render 'post' post1}}`); }, /Please refactor [\w\{\}"` ]+ to a component/); postController.send('someAction'); } ['@test render helper emits useful backtracking re-render assertion message']() { this.owner.register('controller:outer', Controller.extend()); this.owner.register( 'controller:inner', Controller.extend({ propertyWithError: computed(function() { this.set('model.name', 'this will cause a backtracking error'); return 'foo'; }), }) ); let expectedBacktrackingMessage = /modified "model\.name" twice on \[object Object\] in a single render\. It was rendered in "controller:outer \(with the render helper\)" and modified in "controller:inner \(with the render helper\)"/; expectDeprecation(() => { let person = { name: 'Ben' }; this.registerTemplate('outer', `Hi {{model.name}} | {{render 'inner' model}}`); this.registerTemplate('inner', `Hi {{propertyWithError}}`); expectAssertion(() => { this.render(`{{render 'outer' person}}`, { person }); }, expectedBacktrackingMessage); }); } } );