diff --git a/packages/@ember/-internals/glimmer/lib/helpers/array.ts b/packages/@ember/-internals/glimmer/lib/helpers/array.ts new file mode 100644 index 00000000000..a1730944ca6 --- /dev/null +++ b/packages/@ember/-internals/glimmer/lib/helpers/array.ts @@ -0,0 +1,40 @@ +import { Arguments, VM } from '@glimmer/runtime'; +import { PathReference } from '@glimmer/reference'; +import { Opaque } from '@glimmer/util'; + +/** +@module ember +*/ + +/** + Use the `{{array}}` helper to create an array to pass as an option to your + components. + + ```handlebars + {{my-component people=(array + 'Tom Dade' + 'Yehuda Katz' + this.myOtherPerson) + }} + ``` + + Would result in an object such as: + + ```js + ['Tom Date', 'Yehuda Katz', this.get('myOtherPerson')] + ``` + + Where the 3rd item in the array is bound to updates of the `myOtherPerson` property. + + @method array + @for Ember.Templates.helpers + @param {Array} options + @return {Array} Array + @category array-helper + @since 3.7.0 + @public + */ + +export default function(_vm: VM, args: Arguments): PathReference { + return args.positional.capture(); +} diff --git a/packages/@ember/-internals/glimmer/lib/resolver.ts b/packages/@ember/-internals/glimmer/lib/resolver.ts index 11f2c1ed4e3..90632d6f432 100644 --- a/packages/@ember/-internals/glimmer/lib/resolver.ts +++ b/packages/@ember/-internals/glimmer/lib/resolver.ts @@ -23,6 +23,7 @@ import { default as htmlSafeHelper } from './helpers/-html-safe'; import { default as inputTypeHelper } from './helpers/-input-type'; import { default as normalizeClassHelper } from './helpers/-normalize-class'; import { default as action } from './helpers/action'; +import { default as array } from './helpers/array'; import { default as concat } from './helpers/concat'; import { default as eachIn } from './helpers/each-in'; import { default as get } from './helpers/get'; @@ -58,6 +59,7 @@ const BUILTINS_HELPERS = { concat, get, hash, + array, log, mut, 'query-params': queryParams, diff --git a/packages/@ember/-internals/glimmer/tests/integration/helpers/array-test.js b/packages/@ember/-internals/glimmer/tests/integration/helpers/array-test.js new file mode 100644 index 00000000000..32ce1dd99c8 --- /dev/null +++ b/packages/@ember/-internals/glimmer/tests/integration/helpers/array-test.js @@ -0,0 +1,236 @@ +import { RenderingTest, moduleFor } from '../../utils/test-case'; +import { strip } from '../../utils/abstract-test-case'; +import { Component } from '../../utils/helpers'; +import { set } from '@ember/-internals/metal'; + +moduleFor( + 'Helpers test: {{array}}', + class extends RenderingTest { + ['@test returns an array']() { + this.render(strip`{{#with (array "Sergio") as |people|}} + {{#each people as |personName|}} + {{personName}} + {{/each}} + {{/with}}`); + + this.assertText('Sergio'); + + this.assertStableRerender(); + } + + ['@test can have more than one value']() { + this.render(strip`{{#with (array "Sergio" "Robert") as |people|}} + {{#each people as |personName|}} + {{personName}}, + {{/each}} + {{/with}}`); + + this.assertText('Sergio,Robert,'); + + this.assertStableRerender(); + } + + ['@test binds values when variables are used']() { + this.render( + strip`{{#with (array personOne) as |people|}} + {{#each people as |personName|}} + {{personName}} + {{/each}} + {{/with}}`, + { + personOne: 'Tom', + } + ); + + this.assertText('Tom'); + + this.assertStableRerender(); + + this.runTask(() => set(this.context, 'personOne', 'Yehuda')); + this.assertText('Yehuda'); + } + + ['@test binds multiple values when variables are used']() { + this.render( + strip`{{#with (array personOne personTwo) as |people|}} + {{#each people as |personName|}} + {{personName}}, + {{/each}} + {{/with}}`, + { + personOne: 'Tom', + personTwo: 'Yehuda', + } + ); + + this.assertText('Tom,Yehuda,'); + + this.assertStableRerender(); + + this.runTask(() => set(this.context, 'personOne', 'Sergio')); + + this.assertText('Sergio,Yehuda,'); + + this.runTask(() => set(this.context, 'personTwo', 'Tom')); + + this.assertText('Sergio,Tom,'); + } + + ['@test array helpers can be nested']() { + this.render( + strip`{{#with (array (array personOne personTwo)) as |listOfPeople|}} + {{#each listOfPeople as |people|}} + List: + {{#each people as |personName|}} + {{personName}}, + {{/each}} + {{/each}} + {{/with}}`, + { + personOne: 'Tom', + personTwo: 'Yehuda', + } + ); + + this.assertText('List:Tom,Yehuda,'); + + this.assertStableRerender(); + + this.runTask(() => set(this.context, 'personOne', 'Chad')); + + this.assertText('List:Chad,Yehuda,'); + + this.runTask(() => set(this.context, 'personTwo', 'Balint')); + + this.assertText('List:Chad,Balint,'); + } + + ['@test should yield hash of an array of internal properties']() { + let fooBarInstance; + let FooBarComponent = Component.extend({ + init() { + this._super(); + fooBarInstance = this; + this.model = { personOne: 'Chad' }; + }, + }); + + this.registerComponent('foo-bar', { + ComponentClass: FooBarComponent, + template: `{{yield (hash people=(array model.personOne))}}`, + }); + + this.render(strip`{{#foo-bar as |values|}} + {{#each values.people as |personName|}} + {{personName}} + {{/each}} + {{/foo-bar}}`); + + this.assertText('Chad'); + + this.assertStableRerender(); + + this.runTask(() => set(fooBarInstance, 'model.personOne', 'Godfrey')); + + this.assertText('Godfrey'); + + this.runTask(() => set(fooBarInstance, 'model', { personOne: 'Chad' })); + + this.assertText('Chad'); + } + + ['@test should yield hash of an array of internal and external properties']() { + let fooBarInstance; + let FooBarComponent = Component.extend({ + init() { + this._super(); + fooBarInstance = this; + this.model = { personOne: 'Chad' }; + }, + }); + + this.registerComponent('foo-bar', { + ComponentClass: FooBarComponent, + template: `{{yield (hash people=(array model.personOne personTwo))}}`, + }); + + this.render( + strip`{{#foo-bar personTwo=model.personTwo as |values|}} + {{#each values.people as |personName|}} + {{personName}}, + {{/each}} + {{/foo-bar}}`, + { + model: { personTwo: 'Tom' }, + } + ); + + this.assertText('Chad,Tom,'); + + this.assertStableRerender(); + + this.runTask(() => { + set(fooBarInstance, 'model.personOne', 'Godfrey'); + set(this.context, 'model.personTwo', 'Yehuda'); + }); + + this.assertText('Godfrey,Yehuda,'); + + this.runTask(() => { + set(fooBarInstance, 'model', { personOne: 'Chad' }); + set(this.context, 'model', { personTwo: 'Tom' }); + }); + + this.assertText('Chad,Tom,'); + } + + ['@test should render when passing as argument to a component invocation']() { + let FooBarComponent = Component.extend({}); + + this.registerComponent('foo-bar', { + ComponentClass: FooBarComponent, + template: strip`{{#each people as |personName|}} + {{personName}}, + {{/each}}`, + }); + + this.render(strip`{{foo-bar people=(array "Tom" personTwo)}}`, { personTwo: 'Chad' }); + + this.assertText('Tom,Chad,'); + + this.assertStableRerender(); + + this.runTask(() => set(this.context, 'personTwo', 'Godfrey')); + + this.assertText('Tom,Godfrey,'); + } + + ['@test should return an entirely new array when any argument change']() { + let fooBarInstance; + let FooBarComponent = Component.extend({ + init() { + this._super(); + fooBarInstance = this; + }, + }); + + this.registerComponent('foo-bar', { + ComponentClass: FooBarComponent, + template: strip`{{#each people as |personName|}} + {{personName}}, + {{/each}}`, + }); + + this.render(strip`{{foo-bar people=(array "Tom" personTwo)}}`, { personTwo: 'Chad' }); + + let firstArray = fooBarInstance.people; + + this.runTask(() => set(this.context, 'personTwo', 'Godfrey')); + + this.assert.ok( + firstArray !== fooBarInstance.people, + 'should have created an entirely new array' + ); + } + } +);