diff --git a/packages/language-service/test/definitions_spec.ts b/packages/language-service/test/definitions_spec.ts index a0e299f082..ba34e5254d 100644 --- a/packages/language-service/test/definitions_spec.ts +++ b/packages/language-service/test/definitions_spec.ts @@ -30,7 +30,7 @@ describe('definitions', () => { }); it('should be able to find field in an interpolation', () => { - const fileName = addCode(` + const fileName = mockHost.addCode(` @Component({ template: '{{«name»}}' }) @@ -38,7 +38,7 @@ describe('definitions', () => { «ᐱnameᐱ: string;» }`); - const marker = getReferenceMarkerFor(fileName, 'name'); + const marker = mockHost.getReferenceMarkerFor(fileName, 'name'); const result = ngService.getDefinitionAt(fileName, marker.start); expect(result).toBeDefined(); const {textSpan, definitions} = result !; @@ -51,11 +51,11 @@ describe('definitions', () => { expect(def.fileName).toBe(fileName); expect(def.name).toBe('name'); expect(def.kind).toBe('property'); - expect(def.textSpan).toEqual(getDefinitionMarkerFor(fileName, 'name')); + expect(def.textSpan).toEqual(mockHost.getDefinitionMarkerFor(fileName, 'name')); }); it('should be able to find a field in a attribute reference', () => { - const fileName = addCode(` + const fileName = mockHost.addCode(` @Component({ template: '' }) @@ -63,7 +63,7 @@ describe('definitions', () => { «ᐱnameᐱ: string;» }`); - const marker = getReferenceMarkerFor(fileName, 'name'); + const marker = mockHost.getReferenceMarkerFor(fileName, 'name'); const result = ngService.getDefinitionAt(fileName, marker.start); expect(result).toBeDefined(); const {textSpan, definitions} = result !; @@ -76,11 +76,11 @@ describe('definitions', () => { expect(def.fileName).toBe(fileName); expect(def.name).toBe('name'); expect(def.kind).toBe('property'); - expect(def.textSpan).toEqual(getDefinitionMarkerFor(fileName, 'name')); + expect(def.textSpan).toEqual(mockHost.getDefinitionMarkerFor(fileName, 'name')); }); it('should be able to find a method from a call', () => { - const fileName = addCode(` + const fileName = mockHost.addCode(` @Component({ template: '
' }) @@ -88,12 +88,12 @@ describe('definitions', () => { «ᐱmyClickᐱ() { }» }`); - const marker = getReferenceMarkerFor(fileName, 'myClick'); + const marker = mockHost.getReferenceMarkerFor(fileName, 'myClick'); const result = ngService.getDefinitionAt(fileName, marker.start); expect(result).toBeDefined(); const {textSpan, definitions} = result !; - expect(textSpan).toEqual(getLocationMarkerFor(fileName, 'my')); + expect(textSpan).toEqual(mockHost.getLocationMarkerFor(fileName, 'my')); expect(definitions).toBeDefined(); expect(definitions !.length).toBe(1); const def = definitions ![0]; @@ -101,11 +101,11 @@ describe('definitions', () => { expect(def.fileName).toBe(fileName); expect(def.name).toBe('myClick'); expect(def.kind).toBe('method'); - expect(def.textSpan).toEqual(getDefinitionMarkerFor(fileName, 'myClick')); + expect(def.textSpan).toEqual(mockHost.getDefinitionMarkerFor(fileName, 'myClick')); }); it('should be able to find a field reference in an *ngIf', () => { - const fileName = addCode(` + const fileName = mockHost.addCode(` @Component({ template: '
' }) @@ -113,7 +113,7 @@ describe('definitions', () => { «ᐱincludeᐱ = true;» }`); - const marker = getReferenceMarkerFor(fileName, 'include'); + const marker = mockHost.getReferenceMarkerFor(fileName, 'include'); const result = ngService.getDefinitionAt(fileName, marker.start); expect(result).toBeDefined(); const {textSpan, definitions} = result !; @@ -126,25 +126,25 @@ describe('definitions', () => { expect(def.fileName).toBe(fileName); expect(def.name).toBe('include'); expect(def.kind).toBe('property'); - expect(def.textSpan).toEqual(getDefinitionMarkerFor(fileName, 'include')); + expect(def.textSpan).toEqual(mockHost.getDefinitionMarkerFor(fileName, 'include')); }); it('should be able to find a reference to a component', () => { - const fileName = addCode(` + const fileName = mockHost.addCode(` @Component({ template: '~{start-my}<«test-comp»>~{end-my}' }) export class MyComponent { }`); // Get the marker for «test-comp» in the code added above. - const marker = getReferenceMarkerFor(fileName, 'test-comp'); + const marker = mockHost.getReferenceMarkerFor(fileName, 'test-comp'); const result = ngService.getDefinitionAt(fileName, marker.start); expect(result).toBeDefined(); const {textSpan, definitions} = result !; // Get the marker for bounded text in the code added above. - const boundedText = getLocationMarkerFor(fileName, 'my'); + const boundedText = mockHost.getLocationMarkerFor(fileName, 'my'); expect(textSpan).toEqual(boundedText); // There should be exactly 1 definition @@ -156,25 +156,25 @@ describe('definitions', () => { expect(def.fileName).toBe(refFileName); expect(def.name).toBe('TestComponent'); expect(def.kind).toBe('component'); - expect(def.textSpan).toEqual(getLocationMarkerFor(refFileName, 'test-comp')); + expect(def.textSpan).toEqual(mockHost.getLocationMarkerFor(refFileName, 'test-comp')); }); it('should be able to find an event provider', () => { - const fileName = addCode(` + const fileName = mockHost.addCode(` @Component({ template: '' }) export class MyComponent { myHandler() {} }`); // Get the marker for «test» in the code added above. - const marker = getReferenceMarkerFor(fileName, 'test'); + const marker = mockHost.getReferenceMarkerFor(fileName, 'test'); const result = ngService.getDefinitionAt(fileName, marker.start); expect(result).toBeDefined(); const {textSpan, definitions} = result !; // Get the marker for bounded text in the code added above - const boundedText = getLocationMarkerFor(fileName, 'my'); + const boundedText = mockHost.getLocationMarkerFor(fileName, 'my'); expect(textSpan).toEqual(boundedText); // There should be exactly 1 definition @@ -186,12 +186,12 @@ describe('definitions', () => { expect(def.fileName).toBe(refFileName); expect(def.name).toBe('testEvent'); expect(def.kind).toBe('event'); - expect(def.textSpan).toEqual(getDefinitionMarkerFor(refFileName, 'test')); + expect(def.textSpan).toEqual(mockHost.getDefinitionMarkerFor(refFileName, 'test')); }); it('should be able to find an input provider', () => { // '/app/parsing-cases.ts', 'tcName', - const fileName = addCode(` + const fileName = mockHost.addCode(` @Component({ template: '' }) @@ -200,14 +200,14 @@ describe('definitions', () => { }`); // Get the marker for «test» in the code added above. - const marker = getReferenceMarkerFor(fileName, 'tcName'); + const marker = mockHost.getReferenceMarkerFor(fileName, 'tcName'); const result = ngService.getDefinitionAt(fileName, marker.start); expect(result).toBeDefined(); const {textSpan, definitions} = result !; // Get the marker for bounded text in the code added above - const boundedText = getLocationMarkerFor(fileName, 'my'); + const boundedText = mockHost.getLocationMarkerFor(fileName, 'my'); expect(textSpan).toEqual(boundedText); // There should be exactly 1 definition @@ -219,11 +219,11 @@ describe('definitions', () => { expect(def.fileName).toBe(refFileName); expect(def.name).toBe('name'); expect(def.kind).toBe('property'); - expect(def.textSpan).toEqual(getDefinitionMarkerFor(refFileName, 'tcName')); + expect(def.textSpan).toEqual(mockHost.getDefinitionMarkerFor(refFileName, 'tcName')); }); it('should be able to find a pipe', () => { - const fileName = addCode(` + const fileName = mockHost.addCode(` @Component({ template: '
' }) @@ -232,14 +232,14 @@ describe('definitions', () => { }`); // Get the marker for «test» in the code added above. - const marker = getReferenceMarkerFor(fileName, 'async'); + const marker = mockHost.getReferenceMarkerFor(fileName, 'async'); const result = ngService.getDefinitionAt(fileName, marker.start); expect(result).toBeDefined(); const {textSpan, definitions} = result !; // Get the marker for bounded text in the code added above - const boundedText = getLocationMarkerFor(fileName, 'my'); + const boundedText = mockHost.getLocationMarkerFor(fileName, 'my'); expect(textSpan).toEqual(boundedText); expect(definitions).toBeDefined(); @@ -253,97 +253,4 @@ describe('definitions', () => { // Not asserting the textSpan of definition because it's external file } }); - - it('should be able to find a template from a url', () => { - const fileName = addCode(` - @Component({ - templateUrl: './«test».ng', - }) - export class MyComponent {}`); - - const marker = getReferenceMarkerFor(fileName, 'test'); - const result = ngService.getDefinitionAt(fileName, marker.start); - - expect(result).toBeDefined(); - const {textSpan, definitions} = result !; - - expect(textSpan).toEqual({start: marker.start - 2, length: 9}); - - expect(definitions).toBeDefined(); - expect(definitions !.length).toBe(1); - const [def] = definitions !; - expect(def.fileName).toBe('/app/test.ng'); - expect(def.textSpan).toEqual({start: 0, length: 0}); - }); - - /** - * Append a snippet of code to `app.component.ts` and return the file name. - * There must not be any name collision with existing code. - * @param code Snippet of code - */ - function addCode(code: string) { - const fileName = '/app/app.component.ts'; - const originalContent = mockHost.getFileContent(fileName); - const newContent = originalContent + code; - mockHost.override(fileName, newContent); - return fileName; - } - - /** - * Returns the definition marker ᐱselectorᐱ for the specified 'selector'. - * Asserts that marker exists. - * @param fileName name of the file - * @param selector name of the marker - */ - function getDefinitionMarkerFor(fileName: string, selector: string): ts.TextSpan { - const markers = mockHost.getReferenceMarkers(fileName); - expect(markers).toBeDefined(); - expect(Object.keys(markers !.definitions)).toContain(selector); - expect(markers !.definitions[selector].length).toBe(1); - const marker = markers !.definitions[selector][0]; - expect(marker.start).toBeLessThanOrEqual(marker.end); - return { - start: marker.start, - length: marker.end - marker.start, - }; - } - - /** - * Returns the reference marker «selector» for the specified 'selector'. - * Asserts that marker exists. - * @param fileName name of the file - * @param selector name of the marker - */ - function getReferenceMarkerFor(fileName: string, selector: string): ts.TextSpan { - const markers = mockHost.getReferenceMarkers(fileName); - expect(markers).toBeDefined(); - expect(Object.keys(markers !.references)).toContain(selector); - expect(markers !.references[selector].length).toBe(1); - const marker = markers !.references[selector][0]; - expect(marker.start).toBeLessThanOrEqual(marker.end); - return { - start: marker.start, - length: marker.end - marker.start, - }; - } - - /** - * Returns the location marker ~{selector} for the specified 'selector'. - * Asserts that marker exists. - * @param fileName name of the file - * @param selector name of the marker - */ - function getLocationMarkerFor(fileName: string, selector: string): ts.TextSpan { - const markers = mockHost.getMarkerLocations(fileName); - expect(markers).toBeDefined(); - const start = markers ![`start-${selector}`]; - expect(start).toBeDefined(); - const end = markers ![`end-${selector}`]; - expect(end).toBeDefined(); - expect(start).toBeLessThanOrEqual(end); - return { - start: start, - length: end - start, - }; - } }); diff --git a/packages/language-service/test/hover_spec.ts b/packages/language-service/test/hover_spec.ts index 3cbc062dde..29f06808af 100644 --- a/packages/language-service/test/hover_spec.ts +++ b/packages/language-service/test/hover_spec.ts @@ -6,114 +6,151 @@ * found in the LICENSE file at https://angular.io/license */ -import 'reflect-metadata'; - import * as ts from 'typescript'; import {createLanguageService} from '../src/language_service'; -import {Hover} from '../src/types'; +import {LanguageService} from '../src/types'; import {TypeScriptServiceHost} from '../src/typescript_host'; import {toh} from './test_data'; import {MockTypescriptHost} from './test_utils'; describe('hover', () => { - let documentRegistry = ts.createDocumentRegistry(); - let mockHost = new MockTypescriptHost(['/app/main.ts', '/app/parsing-cases.ts'], toh); - let service = ts.createLanguageService(mockHost, documentRegistry); - let ngHost = new TypeScriptServiceHost(mockHost, service); - let ngService = createLanguageService(ngHost); + let mockHost: MockTypescriptHost; + let tsLS: ts.LanguageService; + let ngLSHost: TypeScriptServiceHost; + let ngLS: LanguageService; + beforeEach(() => { + mockHost = new MockTypescriptHost(['/app/main.ts', '/app/parsing-cases.ts'], toh); + tsLS = ts.createLanguageService(mockHost); + ngLSHost = new TypeScriptServiceHost(mockHost, tsLS); + ngLS = createLanguageService(ngLSHost); + }); it('should be able to find field in an interpolation', () => { - hover( - ` @Component({template: '{{«name»}}'}) export class MyComponent { name: string; }`, - '(property) MyComponent.name'); + const fileName = mockHost.addCode(` + @Component({ + template: '{{«name»}}' + }) + export class MyComponent { + name: string; + }`); + const marker = mockHost.getReferenceMarkerFor(fileName, 'name'); + const quickInfo = ngLS.getHoverAt(fileName, marker.start); + expect(quickInfo).toBeTruthy(); + const {textSpan, displayParts} = quickInfo !; + expect(textSpan).toEqual(marker); + expect(toText(displayParts)).toBe('(property) MyComponent.name'); }); it('should be able to find a field in a attribute reference', () => { - hover( - ` @Component({template: ''}) export class MyComponent { name: string; }`, - '(property) MyComponent.name'); + const fileName = mockHost.addCode(` + @Component({ + template: '' + }) + export class MyComponent { + name: string; + }`); + const marker = mockHost.getReferenceMarkerFor(fileName, 'name'); + const quickInfo = ngLS.getHoverAt(fileName, marker.start); + expect(quickInfo).toBeTruthy(); + const {textSpan, displayParts} = quickInfo !; + expect(textSpan).toEqual(marker); + expect(toText(displayParts)).toBe('(property) MyComponent.name'); }); it('should be able to find a method from a call', () => { - hover( - ` @Component({template: '
'}) export class MyComponent { myClick() { }}`, - '(method) MyComponent.myClick'); + const fileName = mockHost.addCode(` + @Component({ + template: '
' + }) + export class MyComponent { + myClick() { } + }`); + const marker = mockHost.getDefinitionMarkerFor(fileName, 'myClick'); + const quickInfo = ngLS.getHoverAt(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'); }); it('should be able to find a field reference in an *ngIf', () => { - hover( - ` @Component({template: '
'}) export class MyComponent { include = true;}`, - '(property) MyComponent.include'); + const fileName = mockHost.addCode(` + @Component({ + template: '
' + }) + export class MyComponent { + include = true; + }`); + const marker = mockHost.getReferenceMarkerFor(fileName, 'include'); + const quickInfo = ngLS.getHoverAt(fileName, marker.start); + expect(quickInfo).toBeTruthy(); + const {textSpan, displayParts} = quickInfo !; + expect(textSpan).toEqual(marker); + expect(toText(displayParts)).toBe('(property) MyComponent.include'); }); it('should be able to find a reference to a component', () => { - hover( - ` @Component({template: '«<ᐱtestᐱ-comp>
»'}) export class MyComponent { }`, - '(component) TestComponent'); + const fileName = mockHost.addCode(` + @Component({ + template: '«<ᐱtestᐱ-comp>
»' + }) + export class MyComponent { }`); + const marker = mockHost.getDefinitionMarkerFor(fileName, 'test'); + const quickInfo = ngLS.getHoverAt(fileName, marker.start); + expect(quickInfo).toBeTruthy(); + const {textSpan, displayParts} = quickInfo !; + expect(textSpan).toEqual(marker); + expect(toText(displayParts)).toBe('(component) TestComponent'); }); it('should be able to find an event provider', () => { - hover( - ` @Component({template: ''}) export class MyComponent { myHandler() {} }`, - '(event) TestComponent.testEvent'); + const fileName = mockHost.addCode(` + @Component({ + template: '' + }) + export class MyComponent { + myHandler() {} + }`); + const marker = mockHost.getDefinitionMarkerFor(fileName, 'test'); + const quickInfo = ngLS.getHoverAt(fileName, marker.start); + expect(quickInfo).toBeTruthy(); + const {textSpan, displayParts} = quickInfo !; + expect(textSpan).toEqual(marker); + expect(toText(displayParts)).toBe('(event) TestComponent.testEvent'); }); it('should be able to find an input provider', () => { - hover( - ` @Component({template: ''}) export class MyComponent { name = 'my name'; }`, - '(property) TestComponent.name'); + const fileName = mockHost.addCode(` + @Component({ + template: '' + }) + export class MyComponent { + name = 'my name'; + }`); + const marker = mockHost.getDefinitionMarkerFor(fileName, 'tcName'); + const quickInfo = ngLS.getHoverAt(fileName, marker.start); + expect(quickInfo).toBeTruthy(); + const {textSpan, displayParts} = quickInfo !; + expect(textSpan).toEqual(marker); + expect(toText(displayParts)).toBe('(property) TestComponent.name'); }); it('should be able to ignore a reference declaration', () => { - addCode( - ` @Component({template: '
'}) export class MyComponent { }`, - fileName => { - const markers = mockHost.getReferenceMarkers(fileName) !; - const hover = ngService.getHoverAt(fileName, markers.references.chart[0].start); - expect(hover).toBeUndefined(); - }); + const fileName = mockHost.addCode(` + @Component({ + template: '
' + }) + export class MyComponent { }`); + const marker = mockHost.getReferenceMarkerFor(fileName, 'chart'); + const quickInfo = ngLS.getHoverAt(fileName, marker.start); + expect(quickInfo).toBeUndefined(); }); - - function hover(code: string, hoverText: string) { - addCode(code, fileName => { - let tests = 0; - const markers = mockHost.getReferenceMarkers(fileName) !; - const keys = Object.keys(markers.references).concat(Object.keys(markers.definitions)); - for (const referenceName of keys) { - const references = (markers.references[referenceName] || - []).concat(markers.definitions[referenceName] || []); - for (const reference of references) { - tests++; - const quickInfo = ngService.getHoverAt(fileName, reference.start); - if (!quickInfo) throw new Error(`Expected a hover at location ${reference.start}`); - expect(quickInfo.textSpan).toEqual({ - start: reference.start, - length: reference.end - reference.start, - }); - expect(toText(quickInfo)).toEqual(hoverText); - } - } - expect(tests).toBeGreaterThan(0); // If this fails the test is wrong. - }); - } - - function addCode(code: string, cb: (fileName: string, content?: string) => void) { - const fileName = '/app/app.component.ts'; - const originalContent = mockHost.getFileContent(fileName); - const newContent = originalContent + code; - mockHost.override(fileName, originalContent + code); - try { - cb(fileName, newContent); - } finally { - mockHost.override(fileName, undefined !); - } - } - - function toText(quickInfo: ts.QuickInfo): string { - const displayParts = quickInfo.displayParts || []; - return displayParts.map(p => p.text).join(''); - } }); + +function toText(displayParts?: ts.SymbolDisplayPart[]): string { + return (displayParts || []).map(p => p.text).join(''); +} diff --git a/packages/language-service/test/test_utils.ts b/packages/language-service/test/test_utils.ts index 405ebbdcf5..010847527e 100644 --- a/packages/language-service/test/test_utils.ts +++ b/packages/language-service/test/test_utils.ts @@ -239,6 +239,78 @@ export class MockTypescriptHost implements ts.LanguageServiceHost { } return name; } + + + /** + * Append a snippet of code to `app.component.ts` and return the file name. + * There must not be any name collision with existing code. + * @param code Snippet of code + */ + addCode(code: string) { + const fileName = '/app/app.component.ts'; + const originalContent = this.getFileContent(fileName); + const newContent = originalContent + code; + this.override(fileName, newContent); + return fileName; + } + + /** + * Returns the definition marker ᐱselectorᐱ for the specified 'selector'. + * Asserts that marker exists. + * @param fileName name of the file + * @param selector name of the marker + */ + getDefinitionMarkerFor(fileName: string, selector: string): ts.TextSpan { + const markers = this.getReferenceMarkers(fileName); + expect(markers).toBeDefined(); + expect(Object.keys(markers !.definitions)).toContain(selector); + expect(markers !.definitions[selector].length).toBe(1); + const marker = markers !.definitions[selector][0]; + expect(marker.start).toBeLessThanOrEqual(marker.end); + return { + start: marker.start, + length: marker.end - marker.start, + }; + } + + /** + * Returns the reference marker «selector» for the specified 'selector'. + * Asserts that marker exists. + * @param fileName name of the file + * @param selector name of the marker + */ + getReferenceMarkerFor(fileName: string, selector: string): ts.TextSpan { + const markers = this.getReferenceMarkers(fileName); + expect(markers).toBeDefined(); + expect(Object.keys(markers !.references)).toContain(selector); + expect(markers !.references[selector].length).toBe(1); + const marker = markers !.references[selector][0]; + expect(marker.start).toBeLessThanOrEqual(marker.end); + return { + start: marker.start, + length: marker.end - marker.start, + }; + } + + /** + * Returns the location marker ~{selector} for the specified 'selector'. + * Asserts that marker exists. + * @param fileName name of the file + * @param selector name of the marker + */ + getLocationMarkerFor(fileName: string, selector: string): ts.TextSpan { + const markers = this.getMarkerLocations(fileName); + expect(markers).toBeDefined(); + const start = markers ![`start-${selector}`]; + expect(start).toBeDefined(); + const end = markers ![`end-${selector}`]; + expect(end).toBeDefined(); + expect(start).toBeLessThanOrEqual(end); + return { + start: start, + length: end - start, + }; + } } function iterableToArray(iterator: IterableIterator) {