fix(differ): clean up stale identity change refs

Closes #7193
This commit is contained in:
Kara Erickson 2016-02-24 08:40:02 -08:00 committed by vsavkin
parent 8bb66a5eb3
commit ab36ea097b
3 changed files with 51 additions and 5 deletions

View File

@ -325,6 +325,9 @@ export class DefaultIterableDiffer implements IterableDiffer {
if (this._removalsTail !== null) { if (this._removalsTail !== null) {
this._removalsTail._nextRemoved = null; this._removalsTail._nextRemoved = null;
} }
if (this._identityChangesTail !== null) {
this._identityChangesTail._nextIdentityChange = null;
}
} }
/** @internal */ /** @internal */

View File

@ -365,7 +365,7 @@ export function main() {
it('should not replace tracked items', it('should not replace tracked items',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = var template =
`<template ngFor #item [ngForOf]="items" [ngForTrackBy]="customTrackBy" #i="index"> `<template ngFor #item [ngForOf]="items" [ngForTrackBy]="trackById" #i="index">
<p>{{items[i]}}</p> <p>{{items[i]}}</p>
</template>`; </template>`;
tcb.overrideTemplate(TestComponent, template) tcb.overrideTemplate(TestComponent, template)
@ -387,7 +387,7 @@ export function main() {
it('should update implicit local variable on view', it('should update implicit local variable on view',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = var template =
`<div><template ngFor #item [ngForOf]="items" [ngForTrackBy]="customTrackBy">{{item['color']}}</template></div>`; `<div><template ngFor #item [ngForOf]="items" [ngForTrackBy]="trackById">{{item['color']}}</template></div>`;
tcb.overrideTemplate(TestComponent, template) tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent) .createAsync(TestComponent)
.then((fixture) => { .then((fixture) => {
@ -403,7 +403,7 @@ export function main() {
it('should move items around and keep them updated ', it('should move items around and keep them updated ',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = var template =
`<div><template ngFor #item [ngForOf]="items" [ngForTrackBy]="customTrackBy">{{item['color']}}</template></div>`; `<div><template ngFor #item [ngForOf]="items" [ngForTrackBy]="trackById">{{item['color']}}</template></div>`;
tcb.overrideTemplate(TestComponent, template) tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent) .createAsync(TestComponent)
.then((fixture) => { .then((fixture) => {
@ -418,6 +418,24 @@ export function main() {
async.done(); async.done();
}); });
})); }));
it('should handle added and removed items properly when tracking by index',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template =
`<div><template ngFor #item [ngForOf]="items" [ngForTrackBy]="trackByIndex">{{item}}</template></div>`;
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();
});
}));
}); });
}); });
} }
@ -432,7 +450,8 @@ class TestComponent {
@ContentChild(TemplateRef) contentTpl: TemplateRef; @ContentChild(TemplateRef) contentTpl: TemplateRef;
items: any; items: any;
constructor() { this.items = [1, 2]; } constructor() { this.items = [1, 2]; }
customTrackBy(index: number, item: any): string { return item['id']; } trackById(index: number, item: any): string { return item['id']; }
trackByIndex(index: number, item: any): number { return index; }
} }
@Component({selector: 'outer-cmp'}) @Component({selector: 'outer-cmp'})

View File

@ -338,7 +338,7 @@ export function main() {
}); });
}); });
describe('trackBy function', function() { describe('trackBy function by id', function() {
var differ; var differ;
var trackByItemId = (index: number, item: any): any => item.id; var trackByItemId = (index: number, item: any): any => item.id;
@ -433,5 +433,29 @@ export function main() {
})); }));
}); });
}); });
describe('trackBy function by index', function() {
var differ;
var trackByIndex = (index: number, item: any): number => index;
beforeEach(() => { differ = new DefaultIterableDiffer(trackByIndex); });
it('should track removals normally', () => {
differ.check(['a', 'b', 'c', 'd']);
differ.check(['e', 'f', 'g', 'h']);
differ.check(['e', 'f', 'h']);
expect(differ.toString())
.toEqual(iterableChangesAsString({
collection: ['e', 'f', 'h'],
previous: ['e', 'f', 'h', 'h[3->null]'],
removals: ['h[3->null]'],
identityChanges: ['h']
}));
});
});
}); });
} }