diff --git a/packages/ember-glimmer/tests/integration/components/curly-components-test.js b/packages/ember-glimmer/tests/integration/components/curly-components-test.js index 98538837bfa..ea0d5d0777d 100644 --- a/packages/ember-glimmer/tests/integration/components/curly-components-test.js +++ b/packages/ember-glimmer/tests/integration/components/curly-components-test.js @@ -1,6 +1,8 @@ /* globals EmberDev */ +import Ember from 'ember-metal/core'; import { set } from 'ember-metal/property_set'; -import { Component } from '../../utils/helpers'; +import { Component, compile } from '../../utils/helpers'; +import { A as emberA } from 'ember-runtime/system/native_array'; import { strip } from '../../utils/abstract-test-case'; import { moduleFor, RenderingTest } from '../../utils/test-case'; import { classes } from '../../utils/test-helpers'; @@ -601,15 +603,15 @@ moduleFor('Components test: curly components', class extends RenderingTest { } ['@test it can render a basic component with a block']() { - this.registerComponent('foo-bar', { template: '{{yield}}' }); + this.registerComponent('foo-bar', { template: '{{yield}} - In component' }); this.render('{{#foo-bar}}hello{{/foo-bar}}'); - this.assertComponentElement(this.firstChild, { content: 'hello' }); + this.assertComponentElement(this.firstChild, { content: 'hello - In component' }); this.runTask(() => this.rerender()); - this.assertComponentElement(this.firstChild, { content: 'hello' }); + this.assertComponentElement(this.firstChild, { content: 'hello - In component' }); } ['@test it renders the layout with the component instance as the context']() { @@ -956,4 +958,1069 @@ moduleFor('Components test: curly components', class extends RenderingTest { this.assertComponentElement(this.firstChild, { content: 'true' }); } + ['@test lookup of component takes priority over property']() { + this.registerComponent('some-component', { + template: 'some-component' + }); + + this.render( + '{{some-prop}} {{some-component}}', + { + 'some-component': 'not-some-component', + 'some-prop': 'some-prop' + } + ); + + this.assertText('some-prop some-component'); + + this.runTask(() => this.rerender()); + + this.assertText('some-prop some-component'); + } + + ['@test component without dash is not looked up']() { + this.registerComponent('somecomponent', { + template: 'somecomponent' + }); + + this.render( + '{{somecomponent}}', + { + 'somecomponent': 'notsomecomponent' + } + ); + + this.assertText('notsomecomponent'); + + this.runTask(() => this.rerender()); + + this.assertText('notsomecomponent'); + + this.runTask(() => this.context.set('somecomponent', 'not not notsomecomponent')); + + this.assertText('not not notsomecomponent'); + + this.runTask(() => this.context.set('somecomponent', 'notsomecomponent')); + + this.assertText('notsomecomponent'); + } + + ['@test non-block with properties on attrs']() { + this.registerComponent('non-block', { + template: 'In layout - someProp: {{attrs.someProp}}' + }); + + this.render('{{non-block someProp=prop}}', { + prop: 'something here' + }); + + this.assertText('In layout - someProp: something here'); + + this.runTask(() => this.rerender()); + + this.assertText('In layout - someProp: something here'); + + this.runTask(() => this.context.set('prop', 'other thing there')); + + this.assertText('In layout - someProp: other thing there'); + + this.runTask(() => this.context.set('prop', 'something here')); + + this.assertText('In layout - someProp: something here'); + } + + ['@skip non-block with properties overridden in init']() { + let instance; + this.registerComponent('non-block', { + ComponentClass: Component.extend({ + init() { + this._super(...arguments); + instance = this; + this.someProp = 'value set in instance'; + } + }), + template: 'In layout - someProp: {{someProp}}' + }); + + this.render('{{non-block someProp=prop}}', { + prop: 'something passed when invoked' + }); + + this.assertText('In layout - someProp: value set in instance'); + + this.runTask(() => this.rerender()); + + this.assertText('In layout - someProp: value set in instance'); + + this.runTask(() => this.context.set('prop', 'updated something passed when invoked')); + + this.assertText('In layout - someProp: updated something passed when invoked'); + + this.runTask(() => instance.set('someProp', 'update value set in instance')); + + this.assertText('In layout - someProp: update value set in instance'); + + this.runTask(() => this.context.set('prop', 'something passed when invoked')); + this.runTask(() => instance.set('someProp', 'value set in instance')); + + this.assertText('In layout - someProp: value set in instance'); + } + + ['@htmlbars rerendering component with attrs from parent'](assert) { + let willUpdate = 0; + let didReceiveAttrs = 0; + + this.registerComponent('non-block', { + ComponentClass: Component.extend({ + didReceiveAttrs() { + didReceiveAttrs++; + }, + + willUpdate() { + willUpdate++; + } + }), + template: 'In layout - someProp: {{someProp}}' + }); + + this.render('{{non-block someProp=someProp}}', { + someProp: 'wycats' + }); + + assert.equal(didReceiveAttrs, 1, 'The didReceiveAttrs hook fired'); + this.assertText('In layout - someProp: wycats'); + + this.runTask(() => this.rerender()); + + this.assertText('In layout - someProp: wycats'); + assert.equal(didReceiveAttrs, 2, 'The didReceiveAttrs hook fired again'); + assert.equal(willUpdate, 1, 'The willUpdate hook fired once'); + + this.runTask(() => this.context.set('someProp', 'tomdale')); + + this.assertText('In layout - someProp: tomdale'); + assert.equal(didReceiveAttrs, 3, 'The didReceiveAttrs hook fired again'); + assert.equal(willUpdate, 2, 'The willUpdate hook fired again'); + + this.runTask(() => this.rerender()); + + this.assertText('In layout - someProp: tomdale'); + assert.equal(didReceiveAttrs, 4, 'The didReceiveAttrs hook fired again'); + assert.equal(willUpdate, 3, 'The willUpdate hook fired again'); + + this.runTask(() => this.context.set('someProp', 'wycats')); + + this.assertText('In layout - someProp: wycats'); + assert.equal(didReceiveAttrs, 5, 'The didReceiveAttrs hook fired again in the R step'); + assert.equal(willUpdate, 4, 'The willUpdate hook fired again in the R step'); + } + + ['@test non-block with properties on self']() { + this.registerComponent('non-block', { + template: 'In layout - someProp: {{someProp}}' + }); + + this.render('{{non-block someProp=prop}}', { + prop: 'something here' + }); + + this.assertText('In layout - someProp: something here'); + + this.runTask(() => this.rerender()); + + this.assertText('In layout - someProp: something here'); + + this.runTask(() => this.context.set('prop', 'something else')); + + this.assertText('In layout - someProp: something else'); + + this.runTask(() => this.context.set('prop', 'something here')); + + this.assertText('In layout - someProp: something here'); + } + + ['@test block with properties on self']() { + this.registerComponent('with-block', { + template: 'In layout - someProp: {{someProp}} - {{yield}}' + }); + + this.render(strip` + {{#with-block someProp=prop}} + In template + {{/with-block}}`, { + prop: 'something here' + } + ); + + this.assertText('In layout - someProp: something here - In template'); + + this.runTask(() => this.rerender()); + + this.assertText('In layout - someProp: something here - In template'); + + this.runTask(() => this.context.set('prop', 'something else')); + + this.assertText('In layout - someProp: something else - In template'); + + this.runTask(() => this.context.set('prop', 'something here')); + + this.assertText('In layout - someProp: something here - In template'); + } + + ['@test block with properties on attrs']() { + this.registerComponent('with-block', { + template: 'In layout - someProp: {{attrs.someProp}} - {{yield}}' + }); + + this.render(strip` + {{#with-block someProp=prop}} + In template + {{/with-block}}`, { + prop: 'something here' + } + ); + + this.assertText('In layout - someProp: something here - In template'); + + this.runTask(() => this.rerender()); + + this.assertText('In layout - someProp: something here - In template'); + + this.runTask(() => this.context.set('prop', 'something else')); + + this.assertText('In layout - someProp: something else - In template'); + + this.runTask(() => this.context.set('prop', 'something here')); + + this.assertText('In layout - someProp: something here - In template'); + } + + ['@htmlbars static arbitrary number of positional parameters'](assert) { + this.registerComponent('sample-component', { + ComponentClass: Component.extend().reopenClass({ + positionalParams: 'names' + }), + template: strip` + {{#each names as |name|}} + {{name}} + {{/each}}` + }); + + this.render(strip` + {{sample-component "Foo" 4 "Bar" id="args-3"}} + {{sample-component "Foo" 4 "Bar" 5 "Baz" id="args-5"}} + {{component "sample-component" "Foo" 4 "Bar" 5 "Baz" id="helper"}}` + ); + + assert.equal(this.$('#args-3').text(), 'Foo4Bar'); + assert.equal(this.$('#args-5').text(), 'Foo4Bar5Baz'); + assert.equal(this.$('#helper').text(), 'Foo4Bar5Baz'); + + this.runTask(() => this.rerender()); + + assert.equal(this.$('#args-3').text(), 'Foo4Bar'); + assert.equal(this.$('#args-5').text(), 'Foo4Bar5Baz'); + assert.equal(this.$('#helper').text(), 'Foo4Bar5Baz'); + } + + ['@htmlbars arbitrary positional parameter conflict with hash parameter is reported']() { + this.registerComponent('sample-component', { + ComponentClass: Component.extend().reopenClass({ + positionalParams: 'names' + }), + template: strip` + {{#each names as |name|}} + {{name}} + {{/each}}` + }); + + expectAssertion(() => { + this.render(`{{sample-component "Foo" 4 "Bar" names=numbers id="args-3"}}`, { + numbers: [1, 2, 3] + }); + }, 'You cannot specify positional parameters and the hash argument `names`.'); + } + + ['@test can use hash parameter instead of arbitrary positional param [GH #12444]'](assert) { + this.registerComponent('sample-component', { + ComponentClass: Component.extend().reopenClass({ + positionalParams: 'names' + }), + template: strip` + {{#each names as |name|}} + {{name}} + {{/each}}` + }); + + this.render('{{sample-component names=things}}', { + things: emberA(['Foo', 4, 'Bar']) + }); + + this.assertText('Foo4Bar'); + + this.runTask(() => this.rerender()); + + this.assertText('Foo4Bar'); + + this.runTask(() => this.context.get('things').pushObject(5)); + + this.assertText('Foo4Bar5'); + + this.runTask(() => this.context.get('things').shiftObject()); + + this.assertText('4Bar5'); + + this.runTask(() => this.context.get('things').clear()); + + this.assertText(''); + + this.runTask(() => this.context.set('things', emberA(['Foo', 4, 'Bar']))); + + this.assertText('Foo4Bar'); + } + + ['@htmlbars can use hash parameter instead of positional param'](assert) { + this.registerComponent('sample-component', { + ComponentClass: Component.extend().reopenClass({ + positionalParams: ['first', 'second'] + }), + template: '{{first}} - {{second}}' + }); + + this.render(strip` + {{sample-component "one" "two" id="two-positional"}} + {{sample-component "one" second="two" id="one-positional"}} + {{sample-component first="one" second="two" id="no-positional"}}`); + + assert.equal(this.$('#two-positional').text(), 'one - two'); + assert.equal(this.$('#one-positional').text(), 'one - two'); + assert.equal(this.$('#no-positional').text(), 'one - two'); + + this.runTask(() => this.rerender()); + + assert.equal(this.$('#two-positional').text(), 'one - two'); + assert.equal(this.$('#one-positional').text(), 'one - two'); + assert.equal(this.$('#no-positional').text(), 'one - two'); + } + + ['@htmlbars dynamic arbitrary number of positional parameters'](assert) { + this.registerComponent('sample-component', { + ComponentClass: Component.extend().reopenClass({ + positionalParams: 'n' + }), + template: strip` + {{#each n as |name|}} + {{name}} + {{/each}}` + }); + + this.render(strip` + {{sample-component user1 user2 id="direct"}} + {{component "sample-component" user1 user2 id="helper"}}`, + { + user1: 'Foo', + user2: 4 + } + ); + + assert.equal(this.$('#direct').text(), 'Foo4', 'direct'); + assert.equal(this.$('#helper').text(), 'Foo4', 'helper'); + + this.runTask(() => this.rerender()); + + assert.equal(this.$('#direct').text(), 'Foo4', 'direct'); + assert.equal(this.$('#helper').text(), 'Foo4', 'helper'); + + this.runTask(() => this.context.set('user1', 'Bar')); + + assert.equal(this.$('#direct').text(), 'Bar4', 'direct'); + //assert.equal(this.$('#helper').text(), 'Bar4', 'helper'); + + this.runTask(() => this.context.set('user2', '5')); + + assert.equal(this.$('#direct').text(), 'Bar5', 'direct'); + //assert.equal(this.$('#helper').text(), 'Bar5', 'helper'); + + this.runTask(() => { + this.context.set('user1', 'Foo'); + this.context.set('user2', 4); + }); + + assert.equal(this.$('#direct').text(), 'Foo4', 'direct'); + assert.equal(this.$('#helper').text(), 'Foo4', 'helper'); + } + + ['@htmlbars with ariaRole specified']() { + this.registerComponent('aria-test', { + template: 'Here!' + }); + + this.render('{{aria-test ariaRole=role}}', { + role: 'main' + }); + + this.assertComponentElement(this.firstChild, { attrs: { role: 'main' } }); + + this.runTask(() => this.rerender()); + + this.assertComponentElement(this.firstChild, { attrs: { role: 'main' } }); + + this.runTask(() => this.context.set('role', 'input')); + + this.assertComponentElement(this.firstChild, { attrs: { role: 'input' } }); + + this.runTask(() => this.context.set('role', 'main')); + + this.assertComponentElement(this.firstChild, { attrs: { role: 'main' } }); + } + + ['@htmlbars `template` specified in component is overriden by block']() { + this.registerComponent('with-template', { + ComponentClass: Component.extend({ + template: compile('Should not be used') + }), + template: '[In layout - {{name}}] {{yield}}' + }); + + this.render(strip` + {{#with-template name="with-block"}} + [In block - {{name}}] + {{/with-template}} + {{with-template name="without-block"}}`, { + name: 'Whoop, whoop!' + } + ); + + this.assertText('[In layout - with-block] [In block - Whoop, whoop!][In layout - without-block] '); + + this.runTask(() => this.rerender()); + + this.assertText('[In layout - with-block] [In block - Whoop, whoop!][In layout - without-block] '); + + this.runTask(() => this.context.set('name', 'Ole, ole')); + + this.assertText('[In layout - with-block] [In block - Ole, ole][In layout - without-block] '); + + this.runTask(() => this.context.set('name', 'Whoop, whoop!')); + + this.assertText('[In layout - with-block] [In block - Whoop, whoop!][In layout - without-block] '); + } + + ['@htmlbars hasBlock is true when block supplied']() { + this.registerComponent('with-block', { + template: strip` + {{#if hasBlock}} + {{yield}} + {{else}} + No Block! + {{/if}}` + }); + + this.render(strip` + {{#with-block}} + In template + {{/with-block}}` + ); + + this.assertText('In template'); + + this.runTask(() => this.rerender()); + + this.assertText('In template'); + } + + ['@test hasBlock is false when no block supplied']() { + this.registerComponent('with-block', { + template: strip` + {{#if hasBlock}} + {{yield}} + {{else}} + No Block! + {{/if}}` + }); + + this.render('{{with-block}}'); + + this.assertText('No Block!'); + + this.runTask(() => this.rerender()); + + this.assertText('No Block!'); + } + + ['@htmlbars hasBlockParams is true when block param supplied']() { + this.registerComponent('with-block', { + template: strip` + {{#if hasBlockParams}} + {{yield this}} - In Component + {{else}} + {{yield}} No Block! + {{/if}}` + }); + + this.render(strip` + {{#with-block as |something|}} + In template + {{/with-block}}` + ); + + this.assertText('In template - In Component'); + + this.runTask(() => this.rerender()); + + this.assertText('In template - In Component'); + } + + ['@test hasBlockParams is false when no block param supplied']() { + this.registerComponent('with-block', { + template: strip` + {{#if hasBlockParams}} + {{yield this}} + {{else}} + {{yield}} No Block Param! + {{/if}}` + }); + + this.render(strip` + {{#with-block}} + In block + {{/with-block}}` + ); + + this.assertText('In block No Block Param!'); + + this.runTask(() => this.rerender()); + + this.assertText('In block No Block Param!'); + } + + ['@htmlbars static named positional parameters']() { + this.registerComponent('sample-component', { + ComponentClass: Component.extend().reopenClass({ + positionalParams: ['name', 'age'] + }), + template: '{{name}}{{age}}' + }); + + this.render('{{sample-component "Quint" 4}}'); + + this.assertText('Quint4'); + + this.runTask(() => this.rerender()); + + this.assertText('Quint4'); + } + + ['@htmlbars dynamic named positional parameters']() { + this.registerComponent('sample-component', { + ComponentClass: Component.extend().reopenClass({ + positionalParams: ['name', 'age'] + }), + template: '{{name}}{{age}}' + }); + + this.render('{{sample-component myName myAge}}', { + myName: 'Quint', + myAge: 4 + }); + + this.assertText('Quint4'); + + this.runTask(() => this.rerender()); + + this.assertText('Quint4'); + + this.runTask(() => this.context.set('myName', 'Sergio')); + + this.assertText('Sergio4'); + + this.runTask(() => this.context.set('myAge', 2)); + + this.assertText('Sergio2'); + + this.runTask(() => { + this.context.set('myName', 'Quint'); + this.context.set('myAge', 4); + }); + + this.assertText('Quint4'); + } + + ['@htmlbars if a value is passed as a non-positional parameter, it raises an assertion']() { + this.registerComponent('sample-component', { + ComponentClass: Component.extend().reopenClass({ + positionalParams: ['name'] + }), + template: '{{name}}' + }); + + expectAssertion(() => { + this.render('{{sample-component notMyName name=myName}}', { + myName: 'Quint', + notMyName: 'Sergio' + }); + }, 'You cannot specify both a positional param (at position 0) and the hash argument `name`.'); + } + + ['@test yield to inverse']() { + this.registerComponent('my-if', { + template: strip` + {{#if predicate}} + Yes:{{yield someValue}} + {{else}} + No:{{yield to="inverse"}} + {{/if}}` + }); + + this.render(strip` + {{#my-if predicate=activated someValue=42 as |result|}} + Hello{{result}} + {{else}} + Goodbye + {{/my-if}}`, + { + activated: true + }); + + this.assertText('Yes:Hello42'); + + this.runTask(() => this.rerender()); + + this.assertText('Yes:Hello42'); + + this.runTask(() => this.context.set('activated', false)); + + this.assertText('No:Goodbye'); + + this.runTask(() => this.context.set('activated', true)); + + this.assertText('Yes:Hello42'); + } + + ['@htmlbars expression hasBlock inverse'](assert) { + this.registerComponent('check-inverse', { + template: strip` + {{#if (hasBlock "inverse")}} + Yes + {{else}} + No + {{/if}}` + }); + + this.render(strip` + {{#check-inverse id="expect-no"}}{{/check-inverse}} + {{#check-inverse id="expect-yes"}}{{else}}{{/check-inverse}}`); + + assert.equal(this.$('#expect-no').text(), 'No'); + assert.equal(this.$('#expect-yes').text(), 'Yes'); + + this.runTask(() => this.rerender()); + + assert.equal(this.$('#expect-no').text(), 'No'); + assert.equal(this.$('#expect-yes').text(), 'Yes'); + } + + ['@htmlbars expression hasBlock default'](assert) { + this.registerComponent('check-block', { + template: strip` + {{#if (hasBlock)}} + Yes + {{else}} + No + {{/if}}` + }); + + this.render(strip` + {{check-block id="expect-no"}} + {{#check-block id="expect-yes"}}{{/check-block}}`); + + assert.equal(this.$('#expect-no').text(), 'No'); + assert.equal(this.$('#expect-yes').text(), 'Yes'); + + this.runTask(() => this.rerender()); + + assert.equal(this.$('#expect-no').text(), 'No'); + assert.equal(this.$('#expect-yes').text(), 'Yes'); + } + + ['@htmlbars non-expression hasBlock'](assert) { + this.registerComponent('check-block', { + template: strip` + {{#if hasBlock}} + Yes + {{else}} + No + {{/if}}` + }); + + this.render(strip` + {{check-block id="expect-no"}} + {{#check-block id="expect-yes"}}{{/check-block}}`); + + assert.equal(this.$('#expect-no').text(), 'No'); + assert.equal(this.$('#expect-yes').text(), 'Yes'); + + this.runTask(() => this.rerender()); + + assert.equal(this.$('#expect-no').text(), 'No'); + assert.equal(this.$('#expect-yes').text(), 'Yes'); + } + + ['@htmlbars expression hasBlockParams'](assert) { + this.registerComponent('check-params', { + template: strip` + {{#if (hasBlockParams)}} + Yes + {{else}} + No + {{/if}}` + }); + + this.render(strip` + {{#check-params id="expect-no"}}{{/check-params}} + {{#check-params id="expect-yes" as |foo|}}{{/check-params}}`); + + assert.equal(this.$('#expect-no').text(), 'No'); + assert.equal(this.$('#expect-yes').text(), 'Yes'); + + this.runTask(() => this.rerender()); + + assert.equal(this.$('#expect-no').text(), 'No'); + assert.equal(this.$('#expect-yes').text(), 'Yes'); + } + + ['@htmlbars non-expression hasBlockParams'](assert) { + this.registerComponent('check-params', { + template: strip` + {{#if hasBlockParams}} + Yes + {{else}} + No + {{/if}}` + }); + + this.render(strip` + {{#check-params id="expect-no"}}{{/check-params}} + {{#check-params id="expect-yes" as |foo|}}{{/check-params}}`); + + assert.equal(this.$('#expect-no').text(), 'No'); + assert.equal(this.$('#expect-yes').text(), 'Yes'); + + this.runTask(() => this.rerender()); + + assert.equal(this.$('#expect-no').text(), 'No'); + assert.equal(this.$('#expect-yes').text(), 'Yes'); + } + + ['@htmlbars component in template of a yielding component should have the proper parentView'](assert) { + let outer, innerTemplate, innerLayout; + + this.registerComponent('x-outer', { + ComponentClass: Component.extend({ + init() { + this._super(...arguments); + outer = this; + } + }), + template: '{{x-inner-in-layout}}{{yield}}' + }); + + this.registerComponent('x-inner-in-template', { + ComponentClass: Component.extend({ + init() { + this._super(...arguments); + innerTemplate = this; + } + }) + }); + + this.registerComponent('x-inner-in-layout', { + ComponentClass: Component.extend({ + init() { + this._super(...arguments); + innerLayout = this; + } + }) + }); + + this.render('{{#x-outer}}{{x-inner-in-template}}{{/x-outer}}'); + + assert.equal(innerTemplate.parentView, outer, 'receives the wrapping component as its parentView in template blocks'); + assert.equal(innerLayout.parentView, outer, 'receives the wrapping component as its parentView in layout'); + assert.equal(outer.parentView, this.context, 'x-outer receives the ambient scope as its parentView'); + + this.runTask(() => this.rerender()); + + assert.equal(innerTemplate.parentView, outer, 'receives the wrapping component as its parentView in template blocks'); + assert.equal(innerLayout.parentView, outer, 'receives the wrapping component as its parentView in layout'); + assert.equal(outer.parentView, this.context, 'x-outer receives the ambient scope as its parentView'); + } + + ['@htmlbars newly-added sub-components get correct parentView'](assert) { + let outer, inner; + + this.registerComponent('x-outer', { + ComponentClass: Component.extend({ + init() { + this._super(...arguments); + outer = this; + } + }) + }); + + this.registerComponent('x-inner', { + ComponentClass: Component.extend({ + init() { + this._super(...arguments); + inner = this; + } + }) + }); + + this.render(strip` + {{#x-outer}} + {{#if showInner}} + {{x-inner}} + {{/if}} + {{/x-outer}}`, + { + showInner: false + } + ); + + assert.equal(outer.parentView, this.context, 'x-outer receives the ambient scope as its parentView'); + + this.runTask(() => this.rerender()); + + assert.equal(outer.parentView, this.context, 'x-outer receives the ambient scope as its parentView (after rerender)'); + + this.runTask(() => this.context.set('showInner', true)); + + assert.equal(outer.parentView, this.context, 'x-outer receives the ambient scope as its parentView'); + assert.equal(inner.parentView, outer, 'receives the wrapping component as its parentView in template blocks'); + + this.runTask(() => this.context.set('showInner', false)); + + assert.equal(outer.parentView, this.context, 'x-outer receives the ambient scope as its parentView'); + } + + ['@htmlbars component should receive the viewRegistry from the parentView'](assert) { + let outer, innerTemplate, innerLayout; + + let viewRegistry = {}; + + this.registerComponent('x-outer', { + ComponentClass: Component.extend({ + init() { + this._super(...arguments); + outer = this; + } + }), + template: '{{x-inner-in-layout}}{{yield}}' + }); + + this.registerComponent('x-inner-in-template', { + ComponentClass: Component.extend({ + init() { + this._super(...arguments); + innerTemplate = this; + } + }) + }); + + this.registerComponent('x-inner-in-layout', { + ComponentClass: Component.extend({ + init() { + this._super(...arguments); + innerLayout = this; + } + }) + }); + + this.render('{{#x-outer}}{{x-inner-in-template}}{{/x-outer}}', { + _viewRegistry: viewRegistry + }); + + assert.equal(innerTemplate._viewRegistry, viewRegistry); + assert.equal(innerLayout._viewRegistry, viewRegistry); + assert.equal(outer._viewRegistry, viewRegistry); + + this.runTask(() => this.rerender()); + + assert.equal(innerTemplate._viewRegistry, viewRegistry); + assert.equal(innerLayout._viewRegistry, viewRegistry); + assert.equal(outer._viewRegistry, viewRegistry); + } + + ['@htmlbars component should rerender when a property is changed during children\'s rendering'](assert) { + expectDeprecation(/modified value twice in a single render/); + + let outer, middle; + + this.registerComponent('x-outer', { + ComponentClass: Component.extend({ + init() { + this._super(...arguments); + outer = this; + }, + value: 1 + }), + template: '{{#x-middle}}{{x-inner value=value}}{{/x-middle}}' + }); + + this.registerComponent('x-middle', { + ComponentClass: Component.extend({ + init() { + this._super(...arguments); + middle = this; + }, + value: null + }), + template: '
{{value}}
{{yield}}' + }); + + this.registerComponent('x-inner', { + ComponentClass: Component.extend({ + value: null, + pushDataUp: Ember.observer('value', function() { + middle.set('value', this.get('value')); + }) + }), + template: '
{{value}}
' + }); + + this.render('{{x-outer}}'); + + assert.equal(this.$('#inner-value').text(), '1', 'initial render of inner'); + assert.equal(this.$('#middle-value').text(), '', 'initial render of middle (observers do not run during init)'); + + this.runTask(() => this.rerender()); + + assert.equal(this.$('#inner-value').text(), '1', 'initial render of inner'); + assert.equal(this.$('#middle-value').text(), '', 'initial render of middle (observers do not run during init)'); + + this.runTask(() => outer.set('value', 2)); + + assert.equal(this.$('#inner-value').text(), '2', 'second render of inner'); + assert.equal(this.$('#middle-value').text(), '2', 'second render of middle'); + + this.runTask(() => outer.set('value', 3)); + + assert.equal(this.$('#inner-value').text(), '3', 'third render of inner'); + assert.equal(this.$('#middle-value').text(), '3', 'third render of middle'); + + this.runTask(() => outer.set('value', 1)); + + assert.equal(this.$('#inner-value').text(), '1', 'reset render of inner'); + assert.equal(this.$('#middle-value').text(), '1', 'reset render of middle'); + } + + ['@test non-block with each rendering child components']() { + this.registerComponent('non-block', { + template: strip` + In layout. {{#each items as |item|}} + [{{child-non-block item=item}}] + {{/each}}` + }); + + this.registerComponent('child-non-block', { + template: 'Child: {{item}}.' + }); + + let items = emberA(['Tom', 'Dick', 'Harry']); + + this.render('{{non-block items=items}}', { items }); + + this.assertText('In layout. [Child: Tom.][Child: Dick.][Child: Harry.]'); + + this.runTask(() => this.rerender()); + + this.assertText('In layout. [Child: Tom.][Child: Dick.][Child: Harry.]'); + + this.runTask(() => this.context.get('items').pushObject('Sergio')); + + this.assertText('In layout. [Child: Tom.][Child: Dick.][Child: Harry.][Child: Sergio.]'); + + this.runTask(() => this.context.get('items').shiftObject()); + + this.assertText('In layout. [Child: Dick.][Child: Harry.][Child: Sergio.]'); + + this.runTask(() => this.context.set('items', emberA(['Tom', 'Dick', 'Harry']))); + + this.assertText('In layout. [Child: Tom.][Child: Dick.][Child: Harry.]'); + } + + ['@test specifying classNames results in correct class'](assert) { + let clickyThing; + + this.registerComponent('some-clicky-thing', { + ComponentClass: Component.extend({ + tagName: 'button', + classNames: ['foo', 'bar'], + init() { + this._super(...arguments); + clickyThing = this; + } + }), + // I am getting a `Cannot read property 'asLayout' of undefined` in + // Glimmer if I do not specify a template here :( + template: '{{yield}}' + }); + + this.render(strip` + {{#some-clicky-thing classNames="baz"}} + Click Me + {{/some-clicky-thing}}` + ); + + // TODO: ember-view is no longer viewable in the classNames array. Bug or + // feature? + let expectedClassNames = ['ember-view', 'foo', 'bar', 'baz']; + + assert.ok(this.$('button').is('.foo.bar.baz.ember-view'), `the element has the correct classes: ${this.$('button').attr('class')}`); + // `ember-view` is no longer in classNames. + // assert.deepEqual(clickyThing.get('classNames'), expectedClassNames, 'classNames are properly combined'); + this.assertComponentElement(this.firstChild, { tagName: 'button', attrs: { 'class': classes(expectedClassNames.join(' ')) } }); + + this.runTask(() => this.rerender()); + + assert.ok(this.$('button').is('.foo.bar.baz.ember-view'), `the element has the correct classes: ${this.$('button').attr('class')} (rerender)`); + // `ember-view` is no longer in classNames. + // assert.deepEqual(clickyThing.get('classNames'), expectedClassNames, 'classNames are properly combined (rerender)'); + this.assertComponentElement(this.firstChild, { tagName: 'button', attrs: { 'class': classes(expectedClassNames.join(' ')) } }); + } + + ['@test specifying custom concatenatedProperties avoids clobbering'](assert) { + let clickyThing; + this.registerComponent('some-clicky-thing', { + ComponentClass: Component.extend({ + concatenatedProperties: ['blahzz'], + blahzz: ['blark', 'pory'], + init() { + this._super(...arguments); + clickyThing = this; + } + }), + template: strip` + {{#each blahzz as |p|}} + {{p}} + {{/each}} + - {{yield}}` + }); + + this.render(strip` + {{#some-clicky-thing blahzz="baz"}} + Click Me + {{/some-clicky-thing}}` + ); + + this.assertText('blarkporybaz- Click Me'); + + // Errors here cause `blahzz` has become just `baz` and `Don't know how to + // {{#each baz}}` + // this.runTask(() => this.rerender()); + + // this.assertText('blarkporybaz- Click Me'); + } }); + diff --git a/packages/ember-htmlbars/tests/integration/component_invocation_test.js b/packages/ember-htmlbars/tests/integration/component_invocation_test.js index 1bc4fac6bc6..07c6a02d3c5 100644 --- a/packages/ember-htmlbars/tests/integration/component_invocation_test.js +++ b/packages/ember-htmlbars/tests/integration/component_invocation_test.js @@ -1,42 +1,23 @@ -import Ember from 'ember-metal/core'; -import EmberView from 'ember-views/views/view'; -import jQuery from 'ember-views/system/jquery'; import compile from 'ember-template-compiler/system/compile'; import ComponentLookup from 'ember-views/component_lookup'; import Component from 'ember-views/components/component'; -import GlimmerComponent from 'ember-htmlbars/glimmer-component'; import { runAppend, runDestroy } from 'ember-runtime/tests/utils'; -import { set } from 'ember-metal/property_set'; -import run from 'ember-metal/run_loop'; -import { A as emberA } from 'ember-runtime/system/native_array'; import buildOwner from 'container/tests/test-helpers/build-owner'; import { OWNER } from 'container/owner'; -var owner, view; +var owner, component; function commonSetup() { owner = buildOwner(); owner.registerOptionsForType('component', { singleton: false }); - owner.registerOptionsForType('view', { singleton: false }); owner.registerOptionsForType('template', { instantiate: false }); owner.register('component-lookup:main', ComponentLookup); } function commonTeardown() { runDestroy(owner); - runDestroy(view); - owner = view = null; -} - -function appendViewFor(template, hash={}) { - let view = EmberView.extend({ - [OWNER]: owner, - template: compile(template) - }).create(hash); - - runAppend(view); - - return view; + runDestroy(component); + owner = component = null; } import isEnabled from 'ember-metal/features'; @@ -53,516 +34,6 @@ QUnit.module('component - invocation', { } }); -QUnit.test('non-block without properties', function() { - expect(1); - - owner.register('template:components/non-block', compile('In layout')); - - view = EmberView.extend({ - [OWNER]: owner, - template: compile('{{non-block}}') - }).create(); - - runAppend(view); - - equal(jQuery('#qunit-fixture').text(), 'In layout'); -}); - -QUnit.test('GlimmerComponent cannot be invoked with curly braces', function() { - owner.register('template:components/non-block', compile('In layout')); - owner.register('component:non-block', GlimmerComponent.extend()); - - expectAssertion(function() { - view = appendViewFor('{{non-block}}'); - }, /cannot invoke the 'non-block' component with curly braces/); -}); - -QUnit.test('block without properties', function() { - expect(1); - - owner.register('template:components/with-block', compile('In layout - {{yield}}')); - - view = EmberView.extend({ - [OWNER]: owner, - template: compile('{{#with-block}}In template{{/with-block}}') - }).create(); - - runAppend(view); - - equal(jQuery('#qunit-fixture').text(), 'In layout - In template'); -}); - -QUnit.test('non-block with properties on attrs', function() { - expect(1); - - owner.register('template:components/non-block', compile('In layout - someProp: {{attrs.someProp}}')); - - view = EmberView.extend({ - [OWNER]: owner, - template: compile('{{non-block someProp="something here"}}') - }).create(); - - runAppend(view); - - equal(jQuery('#qunit-fixture').text(), 'In layout - someProp: something here'); -}); - -QUnit.test('non-block with properties on attrs and component class', function() { - owner.register('component:non-block', Component.extend()); - owner.register('template:components/non-block', compile('In layout - someProp: {{attrs.someProp}}')); - - view = EmberView.extend({ - [OWNER]: owner, - template: compile('{{non-block someProp="something here"}}') - }).create(); - - runAppend(view); - - equal(jQuery('#qunit-fixture').text(), 'In layout - someProp: something here'); -}); - -QUnit.test('non-block with properties on overridden in init', function() { - owner.register('component:non-block', Component.extend({ - someProp: null, - - init() { - this._super(...arguments); - this.someProp = 'value set in init'; - } - })); - owner.register('template:components/non-block', compile('In layout - someProp: {{someProp}}')); - - view = EmberView.extend({ - [OWNER]: owner, - template: compile('{{non-block someProp="something passed when invoked"}}') - }).create(); - - runAppend(view); - - equal(view.$().text(), 'In layout - someProp: value set in init'); -}); - -QUnit.test('lookup of component takes priority over property', function() { - expect(1); - - owner.register('template:components/some-component', compile('some-component')); - - view = EmberView.extend({ - [OWNER]: owner, - template: compile('{{some-prop}} {{some-component}}'), - context: { - 'some-component': 'not-some-component', - 'some-prop': 'some-prop' - } - }).create(); - - runAppend(view); - - equal(jQuery('#qunit-fixture').text(), 'some-prop some-component'); -}); - -QUnit.test('component without dash is not looked up', function() { - expect(1); - - owner.register('template:components/somecomponent', compile('somecomponent')); - - view = EmberView.extend({ - [OWNER]: owner, - template: compile('{{somecomponent}}'), - context: { - 'somecomponent': 'notsomecomponent' - } - }).create(); - - runAppend(view); - - equal(jQuery('#qunit-fixture').text(), 'notsomecomponent'); -}); - -QUnit.test('rerendering component with attrs from parent', function() { - var willUpdate = 0; - var didReceiveAttrs = 0; - - owner.register('component:non-block', Component.extend({ - didReceiveAttrs() { - didReceiveAttrs++; - }, - - willUpdate() { - willUpdate++; - } - })); - owner.register('template:components/non-block', compile('In layout - someProp: {{attrs.someProp}}')); - - view = EmberView.extend({ - [OWNER]: owner, - template: compile('{{non-block someProp=view.someProp}}'), - someProp: 'wycats' - }).create(); - - runAppend(view); - - equal(didReceiveAttrs, 1, 'The didReceiveAttrs hook fired'); - - equal(jQuery('#qunit-fixture').text(), 'In layout - someProp: wycats'); - - run(function() { - view.set('someProp', 'tomdale'); - }); - - equal(jQuery('#qunit-fixture').text(), 'In layout - someProp: tomdale'); - equal(didReceiveAttrs, 2, 'The didReceiveAttrs hook fired again'); - equal(willUpdate, 1, 'The willUpdate hook fired once'); - - run(view, 'rerender'); - - equal(jQuery('#qunit-fixture').text(), 'In layout - someProp: tomdale'); - equal(didReceiveAttrs, 3, 'The didReceiveAttrs hook fired again'); - equal(willUpdate, 2, 'The willUpdate hook fired again'); -}); - - -QUnit.test('[DEPRECATED] non-block with properties on self', function() { - // TODO: attrs - // expectDeprecation("You accessed the `someProp` attribute directly. Please use `attrs.someProp` instead."); - - owner.register('template:components/non-block', compile('In layout - someProp: {{someProp}}')); - - view = EmberView.extend({ - [OWNER]: owner, - template: compile('{{non-block someProp="something here"}}') - }).create(); - - runAppend(view); - - equal(jQuery('#qunit-fixture').text(), 'In layout - someProp: something here'); -}); - -QUnit.test('block with properties on attrs', function() { - expect(1); - - owner.register('template:components/with-block', compile('In layout - someProp: {{attrs.someProp}} - {{yield}}')); - - view = EmberView.extend({ - [OWNER]: owner, - template: compile('{{#with-block someProp="something here"}}In template{{/with-block}}') - }).create(); - - runAppend(view); - - equal(jQuery('#qunit-fixture').text(), 'In layout - someProp: something here - In template'); -}); - -QUnit.test('[DEPRECATED] block with properties on self', function() { - // TODO: attrs - // expectDeprecation("You accessed the `someProp` attribute directly. Please use `attrs.someProp` instead."); - - owner.register('template:components/with-block', compile('In layout - someProp: {{someProp}} - {{yield}}')); - - view = EmberView.extend({ - [OWNER]: owner, - template: compile('{{#with-block someProp="something here"}}In template{{/with-block}}') - }).create(); - - runAppend(view); - - equal(jQuery('#qunit-fixture').text(), 'In layout - someProp: something here - In template'); -}); - -QUnit.test('with ariaRole specified', function() { - expect(1); - - owner.register('template:components/aria-test', compile('Here!')); - - view = EmberView.extend({ - [OWNER]: owner, - template: compile('{{aria-test id="aria-test" ariaRole="main"}}') - }).create(); - - runAppend(view); - - equal(view.$('#aria-test').attr('role'), 'main', 'role attribute is applied'); -}); - -QUnit.test('`template` specified in a component is overridden by block', function() { - expect(1); - - owner.register('component:with-block', Component.extend({ - layout: compile('{{yield}}'), - template: compile('Oh, noes!') - })); - - view = EmberView.extend({ - [OWNER]: owner, - template: compile('{{#with-block}}Whoop, whoop!{{/with-block}}') - }).create(); - - runAppend(view); - - equal(view.$().text(), 'Whoop, whoop!', 'block provided always overrides template property'); -}); - -QUnit.test('hasBlock is true when block supplied', function() { - expect(1); - - owner.register('template:components/with-block', compile('{{#if hasBlock}}{{yield}}{{else}}No Block!{{/if}}')); - - view = EmberView.extend({ - [OWNER]: owner, - template: compile('{{#with-block}}In template{{/with-block}}') - }).create(); - - runAppend(view); - - equal(jQuery('#qunit-fixture').text(), 'In template'); -}); - -QUnit.test('hasBlock is false when no block supplied', function() { - expect(1); - - owner.register('template:components/with-block', compile('{{#if hasBlock}}{{yield}}{{else}}No Block!{{/if}}')); - - view = EmberView.extend({ - [OWNER]: owner, - template: compile('{{with-block}}') - }).create(); - - runAppend(view); - - equal(jQuery('#qunit-fixture').text(), 'No Block!'); -}); - -QUnit.test('hasBlockParams is true when block param supplied', function() { - expect(1); - - owner.register('template:components/with-block', compile('{{#if hasBlockParams}}{{yield this}} - In Component{{else}}{{yield}} No Block!{{/if}}')); - - view = EmberView.extend({ - [OWNER]: owner, - template: compile('{{#with-block as |something|}}In template{{/with-block}}') - }).create(); - - runAppend(view); - - equal(jQuery('#qunit-fixture').text(), 'In template - In Component'); -}); - -QUnit.test('hasBlockParams is false when no block param supplied', function() { - expect(1); - - owner.register('template:components/with-block', compile('{{#if hasBlockParams}}{{yield this}}{{else}}{{yield}} No Block Param!{{/if}}')); - - view = EmberView.extend({ - [OWNER]: owner, - template: compile('{{#with-block}}In block{{/with-block}}') - }).create(); - - runAppend(view); - - equal(jQuery('#qunit-fixture').text(), 'In block No Block Param!'); -}); - -QUnit.test('static named positional parameters', function() { - var SampleComponent = Component.extend(); - SampleComponent.reopenClass({ - positionalParams: ['name', 'age'] - }); - owner.register('template:components/sample-component', compile('{{attrs.name}}{{attrs.age}}')); - owner.register('component:sample-component', SampleComponent); - - view = EmberView.extend({ - [OWNER]: owner, - layout: compile('{{sample-component "Quint" 4}}') - }).create(); - - runAppend(view); - - equal(jQuery('#qunit-fixture').text(), 'Quint4'); -}); - -QUnit.test('dynamic named positional parameters', function() { - var SampleComponent = Component.extend(); - SampleComponent.reopenClass({ - positionalParams: ['name', 'age'] - }); - - owner.register('template:components/sample-component', compile('{{attrs.name}}{{attrs.age}}')); - owner.register('component:sample-component', SampleComponent); - - view = EmberView.extend({ - [OWNER]: owner, - layout: compile('{{sample-component myName myAge}}'), - context: { - myName: 'Quint', - myAge: 4 - } - }).create(); - - runAppend(view); - - equal(jQuery('#qunit-fixture').text(), 'Quint4'); - run(function() { - set(view.context, 'myName', 'Edward'); - set(view.context, 'myAge', '5'); - }); - - equal(jQuery('#qunit-fixture').text(), 'Edward5'); -}); - -QUnit.test('if a value is passed as a non-positional parameter, it takes precedence over the named one', function() { - var SampleComponent = Component.extend(); - SampleComponent.reopenClass({ - positionalParams: ['name'] - }); - - owner.register('template:components/sample-component', compile('{{attrs.name}}')); - owner.register('component:sample-component', SampleComponent); - - view = EmberView.extend({ - [OWNER]: owner, - layout: compile('{{sample-component notMyName name=myName}}'), - context: { - myName: 'Quint', - notMyName: 'Sergio' - } - }).create(); - - expectAssertion(function() { - runAppend(view); - }, `You cannot specify both a positional param (at position 0) and the hash argument \`name\`.`); -}); - -QUnit.test('static arbitrary number of positional parameters', function() { - var SampleComponent = Component.extend(); - SampleComponent.reopenClass({ - positionalParams: 'names' - }); - - owner.register('template:components/sample-component', compile('{{#each attrs.names as |name|}}{{name}}{{/each}}')); - owner.register('component:sample-component', SampleComponent); - - view = EmberView.extend({ - [OWNER]: owner, - layout: compile('{{sample-component "Foo" 4 "Bar" id="args-3"}}{{sample-component "Foo" 4 "Bar" 5 "Baz" id="args-5"}}{{component "sample-component" "Foo" 4 "Bar" 5 "Baz" id="helper"}}') - }).create(); - - runAppend(view); - - equal(view.$('#args-3').text(), 'Foo4Bar'); - equal(view.$('#args-5').text(), 'Foo4Bar5Baz'); - equal(view.$('#helper').text(), 'Foo4Bar5Baz'); -}); - -QUnit.test('arbitrary positional parameter conflict with hash parameter is reported', function() { - var SampleComponent = Component.extend(); - SampleComponent.reopenClass({ - positionalParams: 'names' - }); - - owner.register('template:components/sample-component', compile('{{#each attrs.names as |name|}}{{name}}{{/each}}')); - owner.register('component:sample-component', SampleComponent); - - view = EmberView.extend({ - [OWNER]: owner, - layout: compile('{{sample-component "Foo" 4 "Bar" names=numbers id="args-3"}}'), - context: { - numbers: [1, 2, 3] - } - }).create(); - - expectAssertion(function() { - runAppend(view); - }, `You cannot specify positional parameters and the hash argument \`names\`.`); -}); - -QUnit.test('can use hash parameter instead of arbitrary positional param [GH #12444]', function() { - var SampleComponent = Component.extend(); - SampleComponent.reopenClass({ - positionalParams: 'names' - }); - - owner.register('template:components/sample-component', compile('{{#each attrs.names as |name|}}{{name}}{{/each}}')); - owner.register('component:sample-component', SampleComponent); - - view = EmberView.extend({ - [OWNER]: owner, - layout: compile('{{sample-component names=things id="args-3"}}'), - context: { - things: ['Foo', 4, 'Bar'] - } - }).create(); - - runAppend(view); - - equal(view.$('#args-3').text(), 'Foo4Bar'); -}); - -QUnit.test('can use hash parameter instead of positional param', function() { - var SampleComponent = Component.extend(); - SampleComponent.reopenClass({ - positionalParams: ['first', 'second'] - }); - - owner.register('template:components/sample-component', compile('{{attrs.first}} - {{attrs.second}}')); - owner.register('component:sample-component', SampleComponent); - - view = EmberView.extend({ - [OWNER]: owner, - layout: compile(` - {{sample-component "one" "two" id="two-positional"}} - {{sample-component "one" second="two" id="one-positional"}} - {{sample-component first="one" second="two" id="no-positional"}} - - `), - context: { - things: ['Foo', 4, 'Bar'] - } - }).create(); - - runAppend(view); - - equal(view.$('#two-positional').text(), 'one - two'); - equal(view.$('#one-positional').text(), 'one - two'); - equal(view.$('#no-positional').text(), 'one - two'); -}); - -QUnit.test('dynamic arbitrary number of positional parameters', function() { - var SampleComponent = Component.extend(); - SampleComponent.reopenClass({ - positionalParams: 'n' - }); - owner.register('template:components/sample-component', compile('{{#each attrs.n as |name|}}{{name}}{{/each}}')); - owner.register('component:sample-component', SampleComponent); - - view = EmberView.extend({ - [OWNER]: owner, - layout: compile('{{sample-component user1 user2 id="direct"}}{{component "sample-component" user1 user2 id="helper"}}'), - context: { - user1: 'Foo', - user2: 4 - } - }).create(); - - runAppend(view); - - equal(view.$('#direct').text(), 'Foo4'); - equal(view.$('#helper').text(), 'Foo4'); - run(function() { - set(view.context, 'user1', 'Bar'); - set(view.context, 'user2', '5'); - }); - - equal(view.$('#direct').text(), 'Bar5'); - equal(view.$('#helper').text(), 'Bar5'); - - run(function() { - set(view.context, 'user2', '6'); - }); - - equal(view.$('#direct').text(), 'Bar6'); - equal(view.$('#helper').text(), 'Bar6'); -}); - QUnit.test('moduleName is available on _renderNode when a layout is present', function() { expect(1); @@ -577,12 +48,12 @@ QUnit.test('moduleName is available on _renderNode when a layout is present', fu } })); - view = EmberView.extend({ + component = Component.extend({ [OWNER]: owner, layout: compile('{{sample-component}}') }).create(); - runAppend(view); + runAppend(component); }); QUnit.test('moduleName is available on _renderNode when no layout is present', function() { @@ -595,367 +66,14 @@ QUnit.test('moduleName is available on _renderNode when no layout is present', f } })); - view = EmberView.extend({ + component = Component.extend({ [OWNER]: owner, layout: compile('{{#sample-component}}Derp{{/sample-component}}', { moduleName: templateModuleName }) }).create(); - runAppend(view); -}); - -QUnit.test('{{component}} helper works with positional params', function() { - var SampleComponent = Component.extend(); - SampleComponent.reopenClass({ - positionalParams: ['name', 'age'] - }); - - owner.register('template:components/sample-component', compile('{{attrs.name}}{{attrs.age}}')); - owner.register('component:sample-component', SampleComponent); - - view = EmberView.extend({ - [OWNER]: owner, - layout: compile('{{component "sample-component" myName myAge}}'), - context: { - myName: 'Quint', - myAge: 4 - } - }).create(); - - runAppend(view); - equal(jQuery('#qunit-fixture').text(), 'Quint4'); - run(function() { - set(view.context, 'myName', 'Edward'); - set(view.context, 'myAge', '5'); - }); - - equal(jQuery('#qunit-fixture').text(), 'Edward5'); -}); - -QUnit.test('yield to inverse', function() { - owner.register('template:components/my-if', compile('{{#if predicate}}Yes:{{yield someValue}}{{else}}No:{{yield to="inverse"}}{{/if}}')); - - view = EmberView.extend({ - [OWNER]: owner, - layout: compile('{{#my-if predicate=activated someValue=42 as |result|}}Hello{{result}}{{else}}Goodbye{{/my-if}}'), - context: { - activated: true - } - }).create(); - - runAppend(view); - equal(jQuery('#qunit-fixture').text(), 'Yes:Hello42'); - run(function() { - set(view.context, 'activated', false); - }); - - equal(jQuery('#qunit-fixture').text(), 'No:Goodbye'); -}); - -QUnit.test('parameterized hasBlock inverse', function() { - owner.register('template:components/check-inverse', compile('{{#if (hasBlock "inverse")}}Yes{{else}}No{{/if}}')); - - view = EmberView.extend({ - [OWNER]: owner, - layout: compile('{{#check-inverse id="expect-no"}}{{/check-inverse}} {{#check-inverse id="expect-yes"}}{{else}}{{/check-inverse}}') - }).create(); - - runAppend(view); - equal(jQuery('#qunit-fixture #expect-no').text(), 'No'); - equal(jQuery('#qunit-fixture #expect-yes').text(), 'Yes'); -}); - -QUnit.test('parameterized hasBlock default', function() { - owner.register('template:components/check-block', compile('{{#if (hasBlock)}}Yes{{else}}No{{/if}}')); - - view = EmberView.extend({ - [OWNER]: owner, - layout: compile('{{check-block id="expect-no"}} {{#check-block id="expect-yes"}}{{/check-block}}') - }).create(); - - runAppend(view); - equal(jQuery('#qunit-fixture #expect-no').text(), 'No'); - equal(jQuery('#qunit-fixture #expect-yes').text(), 'Yes'); -}); - -QUnit.test('non-expression hasBlock ', function() { - owner.register('template:components/check-block', compile('{{#if hasBlock}}Yes{{else}}No{{/if}}')); - - view = EmberView.extend({ - [OWNER]: owner, - layout: compile('{{check-block id="expect-no"}} {{#check-block id="expect-yes"}}{{/check-block}}') - }).create(); - - runAppend(view); - equal(jQuery('#qunit-fixture #expect-no').text(), 'No'); - equal(jQuery('#qunit-fixture #expect-yes').text(), 'Yes'); -}); - -QUnit.test('parameterized hasBlockParams', function() { - owner.register('template:components/check-params', compile('{{#if (hasBlockParams)}}Yes{{else}}No{{/if}}')); - - view = EmberView.extend({ - [OWNER]: owner, - layout: compile('{{#check-params id="expect-no"}}{{/check-params}} {{#check-params id="expect-yes" as |foo|}}{{/check-params}}') - }).create(); - - runAppend(view); - equal(jQuery('#qunit-fixture #expect-no').text(), 'No'); - equal(jQuery('#qunit-fixture #expect-yes').text(), 'Yes'); -}); - -QUnit.test('non-expression hasBlockParams', function() { - owner.register('template:components/check-params', compile('{{#if hasBlockParams}}Yes{{else}}No{{/if}}')); - - view = EmberView.extend({ - [OWNER]: owner, - layout: compile('{{#check-params id="expect-no"}}{{/check-params}} {{#check-params id="expect-yes" as |foo|}}{{/check-params}}') - }).create(); - - runAppend(view); - equal(jQuery('#qunit-fixture #expect-no').text(), 'No'); - equal(jQuery('#qunit-fixture #expect-yes').text(), 'Yes'); -}); - -QUnit.test('components in template of a yielding component should have the proper parentView', function() { - var outer, innerTemplate, innerLayout; - - owner.register('component:x-outer', Component.extend({ - init() { - this._super(...arguments); - outer = this; - } - })); - - owner.register('component:x-inner-in-template', Component.extend({ - init() { - this._super(...arguments); - innerTemplate = this; - } - })); - - owner.register('component:x-inner-in-layout', Component.extend({ - init() { - this._super(...arguments); - innerLayout = this; - } - })); - - owner.register('template:components/x-outer', compile('{{x-inner-in-layout}}{{yield}}')); - - view = EmberView.extend({ - [OWNER]: owner, - template: compile('{{#x-outer}}{{x-inner-in-template}}{{/x-outer}}') - }).create(); - - runAppend(view); - - equal(innerTemplate.parentView, outer, 'receives the wrapping component as its parentView in template blocks'); - equal(innerLayout.parentView, outer, 'receives the wrapping component as its parentView in layout'); - equal(outer.parentView, view, 'x-outer receives the ambient scope as its parentView'); -}); - -QUnit.test('newly-added sub-components get correct parentView', function() { - var outer, inner; - - owner.register('component:x-outer', Component.extend({ - init() { - this._super(...arguments); - outer = this; - } - })); - - owner.register('component:x-inner', Component.extend({ - init() { - this._super(...arguments); - inner = this; - } - })); - - view = EmberView.extend({ - [OWNER]: owner, - template: compile('{{#x-outer}}{{#if view.showInner}}{{x-inner}}{{/if}}{{/x-outer}}'), - showInner: false - }).create(); - - runAppend(view); - - run(() => { view.set('showInner', true); }); - - equal(inner.parentView, outer, 'receives the wrapping component as its parentView in template blocks'); - equal(outer.parentView, view, 'x-outer receives the ambient scope as its parentView'); -}); - -QUnit.test('components should receive the viewRegistry from the parent view', function() { - var outer, innerTemplate, innerLayout; - - var viewRegistry = {}; - - owner.register('component:x-outer', Component.extend({ - init() { - this._super(...arguments); - outer = this; - } - })); - - owner.register('component:x-inner-in-template', Component.extend({ - init() { - this._super(...arguments); - innerTemplate = this; - } - })); - - owner.register('component:x-inner-in-layout', Component.extend({ - init() { - this._super(...arguments); - innerLayout = this; - } - })); - - owner.register('template:components/x-outer', compile('{{x-inner-in-layout}}{{yield}}')); - - view = EmberView.extend({ - [OWNER]: owner, - _viewRegistry: viewRegistry, - template: compile('{{#x-outer}}{{x-inner-in-template}}{{/x-outer}}') - }).create(); - - runAppend(view); - - equal(innerTemplate._viewRegistry, viewRegistry); - equal(innerLayout._viewRegistry, viewRegistry); - equal(outer._viewRegistry, viewRegistry); -}); - -QUnit.test('comopnent should rerender when a property is changed during children\'s rendering', function() { - expectDeprecation(/modified value twice in a single render/); - - var outer, middle; - - owner.register('component:x-outer', Component.extend({ - value: 1, - grabReference: Ember.on('init', function() { - outer = this; - }) - })); - - owner.register('component:x-middle', Component.extend({ - value: null, - grabReference: Ember.on('init', function() { - middle = this; - }) - })); - - owner.register('component:x-inner', Component.extend({ - value: null, - pushDataUp: Ember.observer('value', function() { - middle.set('value', this.get('value')); - }) - })); - - owner.register('template:components/x-outer', compile('{{#x-middle}}{{x-inner value=value}}{{/x-middle}}')); - owner.register('template:components/x-middle', compile('
{{value}}
{{yield}}')); - owner.register('template:components/x-inner', compile('
{{value}}
')); - - - view = EmberView.extend({ - [OWNER]: owner, - template: compile('{{x-outer}}') - }).create(); - - runAppend(view); - - equal(view.$('#inner-value').text(), '1', 'initial render of inner'); - equal(view.$('#middle-value').text(), '', 'initial render of middle (observers do not run during init)'); - - run(() => outer.set('value', 2)); - - equal(view.$('#inner-value').text(), '2', 'second render of inner'); - equal(view.$('#middle-value').text(), '2', 'second render of middle'); - - run(() => outer.set('value', 3)); - - equal(view.$('#inner-value').text(), '3', 'third render of inner'); - equal(view.$('#middle-value').text(), '3', 'third render of middle'); -}); - -QUnit.test('non-block with each rendering child components', function() { - expect(2); - - owner.register('template:components/non-block', compile('In layout. {{#each attrs.items as |item|}}[{{child-non-block item=item}}]{{/each}}')); - owner.register('template:components/child-non-block', compile('Child: {{attrs.item}}.')); - - var items = emberA(['Tom', 'Dick', 'Harry']); - - view = EmberView.extend({ - [OWNER]: owner, - template: compile('{{non-block items=view.items}}'), - items: items - }).create(); - - runAppend(view); - - equal(jQuery('#qunit-fixture').text(), 'In layout. [Child: Tom.][Child: Dick.][Child: Harry.]'); - - run(function() { - items.pushObject('James'); - }); - - equal(jQuery('#qunit-fixture').text(), 'In layout. [Child: Tom.][Child: Dick.][Child: Harry.][Child: James.]'); -}); - -QUnit.test('specifying classNames results in correct class', function(assert) { - expect(3); - - let clickyThing; - owner.register('component:some-clicky-thing', Component.extend({ - tagName: 'button', - classNames: ['foo', 'bar'], - init() { - this._super(...arguments); - clickyThing = this; - } - })); - - view = EmberView.extend({ - [OWNER]: owner, - template: compile('{{#some-clicky-thing classNames="baz"}}Click Me{{/some-clicky-thing}}') - }).create(); - - runAppend(view); - - let button = view.$('button'); - ok(button.is('.foo.bar.baz.ember-view'), 'the element has the correct classes: ' + button.attr('class')); - - let expectedClassNames = ['ember-view', 'foo', 'bar', 'baz']; - assert.deepEqual(clickyThing.get('classNames'), expectedClassNames, 'classNames are properly combined'); - - let buttonClassNames = button.attr('class'); - assert.deepEqual(buttonClassNames.split(' '), expectedClassNames, 'all classes are set 1:1 in DOM'); -}); - -QUnit.test('specifying custom concatenatedProperties avoids clobbering', function(assert) { - expect(1); - - let clickyThing; - owner.register('component:some-clicky-thing', Component.extend({ - concatenatedProperties: ['blahzz'], - blahzz: ['blark', 'pory'], - init() { - this._super(...arguments); - clickyThing = this; - } - })); - - view = EmberView.extend({ - [OWNER]: owner, - template: compile('{{#some-clicky-thing blahzz="baz"}}Click Me{{/some-clicky-thing}}') - }).create(); - - runAppend(view); - - assert.deepEqual(clickyThing.get('blahzz'), ['blark', 'pory', 'baz'], 'property is properly combined'); + runAppend(component); }); }