diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts index 898133a63f..9ad75894a9 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts @@ -512,6 +512,11 @@ class TcbDirectiveCtorOp extends TcbOp { const inputs = getBoundInputs(this.dir, this.node, this.tcb); for (const input of inputs) { + // Skip text attributes if configured to do so. + if (!this.tcb.env.config.checkTypeOfAttributes && + input.attribute instanceof TmplAstTextAttribute) { + continue; + } for (const fieldName of input.fieldNames) { // Skip the field if an attribute has already been bound to it; we can't have a duplicate // key in the type constructor call. @@ -654,6 +659,12 @@ class TcbDirectiveInputsOp extends TcbOp { } addParseSpanInfo(assignment, input.attribute.sourceSpan); + // Ignore diagnostics for text attributes if configured to do so. + if (!this.tcb.env.config.checkTypeOfAttributes && + input.attribute instanceof TmplAstTextAttribute) { + markIgnoreDiagnostics(assignment); + } + this.scope.addStatement(ts.createExpressionStatement(assignment)); } @@ -1732,11 +1743,6 @@ function getBoundInputs( return; } - // Skip text attributes if configured to do so. - if (!tcb.env.config.checkTypeOfAttributes && attr instanceof TmplAstTextAttribute) { - return; - } - // Skip the attribute if the directive does not have an input for it. const inputs = directive.inputs.getByBindingPropertyName(attr.name); if (inputs === null) { diff --git a/packages/compiler-cli/src/ngtsc/typecheck/test/type_checker__get_symbol_of_template_node_spec.ts b/packages/compiler-cli/src/ngtsc/typecheck/test/type_checker__get_symbol_of_template_node_spec.ts index cf74e87a2f..464282fae3 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/test/type_checker__get_symbol_of_template_node_spec.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/test/type_checker__get_symbol_of_template_node_spec.ts @@ -9,7 +9,7 @@ import {ASTWithSource, Binary, BindingPipe, Conditional, Interpolation, PropertyRead, TmplAstBoundAttribute, TmplAstBoundText, TmplAstElement, TmplAstNode, TmplAstReference, TmplAstTemplate} from '@angular/compiler'; import * as ts from 'typescript'; -import {absoluteFrom, getSourceFileOrError} from '../../file_system'; +import {absoluteFrom, AbsoluteFsPath, getSourceFileOrError} from '../../file_system'; import {runInEachFileSystem} from '../../file_system/testing'; import {ClassDeclaration} from '../../reflection'; import {DirectiveSymbol, DomBindingSymbol, ElementSymbol, ExpressionSymbol, InputBindingSymbol, OutputBindingSymbol, ReferenceSymbol, Symbol, SymbolKind, TemplateSymbol, TemplateTypeChecker, TypeCheckingConfig, VariableSymbol} from '../api'; @@ -66,44 +66,61 @@ runInEachFileSystem(() => { expect(beforeSymbol).not.toBe(afterSymbol); }); - it('should get a symbol for text attributes corresponding with a directive input', () => { - const fileName = absoluteFrom('/main.ts'); - const dirFile = absoluteFrom('/dir.ts'); - const templateString = `
`; - const {templateTypeChecker, program} = setup( - [ - { - fileName, - templates: {'Cmp': templateString}, - declarations: [{ - name: 'NameDiv', - selector: 'div[name]', - file: dirFile, - type: 'directive', - inputs: {name: 'name'}, - }] - }, - { - fileName: dirFile, - source: `export class NameDiv {name!: string;}`, - templates: {}, - } - ], - ); - const sf = getSourceFileOrError(program, fileName); - const cmp = getClass(sf, 'Cmp'); - const {attributes} = getAstElements(templateTypeChecker, cmp)[0]; + describe('should get a symbol for text attributes corresponding with a directive input', () => { + let fileName: AbsoluteFsPath; + let targets: TypeCheckingTarget[]; + beforeEach(() => { + fileName = absoluteFrom('/main.ts'); + const dirFile = absoluteFrom('/dir.ts'); + const templateString = `
`; + targets = [ + { + fileName, + templates: {'Cmp': templateString} as {[key: string]: string}, + declarations: [{ + name: 'NameDiv', + selector: 'div[name]', + file: dirFile, + type: 'directive' as const, + inputs: {name: 'name'}, + }] + }, + { + fileName: dirFile, + source: `export class NameDiv {name!: string;}`, + templates: {}, + } + ]; + }); - const symbol = templateTypeChecker.getSymbolOfNode(attributes[0], cmp)!; - assertInputBindingSymbol(symbol); - expect( - (symbol.bindings[0].tsSymbol!.declarations[0] as ts.PropertyDeclaration).name.getText()) - .toEqual('name'); + it('checkTypeOfAttributes = true', () => { + const {templateTypeChecker, program} = setup(targets, {checkTypeOfAttributes: true}); + const sf = getSourceFileOrError(program, fileName); + const cmp = getClass(sf, 'Cmp'); + const {attributes} = getAstElements(templateTypeChecker, cmp)[0]; + const symbol = templateTypeChecker.getSymbolOfNode(attributes[0], cmp)!; + assertInputBindingSymbol(symbol); + expect( + (symbol.bindings[0].tsSymbol!.declarations[0] as ts.PropertyDeclaration).name.getText()) + .toEqual('name'); - // Ensure we can go back to the original location using the shim location - const mapping = - templateTypeChecker.getTemplateMappingAtShimLocation(symbol.bindings[0].shimLocation)!; - expect(mapping.span.toString()).toEqual('name'); + // Ensure we can go back to the original location using the shim location + const mapping = + templateTypeChecker.getTemplateMappingAtShimLocation(symbol.bindings[0].shimLocation)!; + expect(mapping.span.toString()).toEqual('name'); + }); + + it('checkTypeOfAttributes = false', () => { + const {templateTypeChecker, program} = setup(targets, {checkTypeOfAttributes: false}); + const sf = getSourceFileOrError(program, fileName); + const cmp = getClass(sf, 'Cmp'); + const {attributes} = getAstElements(templateTypeChecker, cmp)[0]; + const symbol = templateTypeChecker.getSymbolOfNode(attributes[0], cmp)!; + assertInputBindingSymbol(symbol); + expect( + (symbol.bindings[0].tsSymbol!.declarations[0] as ts.PropertyDeclaration).name.getText()) + .toEqual('name'); + }); }); describe('templates', () => { diff --git a/packages/language-service/ivy/test/quick_info_spec.ts b/packages/language-service/ivy/test/quick_info_spec.ts index 404b43531a..45bc6b3eda 100644 --- a/packages/language-service/ivy/test/quick_info_spec.ts +++ b/packages/language-service/ivy/test/quick_info_spec.ts @@ -104,363 +104,379 @@ function quickInfoSkeleton(): TestFile[] { describe('quick info', () => { let env: LanguageServiceTestEnvironment; - beforeEach(() => { - initMockFileSystem('Native'); - env = LanguageServiceTestEnvironment.setup(quickInfoSkeleton()); - }); - - describe('elements', () => { - it('should work for native elements', () => { - expectQuickInfo({ - templateOverride: ``, - expectedSpanText: '', - expectedDisplayString: '(element) button: HTMLButtonElement' - }); + describe('strict templates (happy path)', () => { + beforeEach(() => { + initMockFileSystem('Native'); + env = LanguageServiceTestEnvironment.setup(quickInfoSkeleton()); }); - it('should work for directives which match native element tags', () => { - expectQuickInfo({ - templateOverride: ``, - expectedSpanText: '', - expectedDisplayString: '(directive) AppModule.CompoundCustomButtonDirective' - }); - }); - }); - - describe('templates', () => { - it('should return undefined for ng-templates', () => { - const {documentation} = expectQuickInfo({ - templateOverride: ``, - expectedSpanText: '', - expectedDisplayString: '(template) ng-template' - }); - expect(toText(documentation)) - .toContain('The `` is an Angular element for rendering HTML.'); - }); - }); - - describe('directives', () => { - it('should work for directives', () => { - expectQuickInfo({ - templateOverride: `
`, - expectedSpanText: 'string-model', - expectedDisplayString: '(directive) AppModule.StringModel' - }); - }); - - it('should work for components', () => { - const {documentation} = expectQuickInfo({ - templateOverride: ``, - expectedSpanText: '', - expectedDisplayString: '(component) AppModule.TestComponent' - }); - expect(toText(documentation)).toBe('This Component provides the `test-comp` selector.'); - }); - - it('should work for structural directives', () => { - const {documentation} = expectQuickInfo({ - templateOverride: `
`, - expectedSpanText: 'ngFor', - expectedDisplayString: '(directive) NgForOf' - }); - expect(toText(documentation)).toContain('A fake version of the NgFor directive.'); - }); - - it('should work for directives with compound selectors, some of which are bindings', () => { - expectQuickInfo({ - templateOverride: `{{hero}}`, - expectedSpanText: 'ngFor', - expectedDisplayString: '(directive) NgForOf' - }); - }); - - it('should work for data-let- syntax', () => { - expectQuickInfo({ - templateOverride: - `{{hero}}`, - expectedSpanText: 'hero', - expectedDisplayString: '(variable) hero: Hero' - }); - }); - }); - - describe('bindings', () => { - describe('inputs', () => { - it('should work for input providers', () => { + describe('elements', () => { + it('should work for native elements', () => { expectQuickInfo({ - templateOverride: ``, - expectedSpanText: 'tcName', - expectedDisplayString: '(property) TestComponent.name: string' + templateOverride: ``, + expectedSpanText: '', + expectedDisplayString: '(element) button: HTMLButtonElement' }); }); - it('should work for bind- syntax', () => { + it('should work for directives which match native element tags', () => { expectQuickInfo({ - templateOverride: ``, - expectedSpanText: 'tcName', - expectedDisplayString: '(property) TestComponent.name: string' + templateOverride: ``, + expectedSpanText: '', + expectedDisplayString: '(directive) AppModule.CompoundCustomButtonDirective' }); + }); + }); + + describe('templates', () => { + it('should return undefined for ng-templates', () => { + const {documentation} = expectQuickInfo({ + templateOverride: `
`, + expectedSpanText: '', + expectedDisplayString: '(template) ng-template' + }); + expect(toText(documentation)) + .toContain('The `` is an Angular element for rendering HTML.'); + }); + }); + + describe('directives', () => { + it('should work for directives', () => { expectQuickInfo({ - templateOverride: ``, - expectedSpanText: 'tcName', - expectedDisplayString: '(property) TestComponent.name: string' + templateOverride: `
`, + expectedSpanText: 'string-model', + expectedDisplayString: '(directive) AppModule.StringModel' }); }); - it('should work for structural directive inputs ngForTrackBy', () => { + it('should work for components', () => { + const {documentation} = expectQuickInfo({ + templateOverride: ``, + expectedSpanText: '', + expectedDisplayString: '(component) AppModule.TestComponent' + }); + expect(toText(documentation)).toBe('This Component provides the `test-comp` selector.'); + }); + + it('should work for structural directives', () => { + const {documentation} = expectQuickInfo({ + templateOverride: `
`, + expectedSpanText: 'ngFor', + expectedDisplayString: '(directive) NgForOf' + }); + expect(toText(documentation)).toContain('A fake version of the NgFor directive.'); + }); + + it('should work for directives with compound selectors, some of which are bindings', () => { expectQuickInfo({ - templateOverride: `
`, - expectedSpanText: 'trackBy', + templateOverride: + `{{hero}}`, + expectedSpanText: 'ngFor', + expectedDisplayString: '(directive) NgForOf' + }); + }); + + it('should work for data-let- syntax', () => { + expectQuickInfo({ + templateOverride: + `{{hero}}`, + expectedSpanText: 'hero', + expectedDisplayString: '(variable) hero: Hero' + }); + }); + }); + + describe('bindings', () => { + describe('inputs', () => { + it('should work for input providers', () => { + expectQuickInfo({ + templateOverride: ``, + expectedSpanText: 'tcName', + expectedDisplayString: '(property) TestComponent.name: string' + }); + }); + + it('should work for bind- syntax', () => { + expectQuickInfo({ + templateOverride: ``, + expectedSpanText: 'tcName', + expectedDisplayString: '(property) TestComponent.name: string' + }); + expectQuickInfo({ + templateOverride: ``, + expectedSpanText: 'tcName', + expectedDisplayString: '(property) TestComponent.name: string' + }); + }); + + it('should work for structural directive inputs ngForTrackBy', () => { + expectQuickInfo({ + templateOverride: `
`, + expectedSpanText: 'trackBy', + expectedDisplayString: + '(property) NgForOf.ngForTrackBy: TrackByFunction' + }); + }); + + it('should work for structural directive inputs ngForOf', () => { + expectQuickInfo({ + templateOverride: `
`, + expectedSpanText: 'of', + expectedDisplayString: + '(property) NgForOf.ngForOf: Hero[] | (Hero[] & Iterable) | null | undefined' + }); + }); + + it('should work for two-way binding providers', () => { + expectQuickInfo({ + templateOverride: ``, + expectedSpanText: 'model', + expectedDisplayString: '(property) StringModel.model: string' + }); + }); + }); + + describe('outputs', () => { + it('should work for event providers', () => { + expectQuickInfo({ + templateOverride: ``, + expectedSpanText: 'test', + expectedDisplayString: '(event) TestComponent.testEvent: EventEmitter' + }); + }); + + it('should work for on- syntax binding', () => { + expectQuickInfo({ + templateOverride: ``, + expectedSpanText: 'test', + expectedDisplayString: '(event) TestComponent.testEvent: EventEmitter' + }); + expectQuickInfo({ + templateOverride: ``, + expectedSpanText: 'test', + expectedDisplayString: '(event) TestComponent.testEvent: EventEmitter' + }); + }); + + it('should work for $event from EventEmitter', () => { + expectQuickInfo({ + templateOverride: `
`, + expectedSpanText: '$event', + expectedDisplayString: '(parameter) $event: string' + }); + }); + + it('should work for $event from native element', () => { + expectQuickInfo({ + templateOverride: `
`, + expectedSpanText: '$event', + expectedDisplayString: '(parameter) $event: MouseEvent' + }); + }); + }); + }); + + describe('references', () => { + it('should work for element reference declarations', () => { + const {documentation} = expectQuickInfo({ + templateOverride: `
`, + expectedSpanText: 'chart', + expectedDisplayString: '(reference) chart: HTMLDivElement' + }); + expect(toText(documentation)) + .toEqual( + 'Provides special properties (beyond the regular HTMLElement ' + + 'interface it also has available to it by inheritance) for manipulating
elements.'); + }); + + it('should work for directive references', () => { + expectQuickInfo({ + templateOverride: `
`, + expectedSpanText: 'dirRef', + expectedDisplayString: '(reference) dirRef: StringModel' + }); + }); + + it('should work for ref- syntax', () => { + expectQuickInfo({ + templateOverride: `
`, + expectedSpanText: 'chart', + expectedDisplayString: '(reference) chart: HTMLDivElement' + }); + expectQuickInfo({ + templateOverride: `
`, + expectedSpanText: 'chart', + expectedDisplayString: '(reference) chart: HTMLDivElement' + }); + }); + }); + + describe('variables', () => { + it('should work for array members', () => { + const {documentation} = expectQuickInfo({ + templateOverride: `
{{her¦o}}
`, + expectedSpanText: 'hero', + expectedDisplayString: '(variable) hero: Hero' + }); + expect(toText(documentation)).toEqual('The most heroic being.'); + }); + + it('should work for ReadonlyArray members (#36191)', () => { + expectQuickInfo({ + templateOverride: `
{{her¦o}}
`, + expectedSpanText: 'hero', + expectedDisplayString: '(variable) hero: Readonly' + }); + }); + + it('should work for const array members (#36191)', () => { + expectQuickInfo({ + templateOverride: `
{{na¦me}}
`, + expectedSpanText: 'name', + expectedDisplayString: '(variable) name: { readonly name: "name"; }' + }); + }); + }); + + describe('pipes', () => { + it('should work for pipes', () => { + const templateOverride = `

The hero's birthday is {{birthday | da¦te: "MM/dd/yy"}}

`; + expectQuickInfo({ + templateOverride, + expectedSpanText: 'date', expectedDisplayString: - '(property) NgForOf.ngForTrackBy: TrackByFunction' - }); - }); - - it('should work for structural directive inputs ngForOf', () => { - expectQuickInfo({ - templateOverride: `
`, - expectedSpanText: 'of', - expectedDisplayString: - '(property) NgForOf.ngForOf: Hero[] | (Hero[] & Iterable) | null | undefined' - }); - }); - - it('should work for two-way binding providers', () => { - expectQuickInfo({ - templateOverride: ``, - expectedSpanText: 'model', - expectedDisplayString: '(property) StringModel.model: string' + '(pipe) DatePipe.transform(value: string | number | Date, format?: string | undefined, timezone?: ' + + 'string | undefined, locale?: string | undefined): string | null (+2 overloads)' }); }); }); - describe('outputs', () => { - it('should work for event providers', () => { + describe('expressions', () => { + it('should find members in a text interpolation', () => { expectQuickInfo({ - templateOverride: ``, - expectedSpanText: 'test', - expectedDisplayString: '(event) TestComponent.testEvent: EventEmitter' + templateOverride: `
{{ tit¦le }}
`, + expectedSpanText: 'title', + expectedDisplayString: '(property) AppCmp.title: string' }); }); - it('should work for on- syntax binding', () => { + it('should work for accessed property reads', () => { expectQuickInfo({ - templateOverride: ``, - expectedSpanText: 'test', - expectedDisplayString: '(event) TestComponent.testEvent: EventEmitter' - }); - expectQuickInfo({ - templateOverride: ``, - expectedSpanText: 'test', - expectedDisplayString: '(event) TestComponent.testEvent: EventEmitter' + templateOverride: `
{{title.len¦gth}}
`, + expectedSpanText: 'length', + expectedDisplayString: '(property) String.length: number' }); }); - it('should work for $event from EventEmitter', () => { + it('should find members in an attribute interpolation', () => { expectQuickInfo({ - templateOverride: `
`, - expectedSpanText: '$event', - expectedDisplayString: '(parameter) $event: string' + templateOverride: `
`, + expectedSpanText: 'title', + expectedDisplayString: '(property) AppCmp.title: string' }); }); - it('should work for $event from native element', () => { + it('should find members of input binding', () => { expectQuickInfo({ - templateOverride: `
`, - expectedSpanText: '$event', - expectedDisplayString: '(parameter) $event: MouseEvent' + templateOverride: ``, + expectedSpanText: 'title', + expectedDisplayString: '(property) AppCmp.title: string' }); }); + + it('should find input binding on text attribute', () => { + expectQuickInfo({ + templateOverride: ``, + expectedSpanText: 'tcName', + expectedDisplayString: '(property) TestComponent.name: string' + }); + }); + + it('should find members of event binding', () => { + expectQuickInfo({ + templateOverride: ``, + expectedSpanText: 'title', + expectedDisplayString: '(property) AppCmp.title: string' + }); + }); + + it('should work for method calls', () => { + expectQuickInfo({ + templateOverride: `
`, + expectedSpanText: 'setTitle', + expectedDisplayString: '(method) AppCmp.setTitle(newTitle: string): void' + }); + }); + + it('should work for accessed properties in writes', () => { + expectQuickInfo({ + templateOverride: `
`, + expectedSpanText: 'id', + expectedDisplayString: '(property) Hero.id: number' + }); + }); + + it('should work for method call arguments', () => { + expectQuickInfo({ + templateOverride: `
`, + expectedSpanText: 'name', + expectedDisplayString: '(property) Hero.name: string' + }); + }); + + it('should find members of two-way binding', () => { + expectQuickInfo({ + templateOverride: ``, + expectedSpanText: 'title', + expectedDisplayString: '(property) AppCmp.title: string' + }); + }); + + it('should find members in a structural directive', () => { + expectQuickInfo({ + templateOverride: `
`, + expectedSpanText: 'anyValue', + expectedDisplayString: '(property) AppCmp.anyValue: any' + }); + }); + + it('should work for members in structural directives', () => { + expectQuickInfo({ + templateOverride: `
`, + expectedSpanText: 'heroes', + expectedDisplayString: '(property) AppCmp.heroes: Hero[]' + }); + }); + + it('should work for the $any() cast function', () => { + expectQuickInfo({ + templateOverride: `
{{$an¦y(title)}}
`, + expectedSpanText: '$any', + expectedDisplayString: '(method) $any: any' + }); + }); + + it('should provide documentation', () => { + const {cursor} = env.overrideTemplateWithCursor( + absoluteFrom('/app.ts'), 'AppCmp', `
{{¦title}}
`); + const quickInfo = env.ngLS.getQuickInfoAtPosition(absoluteFrom('/app.html'), cursor); + const documentation = toText(quickInfo!.documentation); + expect(documentation).toBe('This is the title of the `AppCmp` Component.'); + }); }); }); - describe('references', () => { - it('should work for element reference declarations', () => { - const {documentation} = expectQuickInfo({ - templateOverride: `
`, - expectedSpanText: 'chart', - expectedDisplayString: '(reference) chart: HTMLDivElement' - }); - expect(toText(documentation)) - .toEqual( - 'Provides special properties (beyond the regular HTMLElement ' + - 'interface it also has available to it by inheritance) for manipulating
elements.'); - }); - - it('should work for directive references', () => { - expectQuickInfo({ - templateOverride: `
`, - expectedSpanText: 'dirRef', - expectedDisplayString: '(reference) dirRef: StringModel' - }); - }); - - it('should work for ref- syntax', () => { - expectQuickInfo({ - templateOverride: `
`, - expectedSpanText: 'chart', - expectedDisplayString: '(reference) chart: HTMLDivElement' - }); - expectQuickInfo({ - templateOverride: `
`, - expectedSpanText: 'chart', - expectedDisplayString: '(reference) chart: HTMLDivElement' - }); - }); - }); - - describe('variables', () => { - it('should work for array members', () => { - const {documentation} = expectQuickInfo({ - templateOverride: `
{{her¦o}}
`, - expectedSpanText: 'hero', - expectedDisplayString: '(variable) hero: Hero' - }); - expect(toText(documentation)).toEqual('The most heroic being.'); - }); - - it('should work for ReadonlyArray members (#36191)', () => { - expectQuickInfo({ - templateOverride: `
{{her¦o}}
`, - expectedSpanText: 'hero', - expectedDisplayString: '(variable) hero: Readonly' - }); - }); - - it('should work for const array members (#36191)', () => { - expectQuickInfo({ - templateOverride: `
{{na¦me}}
`, - expectedSpanText: 'name', - expectedDisplayString: '(variable) name: { readonly name: "name"; }' - }); - }); - }); - - describe('pipes', () => { - it('should work for pipes', () => { - const templateOverride = `

The hero's birthday is {{birthday | da¦te: "MM/dd/yy"}}

`; - expectQuickInfo({ - templateOverride, - expectedSpanText: 'date', - expectedDisplayString: - '(pipe) DatePipe.transform(value: string | number | Date, format?: string | undefined, timezone?: ' + - 'string | undefined, locale?: string | undefined): string | null (+2 overloads)' - }); - }); - }); - - describe('expressions', () => { - it('should find members in a text interpolation', () => { - expectQuickInfo({ - templateOverride: `
{{ tit¦le }}
`, - expectedSpanText: 'title', - expectedDisplayString: '(property) AppCmp.title: string' - }); - }); - - it('should work for accessed property reads', () => { - expectQuickInfo({ - templateOverride: `
{{title.len¦gth}}
`, - expectedSpanText: 'length', - expectedDisplayString: '(property) String.length: number' - }); - }); - - it('should find members in an attribute interpolation', () => { - expectQuickInfo({ - templateOverride: `
`, - expectedSpanText: 'title', - expectedDisplayString: '(property) AppCmp.title: string' - }); - }); - - it('should find members of input binding', () => { - expectQuickInfo({ - templateOverride: ``, - expectedSpanText: 'title', - expectedDisplayString: '(property) AppCmp.title: string' - }); - }); - - it('should find input binding on text attribute', () => { + describe('non-strict compiler options', () => { + it('should find input binding on text attribute when strictAttributeTypes is false', () => { + initMockFileSystem('Native'); + env = + LanguageServiceTestEnvironment.setup(quickInfoSkeleton(), {strictAttributeTypes: false}); expectQuickInfo({ templateOverride: ``, expectedSpanText: 'tcName', expectedDisplayString: '(property) TestComponent.name: string' }); }); - - it('should find members of event binding', () => { - expectQuickInfo({ - templateOverride: ``, - expectedSpanText: 'title', - expectedDisplayString: '(property) AppCmp.title: string' - }); - }); - - it('should work for method calls', () => { - expectQuickInfo({ - templateOverride: `
`, - expectedSpanText: 'setTitle', - expectedDisplayString: '(method) AppCmp.setTitle(newTitle: string): void' - }); - }); - - it('should work for accessed properties in writes', () => { - expectQuickInfo({ - templateOverride: `
`, - expectedSpanText: 'id', - expectedDisplayString: '(property) Hero.id: number' - }); - }); - - it('should work for method call arguments', () => { - expectQuickInfo({ - templateOverride: `
`, - expectedSpanText: 'name', - expectedDisplayString: '(property) Hero.name: string' - }); - }); - - it('should find members of two-way binding', () => { - expectQuickInfo({ - templateOverride: ``, - expectedSpanText: 'title', - expectedDisplayString: '(property) AppCmp.title: string' - }); - }); - - it('should find members in a structural directive', () => { - expectQuickInfo({ - templateOverride: `
`, - expectedSpanText: 'anyValue', - expectedDisplayString: '(property) AppCmp.anyValue: any' - }); - }); - - it('should work for members in structural directives', () => { - expectQuickInfo({ - templateOverride: `
`, - expectedSpanText: 'heroes', - expectedDisplayString: '(property) AppCmp.heroes: Hero[]' - }); - }); - - it('should work for the $any() cast function', () => { - expectQuickInfo({ - templateOverride: `
{{$an¦y(title)}}
`, - expectedSpanText: '$any', - expectedDisplayString: '(method) $any: any' - }); - }); - - it('should provide documentation', () => { - const {cursor} = env.overrideTemplateWithCursor( - absoluteFrom('/app.ts'), 'AppCmp', `
{{¦title}}
`); - const quickInfo = env.ngLS.getQuickInfoAtPosition(absoluteFrom('/app.html'), cursor); - const documentation = toText(quickInfo!.documentation); - expect(documentation).toBe('This is the title of the `AppCmp` Component.'); - }); }); function expectQuickInfo(