Skip to content
Permalink
Browse files

fix(repeat): ensure backward compat, fix tests

  • Loading branch information...
bigopon committed May 24, 2019
1 parent 551598c commit 62958e54e2b1db05a39e2358c7508294bc0f437e
Showing with 65 additions and 31 deletions.
  1. +32 −19 src/repeat.ts
  2. +33 −12 test/repeat.issue-378.spec.ts
@@ -29,6 +29,9 @@ import {AbstractRepeater} from './abstract-repeater';
@templateController
@inject(BoundViewFactory, TargetInstruction, ViewSlot, ViewResources, ObserverLocator, RepeatStrategyLocator)
export class Repeat extends AbstractRepeater {

static useInnerMatcher = true;

/**
* List of items to bind the repeater to.
*
@@ -269,7 +272,9 @@ export class Repeat extends AbstractRepeater {
if (viewFactory) {
const template = viewFactory.template;
const instructions = viewFactory.instructions;
const instructionIds = Object.keys(instructions);
if (Repeat.useInnerMatcher) {
return extractMatcherBindingExpression(instructions);
}
// if the template has more than 1 immediate child element
// it's a repeat put on a <template/> element
// not valid for matcher binding
@@ -284,24 +289,7 @@ export class Repeat extends AbstractRepeater {
return undefined;
}
const repeatedElementTargetId = repeatedElement.getAttribute('au-target-id');
for (let i = 0; i < instructionIds.length; i++) {
const instructionId = instructionIds[i];
// matcher binding can only works when root element is not a <template/>
// checking first el child
if (instructionId !== repeatedElementTargetId) {
continue;
}
const expressions = instructions[instructionId].expressions as BindingExpression[];
if (expressions) {
for (let ii = 0; ii < expressions.length; ii++) {
if (expressions[ii].targetProperty === 'matcher') {
const matcherBinding = expressions[ii];
expressions.splice(ii, 1);
return matcherBinding;
}
}
}
}
return extractMatcherBindingExpression(instructions, repeatedElementTargetId);
}

return undefined;
@@ -362,3 +350,28 @@ export class Repeat extends AbstractRepeater {
}
}
}

/**
* Iterate a record of TargetInstruction and their expressions to find first binding expression that targets property named "matcher"
*/
const extractMatcherBindingExpression = (instructions: Record<string, TargetInstruction>, targetedElementId?: string): BindingExpression | undefined => {
const instructionIds = Object.keys(instructions);
for (let i = 0; i < instructionIds.length; i++) {
const instructionId = instructionIds[i];
// matcher binding can only works when root element is not a <template/>
// checking first el child
if (targetedElementId !== undefined && instructionId !== targetedElementId) {
continue;
}
const expressions = instructions[instructionId].expressions as BindingExpression[];
if (expressions) {
for (let ii = 0; ii < expressions.length; ii++) {
if (expressions[ii].targetProperty === 'matcher') {
const matcherBindingExpression = expressions[ii];
expressions.splice(ii, 1);
return matcherBindingExpression;
}
}
}
}
};
@@ -2,10 +2,12 @@ import './setup';
import { StageComponent } from 'aurelia-testing';
import { bootstrap } from 'aurelia-bootstrapper';
import { waitForFrames } from './test-utilities';
import { Repeat } from '../src/repeat';

// https://github.com/aurelia/templating-resources/issues/378
fdescribe('repeat.issue-378.spec.ts', () => {
it('works with <div repeat[Array] /> -->> <... matcher />', async () => {
Repeat.useInnerMatcher = false;
const model = new class {
products = [
{ id: 0, name: 'Motherboard' },
@@ -45,9 +47,11 @@ fdescribe('repeat.issue-378.spec.ts', () => {
expect(model.selectedProduct).toBe(model.products[2]);

component.dispose();
Repeat.useInnerMatcher = true;
});

it('works with <div repeat[Map] /> -->> <... matcher />', async () => {
Repeat.useInnerMatcher = false;
const model = new class {
products = new Map([
[0, 'Motherboard'],
@@ -87,17 +91,19 @@ fdescribe('repeat.issue-378.spec.ts', () => {
expect(model.selectedProduct).toEqual(Array.from(model.products)[2]);

component.dispose();
Repeat.useInnerMatcher = true;
});

describe('QA', () => {
it('works with matcher', async () => {
const model = new class App {

components: any[];
componentsAlternative: any;
objComponents: any[];
objComponentsAlternative: any[];
amatcher: (a: any, b: any) => boolean;
matcherCalls: number;
matcherCallCount: number;

constructor() {
this.components = [];
@@ -113,9 +119,9 @@ fdescribe('repeat.issue-378.spec.ts', () => {
this.objComponentsAlternative.push({ id: i, text: i - 1, cls: 'obj-comp-alt' });
}

this.matcherCalls = 0;
this.matcherCallCount = 0;
this.amatcher = (a, b) => {
this.matcherCalls++;
this.matcherCallCount++;
return a.id === b.id;
};
}
@@ -130,18 +136,33 @@ fdescribe('repeat.issue-378.spec.ts', () => {
this.objComponents = this.objComponents.slice(0).reverse();
}
};
const view = `
<button click.delegate="swapArrays()">Swap arrays</button>
const view =
`<button click.delegate="swapArrays()">Swap arrays</button>
<br>
<template
<dummy
repeat.for="obj of objComponents"
matcher.bind="amatcher"
class="a-matcher \${obj.cls}">
[\${$index}]
</template>`;
class="a-matcher"
obj.bind="obj"
index.bind="$index">
</dummy>`;

const component = StageComponent
.withResources()
.withResources([
class Dummy {
static $view = `<template class="\${obj.cls}" dataa-counter="\${counter}">[\${index}]</template>`;
static $resource = {
name: 'dummy',
bindables: ['obj', 'index']
};

counter = 0;

attached() {
this.counter++;
}
}
] as any)
.inView(view)
.boundTo(model);

@@ -152,10 +173,10 @@ fdescribe('repeat.issue-378.spec.ts', () => {
expect(document.querySelectorAll('.obj-comp').length).toBe(10);

model.swapArrays();
await waitForFrames(2);
await waitForFrames(1);

expect(document.querySelectorAll('.obj-comp-alt').length).toBe(10);
expect(model.matcherCalls).toBeGreaterThan(40);
expect(model.matcherCallCount).toBeGreaterThan(40);

component.dispose();
});

0 comments on commit 62958e5

Please sign in to comment.
You can’t perform that action at this time.