test(ivy): more precise TestBed failure causes for View/Content Queries (FW-670) (#27447)

PR Close #27447
This commit is contained in:
Andrew Kushnir 2018-12-03 17:13:23 -08:00 committed by Igor Minar
parent 295e0f65a1
commit 130ae158c4
2 changed files with 395 additions and 384 deletions

View File

@ -968,4 +968,82 @@ describe('ngtsc behavioral tests', () => {
const jsContents = env.getContents('test.js'); const jsContents = env.getContents('test.js');
expect(jsContents).toMatch(/directives: \[DirA,\s+DirB\]/); expect(jsContents).toMatch(/directives: \[DirA,\s+DirB\]/);
}); });
describe('duplicate local refs', () => {
const getComponentScript = (template: string): string => `
import {Component, Directive, NgModule} from '@angular/core';
@Component({selector: 'my-cmp', template: \`${template}\`})
class Cmp {}
@NgModule({declarations: [Cmp]})
class Module {}
`;
// Components with templates listed below should
// throw the "ref is already defined" error
const invalidCases = [
`
<div #ref></div>
<div #ref></div>
`,
`
<div #ref>
<div #ref></div>
</div>
`,
`
<div>
<div #ref></div>
</div>
<div>
<div #ref></div>
</div>
`,
`
<ng-container>
<div #ref></div>
</ng-container>
<div #ref></div>
`
];
// Components with templates listed below should not throw
// the error, since refs are located in different scopes
const validCases = [
`
<ng-template>
<div #ref></div>
</ng-template>
<div #ref></div>
`,
`
<div *ngIf="visible" #ref></div>
<div #ref></div>
`,
`
<div *ngFor="let item of items" #ref></div>
<div #ref></div>
`
];
invalidCases.forEach(template => {
it('should throw in case of duplicate refs', () => {
env.tsconfig();
env.write('test.ts', getComponentScript(template));
const errors = env.driveDiagnostics();
expect(errors[0].messageText)
.toContain('Internal Error: The name ref is already defined in scope');
});
});
validCases.forEach(template => {
it('should not throw in case refs are in different scopes', () => {
env.tsconfig();
env.write('test.ts', getComponentScript(template));
const errors = env.driveDiagnostics();
expect(errors.length).toBe(0);
});
});
});
}); });

View File

@ -9,12 +9,11 @@
import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, Component, ContentChild, ContentChildren, Directive, QueryList, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef, asNativeElements} from '@angular/core'; import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, Component, ContentChild, ContentChildren, Directive, QueryList, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef, asNativeElements} from '@angular/core';
import {ComponentFixture, TestBed, async} from '@angular/core/testing'; import {ComponentFixture, TestBed, async} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/src/matchers'; import {expect} from '@angular/platform-browser/testing/src/matchers';
import {fixmeIvy} from '@angular/private/testing'; import {fixmeIvy, modifiedInIvy} from '@angular/private/testing';
import {Subject} from 'rxjs'; import {Subject} from 'rxjs';
import {stringify} from '../../src/util'; import {stringify} from '../../src/util';
// FW-670: Internal Error: The name q is already defined in scope
describe('Query API', () => { describe('Query API', () => {
beforeEach(() => TestBed.configureTestingModule({ beforeEach(() => TestBed.configureTestingModule({
@ -54,49 +53,51 @@ describe('Query API', () => {
})); }));
describe('querying by directive type', () => { describe('querying by directive type', () => {
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && fixmeIvy(
'FW-781 - Directives invocation sequence on root and nested elements is different in Ivy') &&
it('should contain all direct child directives in the light dom (constructor)', () => { it('should contain all direct child directives in the light dom (constructor)', () => {
const template = '<div text="1"></div>' + const template = `
'<needs-query text="2"><div text="3">' + <div text="1"></div>
'<div text="too-deep"></div>' + <needs-query text="2">
'</div></needs-query>' + <div text="3">
'<div text="4"></div>'; <div text="too-deep"></div>
</div>
</needs-query>
<div text="4"></div>
`;
const view = createTestCmpAndDetectChanges(MyComp0, template); const view = createTestCmpAndDetectChanges(MyComp0, template);
expect(asNativeElements(view.debugElement.children)).toHaveText('2|3|'); expect(asNativeElements(view.debugElement.children)).toHaveText('2|3|');
}); });
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && it('should contain all direct child directives in the content dom', () => {
it('should contain all direct child directives in the content dom', () => { const template = '<needs-content-children #q><div text="foo"></div></needs-content-children>';
const template = const view = createTestCmpAndDetectChanges(MyComp0, template);
'<needs-content-children #q><div text="foo"></div></needs-content-children>';
const view = createTestCmpAndDetectChanges(MyComp0, template);
const q = view.debugElement.children[0].references !['q']; const q = view.debugElement.children[0].references !['q'];
view.detectChanges(); view.detectChanges();
expect(q.textDirChildren.length).toEqual(1); expect(q.textDirChildren.length).toEqual(1);
expect(q.numberOfChildrenAfterContentInit).toEqual(1); expect(q.numberOfChildrenAfterContentInit).toEqual(1);
}); });
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && it('should contain the first content child', () => {
it('should contain the first content child', () => { const template =
const template = '<needs-content-child #q><div *ngIf="shouldShow" text="foo"></div></needs-content-child>';
'<needs-content-child #q><div *ngIf="shouldShow" text="foo"></div></needs-content-child>'; const view = createTestCmp(MyComp0, template);
const view = createTestCmp(MyComp0, template); view.componentInstance.shouldShow = true;
view.componentInstance.shouldShow = true; view.detectChanges();
view.detectChanges(); const q: NeedsContentChild = view.debugElement.children[0].references !['q'];
const q: NeedsContentChild = view.debugElement.children[0].references !['q']; expect(q.logs).toEqual([['setter', 'foo'], ['init', 'foo'], ['check', 'foo']]);
expect(q.logs).toEqual([['setter', 'foo'], ['init', 'foo'], ['check', 'foo']]);
view.componentInstance.shouldShow = false; view.componentInstance.shouldShow = false;
view.detectChanges(); view.detectChanges();
expect(q.logs).toEqual([ expect(q.logs).toEqual([
['setter', 'foo'], ['init', 'foo'], ['check', 'foo'], ['setter', null], ['setter', 'foo'], ['init', 'foo'], ['check', 'foo'], ['setter', null], ['check', null]
['check', null] ]);
]); });
});
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && fixmeIvy(
'FW-781 - Directives invocation sequence on root and nested elements is different in Ivy') &&
it('should contain the first content child when target is on <ng-template> with embedded view (issue #16568)', it('should contain the first content child when target is on <ng-template> with embedded view (issue #16568)',
() => { () => {
const template = const template =
@ -111,7 +112,7 @@ describe('Query API', () => {
expect(directive.child.text).toEqual('foo'); expect(directive.child.text).toEqual('foo');
}); });
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && fixmeIvy('FW-782 - View queries are executed twice in some cases') &&
it('should contain the first view child', () => { it('should contain the first view child', () => {
const template = '<needs-view-child #q></needs-view-child>'; const template = '<needs-view-child #q></needs-view-child>';
const view = createTestCmpAndDetectChanges(MyComp0, template); const view = createTestCmpAndDetectChanges(MyComp0, template);
@ -127,7 +128,7 @@ describe('Query API', () => {
]); ]);
}); });
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && fixmeIvy('FW-782 - View queries are executed twice in some cases') &&
it('should set static view and content children already after the constructor call', () => { it('should set static view and content children already after the constructor call', () => {
const template = const template =
'<needs-static-content-view-child #q><div text="contentFoo"></div></needs-static-content-view-child>'; '<needs-static-content-view-child #q><div text="contentFoo"></div></needs-static-content-view-child>';
@ -141,7 +142,7 @@ describe('Query API', () => {
expect(q.viewChild.text).toEqual('viewFoo'); expect(q.viewChild.text).toEqual('viewFoo');
}); });
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && fixmeIvy('FW-782 - View queries are executed twice in some cases') &&
it('should contain the first view child across embedded views', () => { it('should contain the first view child across embedded views', () => {
TestBed.overrideComponent( TestBed.overrideComponent(
MyComp0, {set: {template: '<needs-view-child #q></needs-view-child>'}}); MyComp0, {set: {template: '<needs-view-child #q></needs-view-child>'}});
@ -170,7 +171,8 @@ describe('Query API', () => {
expect(q.logs).toEqual([['setter', null], ['check', null]]); expect(q.logs).toEqual([['setter', null], ['check', null]]);
}); });
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && fixmeIvy(
'FW-781 - Directives invocation sequence on root and nested elements is different in Ivy') &&
it('should contain all directives in the light dom when descendants flag is used', () => { it('should contain all directives in the light dom when descendants flag is used', () => {
const template = '<div text="1"></div>' + const template = '<div text="1"></div>' +
'<needs-query-desc text="2"><div text="3">' + '<needs-query-desc text="2"><div text="3">' +
@ -182,7 +184,8 @@ describe('Query API', () => {
expect(asNativeElements(view.debugElement.children)).toHaveText('2|3|4|'); expect(asNativeElements(view.debugElement.children)).toHaveText('2|3|4|');
}); });
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && fixmeIvy(
'FW-781 - Directives invocation sequence on root and nested elements is different in Ivy') &&
it('should contain all directives in the light dom', () => { it('should contain all directives in the light dom', () => {
const template = '<div text="1"></div>' + const template = '<div text="1"></div>' +
'<needs-query text="2"><div text="3"></div></needs-query>' + '<needs-query text="2"><div text="3"></div></needs-query>' +
@ -192,7 +195,8 @@ describe('Query API', () => {
expect(asNativeElements(view.debugElement.children)).toHaveText('2|3|'); expect(asNativeElements(view.debugElement.children)).toHaveText('2|3|');
}); });
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && fixmeIvy(
'FW-781 - Directives invocation sequence on root and nested elements is different in Ivy') &&
it('should reflect dynamically inserted directives', () => { it('should reflect dynamically inserted directives', () => {
const template = '<div text="1"></div>' + const template = '<div text="1"></div>' +
'<needs-query text="2"><div *ngIf="shouldShow" [text]="\'3\'"></div></needs-query>' + '<needs-query text="2"><div *ngIf="shouldShow" [text]="\'3\'"></div></needs-query>' +
@ -205,19 +209,19 @@ describe('Query API', () => {
expect(asNativeElements(view.debugElement.children)).toHaveText('2|3|'); expect(asNativeElements(view.debugElement.children)).toHaveText('2|3|');
}); });
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && it('should be cleanly destroyed when a query crosses view boundaries', () => {
it('should be cleanly destroyed when a query crosses view boundaries', () => { const template = '<div text="1"></div>' +
const template = '<div text="1"></div>' + '<needs-query text="2"><div *ngIf="shouldShow" [text]="\'3\'"></div></needs-query>' +
'<needs-query text="2"><div *ngIf="shouldShow" [text]="\'3\'"></div></needs-query>' + '<div text="4"></div>';
'<div text="4"></div>'; const view = createTestCmpAndDetectChanges(MyComp0, template);
const view = createTestCmpAndDetectChanges(MyComp0, template);
view.componentInstance.shouldShow = true; view.componentInstance.shouldShow = true;
view.detectChanges(); view.detectChanges();
view.destroy(); view.destroy();
}); });
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && fixmeIvy(
'FW-781 - Directives invocation sequence on root and nested elements is different in Ivy') &&
it('should reflect moved directives', () => { it('should reflect moved directives', () => {
const template = '<div text="1"></div>' + const template = '<div text="1"></div>' +
'<needs-query text="2"><div *ngFor="let i of list" [text]="i"></div></needs-query>' + '<needs-query text="2"><div *ngFor="let i of list" [text]="i"></div></needs-query>' +
@ -230,7 +234,7 @@ describe('Query API', () => {
expect(asNativeElements(view.debugElement.children)).toHaveText('2|3d|2d|'); expect(asNativeElements(view.debugElement.children)).toHaveText('2|3d|2d|');
}); });
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && fixmeIvy('FW-682 - TestBed: tests assert that compilation produces specific error') &&
it('should throw with descriptive error when query selectors are not present', () => { it('should throw with descriptive error when query selectors are not present', () => {
TestBed.configureTestingModule({declarations: [MyCompBroken0, HasNullQueryCondition]}); TestBed.configureTestingModule({declarations: [MyCompBroken0, HasNullQueryCondition]});
const template = '<has-null-query-condition></has-null-query-condition>'; const template = '<has-null-query-condition></has-null-query-condition>';
@ -242,33 +246,29 @@ describe('Query API', () => {
}); });
describe('query for TemplateRef', () => { describe('query for TemplateRef', () => {
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && it('should find TemplateRefs in the light and shadow dom', () => {
it('should find TemplateRefs in the light and shadow dom', () => { const template = '<needs-tpl><ng-template><div>light</div></ng-template></needs-tpl>';
const template = '<needs-tpl><ng-template><div>light</div></ng-template></needs-tpl>'; const view = createTestCmpAndDetectChanges(MyComp0, template);
const view = createTestCmpAndDetectChanges(MyComp0, template); const needsTpl: NeedsTpl = view.debugElement.children[0].injector.get(NeedsTpl);
const needsTpl: NeedsTpl = view.debugElement.children[0].injector.get(NeedsTpl);
expect(needsTpl.vc.createEmbeddedView(needsTpl.query.first).rootNodes[0]) expect(needsTpl.vc.createEmbeddedView(needsTpl.query.first).rootNodes[0]).toHaveText('light');
.toHaveText('light'); expect(needsTpl.vc.createEmbeddedView(needsTpl.viewQuery.first).rootNodes[0])
expect(needsTpl.vc.createEmbeddedView(needsTpl.viewQuery.first).rootNodes[0]) .toHaveText('shadow');
.toHaveText('shadow'); });
});
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && it('should find named TemplateRefs', () => {
it('should find named TemplateRefs', () => { const template =
const template = '<needs-named-tpl><ng-template #tpl><div>light</div></ng-template></needs-named-tpl>';
'<needs-named-tpl><ng-template #tpl><div>light</div></ng-template></needs-named-tpl>'; const view = createTestCmpAndDetectChanges(MyComp0, template);
const view = createTestCmpAndDetectChanges(MyComp0, template); const needsTpl: NeedsNamedTpl = view.debugElement.children[0].injector.get(NeedsNamedTpl);
const needsTpl: NeedsNamedTpl = view.debugElement.children[0].injector.get(NeedsNamedTpl); expect(needsTpl.vc.createEmbeddedView(needsTpl.contentTpl).rootNodes[0]).toHaveText('light');
expect(needsTpl.vc.createEmbeddedView(needsTpl.contentTpl).rootNodes[0]) expect(needsTpl.vc.createEmbeddedView(needsTpl.viewTpl).rootNodes[0]).toHaveText('shadow');
.toHaveText('light'); });
expect(needsTpl.vc.createEmbeddedView(needsTpl.viewTpl).rootNodes[0])
.toHaveText('shadow');
});
}); });
describe('read a different token', () => { describe('read a different token', () => {
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && modifiedInIvy(
'Breaking change in Ivy: no longer allow multiple local refs with the same name, all local refs are now unique') &&
it('should contain all content children', () => { it('should contain all content children', () => {
const template = const template =
'<needs-content-children-read #q text="ca"><div #q text="cb"></div></needs-content-children-read>'; '<needs-content-children-read #q text="ca"><div #q text="cb"></div></needs-content-children-read>';
@ -281,232 +281,203 @@ describe('Query API', () => {
]); ]);
}); });
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && it('should contain the first content child', () => {
it('should contain the first content child', () => { const template =
const template = '<needs-content-child-read><div #q text="ca"></div></needs-content-child-read>';
'<needs-content-child-read><div #q text="ca"></div></needs-content-child-read>'; const view = createTestCmpAndDetectChanges(MyComp0, template);
const view = createTestCmpAndDetectChanges(MyComp0, template);
const comp: NeedsContentChildWithRead = const comp: NeedsContentChildWithRead =
view.debugElement.children[0].injector.get(NeedsContentChildWithRead); view.debugElement.children[0].injector.get(NeedsContentChildWithRead);
expect(comp.textDirChild.text).toEqual('ca'); expect(comp.textDirChild.text).toEqual('ca');
}); });
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && it('should contain the first descendant content child', () => {
it('should contain the first descendant content child', () => { const template = '<needs-content-child-read>' +
const template = '<needs-content-child-read>' + '<div dir><div #q text="ca"></div></div>' +
'<div dir><div #q text="ca"></div></div>' + '</needs-content-child-read>';
'</needs-content-child-read>'; const view = createTestCmpAndDetectChanges(MyComp0, template);
const view = createTestCmpAndDetectChanges(MyComp0, template);
const comp: NeedsContentChildWithRead = const comp: NeedsContentChildWithRead =
view.debugElement.children[0].injector.get(NeedsContentChildWithRead); view.debugElement.children[0].injector.get(NeedsContentChildWithRead);
expect(comp.textDirChild.text).toEqual('ca'); expect(comp.textDirChild.text).toEqual('ca');
}); });
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && it('should contain the first descendant content child templateRef', () => {
it('should contain the first descendant content child templateRef', () => { const template = '<needs-content-child-template-ref-app>' +
const template = '<needs-content-child-template-ref-app>' + '</needs-content-child-template-ref-app>';
'</needs-content-child-template-ref-app>'; const view = createTestCmp(MyComp0, template);
const view = createTestCmp(MyComp0, template);
// can't // can't execute checkNoChanges as our view modifies our content children (via a query).
// execute view.detectChanges(false);
// checkNoChanges expect(view.nativeElement).toHaveText('OUTER');
// as });
// our
// view
// modifies
// our
// content
// children
// (via
// a
// query).
view.detectChanges(false);
expect(view.nativeElement).toHaveText('OUTER');
});
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && it('should contain the first view child', () => {
it('should contain the first view child', () => { const template = '<needs-view-child-read></needs-view-child-read>';
const template = '<needs-view-child-read></needs-view-child-read>'; const view = createTestCmpAndDetectChanges(MyComp0, template);
const view = createTestCmpAndDetectChanges(MyComp0, template);
const comp: NeedsViewChildWithRead = const comp: NeedsViewChildWithRead =
view.debugElement.children[0].injector.get(NeedsViewChildWithRead); view.debugElement.children[0].injector.get(NeedsViewChildWithRead);
expect(comp.textDirChild.text).toEqual('va'); expect(comp.textDirChild.text).toEqual('va');
}); });
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && it('should contain all child directives in the view', () => {
it('should contain all child directives in the view', () => { const template = '<needs-view-children-read></needs-view-children-read>';
const template = '<needs-view-children-read></needs-view-children-read>'; const view = createTestCmpAndDetectChanges(MyComp0, template);
const view = createTestCmpAndDetectChanges(MyComp0, template);
const comp: NeedsViewChildrenWithRead = const comp: NeedsViewChildrenWithRead =
view.debugElement.children[0].injector.get(NeedsViewChildrenWithRead); view.debugElement.children[0].injector.get(NeedsViewChildrenWithRead);
expect(comp.textDirChildren.map(textDirective => textDirective.text)).toEqual([ expect(comp.textDirChildren.map(textDirective => textDirective.text)).toEqual(['va', 'vb']);
'va', 'vb' });
]);
});
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && it('should support reading a ViewContainer', () => {
it('should support reading a ViewContainer', () => { const template =
const template = '<needs-viewcontainer-read><ng-template>hello</ng-template></needs-viewcontainer-read>';
'<needs-viewcontainer-read><ng-template>hello</ng-template></needs-viewcontainer-read>'; const view = createTestCmpAndDetectChanges(MyComp0, template);
const view = createTestCmpAndDetectChanges(MyComp0, template);
const comp: NeedsViewContainerWithRead = const comp: NeedsViewContainerWithRead =
view.debugElement.children[0].injector.get(NeedsViewContainerWithRead); view.debugElement.children[0].injector.get(NeedsViewContainerWithRead);
comp.createView(); comp.createView();
expect(view.debugElement.children[0].nativeElement).toHaveText('hello'); expect(view.debugElement.children[0].nativeElement).toHaveText('hello');
}); });
}); });
describe('changes', () => { describe('changes', () => {
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && it('should notify query on change', async(() => {
it('should notify query on change', async(() => { const template = '<needs-query #q>' +
const template = '<needs-query #q>' + '<div text="1"></div>' +
'<div text="1"></div>' + '<div *ngIf="shouldShow" text="2"></div>' +
'<div *ngIf="shouldShow" text="2"></div>' + '</needs-query>';
'</needs-query>'; const view = createTestCmpAndDetectChanges(MyComp0, template);
const view = createTestCmpAndDetectChanges(MyComp0, template);
const q = view.debugElement.children[0].references !['q']; const q = view.debugElement.children[0].references !['q'];
q.query.changes.subscribe({ q.query.changes.subscribe({
next: () => { next: () => {
expect(q.query.first.text).toEqual('1'); expect(q.query.first.text).toEqual('1');
expect(q.query.last.text).toEqual('2'); expect(q.query.last.text).toEqual('2');
} }
}); });
view.componentInstance.shouldShow = true; view.componentInstance.shouldShow = true;
view.detectChanges(); view.detectChanges();
})); }));
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && it('should correctly clean-up when destroyed together with the directives it is querying',
it('should correctly clean-up when destroyed together with the directives it is querying', () => {
() => { const template = '<needs-query #q *ngIf="shouldShow"><div text="foo"></div></needs-query>';
const template = const view = createTestCmpAndDetectChanges(MyComp0, template);
'<needs-query #q *ngIf="shouldShow"><div text="foo"></div></needs-query>'; view.componentInstance.shouldShow = true;
const view = createTestCmpAndDetectChanges(MyComp0, template); view.detectChanges();
view.componentInstance.shouldShow = true;
view.detectChanges();
let isQueryListCompleted = false; let isQueryListCompleted = false;
const q: NeedsQuery = view.debugElement.children[0].references !['q']; const q: NeedsQuery = view.debugElement.children[0].references !['q'];
const changes = <Subject<any>>q.query.changes; const changes = <Subject<any>>q.query.changes;
expect(q.query.length).toEqual(1); expect(q.query.length).toEqual(1);
expect(changes.closed).toBeFalsy(); expect(changes.closed).toBeFalsy();
changes.subscribe(() => {}, () => {}, () => { isQueryListCompleted = true; }); changes.subscribe(() => {}, () => {}, () => { isQueryListCompleted = true; });
view.componentInstance.shouldShow = false; view.componentInstance.shouldShow = false;
view.detectChanges(); view.detectChanges();
expect(changes.closed).toBeTruthy(); expect(changes.closed).toBeTruthy();
expect(isQueryListCompleted).toBeTruthy(); expect(isQueryListCompleted).toBeTruthy();
view.componentInstance.shouldShow = true; view.componentInstance.shouldShow = true;
view.detectChanges(); view.detectChanges();
const q2: NeedsQuery = view.debugElement.children[0].references !['q']; const q2: NeedsQuery = view.debugElement.children[0].references !['q'];
expect(q2.query.length).toEqual(1); expect(q2.query.length).toEqual(1);
expect(changes.closed).toBeTruthy(); expect(changes.closed).toBeTruthy();
expect((<Subject<any>>q2.query.changes).closed).toBeFalsy(); expect((<Subject<any>>q2.query.changes).closed).toBeFalsy();
}); });
}); });
describe('querying by var binding', () => { describe('querying by var binding', () => {
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && it('should contain all the child directives in the light dom with the given var binding',
it('should contain all the child directives in the light dom with the given var binding', () => {
() => { const template = '<needs-query-by-ref-binding #q>' +
const template = '<needs-query-by-ref-binding #q>' + '<div *ngFor="let item of list" [text]="item" #textLabel="textDir"></div>' +
'<div *ngFor="let item of list" [text]="item" #textLabel="textDir"></div>' + '</needs-query-by-ref-binding>';
'</needs-query-by-ref-binding>'; const view = createTestCmpAndDetectChanges(MyComp0, template);
const view = createTestCmpAndDetectChanges(MyComp0, template); const q = view.debugElement.children[0].references !['q'];
const q = view.debugElement.children[0].references !['q'];
view.componentInstance.list = ['1d', '2d']; view.componentInstance.list = ['1d', '2d'];
view.detectChanges(); view.detectChanges();
expect(q.query.first.text).toEqual('1d'); expect(q.query.first.text).toEqual('1d');
expect(q.query.last.text).toEqual('2d'); expect(q.query.last.text).toEqual('2d');
}); });
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && it('should support querying by multiple var bindings', () => {
it('should support querying by multiple var bindings', () => { const template = '<needs-query-by-ref-bindings #q>' +
const template = '<needs-query-by-ref-bindings #q>' + '<div text="one" #textLabel1="textDir"></div>' +
'<div text="one" #textLabel1="textDir"></div>' + '<div text="two" #textLabel2="textDir"></div>' +
'<div text="two" #textLabel2="textDir"></div>' + '</needs-query-by-ref-bindings>';
'</needs-query-by-ref-bindings>'; const view = createTestCmpAndDetectChanges(MyComp0, template);
const view = createTestCmpAndDetectChanges(MyComp0, template); const q = view.debugElement.children[0].references !['q'];
const q = view.debugElement.children[0].references !['q'];
expect(q.query.first.text).toEqual('one'); expect(q.query.first.text).toEqual('one');
expect(q.query.last.text).toEqual('two'); expect(q.query.last.text).toEqual('two');
}); });
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && it('should support dynamically inserted directives', () => {
it('should support dynamically inserted directives', () => { const template = '<needs-query-by-ref-binding #q>' +
const template = '<needs-query-by-ref-binding #q>' + '<div *ngFor="let item of list" [text]="item" #textLabel="textDir"></div>' +
'<div *ngFor="let item of list" [text]="item" #textLabel="textDir"></div>' + '</needs-query-by-ref-binding>';
'</needs-query-by-ref-binding>'; const view = createTestCmpAndDetectChanges(MyComp0, template);
const view = createTestCmpAndDetectChanges(MyComp0, template); const q = view.debugElement.children[0].references !['q'];
const q = view.debugElement.children[0].references !['q'];
view.componentInstance.list = ['1d', '2d']; view.componentInstance.list = ['1d', '2d'];
view.detectChanges(); view.detectChanges();
view.componentInstance.list = ['2d', '1d']; view.componentInstance.list = ['2d', '1d'];
view.detectChanges(); view.detectChanges();
expect(q.query.last.text).toEqual('1d'); expect(q.query.last.text).toEqual('1d');
}); });
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && it('should contain all the elements in the light dom with the given var binding', () => {
it('should contain all the elements in the light dom with the given var binding', () => { const template = '<needs-query-by-ref-binding #q>' +
const template = '<needs-query-by-ref-binding #q>' + '<div *ngFor="let item of list">' +
'<div *ngFor="let item of list">' + '<div #textLabel>{{item}}</div>' +
'<div #textLabel>{{item}}</div>' + '</div>' +
'</div>' + '</needs-query-by-ref-binding>';
'</needs-query-by-ref-binding>'; const view = createTestCmpAndDetectChanges(MyComp0, template);
const view = createTestCmpAndDetectChanges(MyComp0, template); const q = view.debugElement.children[0].references !['q'];
const q = view.debugElement.children[0].references !['q'];
view.componentInstance.list = ['1d', '2d']; view.componentInstance.list = ['1d', '2d'];
view.detectChanges(); view.detectChanges();
expect(q.query.first.nativeElement).toHaveText('1d'); expect(q.query.first.nativeElement).toHaveText('1d');
expect(q.query.last.nativeElement).toHaveText('2d'); expect(q.query.last.nativeElement).toHaveText('2d');
}); });
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && it('should contain all the elements in the light dom even if they get projected', () => {
it('should contain all the elements in the light dom even if they get projected', () => { const template = '<needs-query-and-project #q>' +
const template = '<needs-query-and-project #q>' + '<div text="hello"></div><div text="world"></div>' +
'<div text="hello"></div><div text="world"></div>' + '</needs-query-and-project>';
'</needs-query-and-project>'; const view = createTestCmpAndDetectChanges(MyComp0, template);
const view = createTestCmpAndDetectChanges(MyComp0, template);
expect(asNativeElements(view.debugElement.children)).toHaveText('hello|world|'); expect(asNativeElements(view.debugElement.children)).toHaveText('hello|world|');
}); });
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && it('should support querying the view by using a view query', () => {
it('should support querying the view by using a view query', () => { const template = '<needs-view-query-by-ref-binding #q></needs-view-query-by-ref-binding>';
const template = '<needs-view-query-by-ref-binding #q></needs-view-query-by-ref-binding>'; const view = createTestCmpAndDetectChanges(MyComp0, template);
const view = createTestCmpAndDetectChanges(MyComp0, template);
const q: NeedsViewQueryByLabel = view.debugElement.children[0].references !['q']; const q: NeedsViewQueryByLabel = view.debugElement.children[0].references !['q'];
expect(q.query.first.nativeElement).toHaveText('text'); expect(q.query.first.nativeElement).toHaveText('text');
}); });
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && it('should contain all child directives in the view dom', () => {
it('should contain all child directives in the view dom', () => { const template = '<needs-view-children #q></needs-view-children>';
const template = '<needs-view-children #q></needs-view-children>'; const view = createTestCmpAndDetectChanges(MyComp0, template);
const view = createTestCmpAndDetectChanges(MyComp0, template); const q = view.debugElement.children[0].references !['q'];
const q = view.debugElement.children[0].references !['q']; expect(q.textDirChildren.length).toEqual(1);
expect(q.textDirChildren.length).toEqual(1); expect(q.numberOfChildrenAfterViewInit).toEqual(1);
expect(q.numberOfChildrenAfterViewInit).toEqual(1); });
});
}); });
describe('querying in the view', () => { describe('querying in the view', () => {
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && fixmeIvy(
'FW-781 - Directives invocation sequence on root and nested elements is different in Ivy') &&
it('should contain all the elements in the view with that have the given directive', () => { it('should contain all the elements in the view with that have the given directive', () => {
const template = '<needs-view-query #q><div text="ignoreme"></div></needs-view-query>'; const template = '<needs-view-query #q><div text="ignoreme"></div></needs-view-query>';
const view = createTestCmpAndDetectChanges(MyComp0, template); const view = createTestCmpAndDetectChanges(MyComp0, template);
@ -514,7 +485,8 @@ describe('Query API', () => {
expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', '2', '3', '4']); expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', '2', '3', '4']);
}); });
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && fixmeIvy(
'FW-781 - Directives invocation sequence on root and nested elements is different in Ivy') &&
it('should not include directive present on the host element', () => { it('should not include directive present on the host element', () => {
const template = '<needs-view-query #q text="self"></needs-view-query>'; const template = '<needs-view-query #q text="self"></needs-view-query>';
const view = createTestCmpAndDetectChanges(MyComp0, template); const view = createTestCmpAndDetectChanges(MyComp0, template);
@ -522,131 +494,101 @@ describe('Query API', () => {
expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', '2', '3', '4']); expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', '2', '3', '4']);
}); });
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && it('should reflect changes in the component', () => {
it('should reflect changes in the component', () => { const template = '<needs-view-query-if #q></needs-view-query-if>';
const template = '<needs-view-query-if #q></needs-view-query-if>'; const view = createTestCmpAndDetectChanges(MyComp0, template);
const view = createTestCmpAndDetectChanges(MyComp0, template); const q: NeedsViewQueryIf = view.debugElement.children[0].references !['q'];
const q: NeedsViewQueryIf = view.debugElement.children[0].references !['q']; expect(q.query.length).toBe(0);
expect(q.query.length).toBe(0);
q.show = true; q.show = true;
view.detectChanges(); view.detectChanges();
expect(q.query.length).toBe(1); expect(q.query.length).toBe(1);
expect(q.query.first.text).toEqual('1'); expect(q.query.first.text).toEqual('1');
}); });
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && it('should not be affected by other changes in the component', () => {
it('should not be affected by other changes in the component', () => { const template = '<needs-view-query-nested-if #q></needs-view-query-nested-if>';
const template = '<needs-view-query-nested-if #q></needs-view-query-nested-if>'; const view = createTestCmpAndDetectChanges(MyComp0, template);
const view = createTestCmpAndDetectChanges(MyComp0, template); const q: NeedsViewQueryNestedIf = view.debugElement.children[0].references !['q'];
const q: NeedsViewQueryNestedIf = view.debugElement.children[0].references !['q'];
expect(q.query.length).toEqual(1); expect(q.query.length).toEqual(1);
expect(q.query.first.text).toEqual('1'); expect(q.query.first.text).toEqual('1');
q.show = false; q.show = false;
view.detectChanges(); view.detectChanges();
expect(q.query.length).toEqual(1); expect(q.query.length).toEqual(1);
expect(q.query.first.text).toEqual('1'); expect(q.query.first.text).toEqual('1');
}); });
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && it('should maintain directives in pre-order depth-first DOM order after dynamic insertion',
it('should maintain directives in pre-order depth-first DOM order after dynamic insertion', () => {
() => { const template = '<needs-view-query-order #q></needs-view-query-order>';
const template = '<needs-view-query-order #q></needs-view-query-order>'; const view = createTestCmpAndDetectChanges(MyComp0, template);
const view = createTestCmpAndDetectChanges(MyComp0, template); const q: NeedsViewQueryOrder = view.debugElement.children[0].references !['q'];
const q: NeedsViewQueryOrder = view.debugElement.children[0].references !['q'];
expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', '2', '3', '4']); expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', '2', '3', '4']);
q.list = ['-3', '2']; q.list = ['-3', '2'];
view.detectChanges(); view.detectChanges();
expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', '-3', '2', '4']); expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', '-3', '2', '4']);
}); });
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && it('should maintain directives in pre-order depth-first DOM order after dynamic insertion',
it('should maintain directives in pre-order depth-first DOM order after dynamic insertion', () => {
() => { const template = '<needs-view-query-order-with-p #q></needs-view-query-order-with-p>';
const template = '<needs-view-query-order-with-p #q></needs-view-query-order-with-p>'; const view = createTestCmpAndDetectChanges(MyComp0, template);
const view = createTestCmpAndDetectChanges(MyComp0, template); const q: NeedsViewQueryOrderWithParent = view.debugElement.children[0].references !['q'];
const q: NeedsViewQueryOrderWithParent = expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', '2', '3', '4']);
view.debugElement.children[0].references !['q'];
expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', '2', '3', '4']);
q.list = ['-3', '2']; q.list = ['-3', '2'];
view.detectChanges(); view.detectChanges();
expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', '-3', '2', '4']); expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', '-3', '2', '4']);
}); });
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && it('should handle long ngFor cycles', () => {
it('should handle long ngFor cycles', () => { const template = '<needs-view-query-order #q></needs-view-query-order>';
const template = '<needs-view-query-order #q></needs-view-query-order>'; const view = createTestCmpAndDetectChanges(MyComp0, template);
const view = createTestCmpAndDetectChanges(MyComp0, template); const q: NeedsViewQueryOrder = view.debugElement.children[0].references !['q'];
const q: NeedsViewQueryOrder = view.debugElement.children[0].references !['q'];
// no // no significance to 50, just a reasonably large cycle.
// significance for (let i = 0; i < 50; i++) {
// to const newString = i.toString();
// 50, q.list = [newString];
// just view.detectChanges();
// a expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', newString, '4']);
// reasonably }
// large });
// cycle.
for (let i = 0; i < 50; i++) {
const newString = i.toString();
q.list = [newString];
view.detectChanges();
expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', newString, '4']);
}
});
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && it('should support more than three queries', () => {
it('should support more than three queries', () => { const template = '<needs-four-queries #q><div text="1"></div></needs-four-queries>';
const template = '<needs-four-queries #q><div text="1"></div></needs-four-queries>'; const view = createTestCmpAndDetectChanges(MyComp0, template);
const view = createTestCmpAndDetectChanges(MyComp0, template); const q = view.debugElement.children[0].references !['q'];
const q = view.debugElement.children[0].references !['q']; expect(q.query1).toBeDefined();
expect(q.query1).toBeDefined(); expect(q.query2).toBeDefined();
expect(q.query2).toBeDefined(); expect(q.query3).toBeDefined();
expect(q.query3).toBeDefined(); expect(q.query4).toBeDefined();
expect(q.query4).toBeDefined(); });
});
}); });
describe('query over moved templates', () => { describe('query over moved templates', () => {
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && it('should include manually projected templates in queries', () => {
it('should include manually projected templates in queries', () => { const template =
const template = '<manual-projecting #q><ng-template><div text="1"></div></ng-template></manual-projecting>';
'<manual-projecting #q><ng-template><div text="1"></div></ng-template></manual-projecting>'; const view = createTestCmpAndDetectChanges(MyComp0, template);
const view = createTestCmpAndDetectChanges(MyComp0, template); const q = view.debugElement.children[0].references !['q'];
const q = view.debugElement.children[0].references !['q']; expect(q.query.length).toBe(0);
expect(q.query.length).toBe(0);
q.create(); q.create();
view.detectChanges(); view.detectChanges();
expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1']); expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1']);
q.destroy(); q.destroy();
view.detectChanges(); view.detectChanges();
expect(q.query.length).toBe(0); expect(q.query.length).toBe(0);
}); });
// Note: // Note: this test is just document our current behavior, which we do for performance reasons.
// This fixmeIvy('FW-782 - View queries are executed twice in some cases') &&
// tests
// is
// just
// document
// our
// current
// behavior,
// which
// we
// do
// for
// performance
// reasons.
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') &&
it('should not affected queries for projected templates if views are detached or moved', () => { it('should not affected queries for projected templates if views are detached or moved', () => {
const template = const template =
'<manual-projecting #q><ng-template let-x="x"><div [text]="x"></div></ng-template></manual-projecting>'; '<manual-projecting #q><ng-template let-x="x"><div [text]="x"></div></ng-template></manual-projecting>';
@ -672,7 +614,8 @@ describe('Query API', () => {
expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', '2']); expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', '2']);
}); });
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && fixmeIvy(
'FW-763 - LView tree not properly constructed / destroyed for dynamically inserted components') &&
it('should remove manually projected templates if their parent view is destroyed', () => { it('should remove manually projected templates if their parent view is destroyed', () => {
const template = ` const template = `
<manual-projecting #q><ng-template #tpl><div text="1"></div></ng-template></manual-projecting> <manual-projecting #q><ng-template #tpl><div text="1"></div></ng-template></manual-projecting>
@ -692,21 +635,19 @@ describe('Query API', () => {
expect(q.query.length).toBe(0); expect(q.query.length).toBe(0);
}); });
fixmeIvy('FW-670: Internal Error: The name q is already defined in scope') && fixmeIvy('unknown') &&
it('should not throw if a content template is queried and created in the view during change detection', it('should not throw if a content template is queried and created in the view during change detection',
() => { () => {
@Component( @Component(
{selector: 'auto-projecting', template: '<div *ngIf="true; then: content"></div>'}) {selector: 'auto-projecting', template: '<div *ngIf="true; then: content"></div>'})
class AutoProjecting { class AutoProjecting {
// TODO(issue/24571): // TODO(issue/24571):
// remove // remove '!'.
// '!'.
@ContentChild(TemplateRef) @ContentChild(TemplateRef)
content !: TemplateRef<any>; content !: TemplateRef<any>;
// TODO(issue/24571): // TODO(issue/24571):
// remove // remove '!'.
// '!'.
@ContentChildren(TextDirective) @ContentChildren(TextDirective)
query !: QueryList<TextDirective>; query !: QueryList<TextDirective>;
} }
@ -717,17 +658,9 @@ describe('Query API', () => {
const view = createTestCmpAndDetectChanges(MyComp0, template); const view = createTestCmpAndDetectChanges(MyComp0, template);
const q = view.debugElement.children[0].references !['q']; const q = view.debugElement.children[0].references !['q'];
// This // This should be 1, but due to
// should
// be
// 1,
// but
// due
// to
// https://github.com/angular/angular/issues/15117 // https://github.com/angular/angular/issues/15117
// this // this is 0.
// is
// 0.
expect(q.query.length).toBe(0); expect(q.query.length).toBe(0);
}); });