diff --git a/packages/language-service/test/hover_spec.ts b/packages/language-service/test/hover_spec.ts index 75a6592343..593fba3c62 100644 --- a/packages/language-service/test/hover_spec.ts +++ b/packages/language-service/test/hover_spec.ts @@ -14,6 +14,7 @@ import {TypeScriptServiceHost} from '../src/typescript_host'; import {MockTypescriptHost} from './test_utils'; const TEST_TEMPLATE = '/app/test.ng'; +const PARSING_CASES = '/app/parsing-cases.ts'; describe('hover', () => { const mockHost = new MockTypescriptHost(['/app/main.ts']); @@ -23,147 +24,100 @@ describe('hover', () => { beforeEach(() => { mockHost.reset(); }); - it('should be able to find field in an interpolation', () => { - const fileName = mockHost.addCode(` - @Component({ - template: '{{«name»}}' - }) - export class MyComponent { - name: string; - }`); - const marker = mockHost.getReferenceMarkerFor(fileName, 'name'); - const quickInfo = ngLS.getQuickInfoAtPosition(fileName, marker.start); - expect(quickInfo).toBeTruthy(); - const {textSpan, displayParts} = quickInfo !; - expect(textSpan).toEqual(marker); - expect(toText(displayParts)).toBe('(property) MyComponent.name: string'); - }); - - it('should be able to find an interpolated value in an attribute', () => { - mockHost.override(TEST_TEMPLATE, `
`); - const marker = mockHost.getReferenceMarkerFor(TEST_TEMPLATE, 'title'); - const quickInfo = ngLS.getQuickInfoAtPosition(TEST_TEMPLATE, marker.start); - expect(quickInfo).toBeTruthy(); - const {textSpan, displayParts} = quickInfo !; - expect(textSpan).toEqual(marker); - expect(toText(displayParts)).toBe('(property) TemplateReference.title: string'); - }); - - it('should be able to find a field in a attribute reference', () => { - const fileName = mockHost.addCode(` - @Component({ - template: '' - }) - export class MyComponent { - name: string; - }`); - const marker = mockHost.getReferenceMarkerFor(fileName, 'name'); - const quickInfo = ngLS.getQuickInfoAtPosition(fileName, marker.start); - expect(quickInfo).toBeTruthy(); - const {textSpan, displayParts} = quickInfo !; - expect(textSpan).toEqual(marker); - expect(toText(displayParts)).toBe('(property) MyComponent.name: string'); - }); - - it('should be able to find a method from a call', () => { - const fileName = mockHost.addCode(` - @Component({ - template: '
' - }) - export class MyComponent { - myClick() { } - }`); - const marker = mockHost.getDefinitionMarkerFor(fileName, 'myClick'); - const quickInfo = ngLS.getQuickInfoAtPosition(fileName, marker.start); - expect(quickInfo).toBeTruthy(); - const {textSpan, displayParts} = quickInfo !; - expect(textSpan).toEqual(marker); - expect(textSpan.length).toBe('myClick()'.length); - expect(toText(displayParts)).toBe('(method) MyComponent.myClick: () => void'); - }); - - it('should be able to find a field reference in an *ngIf', () => { - const fileName = mockHost.addCode(` - @Component({ - template: '
' - }) - export class MyComponent { - include = true; - }`); - const marker = mockHost.getReferenceMarkerFor(fileName, 'include'); - const quickInfo = ngLS.getQuickInfoAtPosition(fileName, marker.start); - expect(quickInfo).toBeTruthy(); - const {textSpan, displayParts} = quickInfo !; - expect(textSpan).toEqual(marker); - expect(toText(displayParts)).toBe('(property) MyComponent.include: boolean'); - }); - - it('should be able to find a reference to a component', () => { - mockHost.override(TEST_TEMPLATE, '<~{cursor}test-comp>'); - const marker = mockHost.getLocationMarkerFor(TEST_TEMPLATE, 'cursor'); - const quickInfo = ngLS.getQuickInfoAtPosition(TEST_TEMPLATE, marker.start); - expect(quickInfo).toBeDefined(); - const {displayParts, documentation} = quickInfo !; - expect(toText(displayParts)).toBe('(component) AppModule.TestComponent: typeof TestComponent'); - expect(toText(documentation)).toBe('This Component provides the `test-comp` selector.'); - }); - - it('should be able to find a reference to a directive', () => { - const content = - mockHost.override(TEST_TEMPLATE, `
`); - const marker = mockHost.getLocationMarkerFor(TEST_TEMPLATE, 'cursor'); - const quickInfo = ngLS.getQuickInfoAtPosition(TEST_TEMPLATE, marker.start); - expect(quickInfo).toBeDefined(); - const {displayParts, textSpan} = quickInfo !; - expect(toText(displayParts)).toBe('(directive) AppModule.StringModel: typeof StringModel'); - expect(content.substring(textSpan.start, textSpan.start + textSpan.length)) - .toBe('string-model'); - }); - - it('should be able to find an event provider', () => { - const fileName = mockHost.addCode(` - @Component({ - template: '' - }) - export class MyComponent { - myHandler() {} - }`); - const marker = mockHost.getDefinitionMarkerFor(fileName, 'test'); - const quickInfo = ngLS.getQuickInfoAtPosition(fileName, marker.start); - expect(quickInfo).toBeTruthy(); - const {textSpan, displayParts} = quickInfo !; - expect(textSpan).toEqual(marker); - expect(toText(displayParts)).toBe('(event) TestComponent.testEvent: EventEmitter'); - }); - - it('should be able to find an input provider', () => { - const fileName = mockHost.addCode(` - @Component({ - template: '' - }) - export class MyComponent { - name = 'my name'; - }`); - const marker = mockHost.getDefinitionMarkerFor(fileName, 'tcName'); - const quickInfo = ngLS.getQuickInfoAtPosition(fileName, marker.start); - expect(quickInfo).toBeTruthy(); - const {textSpan, displayParts} = quickInfo !; - expect(textSpan).toEqual(marker); - expect(toText(displayParts)).toBe('(property) TestComponent.name: string'); - }); - - describe('over structural directive', () => { - it('should be able to find the directive', () => { - mockHost.override(TEST_TEMPLATE, `
`); - const marker = mockHost.getDefinitionMarkerFor(TEST_TEMPLATE, 'ngFor'); + describe('location of hover', () => { + it('should find members in a text interpolation', () => { + mockHost.override(TEST_TEMPLATE, '
{{«title»}}
'); + const marker = mockHost.getReferenceMarkerFor(TEST_TEMPLATE, 'title'); const quickInfo = ngLS.getQuickInfoAtPosition(TEST_TEMPLATE, marker.start); expect(quickInfo).toBeTruthy(); const {textSpan, displayParts} = quickInfo !; expect(textSpan).toEqual(marker); - expect(toText(displayParts)).toBe('(directive) NgForOf: typeof NgForOf'); + expect(toText(displayParts)).toBe('(property) TemplateReference.title: string'); }); - it('should be able to find the directive property', () => { + it('should find members in an attribute interpolation', () => { + mockHost.override(TEST_TEMPLATE, `
`); + const marker = mockHost.getReferenceMarkerFor(TEST_TEMPLATE, 'title'); + const quickInfo = ngLS.getQuickInfoAtPosition(TEST_TEMPLATE, marker.start); + expect(quickInfo).toBeTruthy(); + const {textSpan, displayParts} = quickInfo !; + expect(textSpan).toEqual(marker); + expect(toText(displayParts)).toBe('(property) TemplateReference.title: string'); + }); + + it('should find members in a property binding', () => { + mockHost.override(TEST_TEMPLATE, ``); + const marker = mockHost.getReferenceMarkerFor(TEST_TEMPLATE, 'title'); + const quickInfo = ngLS.getQuickInfoAtPosition(TEST_TEMPLATE, marker.start); + expect(quickInfo).toBeTruthy(); + const {textSpan, displayParts} = quickInfo !; + expect(textSpan).toEqual(marker); + expect(toText(displayParts)).toBe('(property) TemplateReference.title: string'); + }); + + it('should find members in an event binding', () => { + mockHost.override(TEST_TEMPLATE, ``); + const marker = mockHost.getReferenceMarkerFor(TEST_TEMPLATE, 'title'); + const quickInfo = ngLS.getQuickInfoAtPosition(TEST_TEMPLATE, marker.start); + expect(quickInfo).toBeTruthy(); + const {textSpan, displayParts} = quickInfo !; + expect(textSpan).toEqual(marker); + expect(toText(displayParts)).toBe('(property) TemplateReference.title: string'); + }); + + it('should find members in a two-way binding', () => { + mockHost.override(TEST_TEMPLATE, ``); + const marker = mockHost.getReferenceMarkerFor(TEST_TEMPLATE, 'title'); + const quickInfo = ngLS.getQuickInfoAtPosition(TEST_TEMPLATE, marker.start); + expect(quickInfo).toBeTruthy(); + const {textSpan, displayParts} = quickInfo !; + expect(textSpan).toEqual(marker); + expect(toText(displayParts)).toBe('(property) TemplateReference.title: string'); + }); + + it('should find members in a structural directive', () => { + mockHost.override(TEST_TEMPLATE, `
`); + const marker = mockHost.getReferenceMarkerFor(TEST_TEMPLATE, 'anyValue'); + const quickInfo = ngLS.getQuickInfoAtPosition(TEST_TEMPLATE, marker.start); + expect(quickInfo).toBeTruthy(); + const {textSpan, displayParts} = quickInfo !; + expect(textSpan).toEqual(marker); + expect(toText(displayParts)).toBe('(property) TemplateReference.anyValue: any'); + }); + }); + + describe('hovering on expression nodes', () => { + it('should provide documentation', () => { + mockHost.override(TEST_TEMPLATE, `
{{~{cursor}title}}
`); + const marker = mockHost.getLocationMarkerFor(TEST_TEMPLATE, 'cursor'); + const quickInfo = ngLS.getQuickInfoAtPosition(TEST_TEMPLATE, marker.start); + expect(quickInfo).toBeDefined(); + const documentation = toText(quickInfo !.documentation); + expect(documentation).toBe('This is the title of the `TemplateReference` Component.'); + }); + + it('should work for property reads', () => { + mockHost.override(TEST_TEMPLATE, `
{{«title»}}
`); + const marker = mockHost.getReferenceMarkerFor(TEST_TEMPLATE, 'title'); + const quickInfo = ngLS.getQuickInfoAtPosition(TEST_TEMPLATE, marker.start); + expect(quickInfo).toBeTruthy(); + const {textSpan, displayParts} = quickInfo !; + expect(textSpan).toEqual(marker); + expect(textSpan.length).toBe('title'.length); + expect(toText(displayParts)).toBe('(property) TemplateReference.title: string'); + }); + + it('should work for method calls', () => { + mockHost.override(TEST_TEMPLATE, `
`); + const marker = mockHost.getDefinitionMarkerFor(TEST_TEMPLATE, 'myClick'); + const quickInfo = ngLS.getQuickInfoAtPosition(TEST_TEMPLATE, marker.start); + expect(quickInfo).toBeTruthy(); + const {textSpan, displayParts} = quickInfo !; + expect(textSpan).toEqual(marker); + expect(toText(displayParts)).toBe('(method) TemplateReference.myClick: (event: any) => void'); + }); + + it('should work for structural directive inputs', () => { mockHost.override( TEST_TEMPLATE, `
`); const marker = mockHost.getDefinitionMarkerFor(TEST_TEMPLATE, 'trackBy'); @@ -174,7 +128,7 @@ describe('hover', () => { expect(toText(displayParts)).toBe('(method) NgForOf.ngForTrackBy: TrackByFunction'); }); - it('should be able to find the property value', () => { + it('should work for members in structural directives', () => { mockHost.override(TEST_TEMPLATE, `
`); const marker = mockHost.getReferenceMarkerFor(TEST_TEMPLATE, 'heroes'); const quickInfo = ngLS.getQuickInfoAtPosition(TEST_TEMPLATE, marker.start); @@ -183,108 +137,142 @@ describe('hover', () => { expect(textSpan).toEqual(marker); expect(toText(displayParts)).toBe('(property) TemplateReference.heroes: Hero[]'); }); - }); - it('should be able to find a reference to a two-way binding', () => { - mockHost.override(TEST_TEMPLATE, ``); - const marker = mockHost.getDefinitionMarkerFor(TEST_TEMPLATE, 'model'); - const quickInfo = ngLS.getQuickInfoAtPosition(TEST_TEMPLATE, marker.start); - expect(quickInfo).toBeTruthy(); - const {textSpan, displayParts} = quickInfo !; - expect(textSpan).toEqual(marker); - expect(toText(displayParts)).toBe('(property) StringModel.model: string'); - }); - - it('should be able to ignore a reference declaration', () => { - const fileName = mockHost.addCode(` - @Component({ - template: '
' - }) - export class MyComponent { }`); - const marker = mockHost.getReferenceMarkerFor(fileName, 'chart'); - const quickInfo = ngLS.getQuickInfoAtPosition(fileName, marker.start); - expect(quickInfo).toBeUndefined(); - }); - - it('should be able to find the NgModule of a component', () => { - const fileName = '/app/app.component.ts'; - mockHost.override(fileName, ` - import {Component} from '@angular/core'; - - @Component({ - template: '
' - }) - export class «AppComponent» { - name: string; - }`); - const marker = mockHost.getReferenceMarkerFor(fileName, 'AppComponent'); - const quickInfo = ngLS.getQuickInfoAtPosition(fileName, marker.start); - expect(quickInfo).toBeTruthy(); - const {textSpan, displayParts} = quickInfo !; - expect(textSpan).toEqual(marker); - expect(toText(displayParts)).toBe('(component) AppModule.AppComponent: class'); - }); - - it('should be able to find the NgModule of a directive', () => { - const fileName = '/app/parsing-cases.ts'; - const content = mockHost.readFile(fileName) !; - const position = content.indexOf('StringModel'); - expect(position).toBeGreaterThan(0); - const quickInfo = ngLS.getQuickInfoAtPosition(fileName, position); - expect(quickInfo).toBeTruthy(); - const {textSpan, displayParts} = quickInfo !; - expect(textSpan).toEqual({ - start: position, - length: 'StringModel'.length, + it('should work for the $any() cast function', () => { + const content = mockHost.override(TEST_TEMPLATE, '
{{$any(title)}}
'); + const position = content.indexOf('$any'); + const quickInfo = ngLS.getQuickInfoAtPosition(TEST_TEMPLATE, position); + expect(quickInfo).toBeDefined(); + const {textSpan, displayParts} = quickInfo !; + expect(textSpan).toEqual({ + start: position, + length: '$any(title)'.length, + }); + expect(toText(displayParts)).toBe('(method) $any: $any'); }); - expect(toText(displayParts)).toBe('(directive) AppModule.StringModel: class'); }); - it('should be able to provide quick info for $any() cast function', () => { - const content = mockHost.override(TEST_TEMPLATE, '
{{$any(title)}}
'); - const position = content.indexOf('$any'); - const quickInfo = ngLS.getQuickInfoAtPosition(TEST_TEMPLATE, position); - expect(quickInfo).toBeDefined(); - const {textSpan, displayParts} = quickInfo !; - expect(textSpan).toEqual({ - start: position, - length: '$any(title)'.length, + describe('hovering on template nodes', () => { + it('should provide documentation', () => { + mockHost.override(TEST_TEMPLATE, `<~{cursor}test-comp>
`); + const marker = mockHost.getLocationMarkerFor(TEST_TEMPLATE, 'cursor'); + const quickInfo = ngLS.getQuickInfoAtPosition(TEST_TEMPLATE, marker.start); + expect(quickInfo).toBeDefined(); + const documentation = toText(quickInfo !.documentation); + expect(documentation).toBe('This Component provides the `test-comp` selector.'); + }); + + it('should work for components', () => { + mockHost.override(TEST_TEMPLATE, '<~{cursor}test-comp>
'); + const marker = mockHost.getLocationMarkerFor(TEST_TEMPLATE, 'cursor'); + const quickInfo = ngLS.getQuickInfoAtPosition(TEST_TEMPLATE, marker.start); + expect(quickInfo).toBeDefined(); + const {displayParts, documentation} = quickInfo !; + expect(toText(displayParts)) + .toBe('(component) AppModule.TestComponent: typeof TestComponent'); + expect(toText(documentation)).toBe('This Component provides the `test-comp` selector.'); + }); + + it('should work for directives', () => { + const content = mockHost.override(TEST_TEMPLATE, `
`); + const marker = mockHost.getLocationMarkerFor(TEST_TEMPLATE, 'cursor'); + const quickInfo = ngLS.getQuickInfoAtPosition(TEST_TEMPLATE, marker.start); + expect(quickInfo).toBeDefined(); + const {displayParts, textSpan} = quickInfo !; + expect(toText(displayParts)).toBe('(directive) AppModule.StringModel: typeof StringModel'); + expect(content.substring(textSpan.start, textSpan.start + textSpan.length)) + .toBe('string-model'); + }); + + it('should work for event providers', () => { + mockHost.override(TEST_TEMPLATE, ``); + const marker = mockHost.getDefinitionMarkerFor(TEST_TEMPLATE, 'test'); + const quickInfo = ngLS.getQuickInfoAtPosition(TEST_TEMPLATE, marker.start); + expect(quickInfo).toBeTruthy(); + const {textSpan, displayParts} = quickInfo !; + expect(textSpan).toEqual(marker); + expect(toText(displayParts)).toBe('(event) TestComponent.testEvent: EventEmitter'); + }); + + it('should work for input providers', () => { + mockHost.override(TEST_TEMPLATE, ``); + const marker = mockHost.getDefinitionMarkerFor(TEST_TEMPLATE, 'tcName'); + const quickInfo = ngLS.getQuickInfoAtPosition(TEST_TEMPLATE, marker.start); + expect(quickInfo).toBeTruthy(); + const {textSpan, displayParts} = quickInfo !; + expect(textSpan).toEqual(marker); + expect(toText(displayParts)).toBe('(property) TestComponent.name: string'); + }); + + it('should work for two-way binding providers', () => { + mockHost.override( + TEST_TEMPLATE, ``); + const marker = mockHost.getDefinitionMarkerFor(TEST_TEMPLATE, 'model'); + const quickInfo = ngLS.getQuickInfoAtPosition(TEST_TEMPLATE, marker.start); + expect(quickInfo).toBeTruthy(); + const {textSpan, displayParts} = quickInfo !; + expect(textSpan).toEqual(marker); + expect(toText(displayParts)).toBe('(property) StringModel.model: string'); + }); + + it('should work for structural directives', () => { + mockHost.override(TEST_TEMPLATE, `
`); + const marker = mockHost.getDefinitionMarkerFor(TEST_TEMPLATE, 'ngFor'); + const quickInfo = ngLS.getQuickInfoAtPosition(TEST_TEMPLATE, marker.start); + expect(quickInfo).toBeTruthy(); + const {textSpan, displayParts} = quickInfo !; + expect(textSpan).toEqual(marker); + expect(toText(displayParts)).toBe('(directive) NgForOf: typeof NgForOf'); }); - expect(toText(displayParts)).toBe('(method) $any: $any'); }); - it('should provide documentation for a property', () => { - mockHost.override(TEST_TEMPLATE, `
{{~{cursor}title}}
`); - const marker = mockHost.getLocationMarkerFor(TEST_TEMPLATE, 'cursor'); - const quickInfo = ngLS.getQuickInfoAtPosition(TEST_TEMPLATE, marker.start); - expect(quickInfo).toBeDefined(); - const documentation = toText(quickInfo !.documentation); - expect(documentation).toBe('This is the title of the `TemplateReference` Component.'); + describe('hovering on TypeScript nodes', () => { + it('should work for component TypeScript declarations', () => { + const content = mockHost.readFile(PARSING_CASES) !; + const position = content.indexOf('TemplateReference'); + expect(position).toBeGreaterThan(0); + const quickInfo = ngLS.getQuickInfoAtPosition(PARSING_CASES, position); + expect(quickInfo).toBeTruthy(); + const {textSpan, displayParts} = quickInfo !; + expect(textSpan).toEqual({ + start: position, + length: 'TemplateReference'.length, + }); + expect(toText(displayParts)).toBe('(component) AppModule.TemplateReference: class'); + }); + + it('should work for directive TypeScript declarations', () => { + const content = mockHost.readFile(PARSING_CASES) !; + const position = content.indexOf('StringModel'); + expect(position).toBeGreaterThan(0); + const quickInfo = ngLS.getQuickInfoAtPosition(PARSING_CASES, position); + expect(quickInfo).toBeTruthy(); + const {textSpan, displayParts} = quickInfo !; + expect(textSpan).toEqual({ + start: position, + length: 'StringModel'.length, + }); + expect(toText(displayParts)).toBe('(directive) AppModule.StringModel: class'); + }); }); - it('should provide documentation for a selector', () => { - mockHost.override(TEST_TEMPLATE, `<~{cursor}test-comp>
`); - const marker = mockHost.getLocationMarkerFor(TEST_TEMPLATE, 'cursor'); - const quickInfo = ngLS.getQuickInfoAtPosition(TEST_TEMPLATE, marker.start); - expect(quickInfo).toBeDefined(); - const documentation = toText(quickInfo !.documentation); - expect(documentation).toBe('This Component provides the `test-comp` selector.'); - }); + describe('non-goals', () => { + it('should ignore reference declarations', () => { + mockHost.override(TEST_TEMPLATE, `
`); + const marker = mockHost.getReferenceMarkerFor(TEST_TEMPLATE, 'chart'); + const quickInfo = ngLS.getQuickInfoAtPosition(TEST_TEMPLATE, marker.start); + expect(quickInfo).toBeUndefined(); + }); - it('should not expand i18n templates', () => { - const fileName = mockHost.addCode(` - @Component({ - template: '
{{«name»}}
' - }) - export class MyComponent { - name: string; - }`); - const marker = mockHost.getReferenceMarkerFor(fileName, 'name'); - const quickInfo = ngLS.getQuickInfoAtPosition(fileName, marker.start); - expect(quickInfo).toBeTruthy(); - const {textSpan, displayParts} = quickInfo !; - expect(textSpan).toEqual(marker); - expect(toText(displayParts)).toBe('(property) MyComponent.name: string'); + it('should not expand i18n templates', () => { + mockHost.override(TEST_TEMPLATE, `
{{«title»}}
`); + const marker = mockHost.getReferenceMarkerFor(TEST_TEMPLATE, 'title'); + const quickInfo = ngLS.getQuickInfoAtPosition(TEST_TEMPLATE, marker.start); + expect(quickInfo).toBeTruthy(); + const {textSpan, displayParts} = quickInfo !; + expect(textSpan).toEqual(marker); + expect(toText(displayParts)).toBe('(property) TemplateReference.title: string'); + }); }); }); diff --git a/packages/language-service/test/utils_spec.ts b/packages/language-service/test/utils_spec.ts index ee0670ea93..ce50568219 100644 --- a/packages/language-service/test/utils_spec.ts +++ b/packages/language-service/test/utils_spec.ts @@ -11,7 +11,7 @@ import * as ts from 'typescript'; import {getDirectiveClassLike, getPathToNodeAtPosition} from '../src/utils'; -describe('getDirectiveClassLike()', () => { +describe('getDirectiveClassLike', () => { it('should return a directive class', () => { const sourceFile = ts.createSourceFile( 'foo.ts', ` @@ -46,8 +46,8 @@ describe('getPathToNodeAtPosition', () => { nodes.push(...rootNodes); }); - it('must capture element', () => { - // First, try to get a Path to the Element + it('should capture element', () => { + // Try to get a path to an element // <|div c> // ^ cursor is here const position = html.indexOf('div'); @@ -58,8 +58,8 @@ describe('getPathToNodeAtPosition', () => { expect(path.head).toBe(path.tail); }); - it('must capture attribute', () => { - // Then, try to get a Path to the Attribute + it('should capture attribute', () => { + // Try to get a path to an attribute //
// ^ cusor is here, before the attribute const position = html.indexOf('c'); @@ -69,8 +69,8 @@ describe('getPathToNodeAtPosition', () => { expect(path.tail instanceof ng.Attribute).toBe(true); }); - it('must capture attribute before cursor', () => { - // Finally, try to get a Path to the attribute after the 'c' text + it('should capture attribute before cursor', () => { + // Try to get a path to an attribute //
// ^ cursor is here, after the attribute const position = html.indexOf('c') + 1;