packages/__tests__/src/3-runtime-html/arrow-fn.spec.ts
import { BindingBehavior, ValueConverter, CustomAttribute, INode } from '@aurelia/runtime-html';
import { assert, createFixture } from '@aurelia/testing';
describe('3-runtime-html/arrow-fn.spec.ts', function () {
// leave this test at the top - if any tests below this one fail for unknown reasons, then corrupted parser state may not be properly recovered
it('corrupt the parser state to ensure its correctly reset afterwards', function () {
let err: Error;
try {
createFixture
.html`\${((e) => ({ e.v })({ v: 1 }))}`
.build();
} catch (e) {
err = e;
}
assert.match(err.message, /AUR0167/);
});
it('works with IIFE', function () {
const { assertText } = createFixture
.html`\${(a => a)(1)}`
.build();
assertText('1');
});
it('works with paren wrapping {}', function () {
const { assertText } = createFixture
.html`\${(((e) => ({ a: e.v }))({ v: 1 })).a}`
.build();
assertText('1');
});
it('can sort number array', function () {
const { assertText } = createFixture
.html`<div repeat.for='i of items.sort((a, b) => a - b)'>\${i}</div>`
.component({ items: [5, 7, 6] })
.build();
assertText('567');
});
it('can observe property accessed in each parameter', function () {
const { component, assertText } = createFixture
.component({ items: [{ v: 0 }, { v: 1 }] })
.html`<div repeat.for='i of items.filter(i => i.v > 0)'>\${i.v}</div>`
.build();
assertText('1');
component.items[0].v = 1;
assertText('11');
});
it('can reduce number array', function () {
const { assertText } = createFixture
.html`\${items.reduce((sum, x) => sum + x, 0)}`
.component({ items: [3, 4] })
.build();
assertText('7');
});
it('can call nested arrow inline', function () {
const { assertText } = createFixture
.html`\${(a => b => a + b)(1)(2)}`
.build();
assertText('3');
});
it('can call arrow inline with rest', function () {
const { assertText } = createFixture
.html`\${((...args) => args[0] + args[1] + args[2])(1, 2, 3)}`
.build();
assertText('6');
});
it('can flatMap nested fn', function () {
const { assertText } = createFixture
.component({
items: [
{ name: 'a1', children: [{ name: 'b1', children: [{ name: 'c1' }] }] },
{ name: 'a2', children: [{ name: 'b2', children: [{ name: 'c2' }] }] }
]
})
.html`<div repeat.for='item of items.flatMap(x => [x].concat(x.children.flatMap(y => [y].concat(y.children))))'>\${item.name}-</div>`
.build();
assertText('a1-b1-c1-a2-b2-c2-');
});
it('can flatMap nested fn and access parent scope', function () {
const { assertText } = createFixture
.component({
items: [
{ name: 'a1', children: [{ name: 'b1', children: [{ name: 'c1' }] }] },
{ name: 'a2', children: [{ name: 'b2', children: [{ name: 'c2' }] }] }
]
})
.html`<div repeat.for='item of items.flatMap(x => x.children.flatMap(y => ([x, y].concat(y.children))))'>\${item.name}-</div>`
.build();
assertText('a1-b1-c1-a2-b2-c2-');
});
it('can access the correct scope via $this', function () {
const { assertText } = createFixture
.html`\${(a => $this.a)('2')}`
.component({ a: '1' })
.build();
assertText('1');
});
it('can access the correct scope via $this in nested arrow', function () {
const { assertText } = createFixture
.html`\${(a => a => $this.a)('3')('2')}`
.component({ a: '1' })
.build();
assertText('1');
});
it('can access the correct scope via $parent', function () {
const { assertText } = createFixture
.html`<div with.bind='{a:2}'><div with.bind='{a:3}'><div with.bind='{a:4}'>\${(a => $parent.a)('5')}</div></div></div>`
.component({ a: '1' })
.build();
assertText('3');
});
it('can access the correct scope via $parent in nested arrow', function () {
const { assertText } = createFixture
.html`<div with.bind='{a:2}'><div with.bind='{a:3}'><div with.bind='{a:4}'>\${(a => a => $parent.a)('6')('5')}</div></div></div>`
.component({ a: '1' })
.build();
assertText('3');
});
it('can access the correct scope via $parent.$parent in nested arrow', function () {
const { assertText } = createFixture
.html`<div with.bind='{a:2}'><div with.bind='{a:3}'><div with.bind='{a:4}'>\${(a => a => $parent.$parent.a)('6')('5')}</div></div></div>`
.component({ a: '1' })
.build();
assertText('2');
});
it('works with attribute binding + binding command', function () {
const { getBy } = createFixture
.component({ getValue: v => `light${v}` })
.html`<div square.bind='v => getValue(v)'>`
.deps(CustomAttribute.define('square', class {
static inject = [INode];
value: (v: string) => string;
constructor(private readonly host: HTMLElement) {}
binding() {
this.host.setAttribute('data-color', this.value('red'));
}
}))
.build();
assert.strictEqual(getBy('div').getAttribute('data-color'), 'lightred');
});
it('works with attribute multi binding syntax', function () {
const { getBy } = createFixture
.component({
getValue: v => `light${v}`,
getDarkValue: v => `dark${v}`,
})
.html`<div square='fn1.bind: v => getValue(v); fn2.bind: v => getDarkValue(v)'>`
.deps(CustomAttribute.define({ name: 'square', bindables: ['fn1', 'fn2'] }, class {
static inject = [INode];
fn1: (v: string) => string;
fn2: (v: string) => string;
constructor(private readonly host: HTMLElement) {}
binding() {
this.host.setAttribute('data-color-light', this.fn1('red'));
this.host.setAttribute('data-color-dark', this.fn2('green'));
}
}))
.build();
assert.strictEqual(getBy('div').getAttribute('data-color-light'), 'lightred');
assert.strictEqual(getBy('div').getAttribute('data-color-dark'), 'darkgreen');
});
it('works with event', function () {
let i = 0;
const { getBy } = createFixture
.html`<button click.trigger='() => clicked()'>`
.component({ clicked: () => i = 1 })
.build();
getBy('button').click();
assert.strictEqual(i, 1);
});
it('works with binding behavior', function () {
const { assertText } = createFixture
.html`<div repeat.for='i of items.sort((a, b) => a - b) & log'>\${i}</div>`
.component({ items: [5, 7, 6] })
.deps(BindingBehavior.define('log', class {}))
.build();
assertText('567');
});
it('works with value converter', function () {
const { assertText } = createFixture
.html`<div repeat.for='i of items.sort((a, b) => a - b) | identity'>\${i}</div>`
.component({ items: [5, 7, 6] })
.deps(ValueConverter.define('identity', class {
toView() {
return [1];
}
}))
.build();
assertText('1');
});
describe('array obervation', function () {
it('observes on .map()', function () {
const { component, flush, assertText } = createFixture
.component({ items: [1] })
.html`\${items.map(i => i + 1)}`
.build();
assertText('2');
component.items.push(2);
flush();
assertText('2,3');
component.items.push(3);
flush();
assertText('2,3,4');
});
it('observes on repeat + .map()', function () {
const { component, flush, assertText } = createFixture
.component({ items: [1] })
.html`<div repeat.for='i of items.map(i => i + 1)'>\${i}`
.build();
assertText('2');
component.items.push(2);
flush();
assertText('23');
component.items.push(3);
flush();
assertText('234');
});
it('observes on <let> + .map()', function () {
const { component, flush, assertText } = createFixture
.component({ items: [1] })
.html`<let i.bind='items.map(i => i + 1)'></let>\${i}`
.build();
assertText('2');
component.items.push(2);
flush();
assertText('2,3');
component.items.push(3);
flush();
assertText('2,3,4');
});
it('observes on .filter()', function () {
const { component, flush, assertText } = createFixture
.component({ items: [1] })
.html`\${items.filter(i => i > 1)}`
.build();
assertText('');
component.items.push(2);
flush();
assertText('2');
component.items.push(3);
flush();
assertText('2,3');
});
it('can call .filter() with function call inside', function () {
const { assertText } = createFixture
.component({ query: 'item', items: [{ name: 'item 1' }, { name: 'gib' }] })
.html`<div repeat.for="item of items.filter(i => i.name.includes(query))">\${item.name}</div>`
.build();
assertText('item 1');
});
it('observes on .at()', function () {
const { component, flush, assertText } = createFixture
.component({ items: [1] })
.html`\${items.at(-1)}`
.build();
assertText('1');
component.items.push(2);
flush();
assertText('2');
component.items.splice(1);
flush();
assertText('1');
});
it('observes on .includes()', function () {
const { component, flush, assertText } = createFixture
.component({ items: [1] })
.html`\${items.includes(2)}`
.build();
assertText('false');
component.items.push(2);
flush();
assertText('true');
component.items.splice(0);
flush();
assertText('false');
});
it('observes on .indexOf()', function () {
const { component, flush, assertText } = createFixture
.component({ items: [1] })
.html`\${items.indexOf(2)}`
.build();
assertText('-1');
component.items.push(2);
flush();
assertText('1');
component.items.splice(0);
flush();
assertText('-1');
});
it('observes on .lastIndexOf()', function () {
const { component, flush, assertText } = createFixture
.component({ items: [1] })
.html`\${items.lastIndexOf(2)}`
.build();
assertText('-1');
component.items.push(2);
flush();
assertText('1');
component.items.splice(0);
flush();
assertText('-1');
});
it('observes on .findIndex()', function () {
const { component, flush, assertText } = createFixture
.component({ items: [1] })
.html`\${items.findIndex(x => x === 2)}`
.build();
assertText('-1');
component.items.push(2);
flush();
assertText('1');
component.items.splice(0);
flush();
assertText('-1');
});
it('observes on .find()', function () {
const { component, flush, assertText } = createFixture
.component({ items: [1] })
.html`\${items.find(x => x === 2)}`
.build();
assertText('');
component.items.push(2);
flush();
assertText('2');
component.items.splice(0);
flush();
assertText('');
});
it('observes on .flat()', function () {
const { component, flush, assertText } = createFixture
.component({ items: [[1]] })
.html`\${items.flat()}`
.build();
assertText('1');
component.items.push([2]);
flush();
assertText('1,2');
component.items.splice(1);
flush();
assertText('1');
});
it('observes on .flatMap()', function () {
const { component, flush, assertText } = createFixture
.component({ items: [1] })
.html`\${items.flatMap(i => [i + 1])}`
.build();
assertText('2');
component.items.push(2);
flush();
assertText('2,3');
component.items.splice(1);
flush();
assertText('2');
});
it('observes on .join()', function () {
const { component, flush, assertText } = createFixture
.component({ items: [1] })
.html`\${items.join(', ')}`
.build();
assertText('1');
component.items.push(2);
flush();
assertText('1, 2');
component.items.splice(1);
flush();
assertText('1');
});
it('observes on .reduce()', function () {
const { component, flush, assertText } = createFixture
.component({ items: [1] })
.html`\${items.reduce((acc, i) => acc + i, 0)}`
.build();
assertText('1');
component.items.push(2);
flush();
assertText('3');
component.items.splice(1);
flush();
assertText('1');
});
it('observes on .reduceRight()', function () {
const { component, flush, assertText } = createFixture
.component({ items: [1] })
.html`\${items.reduceRight((acc, i) => acc + i)}`
.build();
assertText('1');
component.items.push(2);
flush();
assertText('3');
component.items.splice(1);
flush();
assertText('1');
});
it('observes on .slice()', function () {
const { component, flush, assertText } = createFixture
.component({ items: [1] })
.html`\${items.slice(0)}`
.build();
assertText('1');
component.items.push(2);
flush();
assertText('1,2');
component.items.splice(1);
flush();
assertText('1');
});
it('observes on .every()', function () {
const { component, flush, assertText } = createFixture
.component({ items: [1] })
.html`\${items.every(i => i < 2)}`
.build();
assertText('true');
component.items.push(2);
flush();
assertText('false');
component.items.splice(1);
flush();
assertText('true');
});
it('observes on .some()', function () {
const { component, flush, assertText } = createFixture
.component({ items: [1] })
.html`\${items.some(i => i > 1)}`
.build();
assertText('false');
component.items.push(2);
flush();
assertText('true');
component.items.splice(1);
flush();
assertText('false');
});
it('observes on text + .sort()', function () {
const { component, flush, assertText } = createFixture
.component({ items: [1, 4, 3] })
// this'll result in double evaluation as
// text binding auto observes array
// though in realworld usage, it'll be normally with a .join() call
// so it'll be a single trigger on array mutation
.html`\${items.sort((a, b) => a - b)}`
.build();
assertText('1,3,4');
component.items.push(2);
flush();
assertText('1,2,3,4');
component.items.splice(1);
flush();
assertText('1');
});
it('observes on repeat + .slice().sort', function () {
const { component, assertText } = createFixture
.component({ items: [{ id: 4, }, { id: 5, }, { id: 3, }, { id: 1 }] })
.html`<div repeat.for='i of items.slice(0).sort((a, b) => a.id - b.id)'>\${i.id},`
.build();
assertText('1,3,4,5,');
component.items.push({ id: 2 });
assertText('1,2,3,4,5,');
component.items.splice(2);
assertText('4,5,');
});
// the following results in a error in the repeater
// as when items.push() is call,
// the repeater'll receive 2 signals at once: push + sort
// todo: tweak the flush queue and incorporate it into bindings
// so that it'll notify in breath first manner
// eslint-disable-next-line mocha/no-skipped-tests
it.skip('observes on ..sort', function () {
const { component, assertText } = createFixture
.component({ items: [{ id: 4, }, { id: 5, }, { id: 3, }, { id: 1 }] })
.html`<div repeat.for='i of items.sort((a, b) => a.id - b.id)'>\${i.id},`
.build();
assertText('1,3,4,5,');
console.log('pushing');
component.items.push({ id: 2 });
assertText('1,2,3,4,5,');
console.log('splicing');
component.items.splice(2);
assertText('4,5,');
});
});
});