diff --git a/packages/language-service/ivy/test/definitions_spec.ts b/packages/language-service/ivy/test/definitions_spec.ts
index 056bf1b2a94..db341d9ab77 100644
--- a/packages/language-service/ivy/test/definitions_spec.ts
+++ b/packages/language-service/ivy/test/definitions_spec.ts
@@ -6,102 +6,100 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {absoluteFrom, AbsoluteFsPath} from '@angular/compiler-cli/src/ngtsc/file_system';
import {initMockFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing';
-import {extractCursorInfo, LanguageServiceTestEnvironment} from './env';
-import {assertFileNames, createModuleWithDeclarations, humanizeDocumentSpanLike} from './test_utils';
+import {assertFileNames, createModuleAndProjectWithDeclarations, humanizeDocumentSpanLike, LanguageServiceTestEnv, OpenBuffer} from '../testing';
describe('definitions', () => {
it('gets definition for template reference in overridden template', () => {
initMockFileSystem('Native');
- const templateFile = {contents: '', name: absoluteFrom('/app.html')};
- const appFile = {
- name: absoluteFrom('/app.ts'),
- contents: `
+ const files = {
+ 'app.html': '',
+ 'app.ts': `
import {Component} from '@angular/core';
@Component({templateUrl: '/app.html'})
export class AppCmp {}
`,
};
+ const env = LanguageServiceTestEnv.setup();
- const env = createModuleWithDeclarations([appFile], [templateFile]);
- const {cursor} = env.updateFileWithCursor(
- absoluteFrom('/app.html'), ' {{myIn¦put.value}}');
- env.expectNoSourceDiagnostics();
- const {definitions} = env.ngLS.getDefinitionAndBoundSpan(absoluteFrom('/app.html'), cursor)!;
+ const project = createModuleAndProjectWithDeclarations(env, 'test', files);
+ const template = project.openFile('app.html');
+ template.contents = ' {{myInput.value}}';
+ project.expectNoSourceDiagnostics();
+
+ template.moveCursorToText('{{myIn¦put.value}}');
+ const {definitions} = getDefinitionsAndAssertBoundSpan(env, template);
expect(definitions![0].name).toEqual('myInput');
assertFileNames(Array.from(definitions!), ['app.html']);
});
it('returns the pipe class as definition when checkTypeOfPipes is false', () => {
initMockFileSystem('Native');
- const {cursor, text} = extractCursorInfo('{{"1/1/2020" | dat¦e}}');
- const templateFile = {contents: text, name: absoluteFrom('/app.html')};
- const appFile = {
- name: absoluteFrom('/app.ts'),
- contents: `
+ const files = {
+ 'app.ts': `
import {Component, NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
@Component({templateUrl: 'app.html'})
export class AppCmp {}
`,
+ 'app.html': '{{"1/1/2020" | date}}'
};
// checkTypeOfPipes is set to false when strict templates is false
- const env = createModuleWithDeclarations([appFile], [templateFile], {strictTemplates: false});
- const {textSpan, definitions} =
- getDefinitionsAndAssertBoundSpan(env, absoluteFrom('/app.html'), cursor);
- expect(text.substr(textSpan.start, textSpan.length)).toEqual('date');
+ const env = LanguageServiceTestEnv.setup();
+ const project =
+ createModuleAndProjectWithDeclarations(env, 'test', files, {strictTemplates: false});
+ const template = project.openFile('app.html');
+ project.expectNoSourceDiagnostics();
+ template.moveCursorToText('da¦te');
- expect(definitions.length).toEqual(1);
- const [def] = definitions;
+ const {textSpan, definitions} = getDefinitionsAndAssertBoundSpan(env, template);
+ expect(template.contents.substr(textSpan.start, textSpan.length)).toEqual('date');
+ expect(definitions!.length).toEqual(1);
+ const [def] = definitions!;
expect(def.textSpan).toContain('DatePipe');
expect(def.contextSpan).toContain('DatePipe');
});
it('gets definitions for all inputs when attribute matches more than one', () => {
initMockFileSystem('Native');
- const {cursor, text} = extractCursorInfo('
');
- const templateFile = {contents: text, name: absoluteFrom('/app.html')};
- const dirFile = {
- name: absoluteFrom('/dir.ts'),
- contents: `
+ const files = {
+ 'app.ts': `
+ import {Component, NgModule} from '@angular/core';
+ import {CommonModule} from '@angular/common';
+
+ @Component({templateUrl: 'app.html'})
+ export class AppCmp {}
+ `,
+ 'app.html': '',
+ 'dir.ts': `
import {Directive, Input} from '@angular/core';
@Directive({selector: '[dir]'})
export class MyDir {
@Input() inputA!: any;
}`,
- };
- const dirFile2 = {
- name: absoluteFrom('/dir2.ts'),
- contents: `
+ 'dir2.ts': `
import {Directive, Input} from '@angular/core';
@Directive({selector: '[dir]'})
export class MyDir2 {
@Input() inputA!: any;
- }`,
- };
- const appFile = {
- name: absoluteFrom('/app.ts'),
- contents: `
- import {Component, NgModule} from '@angular/core';
- import {CommonModule} from '@angular/common';
+ }`
- @Component({templateUrl: 'app.html'})
- export class AppCmp {}
- `
};
- const env = createModuleWithDeclarations([appFile, dirFile, dirFile2], [templateFile]);
- const {textSpan, definitions} =
- getDefinitionsAndAssertBoundSpan(env, absoluteFrom('/app.html'), cursor);
- expect(text.substr(textSpan.start, textSpan.length)).toEqual('inputA');
+ const env = LanguageServiceTestEnv.setup();
+ const project = createModuleAndProjectWithDeclarations(env, 'test', files);
+ const template = project.openFile('app.html');
+ template.moveCursorToText('inpu¦tA="abc"');
- expect(definitions.length).toEqual(2);
- const [def, def2] = definitions;
+ const {textSpan, definitions} = getDefinitionsAndAssertBoundSpan(env, template);
+ expect(template.contents.substr(textSpan.start, textSpan.length)).toEqual('inputA');
+
+ expect(definitions!.length).toEqual(2);
+ const [def, def2] = definitions!;
expect(def.textSpan).toContain('inputA');
expect(def2.textSpan).toContain('inputA');
// TODO(atscott): investigate why the text span includes more than just 'inputA'
@@ -111,31 +109,23 @@ describe('definitions', () => {
it('gets definitions for all outputs when attribute matches more than one', () => {
initMockFileSystem('Native');
- const {cursor, text} = extractCursorInfo('');
- const templateFile = {contents: text, name: absoluteFrom('/app.html')};
- const dirFile = {
- name: absoluteFrom('/dir.ts'),
- contents: `
+ const files = {
+ 'app.html': '',
+ 'dir.ts': `
import {Directive, Output, EventEmitter} from '@angular/core';
@Directive({selector: '[dir]'})
export class MyDir {
@Output() someEvent = new EventEmitter();
}`,
- };
- const dirFile2 = {
- name: absoluteFrom('/dir2.ts'),
- contents: `
+ 'dir2.ts': `
import {Directive, Output, EventEmitter} from '@angular/core';
@Directive({selector: '[dir]'})
export class MyDir2 {
@Output() someEvent = new EventEmitter();
}`,
- };
- const appFile = {
- name: absoluteFrom('/app.ts'),
- contents: `
+ 'app.ts': `
import {Component, NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
@@ -145,10 +135,13 @@ describe('definitions', () => {
}
`
};
- const env = createModuleWithDeclarations([appFile, dirFile, dirFile2], [templateFile]);
- const {textSpan, definitions} =
- getDefinitionsAndAssertBoundSpan(env, absoluteFrom('/app.html'), cursor);
- expect(text.substr(textSpan.start, textSpan.length)).toEqual('someEvent');
+ const env = LanguageServiceTestEnv.setup();
+ const project = createModuleAndProjectWithDeclarations(env, 'test', files);
+ const template = project.openFile('app.html');
+ template.moveCursorToText('(someEv¦ent)');
+
+ const {textSpan, definitions} = getDefinitionsAndAssertBoundSpan(env, template);
+ expect(template.contents.substr(textSpan.start, textSpan.length)).toEqual('someEvent');
expect(definitions.length).toEqual(2);
const [def, def2] = definitions;
@@ -159,10 +152,9 @@ describe('definitions', () => {
assertFileNames([def, def2], ['dir2.ts', 'dir.ts']);
});
- function getDefinitionsAndAssertBoundSpan(
- env: LanguageServiceTestEnvironment, fileName: AbsoluteFsPath, cursor: number) {
+ function getDefinitionsAndAssertBoundSpan(env: LanguageServiceTestEnv, file: OpenBuffer) {
env.expectNoSourceDiagnostics();
- const definitionAndBoundSpan = env.ngLS.getDefinitionAndBoundSpan(fileName, cursor);
+ const definitionAndBoundSpan = file.getDefinitionAndBoundSpan();
const {textSpan, definitions} = definitionAndBoundSpan!;
expect(definitions).toBeTruthy();
return {textSpan, definitions: definitions!.map(d => humanizeDocumentSpanLike(d, env))};
diff --git a/packages/language-service/ivy/testing/src/env.ts b/packages/language-service/ivy/testing/src/env.ts
index 1a69f3c302d..482c81334cb 100644
--- a/packages/language-service/ivy/testing/src/env.ts
+++ b/packages/language-service/ivy/testing/src/env.ts
@@ -12,7 +12,7 @@ import {loadStandardTestFiles} from '@angular/compiler-cli/src/ngtsc/testing';
import * as ts from 'typescript/lib/tsserverlibrary';
import {MockServerHost} from './host';
-import {Project, ProjectFiles} from './project';
+import {Project, ProjectFiles, TestableOptions} from './project';
/**
* Testing environment for the Angular Language Service, which creates an in-memory tsserver
@@ -48,12 +48,12 @@ export class LanguageServiceTestEnv {
constructor(private host: MockServerHost, private projectService: ts.server.ProjectService) {}
- addProject(name: string, files: ProjectFiles): Project {
+ addProject(name: string, files: ProjectFiles, options: TestableOptions = {}): Project {
if (this.projects.has(name)) {
throw new Error(`Project ${name} is already defined`);
}
- const project = Project.initialize(name, this.projectService, files);
+ const project = Project.initialize(name, this.projectService, files, options);
this.projects.set(name, project);
return project;
}
@@ -65,6 +65,12 @@ export class LanguageServiceTestEnv {
}
return scriptInfo.getSnapshot().getText(span.start, span.start + span.length);
}
+
+ expectNoSourceDiagnostics(): void {
+ for (const project of this.projects.values()) {
+ project.expectNoSourceDiagnostics();
+ }
+ }
}
const logger: ts.server.Logger = {
diff --git a/packages/language-service/ivy/testing/src/project.ts b/packages/language-service/ivy/testing/src/project.ts
index 6c5dfccf068..e59805bba2f 100644
--- a/packages/language-service/ivy/testing/src/project.ts
+++ b/packages/language-service/ivy/testing/src/project.ts
@@ -8,7 +8,7 @@
import {StrictTemplateOptions} from '@angular/compiler-cli/src/ngtsc/core/api';
import {absoluteFrom, AbsoluteFsPath, FileSystem, getFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system';
-import { OptimizeFor } from '@angular/compiler-cli/src/ngtsc/typecheck/api';
+import {OptimizeFor} from '@angular/compiler-cli/src/ngtsc/typecheck/api';
import * as ts from 'typescript/lib/tsserverlibrary';
import {LanguageService} from '../../language_service';
import {OpenBuffer} from './buffer';
@@ -44,6 +44,7 @@ function writeTsconfig(
null, 2));
}
+export type TestableOptions = StrictTemplateOptions;
export class Project {
private tsProject: ts.server.Project;
@@ -52,7 +53,8 @@ export class Project {
private buffers = new Map();
static initialize(
- projectName: string, projectService: ts.server.ProjectService, files: ProjectFiles): Project {
+ projectName: string, projectService: ts.server.ProjectService, files: ProjectFiles,
+ options: TestableOptions = {}): Project {
const fs = getFileSystem();
const tsConfigPath = absoluteFrom(`/${projectName}/tsconfig.json`);
@@ -68,7 +70,7 @@ export class Project {
}
}
- writeTsconfig(fs, tsConfigPath, entryFiles, {});
+ writeTsconfig(fs, tsConfigPath, entryFiles, options);
// Ensure the project is live in the ProjectService.
projectService.openClientFile(entryFiles[0]);
diff --git a/packages/language-service/ivy/testing/src/util.ts b/packages/language-service/ivy/testing/src/util.ts
index 9e13b9ded17..50208c58f58 100644
--- a/packages/language-service/ivy/testing/src/util.ts
+++ b/packages/language-service/ivy/testing/src/util.ts
@@ -8,7 +8,7 @@
import {absoluteFrom} from '@angular/compiler-cli/src/ngtsc/file_system';
import {TestFile} from '@angular/compiler-cli/src/ngtsc/file_system/testing';
import {LanguageServiceTestEnv} from './env';
-import {Project, ProjectFiles} from './project';
+import {Project, ProjectFiles, TestableOptions} from './project';
/**
* Given a text snippet which contains exactly one cursor symbol ('¦'), extract both the offset of
@@ -67,7 +67,7 @@ function getFirstClassDeclaration(declaration: string) {
export function createModuleAndProjectWithDeclarations(
env: LanguageServiceTestEnv, projectName: string, projectFiles: ProjectFiles,
- options: any = {}): Project {
+ options: TestableOptions = {}): Project {
const externalClasses: string[] = [];
const externalImports: string[] = [];
for (const [fileName, fileContents] of Object.entries(projectFiles)) {
@@ -90,5 +90,28 @@ export function createModuleAndProjectWithDeclarations(
export class AppModule {}
`;
projectFiles['app-module.ts'] = moduleContents;
- return env.addProject(projectName, projectFiles);
-}
\ No newline at end of file
+ return env.addProject(projectName, projectFiles, options);
+}
+
+export function humanizeDocumentSpanLike(
+ item: T, env: LanguageServiceTestEnv): T&Stringy {
+ return {
+ ...item,
+ textSpan: env.getTextFromTsSpan(item.fileName, item.textSpan),
+ contextSpan: item.contextSpan ? env.getTextFromTsSpan(item.fileName, item.contextSpan) :
+ undefined,
+ originalTextSpan: item.originalTextSpan ?
+ env.getTextFromTsSpan(item.fileName, item.originalTextSpan) :
+ undefined,
+ originalContextSpan: item.originalContextSpan ?
+ env.getTextFromTsSpan(item.fileName, item.originalContextSpan) :
+ undefined,
+ };
+}
+type Stringy = {
+ [P in keyof T]: string;
+};
+
+export function getText(contents: string, textSpan: ts.TextSpan) {
+ return contents.substr(textSpan.start, textSpan.length);
+}