test(language-service): update compiler_spec to use the new testing env (#40679)

This commit updates compiler_spec.ts in the Ivy LS suite to utilize the new
testing environment which was introduced in the previous commit. Eventually
all specs should be converted, but converting one right now helps ensure
that the new testing env is working properly and able to support real tests.

PR Close #40679
This commit is contained in:
Alex Rickabaugh 2021-02-02 14:20:40 -08:00 committed by atscott
parent c9879deded
commit 53c65f468f
5 changed files with 89 additions and 123 deletions

View File

@ -12,6 +12,7 @@ ts_library(
"//packages/compiler-cli/src/ngtsc/testing",
"//packages/compiler-cli/src/ngtsc/typecheck/api",
"//packages/language-service/ivy",
"//packages/language-service/ivy/testing",
"@npm//typescript",
],
)

View File

@ -6,10 +6,10 @@
* found in the LICENSE file at https://angular.io/license
*/
import {absoluteFrom, getSourceFileOrError} from '@angular/compiler-cli/src/ngtsc/file_system';
import {absoluteFrom} from '@angular/compiler-cli/src/ngtsc/file_system';
import {initMockFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing';
import {OptimizeFor} from '@angular/compiler-cli/src/ngtsc/typecheck/api';
import {isNgSpecificDiagnostic, LanguageServiceTestEnv} from '../testing';
import {LanguageServiceTestEnvironment} from './env';
describe('language-service/compiler integration', () => {
@ -50,66 +50,43 @@ describe('language-service/compiler integration', () => {
});
it('should not produce errors from inline test declarations mixing with those of the app', () => {
const appCmpFile = absoluteFrom('/test.cmp.ts');
const appModuleFile = absoluteFrom('/test.mod.ts');
const testFile = absoluteFrom('/test_spec.ts');
const env = LanguageServiceTestEnv.setup();
const project = env.addProject('test', {
'cmp.ts': `
import {Component} from '@angular/core';
const env = LanguageServiceTestEnvironment.setup([
{
name: appCmpFile,
contents: `
import {Component} from '@angular/core';
@Component({
selector: 'app-cmp',
template: 'Some template',
})
export class AppCmp {}
`,
'mod.ts': `
import {NgModule} from '@angular/core';
import {AppCmp} from './cmp';
@Component({
selector: 'app-cmp',
template: 'Some template',
})
export class AppCmp {}
`,
isRoot: true,
},
{
name: appModuleFile,
contents: `
import {NgModule} from '@angular/core';
import {AppCmp} from './test.cmp';
@NgModule({
declarations: [AppCmp],
})
export class AppModule {}
`,
'test_spec.ts': `
import {NgModule} from '@angular/core';
import {AppCmp} from './cmp';
export function test(): void {
@NgModule({
declarations: [AppCmp],
})
export class AppModule {}
`,
isRoot: true,
},
{
name: testFile,
contents: `
import {NgModule} from '@angular/core';
import {AppCmp} from './test.cmp';
export function test(): void {
@NgModule({
declarations: [AppCmp],
})
class TestModule {}
}
`,
isRoot: true,
}
]);
class TestModule {}
}
`,
});
// Expect that this program is clean diagnostically.
const ngCompiler = env.ngLS.compilerFactory.getOrCreate();
const program = ngCompiler.getNextProgram();
expect(ngCompiler.getDiagnosticsForFile(
getSourceFileOrError(program, appCmpFile), OptimizeFor.WholeProgram))
.toEqual([]);
expect(ngCompiler.getDiagnosticsForFile(
getSourceFileOrError(program, appModuleFile), OptimizeFor.WholeProgram))
.toEqual([]);
expect(ngCompiler.getDiagnosticsForFile(
getSourceFileOrError(program, testFile), OptimizeFor.WholeProgram))
.toEqual([]);
expect(project.getDiagnosticsForFile('cmp.ts')).toEqual([]);
expect(project.getDiagnosticsForFile('mod.ts')).toEqual([]);
expect(project.getDiagnosticsForFile('test_spec.ts')).toEqual([]);
});
it('should show type-checking errors from components with poisoned scopes', () => {
@ -122,39 +99,36 @@ describe('language-service/compiler integration', () => {
// that a component declared in an NgModule with a faulty import still generates template
// diagnostics.
const file = absoluteFrom('/test.ts');
const env = LanguageServiceTestEnvironment.setup([{
name: file,
contents: `
import {Component, Directive, Input, NgModule} from '@angular/core';
const env = LanguageServiceTestEnv.setup();
const project = env.addProject('test', {
'test.ts': `
import {Component, Directive, Input, NgModule} from '@angular/core';
@Component({
selector: 'test-cmp',
template: '<div [dir]="3"></div>',
})
export class Cmp {}
@Component({
selector: 'test-cmp',
template: '<div [dir]="3"></div>',
})
export class Cmp {}
@Directive({
selector: '[dir]',
})
export class Dir {
@Input() dir!: string;
}
@Directive({
selector: '[dir]',
})
export class Dir {
@Input() dir!: string;
}
export class NotAModule {}
export class NotAModule {}
@NgModule({
declarations: [Cmp, Dir],
imports: [NotAModule],
})
export class Mod {}
`,
isRoot: true,
}]);
@NgModule({
declarations: [Cmp, Dir],
imports: [NotAModule],
})
export class Mod {}
`,
});
const diags = env.ngLS.getSemanticDiagnostics(file);
expect(diags.map(diag => diag.messageText))
.toContain(`Type 'number' is not assignable to type 'string'.`);
const diags = project.getDiagnosticsForFile('test.ts').map(diag => diag.messageText);
expect(diags).toContain(`Type 'number' is not assignable to type 'string'.`);
});
it('should handle broken imports during incremental build steps', () => {
@ -174,9 +148,6 @@ describe('language-service/compiler integration', () => {
// build step is performed. The compiler should recognize that the module's previous analysis
// is stale, even though it was not able to fully understand the import during the first pass.
const moduleFile = absoluteFrom('/mod.ts');
const componentFile = absoluteFrom('/cmp.ts');
const componentSource = (isExported: boolean): string => `
import {Component} from '@angular/core';
@ -187,43 +158,31 @@ describe('language-service/compiler integration', () => {
${isExported ? 'export' : ''} class Cmp {}
`;
const env = LanguageServiceTestEnvironment.setup([
{
name: moduleFile,
contents: `
import {NgModule} from '@angular/core';
const env = LanguageServiceTestEnv.setup();
const project = env.addProject('test', {
'mod.ts': `
import {NgModule} from '@angular/core';
import {Cmp} from './cmp';
import {Cmp} from './cmp';
@NgModule({
declarations: [Cmp],
})
export class Mod {}
`,
isRoot: true,
},
{
name: componentFile,
contents: componentSource(/* start with component not exported */ false),
isRoot: true,
}
]);
@NgModule({
declarations: [Cmp],
})
export class Mod {}
`,
'cmp.ts': componentSource(/* start with component not exported */ false),
});
// Angular should be complaining about the module not being understandable.
const programBefore = env.tsLS.getProgram()!;
const moduleSfBefore = programBefore.getSourceFile(moduleFile)!;
const ngDiagsBefore = env.ngLS.compilerFactory.getOrCreate().getDiagnosticsForFile(
moduleSfBefore, OptimizeFor.SingleFile);
const ngDiagsBefore = project.getDiagnosticsForFile('mod.ts').filter(isNgSpecificDiagnostic);
expect(ngDiagsBefore.length).toBe(1);
// Fix the import.
env.updateFile(componentFile, componentSource(/* properly export the component */ true));
const file = project.openFile('cmp.ts');
file.contents = componentSource(/* properly export the component */ true);
// Angular should stop complaining about the NgModule.
const programAfter = env.tsLS.getProgram()!;
const moduleSfAfter = programAfter.getSourceFile(moduleFile)!;
const ngDiagsAfter = env.ngLS.compilerFactory.getOrCreate().getDiagnosticsForFile(
moduleSfAfter, OptimizeFor.SingleFile);
const ngDiagsAfter = project.getDiagnosticsForFile('mod.ts').filter(isNgSpecificDiagnostic);
expect(ngDiagsAfter.length).toBe(0);
});
});

View File

@ -7,8 +7,8 @@
*/
import * as ts from 'typescript/lib/tsserverlibrary';
import {LanguageService} from '../../language_service';
import {Project} from './project';
import {extractCursorInfo} from './util';
/**
@ -18,7 +18,7 @@ export class OpenBuffer {
private _cursor: number = 0;
constructor(
private project: Project, private projectFileName: string,
private ngLS: LanguageService, private projectFileName: string,
private scriptInfo: ts.server.ScriptInfo) {}
get cursor(): number {
@ -49,7 +49,10 @@ export class OpenBuffer {
const {text: snippet, cursor} = extractCursorInfo(snippetWithCursor);
const snippetIndex = this.contents.indexOf(snippet);
if (snippetIndex === -1) {
throw new Error(`Snippet ${snippet} not found in ${this.projectFileName}`);
throw new Error(`Snippet '${snippet}' not found in ${this.projectFileName}`);
}
if (this.contents.indexOf(snippet, snippetIndex + 1) !== -1) {
throw new Error(`Snippet '${snippet}' is not unique within ${this.projectFileName}`);
}
this._cursor = snippetIndex + cursor;
}
@ -59,6 +62,6 @@ export class OpenBuffer {
* location in this buffer.
*/
getDefinitionAndBoundSpan(): ts.DefinitionInfoAndBoundSpan|undefined {
return this.project.ngLS.getDefinitionAndBoundSpan(this.scriptInfo.fileName, this._cursor);
return this.ngLS.getDefinitionAndBoundSpan(this.scriptInfo.fileName, this._cursor);
}
}

View File

@ -50,15 +50,15 @@ export class Project {
readonly ngLS: LanguageService;
private buffers = new Map<string, OpenBuffer>();
static initialize(name: string, projectService: ts.server.ProjectService, files: ProjectFiles):
Project {
static initialize(
projectName: string, projectService: ts.server.ProjectService, files: ProjectFiles): Project {
const fs = getFileSystem();
const tsConfigPath = absoluteFrom(`/${name}/tsconfig.json`);
const tsConfigPath = absoluteFrom(`/${projectName}/tsconfig.json`);
const entryFiles: AbsoluteFsPath[] = [];
for (const projectFilePath of Object.keys(files)) {
const contents = files[projectFilePath];
const filePath = absoluteFrom(`/${name}/${projectFilePath}`);
const filePath = absoluteFrom(`/${projectName}/${projectFilePath}`);
const dirPath = fs.dirname(filePath);
fs.ensureDir(dirPath);
fs.writeFile(filePath, contents);
@ -73,7 +73,7 @@ export class Project {
projectService.openClientFile(entryFiles[0]);
projectService.closeClientFile(entryFiles[0]);
return new Project(name, projectService, tsConfigPath);
return new Project(projectName, projectService, tsConfigPath);
}
constructor(
@ -102,7 +102,7 @@ export class Project {
throw new Error(
`Unable to open ScriptInfo for ${projectFileName} in project ${this.tsConfigPath}`);
}
this.buffers.set(projectFileName, new OpenBuffer(this, projectFileName, scriptInfo));
this.buffers.set(projectFileName, new OpenBuffer(this.ngLS, projectFileName, scriptInfo));
}
return this.buffers.get(projectFileName)!;

View File

@ -23,6 +23,9 @@ export function extractCursorInfo(textWithCursor: string): {cursor: number, text
}
function last<T>(array: T[]): T {
if (array.length === 0) {
throw new Error(`last() called on empty array`);
}
return array[array.length - 1];
}