refactor(core): static-query schematic should check templates (#29713)
Queries can technically be also accessed within component templates e.g. ```html <my-comp [binding]="myQuery"></my-comp> ``` In that case the query with the property "myQuery" is accessed statically and needs to be marked with `static: true`. There are other edge cases that need to be handled as the template property read doesn't necessarily resolve to the actual query property. For example: ```html <foo #myQuery></foo> <my-comp [binding]="myQuery"></my-comp> ``` In this scenario the binding doesn't refer to the actual query because the template reference variable takes precedence. The query doesn't need to be marked with "static: true" this time. This commit ensures that the `static-query` migration schematic now handles this cases properly. Also template property reads that access queries from within a `<ng-template>` are ignored as these can't access the query before the view has been initialized. Resolves FW-1216 PR Close #29713
This commit is contained in:

committed by
Igor Minar

parent
b507d076be
commit
5b32f55a3a
@ -128,4 +128,24 @@ describe('Google3 explicitQueryTiming TSLint rule', () => {
|
||||
expect(failures.length).toBe(1);
|
||||
expect(failures[0].getFailure()).toMatch(/analysis of the query.*"{static: false}"/);
|
||||
});
|
||||
|
||||
it('should detect query usage in component template', () => {
|
||||
writeFile('index.ts', `
|
||||
import {Component, ViewChild} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: \`
|
||||
<span #test></span>
|
||||
<my-comp [binding]="query"></my-comp>
|
||||
\`
|
||||
})
|
||||
export class MyComp {
|
||||
@ViewChild('test') query: any;
|
||||
}
|
||||
`);
|
||||
|
||||
runTSLint();
|
||||
|
||||
expectFileToContain('index.ts', `@ViewChild('test', { static: true }) query: any;`);
|
||||
});
|
||||
});
|
||||
|
@ -1189,6 +1189,144 @@ describe('static-queries migration', () => {
|
||||
.toContain(`@${queryType}('test', { static: true }) query: any;`);
|
||||
});
|
||||
|
||||
it('should detect query usage within component template', () => {
|
||||
writeFile('/index.ts', `
|
||||
import {Component, ${queryType}} from '@angular/core';
|
||||
|
||||
@Component({templateUrl: 'my-template.html'})
|
||||
export class MyComponent {
|
||||
@${queryType}('test') query: any;
|
||||
}
|
||||
`);
|
||||
|
||||
writeFile(`/my-template.html`, `
|
||||
<foo #test></foo>
|
||||
<comp [dir]="query"></comp>
|
||||
`);
|
||||
|
||||
runMigration();
|
||||
|
||||
expect(tree.readContent('/index.ts'))
|
||||
.toContain(`@${queryType}('test', { static: true }) query: any;`);
|
||||
});
|
||||
|
||||
it('should detect query usage with nested property read within component template', () => {
|
||||
writeFile('/index.ts', `
|
||||
import {Component, ${queryType}} from '@angular/core';
|
||||
|
||||
@Component({templateUrl: 'my-template.html'})
|
||||
export class MyComponent {
|
||||
@${queryType}('test') query: any;
|
||||
}
|
||||
`);
|
||||
|
||||
writeFile(`/my-template.html`, `
|
||||
<foo #test></foo>
|
||||
<comp [dir]="query.someProperty"></comp>
|
||||
`);
|
||||
|
||||
runMigration();
|
||||
|
||||
expect(tree.readContent('/index.ts'))
|
||||
.toContain(`@${queryType}('test', { static: true }) query: any;`);
|
||||
});
|
||||
|
||||
it('should not mark query as static if template has template reference with same name', () => {
|
||||
writeFile('/index.ts', `
|
||||
import {Component, ${queryType}} from '@angular/core';
|
||||
|
||||
@Component({templateUrl: 'my-template.html'})
|
||||
export class MyComponent {
|
||||
@${queryType}('test') query: any;
|
||||
}
|
||||
`);
|
||||
|
||||
writeFile(`/my-template.html`, `
|
||||
<foo #test></foo>
|
||||
<same-name #query></same-name>
|
||||
<!-- In that case the "query" from the component cannot be referenced. -->
|
||||
<comp [dir]="query"></comp>
|
||||
`);
|
||||
|
||||
runMigration();
|
||||
|
||||
expect(tree.readContent('/index.ts'))
|
||||
.toContain(`@${queryType}('test', { static: false }) query: any;`);
|
||||
});
|
||||
|
||||
it('should not mark query as static if template has property read with query name but different receiver',
|
||||
() => {
|
||||
writeFile('/index.ts', `
|
||||
import {Component, ${queryType}} from '@angular/core';
|
||||
|
||||
@Component({templateUrl: 'my-template.html'})
|
||||
export class MyComponent {
|
||||
myObject: {someProp: any};
|
||||
@${queryType}('test') someProp: any;
|
||||
}
|
||||
`);
|
||||
|
||||
// This test ensures that we don't accidentally treat template property reads
|
||||
// which do not refer to the query of the component instance, but have the same
|
||||
// "render3Ast.PropertyRead" name, as references to the query declaration.
|
||||
writeFile(`/my-template.html`, `
|
||||
<foo #test></foo>
|
||||
<comp [dir]="myObject.someProp"></comp>
|
||||
`);
|
||||
|
||||
runMigration();
|
||||
|
||||
expect(tree.readContent('/index.ts'))
|
||||
.toContain(`@${queryType}('test', { static: false }) someProp: any;`);
|
||||
});
|
||||
|
||||
it('should ignore queries accessed within <ng-template> element', () => {
|
||||
writeFile('/index.ts', `
|
||||
import {Component, ${queryType}} from '@angular/core';
|
||||
|
||||
@Component({templateUrl: 'my-template.html'})
|
||||
export class MyComponent {
|
||||
@${queryType}('test') query: any;
|
||||
}
|
||||
`);
|
||||
|
||||
writeFile(`/my-template.html`, `
|
||||
<foo #test></foo>
|
||||
|
||||
<ng-template>
|
||||
<my-comp [myInput]="query"></my-comp>
|
||||
</ng-template>
|
||||
`);
|
||||
|
||||
runMigration();
|
||||
|
||||
expect(tree.readContent('/index.ts'))
|
||||
.toContain(`@${queryType}('test', { static: false }) query: any;`);
|
||||
});
|
||||
|
||||
it('should detect inherited queries used in templates', () => {
|
||||
writeFile('/index.ts', `
|
||||
import {Component, ${queryType}} from '@angular/core';
|
||||
|
||||
export class ParentClass {
|
||||
@${queryType}('test') query: any;
|
||||
}
|
||||
|
||||
@Component({templateUrl: 'my-template.html'})
|
||||
export class MyComponent extends ParentClass {}
|
||||
`);
|
||||
|
||||
writeFile(`/my-template.html`, `
|
||||
<foo #test></foo>
|
||||
<my-comp [myInput]="query"></my-comp>
|
||||
`);
|
||||
|
||||
runMigration();
|
||||
|
||||
expect(tree.readContent('/index.ts'))
|
||||
.toContain(`@${queryType}('test', { static: true }) query: any;`);
|
||||
});
|
||||
|
||||
it('should properly handle multiple tsconfig files', () => {
|
||||
writeFile('/src/index.ts', `
|
||||
import {Component, ${queryType}} from '@angular/core';
|
||||
|
Reference in New Issue
Block a user