/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {AsyncTestCompleter, beforeEach, beforeEachProviders, ddescribe, describe, iit, inject, it, xit,} from '@angular/core/testing/testing_internal';
import {TestComponentBuilder} from '@angular/core/testing';
import {ListWrapper} from '../../src/facade/collection';
import {Component, TemplateRef, ContentChild} from '@angular/core';
import {NgFor, NgIf} from '@angular/common';
import {expect} from '@angular/platform-browser/testing/matchers';
import {By} from '@angular/platform-browser/src/dom/debug/by';
export function main() {
describe('ngFor', () => {
const TEMPLATE =
'
{{item.toString()}};
';
it('should reflect initial elements',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
tcb.overrideTemplate(TestComponent, TEMPLATE)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('1;2;');
async.done();
});
}));
it('should reflect added elements',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
tcb.overrideTemplate(TestComponent, TEMPLATE)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
(fixture.debugElement.componentInstance.items).push(3);
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('1;2;3;');
async.done();
});
}));
it('should reflect removed elements',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
tcb.overrideTemplate(TestComponent, TEMPLATE)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
ListWrapper.removeAt(fixture.debugElement.componentInstance.items, 1);
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('1;');
async.done();
});
}));
it('should reflect moved elements',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
tcb.overrideTemplate(TestComponent, TEMPLATE)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
ListWrapper.removeAt(fixture.debugElement.componentInstance.items, 0);
(fixture.debugElement.componentInstance.items).push(1);
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('2;1;');
async.done();
});
}));
it('should reflect a mix of all changes (additions/removals/moves)',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
tcb.overrideTemplate(TestComponent, TEMPLATE)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = [0, 1, 2, 3, 4, 5];
fixture.detectChanges();
fixture.debugElement.componentInstance.items = [6, 2, 7, 0, 4, 8];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('6;2;7;0;4;8;');
async.done();
});
}));
it('should iterate over an array of objects',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
const template =
'';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
// INIT
fixture.debugElement.componentInstance.items =
[{'name': 'misko'}, {'name': 'shyam'}];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('misko;shyam;');
// GROW
(fixture.debugElement.componentInstance.items).push({'name': 'adam'});
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('misko;shyam;adam;');
// SHRINK
ListWrapper.removeAt(fixture.debugElement.componentInstance.items, 2);
ListWrapper.removeAt(fixture.debugElement.componentInstance.items, 0);
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('shyam;');
async.done();
});
}));
it('should gracefully handle nulls',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
const template = '';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('');
async.done();
});
}));
it('should gracefully handle ref changing to null and back',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
tcb.overrideTemplate(TestComponent, TEMPLATE)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('1;2;');
fixture.debugElement.componentInstance.items = null;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('');
fixture.debugElement.componentInstance.items = [1, 2, 3];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('1;2;3;');
async.done();
});
}));
it('should throw on non-iterable ref and suggest using an array',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
tcb.overrideTemplate(TestComponent, TEMPLATE).createAsync(TestComponent).then((fixture) => {
fixture.debugElement.componentInstance.items = 'whaaa';
expect(() => fixture.detectChanges())
.toThrowError(
/Cannot find a differ supporting object 'whaaa' of type 'string'. NgFor only supports binding to Iterables such as Arrays/);
async.done();
});
}));
it('should throw on ref changing to string',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
tcb.overrideTemplate(TestComponent, TEMPLATE)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('1;2;');
fixture.debugElement.componentInstance.items = 'whaaa';
expect(() => fixture.detectChanges()).toThrowError();
async.done();
});
}));
it('should works with duplicates',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
tcb.overrideTemplate(TestComponent, TEMPLATE)
.createAsync(TestComponent)
.then((fixture) => {
var a = new Foo();
fixture.debugElement.componentInstance.items = [a, a];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('foo;foo;');
async.done();
});
}));
it('should repeat over nested arrays',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
const template = '' +
'
' +
'
' +
'{{subitem}}-{{item.length}};' +
'
|' +
'
' +
'
';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = [['a', 'b'], ['c']];
fixture.detectChanges();
fixture.detectChanges();
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('a-2;b-2;|c-1;|');
fixture.debugElement.componentInstance.items = [['e'], ['f', 'g']];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('e-1;|f-2;g-2;|');
async.done();
});
}));
it('should repeat over nested arrays with no intermediate element',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
const template = '' +
'' +
'{{subitem}}-{{item.length}};' +
'
';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = [['a', 'b'], ['c']];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('a-2;b-2;c-1;');
fixture.debugElement.componentInstance.items = [['e'], ['f', 'g']];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('e-1;f-2;g-2;');
async.done();
});
}));
it('should repeat over nested ngIf that are the last node in the ngFor temlate',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
const template =
``;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
const el = fixture.debugElement.nativeElement;
const items = [1];
fixture.debugElement.componentInstance.items = items;
fixture.detectChanges();
expect(el).toHaveText('0|even|');
items.push(1);
fixture.detectChanges();
expect(el).toHaveText('0|even|1|');
items.push(1);
fixture.detectChanges();
expect(el).toHaveText('0|even|1|2|even|');
async.done();
});
}));
it('should display indices correctly',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
const template =
'{{i.toString()}}
';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('0123456789');
fixture.debugElement.componentInstance.items = [1, 2, 6, 7, 4, 3, 5, 8, 9, 0];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('0123456789');
async.done();
});
}));
it('should display first item correctly',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
const template =
'{{isFirst.toString()}}
';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = [0, 1, 2];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('truefalsefalse');
fixture.debugElement.componentInstance.items = [2, 1];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('truefalse');
async.done();
});
}));
it('should display last item correctly',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
const template =
'{{isLast.toString()}}
';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = [0, 1, 2];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('falsefalsetrue');
fixture.debugElement.componentInstance.items = [2, 1];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('falsetrue');
async.done();
});
}));
it('should display even items correctly',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
const template =
'{{isEven.toString()}}
';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = [0, 1, 2];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('truefalsetrue');
fixture.debugElement.componentInstance.items = [2, 1];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('truefalse');
async.done();
});
}));
it('should display odd items correctly',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
const template =
'{{isOdd.toString()}}
';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = [0, 1, 2, 3];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('falsetruefalsetrue');
fixture.debugElement.componentInstance.items = [2, 1];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('falsetrue');
async.done();
});
}));
it('should allow to use a custom template',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
tcb.overrideTemplate(
TestComponent,
'')
.overrideTemplate(
ComponentUsingTestComponent,
'{{i}}: {{item}}; ')
.createAsync(ComponentUsingTestComponent)
.then((fixture) => {
const testComponent = fixture.debugElement.children[0];
testComponent.componentInstance.items = ['a', 'b', 'c'];
fixture.detectChanges();
expect(testComponent.nativeElement).toHaveText('0: a;1: b;2: c;');
async.done();
});
}));
it('should use a default template if a custom one is null',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
tcb.overrideTemplate(TestComponent, ``)
.overrideTemplate(ComponentUsingTestComponent, ' ')
.createAsync(ComponentUsingTestComponent)
.then((fixture) => {
const testComponent = fixture.debugElement.children[0];
testComponent.componentInstance.items = ['a', 'b', 'c'];
fixture.detectChanges();
expect(testComponent.nativeElement).toHaveText('0: a;1: b;2: c;');
async.done();
});
}));
it('should use a custom template when both default and a custom one are present',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
tcb.overrideTemplate(TestComponent, ``)
.overrideTemplate(
ComponentUsingTestComponent,
'{{i}}: {{item}}; ')
.createAsync(ComponentUsingTestComponent)
.then((fixture) => {
const testComponent = fixture.debugElement.children[0];
testComponent.componentInstance.items = ['a', 'b', 'c'];
fixture.detectChanges();
expect(testComponent.nativeElement).toHaveText('0: a;1: b;2: c;');
async.done();
});
}));
describe('track by', () => {
it('should not replace tracked items',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
const template =
`
{{items[i]}}
`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
var buildItemList = () => {
fixture.debugElement.componentInstance.items = [{'id': 'a'}];
fixture.detectChanges();
return fixture.debugElement.queryAll(By.css('p'))[0];
};
var firstP = buildItemList();
var finalP = buildItemList();
expect(finalP.nativeElement).toBe(firstP.nativeElement);
async.done();
});
}));
it('should update implicit local variable on view',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
const template =
`{{item['color']}}
`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = [{'id': 'a', 'color': 'blue'}];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('blue');
fixture.debugElement.componentInstance.items = [{'id': 'a', 'color': 'red'}];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('red');
async.done();
});
}));
it('should move items around and keep them updated ',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
const template =
`{{item['color']}}
`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items =
[{'id': 'a', 'color': 'blue'}, {'id': 'b', 'color': 'yellow'}];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('blueyellow');
fixture.debugElement.componentInstance.items =
[{'id': 'b', 'color': 'orange'}, {'id': 'a', 'color': 'red'}];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('orangered');
async.done();
});
}));
it('should handle added and removed items properly when tracking by index',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
const template =
`{{item}}
`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = ['a', 'b', 'c', 'd'];
fixture.detectChanges();
fixture.debugElement.componentInstance.items = ['e', 'f', 'g', 'h'];
fixture.detectChanges();
fixture.debugElement.componentInstance.items = ['e', 'f', 'h'];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('efh');
async.done();
});
}));
});
});
}
class Foo {
toString() { return 'foo'; }
}
@Component({selector: 'test-cmp', directives: [NgFor, NgIf], template: ''})
class TestComponent {
@ContentChild(TemplateRef) contentTpl: TemplateRef;
items: any;
constructor() { this.items = [1, 2]; }
trackById(index: number, item: any): string { return item['id']; }
trackByIndex(index: number, item: any): number { return index; }
}
@Component({selector: 'outer-cmp', directives: [TestComponent], template: ''})
class ComponentUsingTestComponent {
items: any;
constructor() { this.items = [1, 2]; }
}