/** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ import * as ts from 'typescript/lib/tsserverlibrary'; import {LanguageService} from '../language_service'; import {APP_COMPONENT, setup, TEST_TEMPLATE} from './mock_host'; describe('quick info', () => { const {project, service, tsLS} = setup(); const ngLS = new LanguageService(project, tsLS); beforeEach(() => { service.reset(); }); describe('elements', () => { it('should work for native elements', () => { expectQuickInfo({ templateOverride: ``, expectedSpanText: '', expectedDisplayString: '(element) button: HTMLButtonElement' }); }); 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 [structural directive](guide/structural-directives) that renders'); }); it('should work for directives with compound selectors, some of which are bindings', () => { expectQuickInfo({ templateOverride: `{{item}}`, expectedSpanText: 'ngFor', expectedDisplayString: '(directive) NgForOf>' }); }); it('should work for data-let- syntax', () => { expectQuickInfo({ templateOverride: `{{item}}`, 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)="myClick($event)"', expectedDisplayString: '(event) TestComponent.testEvent: EventEmitter' }); }); it('should work for on- syntax binding', () => { expectQuickInfo({ templateOverride: ``, expectedSpanText: 'on-test="myClick($event)"', expectedDisplayString: '(event) TestComponent.testEvent: EventEmitter' }); expectQuickInfo({ templateOverride: ``, expectedSpanText: 'data-on-test="myClick($event)"', 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 ref- syntax', () => { expectQuickInfo({ templateOverride: `
`, expectedSpanText: 'ref-chart', expectedDisplayString: '(reference) chart: HTMLDivElement' }); expectQuickInfo({ templateOverride: `
`, expectedSpanText: 'data-ref-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) AppComponent.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) AppComponent.title: string' }); }); it('should find members of input binding', () => { expectQuickInfo({ templateOverride: ``, expectedSpanText: 'title', expectedDisplayString: '(property) AppComponent.title: string' }); }); it('should find input binding on text attribute', () => { expectQuickInfo({ templateOverride: ``, expectedSpanText: 'tcName="title"', expectedDisplayString: '(property) TestComponent.name: string' }); }); it('should find members of event binding', () => { expectQuickInfo({ templateOverride: ``, expectedSpanText: 'title', expectedDisplayString: '(property) AppComponent.title: string' }); }); it('should work for method calls', () => { expectQuickInfo({ templateOverride: `
`, expectedSpanText: 'setTitle', expectedDisplayString: '(method) AppComponent.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) AppComponent.title: string' }); }); it('should find members in a structural directive', () => { expectQuickInfo({ templateOverride: `
`, expectedSpanText: 'anyValue', expectedDisplayString: '(property) AppComponent.anyValue: any' }); }); it('should work for members in structural directives', () => { expectQuickInfo({ templateOverride: `
`, expectedSpanText: 'heroes', expectedDisplayString: '(property) AppComponent.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 {position} = service.overwriteInlineTemplate(APP_COMPONENT, `
{{¦title}}
`); const quickInfo = ngLS.getQuickInfoAtPosition(APP_COMPONENT, position); const documentation = toText(quickInfo!.documentation); expect(documentation).toBe('This is the title of the `AppComponent` Component.'); }); it('works with external template', () => { const {position, text} = service.overwrite(TEST_TEMPLATE, ''); const quickInfo = ngLS.getQuickInfoAtPosition(TEST_TEMPLATE, position); expect(quickInfo).toBeTruthy(); const {textSpan, displayParts} = quickInfo!; expect(text.substring(textSpan.start, textSpan.start + textSpan.length)) .toEqual(''); expect(toText(displayParts)).toEqual('(element) button: HTMLButtonElement'); }); }); function expectQuickInfo( {templateOverride, expectedSpanText, expectedDisplayString}: {templateOverride: string, expectedSpanText: string, expectedDisplayString: string}): ts.QuickInfo { const {position, text} = service.overwriteInlineTemplate(APP_COMPONENT, templateOverride); const quickInfo = ngLS.getQuickInfoAtPosition(APP_COMPONENT, position); expect(quickInfo).toBeTruthy(); const {textSpan, displayParts} = quickInfo!; expect(text.substring(textSpan.start, textSpan.start + textSpan.length)) .toEqual(expectedSpanText); expect(toText(displayParts)).toEqual(expectedDisplayString); return quickInfo!; } }); function toText(displayParts?: ts.SymbolDisplayPart[]): string { return (displayParts || []).map(p => p.text).join(''); }