perf(ivy): match query results on the TView level (#31489)

PR Close #31489
This commit is contained in:
Pawel Kozlowski
2019-06-07 10:55:48 +02:00
committed by Kara Erickson
parent 9c954ebc62
commit d52ae7cbab
20 changed files with 1001 additions and 1036 deletions

View File

@ -7,7 +7,7 @@
*/
import {CommonModule} from '@angular/common';
import {Component, ContentChild, ContentChildren, Directive, ElementRef, Input, QueryList, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef} from '@angular/core';
import {Component, ContentChild, ContentChildren, Directive, ElementRef, Input, QueryList, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef, forwardRef} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {expect} from '@angular/platform-browser/testing/src/matchers';
@ -786,6 +786,56 @@ describe('query logic', () => {
expect(queryList.last.nativeElement.id).toBe('c');
});
it('should support a mix of content queries from the declaration and embedded view', () => {
@Directive({selector: '[query-for-lots-of-content]'})
class QueryForLotsOfContent {
@ContentChildren('foo', {descendants: true}) foos1 !: QueryList<ElementRef>;
@ContentChildren('foo', {descendants: true}) foos2 !: QueryList<ElementRef>;
}
@Directive({selector: '[query-for-content]'})
class QueryForContent {
@ContentChildren('foo') foos !: QueryList<ElementRef>;
}
@Component({
selector: 'test-comp',
template: `
<div query-for-lots-of-content>
<ng-template ngFor let-item [ngForOf]="items">
<div query-for-content>
<span #foo></span>
</div>
</ng-template>
</div>
`
})
class TestComponent {
items = [1, 2];
}
TestBed.configureTestingModule(
{declarations: [TestComponent, QueryForContent, QueryForLotsOfContent]});
const fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
const lotsOfContentEl = fixture.debugElement.query(By.directive(QueryForLotsOfContent));
const lotsOfContentInstance = lotsOfContentEl.injector.get(QueryForLotsOfContent);
const contentEl = fixture.debugElement.query(By.directive(QueryForContent));
const contentInstance = contentEl.injector.get(QueryForContent);
expect(lotsOfContentInstance.foos1.length).toBe(2);
expect(lotsOfContentInstance.foos2.length).toBe(2);
expect(contentInstance.foos.length).toBe(1);
fixture.componentInstance.items = [];
fixture.detectChanges();
expect(lotsOfContentInstance.foos1.length).toBe(0);
expect(lotsOfContentInstance.foos2.length).toBe(0);
});
// https://stackblitz.com/edit/angular-rrmmuf?file=src/app/app.component.ts
it('should report results when different instances of TemplateRef are inserted into one ViewContainerRefs',
() => {
@ -864,12 +914,11 @@ describe('query logic', () => {
// https://stackblitz.com/edit/angular-7vvo9j?file=src%2Fapp%2Fapp.component.ts
// https://stackblitz.com/edit/angular-xzwp6n
onlyInIvy('FW-1318: QueryList entries are ordered differently in Ivy.')
.it('should report results when the same TemplateRef is inserted into different ViewContainerRefs',
() => {
@Component({
selector: 'test-comp',
template: `
it('should report results when the same TemplateRef is inserted into different ViewContainerRefs',
() => {
@Component({
selector: 'test-comp',
template: `
<ng-template #tpl let-idx="idx" let-container_idx="container_idx">
<div #foo [id]="'foo_' + container_idx + '_' + idx"></div>
</ng-template>
@ -877,44 +926,44 @@ describe('query logic', () => {
<ng-template vc #vi0="vc"></ng-template>
<ng-template vc #vi1="vc"></ng-template>
`,
})
class TestComponent {
@ViewChild('tpl', {static: false}) tpl !: TemplateRef<any>;
@ViewChild('vi0', {static: false}) vi0 !: ViewContainerManipulatorDirective;
@ViewChild('vi1', {static: false}) vi1 !: ViewContainerManipulatorDirective;
@ViewChildren('foo') query !: QueryList<any>;
}
})
class TestComponent {
@ViewChild('tpl', {static: false}) tpl !: TemplateRef<any>;
@ViewChild('vi0', {static: false}) vi0 !: ViewContainerManipulatorDirective;
@ViewChild('vi1', {static: false}) vi1 !: ViewContainerManipulatorDirective;
@ViewChildren('foo') query !: QueryList<any>;
}
TestBed.configureTestingModule(
{declarations: [ViewContainerManipulatorDirective, TestComponent]});
const fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
TestBed.configureTestingModule(
{declarations: [ViewContainerManipulatorDirective, TestComponent]});
const fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
const queryList = fixture.componentInstance.query;
const {tpl, vi0, vi1} = fixture.componentInstance;
const queryList = fixture.componentInstance.query;
const {tpl, vi0, vi1} = fixture.componentInstance;
expect(queryList.length).toBe(0);
expect(queryList.length).toBe(0);
vi0.insertTpl(tpl !, {idx: 0, container_idx: 0}, 0);
vi1.insertTpl(tpl !, {idx: 0, container_idx: 1}, 0);
fixture.detectChanges();
vi0.insertTpl(tpl !, {idx: 0, container_idx: 0}, 0);
vi1.insertTpl(tpl !, {idx: 0, container_idx: 1}, 0);
fixture.detectChanges();
expect(queryList.length).toBe(2);
let qListArr = queryList.toArray();
expect(qListArr[0].nativeElement.getAttribute('id')).toBe('foo_1_0');
expect(qListArr[1].nativeElement.getAttribute('id')).toBe('foo_0_0');
expect(queryList.length).toBe(2);
let qListArr = queryList.toArray();
expect(qListArr[0].nativeElement.getAttribute('id')).toBe('foo_0_0');
expect(qListArr[1].nativeElement.getAttribute('id')).toBe('foo_1_0');
vi0.remove();
fixture.detectChanges();
vi0.remove();
fixture.detectChanges();
expect(queryList.length).toBe(1);
qListArr = queryList.toArray();
expect(qListArr[0].nativeElement.getAttribute('id')).toBe('foo_1_0');
expect(queryList.length).toBe(1);
qListArr = queryList.toArray();
expect(qListArr[0].nativeElement.getAttribute('id')).toBe('foo_1_0');
vi1.remove();
fixture.detectChanges();
expect(queryList.length).toBe(0);
});
vi1.remove();
fixture.detectChanges();
expect(queryList.length).toBe(0);
});
// https://stackblitz.com/edit/angular-wpd6gv?file=src%2Fapp%2Fapp.component.ts
it('should report results from views inserted in a lifecycle hook', () => {
@ -953,6 +1002,96 @@ describe('query logic', () => {
});
});
describe('non-regression', () => {
it('should query by provider super-type in an embedded view', () => {
@Directive({selector: '[child]'})
class Child {
}
@Directive({selector: '[parent]', providers: [{provide: Child, useExisting: Parent}]})
class Parent extends Child {
}
@Component({
selector: 'test-cmpt',
template:
`<ng-template [ngIf]="true"><ng-template [ngIf]="true"><div parent></div></ng-template></ng-template>`
})
class TestCmpt {
@ViewChildren(Child) instances !: QueryList<Child>;
}
TestBed.configureTestingModule({declarations: [TestCmpt, Parent, Child]});
const fixture = TestBed.createComponent(TestCmpt);
fixture.detectChanges();
expect(fixture.componentInstance.instances.length).toBe(1);
});
it('should flatten multi-provider results', () => {
class MyClass {}
@Component({
selector: 'with-multi-provider',
template: '',
providers:
[{provide: MyClass, useExisting: forwardRef(() => WithMultiProvider), multi: true}]
})
class WithMultiProvider {
}
@Component({selector: 'test-cmpt', template: `<with-multi-provider></with-multi-provider>`})
class TestCmpt {
@ViewChildren(MyClass) queryResults !: QueryList<WithMultiProvider>;
}
TestBed.configureTestingModule({declarations: [TestCmpt, WithMultiProvider]});
const fixture = TestBed.createComponent(TestCmpt);
fixture.detectChanges();
expect(fixture.componentInstance.queryResults.length).toBe(1);
expect(fixture.componentInstance.queryResults.first).toBeAnInstanceOf(WithMultiProvider);
});
it('should flatten multi-provider results when crossing ng-template', () => {
class MyClass {}
@Component({
selector: 'with-multi-provider',
template: '',
providers:
[{provide: MyClass, useExisting: forwardRef(() => WithMultiProvider), multi: true}]
})
class WithMultiProvider {
}
@Component({
selector: 'test-cmpt',
template: `
<ng-template [ngIf]="true"><with-multi-provider></with-multi-provider></ng-template>
<with-multi-provider></with-multi-provider>
`
})
class TestCmpt {
@ViewChildren(MyClass) queryResults !: QueryList<WithMultiProvider>;
}
TestBed.configureTestingModule({declarations: [TestCmpt, WithMultiProvider]});
const fixture = TestBed.createComponent(TestCmpt);
fixture.detectChanges();
expect(fixture.componentInstance.queryResults.length).toBe(2);
expect(fixture.componentInstance.queryResults.first).toBeAnInstanceOf(WithMultiProvider);
expect(fixture.componentInstance.queryResults.last).toBeAnInstanceOf(WithMultiProvider);
});
});
});
function initWithTemplate(compType: Type<any>, template: string) {