import { RenderingTest, moduleFor } from '../../utils/test-case';
import { strip } from '../../utils/abstract-test-case';
import { set, get, setProperties } from '@ember/-internals/metal';
import { Component } from '../../utils/helpers';
import { A as emberA } from '@ember/-internals/runtime';
moduleFor(
'Helpers test: {{unbound}}',
class extends RenderingTest {
['@test should be able to output a property without binding']() {
this.render(`
{{unbound content.anUnboundString}}
`, {
content: {
anUnboundString: 'No spans here, son.',
},
});
this.assertText('No spans here, son.');
this.runTask(() => this.rerender());
this.assertText('No spans here, son.');
this.runTask(() => set(this.context, 'content.anUnboundString', 'HEY'));
this.assertText('No spans here, son.');
this.runTask(() =>
set(this.context, 'content', {
anUnboundString: 'No spans here, son.',
})
);
this.assertText('No spans here, son.');
}
['@test should be able to use unbound helper in #each helper']() {
this.render(`{{#each items as |item|}}- {{unbound item}}
{{/each}}
`, {
items: emberA(['a', 'b', 'c', 1, 2, 3]),
});
this.assertText('abc123');
this.runTask(() => this.rerender());
this.assertText('abc123');
}
['@test should be able to use unbound helper in #each helper (with objects)']() {
this.render(`{{#each items as |item|}}- {{unbound item.wham}}
{{/each}}
`, {
items: emberA([{ wham: 'bam' }, { wham: 1 }]),
});
this.assertText('bam1');
this.runTask(() => this.rerender());
this.assertText('bam1');
this.runTask(() => this.context.items.setEach('wham', 'HEY'));
this.assertText('bam1');
this.runTask(() => set(this.context, 'items', emberA([{ wham: 'bam' }, { wham: 1 }])));
this.assertText('bam1');
}
['@test it should assert unbound cannot be called with multiple arguments']() {
let willThrow = () => {
this.render(`{{unbound foo bar}}`, {
foo: 'BORK',
bar: 'BLOOP',
});
};
expectAssertion(
willThrow,
/unbound helper cannot be called with multiple params or hash params/
);
}
['@test should render on attributes']() {
this.render(``, {
model: { foo: 'BORK' },
});
this.assertHTML('');
this.runTask(() => this.rerender());
this.assertHTML('');
this.runTask(() => set(this.context, 'model.foo', 'OOF'));
this.assertHTML('');
this.runTask(() => set(this.context, 'model', { foo: 'BORK' }));
this.assertHTML('');
}
['@test should property escape unsafe hrefs']() {
let unsafeUrls = emberA([
{
name: 'Bob',
url: 'javascript:bob-is-cool', // jshint ignore:line
},
{
name: 'James',
url: 'vbscript:james-is-cool', // jshint ignore:line
},
{
name: 'Richard',
url: 'javascript:richard-is-cool', // jshint ignore:line
},
]);
this.render(
``,
{
people: unsafeUrls,
}
);
let escapedHtml = strip`
`;
this.assertHTML(escapedHtml);
this.runTask(() => this.rerender());
this.assertHTML(escapedHtml);
this.runTask(() => this.context.people.setEach('url', 'http://google.com'));
this.assertHTML(escapedHtml);
this.runTask(() => set(this.context, 'people', unsafeUrls));
this.assertHTML(escapedHtml);
}
['@skip helper form updates on parent re-render']() {
this.render(`{{unbound foo}}`, {
foo: 'BORK',
});
this.assertText('BORK');
this.runTask(() => this.rerender());
this.assertText('BORK');
this.runTask(() => set(this.context, 'foo', 'OOF'));
this.assertText('BORK');
this.runTask(() => this.rerender());
this.assertText('OOF');
this.runTask(() => set(this.context, 'foo', ''));
this.assertText('OOF');
this.runTask(() => set(this.context, 'foo', 'BORK'));
this.runTask(() => this.rerender());
this.assertText('BORK');
}
// semantics here is not guaranteed
['@test sexpr form does not update no matter what']() {
this.registerHelper('capitalize', args => args[0].toUpperCase());
this.render(`{{capitalize (unbound foo)}}`, {
foo: 'bork',
});
this.assertText('BORK');
this.runTask(() => this.rerender());
this.assertText('BORK');
this.runTask(() => {
set(this.context, 'foo', 'oof');
this.rerender();
});
this.assertText('BORK');
this.runTask(() => set(this.context, 'foo', 'blip'));
this.assertText('BORK');
this.runTask(() => {
set(this.context, 'foo', 'bork');
this.rerender();
});
this.assertText('BORK');
}
['@test sexpr in helper form does not update on parent re-render']() {
this.registerHelper('capitalize', params => params[0].toUpperCase());
this.registerHelper('doublize', params => `${params[0]} ${params[0]}`);
this.render(`{{capitalize (unbound (doublize foo))}}`, {
foo: 'bork',
});
this.assertText('BORK BORK');
this.runTask(() => this.rerender());
this.assertText('BORK BORK');
this.runTask(() => {
set(this.context, 'foo', 'oof');
this.rerender();
});
this.assertText('BORK BORK');
this.runTask(() => set(this.context, 'foo', 'blip'));
this.assertText('BORK BORK');
this.runTask(() => {
set(this.context, 'foo', 'bork');
this.rerender();
});
this.assertText('BORK BORK');
}
['@test should be able to render an unbound helper invocation']() {
this.registerHelper('repeat', ([value], { count }) => {
let a = [];
while (a.length < count) {
a.push(value);
}
return a.join('');
});
this.render(
`{{unbound (repeat foo count=bar)}} {{repeat foo count=bar}} {{unbound (repeat foo count=2)}} {{repeat foo count=4}}`,
{
foo: 'X',
bar: 5,
}
);
this.assertText('XXXXX XXXXX XX XXXX');
this.runTask(() => this.rerender());
this.assertText('XXXXX XXXXX XX XXXX');
this.runTask(() => set(this.context, 'bar', 1));
this.assertText('XXXXX X XX XXXX');
this.runTask(() => set(this.context, 'bar', 5));
this.assertText('XXXXX XXXXX XX XXXX');
}
['@test should be able to render an bound helper invocation mixed with static values']() {
this.registerHelper('surround', ([prefix, value, suffix]) => `${prefix}-${value}-${suffix}`);
this.render(
strip`
{{unbound (surround model.prefix model.value "bar")}} {{surround model.prefix model.value "bar"}} {{unbound (surround "bar" model.value model.suffix)}} {{surround "bar" model.value model.suffix}}`,
{
model: {
prefix: 'before',
value: 'core',
suffix: 'after',
},
}
);
this.assertText('before-core-bar before-core-bar bar-core-after bar-core-after');
this.runTask(() => this.rerender());
this.assertText('before-core-bar before-core-bar bar-core-after bar-core-after');
this.runTask(() => {
setProperties(this.context.model, {
prefix: 'beforeChanged',
value: 'coreChanged',
suffix: 'afterChanged',
});
});
this.assertText(
'before-core-bar beforeChanged-coreChanged-bar bar-core-after bar-coreChanged-afterChanged'
);
this.runTask(() => {
set(this.context, 'model', {
prefix: 'before',
value: 'core',
suffix: 'after',
});
});
this.assertText('before-core-bar before-core-bar bar-core-after bar-core-after');
}
['@test should be able to render unbound forms of multi-arg helpers']() {
this.registerHelper('fauxconcat', params => params.join(''));
this.render(
`{{fauxconcat model.foo model.bar model.bing}} {{unbound (fauxconcat model.foo model.bar model.bing)}}`,
{
model: {
foo: 'a',
bar: 'b',
bing: 'c',
},
}
);
this.assertText('abc abc');
this.runTask(() => this.rerender());
this.assertText('abc abc');
this.runTask(() => set(this.context, 'model.bar', 'X'));
this.assertText('aXc abc');
this.runTask(() =>
set(this.context, 'model', {
foo: 'a',
bar: 'b',
bing: 'c',
})
);
this.assertText('abc abc');
}
['@test should be able to render an unbound helper invocation for helpers with dependent keys']() {
this.registerHelper('capitalizeName', {
destroy() {
this.removeObserver('value.firstName', this, this.recompute);
this._super(...arguments);
},
compute([value]) {
if (this.get('value')) {
this.removeObserver('value.firstName', this, this.recompute);
}
this.set('value', value);
this.addObserver('value.firstName', this, this.recompute);
return value ? get(value, 'firstName').toUpperCase() : '';
},
});
this.registerHelper('concatNames', {
destroy() {
this.teardown();
this._super(...arguments);
},
teardown() {
this.removeObserver('value.firstName', this, this.recompute);
this.removeObserver('value.lastName', this, this.recompute);
},
compute([value]) {
if (this.get('value')) {
this.teardown();
}
this.set('value', value);
this.addObserver('value.firstName', this, this.recompute);
this.addObserver('value.lastName', this, this.recompute);
return (value ? get(value, 'firstName') : '') + (value ? get(value, 'lastName') : '');
},
});
this.render(
`{{capitalizeName person}} {{unbound (capitalizeName person)}} {{concatNames person}} {{unbound (concatNames person)}}`,
{
person: {
firstName: 'shooby',
lastName: 'taylor',
},
}
);
this.assertText('SHOOBY SHOOBY shoobytaylor shoobytaylor');
this.runTask(() => this.rerender());
this.assertText('SHOOBY SHOOBY shoobytaylor shoobytaylor');
this.runTask(() => set(this.context, 'person.firstName', 'sally'));
this.assertText('SALLY SHOOBY sallytaylor shoobytaylor');
this.runTask(() =>
set(this.context, 'person', {
firstName: 'shooby',
lastName: 'taylor',
})
);
this.assertText('SHOOBY SHOOBY shoobytaylor shoobytaylor');
}
['@test should be able to render an unbound helper invocation in #each helper']() {
this.registerHelper('capitalize', params => params[0].toUpperCase());
this.render(
`{{#each people as |person|}}{{capitalize person.firstName}} {{unbound (capitalize person.firstName)}}{{/each}}`,
{
people: emberA([
{
firstName: 'shooby',
lastName: 'taylor',
},
{
firstName: 'cindy',
lastName: 'taylor',
},
]),
}
);
this.assertText('SHOOBY SHOOBYCINDY CINDY');
this.runTask(() => this.rerender());
this.assertText('SHOOBY SHOOBYCINDY CINDY');
this.runTask(() => this.context.people.setEach('firstName', 'chad'));
this.assertText('CHAD SHOOBYCHAD CINDY');
this.runTask(() =>
set(
this.context,
'people',
emberA([
{
firstName: 'shooby',
lastName: 'taylor',
},
{
firstName: 'cindy',
lastName: 'taylor',
},
])
)
);
this.assertText('SHOOBY SHOOBYCINDY CINDY');
}
['@test should be able to render an unbound helper invocation with bound hash options']() {
this.registerHelper('capitalizeName', {
destroy() {
this.removeObserver('value.firstName', this, this.recompute);
this._super(...arguments);
},
compute([value]) {
if (this.get('value')) {
this.removeObserver('value.firstName', this, this.recompute);
}
this.set('value', value);
this.addObserver('value.firstName', this, this.recompute);
return value ? get(value, 'firstName').toUpperCase() : '';
},
});
this.registerHelper('concatNames', {
destroy() {
this.teardown();
this._super(...arguments);
},
teardown() {
this.removeObserver('value.firstName', this, this.recompute);
this.removeObserver('value.lastName', this, this.recompute);
},
compute([value]) {
if (this.get('value')) {
this.teardown();
}
this.set('value', value);
this.addObserver('value.firstName', this, this.recompute);
this.addObserver('value.lastName', this, this.recompute);
return (value ? get(value, 'firstName') : '') + (value ? get(value, 'lastName') : '');
},
});
this.render(
`{{capitalizeName person}} {{unbound (capitalizeName person)}} {{concatNames person}} {{unbound (concatNames person)}}`,
{
person: {
firstName: 'shooby',
lastName: 'taylor',
},
}
);
this.assertText('SHOOBY SHOOBY shoobytaylor shoobytaylor');
this.runTask(() => this.rerender());
this.assertText('SHOOBY SHOOBY shoobytaylor shoobytaylor');
this.runTask(() => set(this.context, 'person.firstName', 'sally'));
this.assertText('SALLY SHOOBY sallytaylor shoobytaylor');
this.runTask(() =>
set(this.context, 'person', {
firstName: 'shooby',
lastName: 'taylor',
})
);
this.assertText('SHOOBY SHOOBY shoobytaylor shoobytaylor');
}
['@test should be able to render bound form of a helper inside unbound form of same helper']() {
this.render(
strip`
{{#if (unbound model.foo)}}
{{#if model.bar}}true{{/if}}
{{#unless model.bar}}false{{/unless}}
{{/if}}
{{#unless (unbound model.notfoo)}}
{{#if model.bar}}true{{/if}}
{{#unless model.bar}}false{{/unless}}
{{/unless}}`,
{
model: {
foo: true,
notfoo: false,
bar: true,
},
}
);
this.assertText('truetrue');
this.runTask(() => this.rerender());
this.assertText('truetrue');
this.runTask(() => set(this.context, 'model.bar', false));
this.assertText('falsefalse');
this.runTask(() =>
set(this.context, 'model', {
foo: true,
notfoo: false,
bar: true,
})
);
this.assertText('truetrue');
}
['@test yielding unbound does not update']() {
let fooBarInstance;
let FooBarComponent = Component.extend({
init() {
this._super(...arguments);
fooBarInstance = this;
},
model: { foo: 'bork' },
});
this.registerComponent('foo-bar', {
ComponentClass: FooBarComponent,
template: `{{yield (unbound model.foo)}}`,
});
this.render(`{{#foo-bar as |value|}}{{value}}{{/foo-bar}}`);
this.assertText('bork');
this.runTask(() => this.rerender());
this.assertText('bork');
this.runTask(() => set(fooBarInstance, 'model.foo', 'oof'));
this.assertText('bork');
this.runTask(() => set(fooBarInstance, 'model', { foo: 'bork' }));
this.assertText('bork');
}
['@test yielding unbound hash does not update']() {
let fooBarInstance;
let FooBarComponent = Component.extend({
init() {
this._super(...arguments);
fooBarInstance = this;
},
model: { foo: 'bork' },
});
this.registerComponent('foo-bar', {
ComponentClass: FooBarComponent,
template: `{{yield (unbound (hash foo=model.foo))}}`,
});
this.render(`{{#foo-bar as |value|}}{{value.foo}}{{/foo-bar}}`);
this.assertText('bork');
this.runTask(() => this.rerender());
this.assertText('bork');
this.runTask(() => set(fooBarInstance, 'model.foo', 'oof'));
this.assertText('bork');
this.runTask(() => set(fooBarInstance, 'model', { foo: 'bork' }));
this.assertText('bork');
}
}
);