refactor(language-service): migrate diagnostic_spec to new test infrastructure (#40966)
refactor(language-service): migrate diagnostic_spec to new test infrastructure PR Close #40966
This commit is contained in:
parent
bc5c9ee234
commit
af3f95bd75
|
@ -6,21 +6,22 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {absoluteFrom} from '@angular/compiler-cli/src/ngtsc/file_system';
|
||||
import {initMockFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing';
|
||||
import {LanguageServiceTestEnvironment} from '@angular/language-service/ivy/test/env';
|
||||
import * as ts from 'typescript';
|
||||
import {createModuleWithDeclarations} from './test_utils';
|
||||
|
||||
import {createModuleAndProjectWithDeclarations, LanguageServiceTestEnv} from '../testing';
|
||||
|
||||
|
||||
describe('getSemanticDiagnostics', () => {
|
||||
let env: LanguageServiceTestEnv;
|
||||
beforeEach(() => {
|
||||
initMockFileSystem('Native');
|
||||
env = LanguageServiceTestEnv.setup();
|
||||
});
|
||||
|
||||
it('should not produce error for a minimal component defintion', () => {
|
||||
const appFile = {
|
||||
name: absoluteFrom('/app.ts'),
|
||||
contents: `
|
||||
const files = {
|
||||
'app.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
|
@ -29,16 +30,15 @@ describe('getSemanticDiagnostics', () => {
|
|||
export class AppComponent {}
|
||||
`
|
||||
};
|
||||
const env = createModuleWithDeclarations([appFile]);
|
||||
const project = createModuleAndProjectWithDeclarations(env, 'test', files);
|
||||
|
||||
const diags = env.ngLS.getSemanticDiagnostics(absoluteFrom('/app.ts'));
|
||||
const diags = project.getDiagnosticsForFile('app.ts');
|
||||
expect(diags.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('should report member does not exist', () => {
|
||||
const appFile = {
|
||||
name: absoluteFrom('/app.ts'),
|
||||
contents: `
|
||||
const files = {
|
||||
'app.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
|
@ -47,67 +47,59 @@ describe('getSemanticDiagnostics', () => {
|
|||
export class AppComponent {}
|
||||
`
|
||||
};
|
||||
const env = createModuleWithDeclarations([appFile]);
|
||||
const project = createModuleAndProjectWithDeclarations(env, 'test', files);
|
||||
|
||||
const diags = env.ngLS.getSemanticDiagnostics(absoluteFrom('/app.ts'));
|
||||
const diags = project.getDiagnosticsForFile('app.ts');
|
||||
expect(diags.length).toBe(1);
|
||||
const {category, file, start, length, messageText} = diags[0];
|
||||
const {category, file, messageText} = diags[0];
|
||||
expect(category).toBe(ts.DiagnosticCategory.Error);
|
||||
expect(file?.fileName).toBe('/app.ts');
|
||||
expect(file?.fileName).toBe('/test/app.ts');
|
||||
expect(messageText).toBe(`Property 'nope' does not exist on type 'AppComponent'.`);
|
||||
});
|
||||
|
||||
it('should process external template', () => {
|
||||
const appFile = {
|
||||
name: absoluteFrom('/app.ts'),
|
||||
contents: `
|
||||
const files = {
|
||||
'app.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
templateUrl: './app.html'
|
||||
})
|
||||
export class AppComponent {}
|
||||
`
|
||||
};
|
||||
const templateFile = {
|
||||
name: absoluteFrom('/app.html'),
|
||||
contents: `
|
||||
Hello world!
|
||||
`
|
||||
`,
|
||||
'app.html': `Hello world!`
|
||||
};
|
||||
|
||||
const env = createModuleWithDeclarations([appFile], [templateFile]);
|
||||
const diags = env.ngLS.getSemanticDiagnostics(absoluteFrom('/app.html'));
|
||||
const project = createModuleAndProjectWithDeclarations(env, 'test', files);
|
||||
const diags = project.getDiagnosticsForFile('app.html');
|
||||
expect(diags).toEqual([]);
|
||||
});
|
||||
|
||||
it('should report member does not exist in external template', () => {
|
||||
const appFile = {
|
||||
name: absoluteFrom('/app.ts'),
|
||||
contents: `
|
||||
const files = {
|
||||
'app.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
templateUrl: './app.html'
|
||||
})
|
||||
export class AppComponent {}
|
||||
`
|
||||
`,
|
||||
'app.html': '{{nope}}'
|
||||
};
|
||||
const templateFile = {name: absoluteFrom('/app.html'), contents: `{{nope}}`};
|
||||
|
||||
const env = createModuleWithDeclarations([appFile], [templateFile]);
|
||||
const diags = env.ngLS.getSemanticDiagnostics(absoluteFrom('/app.html'));
|
||||
const project = createModuleAndProjectWithDeclarations(env, 'test', files);
|
||||
const diags = project.getDiagnosticsForFile('app.html');
|
||||
expect(diags.length).toBe(1);
|
||||
const {category, file, start, length, messageText} = diags[0];
|
||||
const {category, file, messageText} = diags[0];
|
||||
expect(category).toBe(ts.DiagnosticCategory.Error);
|
||||
expect(file?.fileName).toBe('/app.html');
|
||||
expect(file?.fileName).toBe('/test/app.html');
|
||||
expect(messageText).toBe(`Property 'nope' does not exist on type 'AppComponent'.`);
|
||||
});
|
||||
|
||||
it('should report a parse error in external template', () => {
|
||||
const appFile = {
|
||||
name: absoluteFrom('/app.ts'),
|
||||
contents: `
|
||||
const files = {
|
||||
'app.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
|
@ -116,26 +108,25 @@ describe('getSemanticDiagnostics', () => {
|
|||
export class AppComponent {
|
||||
nope = false;
|
||||
}
|
||||
`
|
||||
`,
|
||||
'app.html': '{{nope = true}}'
|
||||
};
|
||||
const templateFile = {name: absoluteFrom('/app.html'), contents: `{{nope = true}}`};
|
||||
|
||||
const env = createModuleWithDeclarations([appFile], [templateFile]);
|
||||
const diags = env.ngLS.getSemanticDiagnostics(absoluteFrom('/app.html'));
|
||||
const project = createModuleAndProjectWithDeclarations(env, 'test', files);
|
||||
const diags = project.getDiagnosticsForFile('app.html');
|
||||
expect(diags.length).toBe(1);
|
||||
|
||||
const {category, file, messageText} = diags[0];
|
||||
expect(category).toBe(ts.DiagnosticCategory.Error);
|
||||
expect(file?.fileName).toBe('/app.html');
|
||||
expect(file?.fileName).toBe('/test/app.html');
|
||||
expect(messageText)
|
||||
.toContain(
|
||||
`Parser Error: Bindings cannot contain assignments at column 8 in [{{nope = true}}]`);
|
||||
});
|
||||
|
||||
it('should report parse errors of components defined in the same ts file', () => {
|
||||
const appFile = {
|
||||
name: absoluteFrom('/app.ts'),
|
||||
contents: `
|
||||
const files = {
|
||||
'app.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({ templateUrl: './app1.html' })
|
||||
|
@ -143,14 +134,10 @@ describe('getSemanticDiagnostics', () => {
|
|||
|
||||
@Component({ templateUrl: './app2.html' })
|
||||
export class AppComponent2 { nope = false; }
|
||||
`
|
||||
};
|
||||
const templateFile1 = {name: absoluteFrom('/app1.html'), contents: `{{nope = false}}`};
|
||||
const templateFile2 = {name: absoluteFrom('/app2.html'), contents: `{{nope = true}}`};
|
||||
|
||||
const moduleFile = {
|
||||
name: absoluteFrom('/app-module.ts'),
|
||||
contents: `
|
||||
`,
|
||||
'app1.html': '{{nope = false}}',
|
||||
'app2.html': '{{nope = true}}',
|
||||
'app-module.ts': `
|
||||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {AppComponent, AppComponent2} from './app';
|
||||
|
@ -160,34 +147,28 @@ describe('getSemanticDiagnostics', () => {
|
|||
imports: [CommonModule],
|
||||
})
|
||||
export class AppModule {}
|
||||
`,
|
||||
isRoot: true
|
||||
`
|
||||
};
|
||||
|
||||
const env =
|
||||
LanguageServiceTestEnvironment.setup([moduleFile, appFile, templateFile1, templateFile2]);
|
||||
|
||||
const diags = env.ngLS.getSemanticDiagnostics(absoluteFrom('/app.ts'));
|
||||
|
||||
const project = env.addProject('test', files);
|
||||
const diags = project.getDiagnosticsForFile('app.ts');
|
||||
expect(diags.map(x => x.messageText).sort()).toEqual([
|
||||
'Parser Error: Bindings cannot contain assignments at column 8 in [{{nope = false}}] in /app1.html@0:0',
|
||||
'Parser Error: Bindings cannot contain assignments at column 8 in [{{nope = true}}] in /app2.html@0:0'
|
||||
'Parser Error: Bindings cannot contain assignments at column 8 in [{{nope = false}}] in /test/app1.html@0:0',
|
||||
'Parser Error: Bindings cannot contain assignments at column 8 in [{{nope = true}}] in /test/app2.html@0:0'
|
||||
]);
|
||||
});
|
||||
|
||||
it('reports a diagnostic for a component without a template', () => {
|
||||
const appFile = {
|
||||
name: absoluteFrom('/app.ts'),
|
||||
contents: `
|
||||
const files = {
|
||||
'app.ts': `
|
||||
import {Component} from '@angular/core';
|
||||
@Component({})
|
||||
export class MyComponent {}
|
||||
`
|
||||
};
|
||||
|
||||
const env = createModuleWithDeclarations([appFile]);
|
||||
const diags = env.ngLS.getSemanticDiagnostics(absoluteFrom('/app.ts'));
|
||||
|
||||
const project = createModuleAndProjectWithDeclarations(env, 'test', files);
|
||||
const diags = project.getDiagnosticsForFile('app.ts');
|
||||
expect(diags.map(x => x.messageText)).toEqual([
|
||||
'component is missing a template',
|
||||
]);
|
||||
|
|
|
@ -5,6 +5,10 @@
|
|||
* 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 {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';
|
||||
|
||||
/**
|
||||
* Given a text snippet which contains exactly one cursor symbol ('¦'), extract both the offset of
|
||||
|
@ -52,3 +56,39 @@ export function isNgSpecificDiagnostic(diag: ts.Diagnostic): boolean {
|
|||
// Angular-specific diagnostics use a negative code space.
|
||||
return diag.code < 0;
|
||||
}
|
||||
|
||||
function getFirstClassDeclaration(declaration: string) {
|
||||
const matches = declaration.match(/(?:export class )(\w+)(?:\s|\{)/);
|
||||
if (matches === null || matches.length !== 2) {
|
||||
throw new Error(`Did not find exactly one exported class in: ${declaration}`);
|
||||
}
|
||||
return matches[1].trim();
|
||||
}
|
||||
|
||||
export function createModuleAndProjectWithDeclarations(
|
||||
env: LanguageServiceTestEnv, projectName: string, projectFiles: ProjectFiles,
|
||||
options: any = {}): Project {
|
||||
const externalClasses: string[] = [];
|
||||
const externalImports: string[] = [];
|
||||
for (const [fileName, fileContents] of Object.entries(projectFiles)) {
|
||||
if (!fileName.endsWith('.ts')) {
|
||||
continue;
|
||||
}
|
||||
const className = getFirstClassDeclaration(fileContents);
|
||||
externalClasses.push(className);
|
||||
externalImports.push(`import {${className}} from './${fileName.replace('.ts', '')}';`);
|
||||
}
|
||||
const moduleContents = `
|
||||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
${externalImports.join('\n')}
|
||||
|
||||
@NgModule({
|
||||
declarations: [${externalClasses.join(',')}],
|
||||
imports: [CommonModule],
|
||||
})
|
||||
export class AppModule {}
|
||||
`;
|
||||
projectFiles['app-module.ts'] = moduleContents;
|
||||
return env.addProject(projectName, projectFiles);
|
||||
}
|
Loading…
Reference in New Issue