refactor(ivy): implement a virtual file-system layer in ngtsc + ngcc (#30921)
To improve cross platform support, all file access (and path manipulation) is now done through a well known interface (`FileSystem`). For testing a number of `MockFileSystem` implementations are provided. These provide an in-memory file-system which emulates operating systems like OS/X, Unix and Windows. The current file system is always available via the static method, `FileSystem.getFileSystem()`. This is also used by a number of static methods on `AbsoluteFsPath` and `PathSegment`, to avoid having to pass `FileSystem` objects around all the time. The result of this is that one must be careful to ensure that the file-system has been initialized before using any of these static methods. To prevent this happening accidentally the current file system always starts out as an instance of `InvalidFileSystem`, which will throw an error if any of its methods are called. You can set the current file-system by calling `FileSystem.setFileSystem()`. During testing you can call the helper function `initMockFileSystem(os)` which takes a string name of the OS to emulate, and will also monkey-patch aspects of the TypeScript library to ensure that TS is also using the current file-system. Finally there is the `NgtscCompilerHost` to be used for any TypeScript compilation, which uses a given file-system. All tests that interact with the file-system should be tested against each of the mock file-systems. A series of helpers have been provided to support such tests: * `runInEachFileSystem()` - wrap your tests in this helper to run all the wrapped tests in each of the mock file-systems. * `addTestFilesToFileSystem()` - use this to add files and their contents to the mock file system for testing. * `loadTestFilesFromDisk()` - use this to load a mirror image of files on disk into the in-memory mock file-system. * `loadFakeCore()` - use this to load a fake version of `@angular/core` into the mock file-system. All ngcc and ngtsc source and tests now use this virtual file-system setup. PR Close #30921
This commit is contained in:

committed by
Kara Erickson

parent
1e7e065423
commit
7186f9c016
@ -7,18 +7,18 @@ ts_library(
|
||||
testonly = True,
|
||||
srcs = [
|
||||
"mocks.ts",
|
||||
"runfile_helpers.ts",
|
||||
"test_support.ts",
|
||||
],
|
||||
visibility = [
|
||||
":__subpackages__",
|
||||
"//packages/compiler-cli/ngcc/test:__subpackages__",
|
||||
"//packages/language-service/test:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//packages:types",
|
||||
"//packages/compiler",
|
||||
"//packages/compiler-cli",
|
||||
"//packages/compiler-cli/src/ngtsc/file_system",
|
||||
"//packages/compiler-cli/test/helpers",
|
||||
"@npm//typescript",
|
||||
],
|
||||
)
|
||||
|
@ -10,6 +10,7 @@ ts_library(
|
||||
"//packages:types",
|
||||
"//packages/compiler",
|
||||
"//packages/compiler-cli",
|
||||
"//packages/compiler-cli/src/ngtsc/file_system",
|
||||
"//packages/compiler/test:test_utils",
|
||||
"@npm//typescript",
|
||||
],
|
||||
|
@ -5,15 +5,13 @@
|
||||
* 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 {AotCompilerOptions} from '@angular/compiler';
|
||||
import {escapeRegExp} from '@angular/compiler/src/util';
|
||||
import {MockCompilerHost, MockData, MockDirectory, arrayToMockDir, settings, toMockFileArray} from '@angular/compiler/test/aot/test_util';
|
||||
import {MockCompilerHost, MockData, MockDirectory, arrayToMockDir, toMockFileArray} from '@angular/compiler/test/aot/test_util';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {NodeJSFileSystem, setFileSystem} from '../../src/ngtsc/file_system';
|
||||
import {NgtscProgram} from '../../src/ngtsc/program';
|
||||
|
||||
|
||||
const IDENTIFIER = /[A-Za-z_$ɵ][A-Za-z0-9_$]*/;
|
||||
const OPERATOR =
|
||||
/!|\?|%|\*|\/|\^|&&?|\|\|?|\(|\)|\{|\}|\[|\]|:|;|<=?|>=?|={1,3}|!==?|=>|\+\+?|--?|@|,|\.|\.\.\./;
|
||||
@ -169,6 +167,7 @@ export function compile(
|
||||
errorCollector: (error: any, fileName?: string) => void = error => { throw error;}): {
|
||||
source: string,
|
||||
} {
|
||||
setFileSystem(new NodeJSFileSystem());
|
||||
const testFiles = toMockFileArray(data);
|
||||
const scripts = testFiles.map(entry => entry.fileName);
|
||||
const angularFilesArray = toMockFileArray(angularFiles);
|
||||
|
@ -5,13 +5,11 @@
|
||||
* 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
|
||||
*/
|
||||
|
||||
/// <reference types="node" />
|
||||
import * as ng from '@angular/compiler-cli';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {TestSupport, expectNoDiagnostics, setup} from '../test_support';
|
||||
|
||||
type MockFiles = {
|
||||
|
@ -114,8 +114,6 @@ export class DiagnosticContext {
|
||||
_reflector: StaticReflector|undefined;
|
||||
_errors: {e: any, path?: string}[] = [];
|
||||
_resolver: CompileMetadataResolver|undefined;
|
||||
// TODO(issue/24571): remove '!'.
|
||||
_refletor !: StaticReflector;
|
||||
// tslint:enable
|
||||
|
||||
constructor(
|
||||
|
@ -10,7 +10,7 @@ import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
import {mainXi18n} from '../src/extract_i18n';
|
||||
import {makeTempDir, setup} from './test_support';
|
||||
import {setup} from './test_support';
|
||||
|
||||
const EXPECTED_XMB = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE messagebundle [
|
||||
|
17
packages/compiler-cli/test/helpers/BUILD.bazel
Normal file
17
packages/compiler-cli/test/helpers/BUILD.bazel
Normal file
@ -0,0 +1,17 @@
|
||||
load("//tools:defaults.bzl", "ts_library")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
ts_library(
|
||||
name = "helpers",
|
||||
testonly = True,
|
||||
srcs = glob([
|
||||
"**/*.ts",
|
||||
]),
|
||||
deps = [
|
||||
"//packages:types",
|
||||
"//packages/compiler-cli/src/ngtsc/file_system",
|
||||
"//packages/compiler-cli/src/ngtsc/file_system/testing",
|
||||
"@npm//typescript",
|
||||
],
|
||||
)
|
9
packages/compiler-cli/test/helpers/index.ts
Normal file
9
packages/compiler-cli/test/helpers/index.ts
Normal file
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
export {getAngularPackagesFromRunfiles, resolveNpmTreeArtifact} from './src/runfile_helpers';
|
||||
export * from './src/mock_file_loading';
|
82
packages/compiler-cli/test/helpers/src/mock_file_loading.ts
Normal file
82
packages/compiler-cli/test/helpers/src/mock_file_loading.ts
Normal file
@ -0,0 +1,82 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
/// <reference types="node" />
|
||||
import {readFileSync, readdirSync, statSync} from 'fs';
|
||||
import {resolve} from 'path';
|
||||
|
||||
import {getAngularPackagesFromRunfiles, resolveNpmTreeArtifact} from '..';
|
||||
import {AbsoluteFsPath, FileSystem, getFileSystem} from '../../../src/ngtsc/file_system';
|
||||
import {Folder, MockFileSystemPosix, TestFile} from '../../../src/ngtsc/file_system/testing';
|
||||
|
||||
export function loadTestFiles(files: TestFile[]) {
|
||||
const fs = getFileSystem();
|
||||
files.forEach(file => {
|
||||
fs.ensureDir(fs.dirname(file.name));
|
||||
fs.writeFile(file.name, file.contents);
|
||||
});
|
||||
}
|
||||
|
||||
export function loadStandardTestFiles(
|
||||
{fakeCore = true, rxjs = false}: {fakeCore?: boolean, rxjs?: boolean} = {}): Folder {
|
||||
const tmpFs = new MockFileSystemPosix(true);
|
||||
const basePath = '/' as AbsoluteFsPath;
|
||||
|
||||
loadTestDirectory(
|
||||
tmpFs, resolveNpmTreeArtifact('typescript'),
|
||||
tmpFs.resolve(basePath, 'node_modules/typescript'));
|
||||
|
||||
loadTestDirectory(
|
||||
tmpFs, resolveNpmTreeArtifact('tslib'), tmpFs.resolve(basePath, 'node_modules/tslib'));
|
||||
|
||||
|
||||
if (fakeCore) {
|
||||
loadFakeCore(tmpFs, basePath);
|
||||
} else {
|
||||
getAngularPackagesFromRunfiles().forEach(({name, pkgPath}) => {
|
||||
loadTestDirectory(tmpFs, pkgPath, tmpFs.resolve('/node_modules/@angular', name));
|
||||
});
|
||||
}
|
||||
|
||||
if (rxjs) {
|
||||
loadTestDirectory(
|
||||
tmpFs, resolveNpmTreeArtifact('rxjs'), tmpFs.resolve(basePath, 'node_modules/rxjs'));
|
||||
}
|
||||
|
||||
return tmpFs.dump();
|
||||
}
|
||||
|
||||
export function loadFakeCore(fs: FileSystem, basePath: string = '/') {
|
||||
loadTestDirectory(
|
||||
fs, resolveNpmTreeArtifact('angular/packages/compiler-cli/test/ngtsc/fake_core/npm_package'),
|
||||
fs.resolve(basePath, 'node_modules/@angular/core'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Load real files from the real file-system into a mock file-system.
|
||||
* @param fs the file-system where the directory is to be loaded.
|
||||
* @param directoryPath the path to the directory we want to load.
|
||||
* @param mockPath the path within the mock file-system where the directory is to be loaded.
|
||||
*/
|
||||
function loadTestDirectory(fs: FileSystem, directoryPath: string, mockPath: AbsoluteFsPath): void {
|
||||
readdirSync(directoryPath).forEach(item => {
|
||||
const srcPath = resolve(directoryPath, item);
|
||||
const targetPath = fs.resolve(mockPath, item);
|
||||
try {
|
||||
if (statSync(srcPath).isDirectory()) {
|
||||
fs.ensureDir(targetPath);
|
||||
loadTestDirectory(fs, srcPath, targetPath);
|
||||
} else {
|
||||
fs.ensureDir(fs.dirname(targetPath));
|
||||
fs.writeFile(targetPath, readFileSync(srcPath, 'utf-8'));
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(`Failed to add ${srcPath} to the mock file-system: ${e.message}`);
|
||||
}
|
||||
});
|
||||
}
|
@ -5,6 +5,7 @@
|
||||
* 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
|
||||
*/
|
||||
/// <reference types="node" />
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
@ -5,8 +5,6 @@
|
||||
* 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 * as fs from 'fs';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {Evaluator} from '../../src/metadata/evaluator';
|
||||
|
@ -2028,7 +2028,7 @@ describe('ngc transformer command-line', () => {
|
||||
const exitCode =
|
||||
main(['-p', path.join(basePath, 'src/tsconfig.json')], message => messages.push(message));
|
||||
expect(exitCode).toBe(1, 'Compile was expected to fail');
|
||||
const srcPathWithSep = `lib${path.sep}`;
|
||||
const srcPathWithSep = `lib/`;
|
||||
expect(messages[0])
|
||||
.toEqual(
|
||||
`${srcPathWithSep}test.component.ts(6,21): Error during template compile of 'TestComponent'
|
||||
|
@ -8,11 +8,13 @@ ts_library(
|
||||
"//packages/compiler",
|
||||
"//packages/compiler-cli",
|
||||
"//packages/compiler-cli/src/ngtsc/diagnostics",
|
||||
"//packages/compiler-cli/src/ngtsc/file_system",
|
||||
"//packages/compiler-cli/src/ngtsc/file_system/testing",
|
||||
"//packages/compiler-cli/src/ngtsc/indexer",
|
||||
"//packages/compiler-cli/src/ngtsc/path",
|
||||
"//packages/compiler-cli/src/ngtsc/routing",
|
||||
"//packages/compiler-cli/src/ngtsc/util",
|
||||
"//packages/compiler-cli/test:test_utils",
|
||||
"//packages/compiler-cli/test/helpers",
|
||||
"@npm//@types/source-map",
|
||||
"@npm//source-map",
|
||||
"@npm//typescript",
|
||||
@ -21,6 +23,7 @@ ts_library(
|
||||
|
||||
jasmine_node_test(
|
||||
name = "ngtsc",
|
||||
timeout = "long",
|
||||
bootstrap = ["angular/tools/testing/init_node_no_angular_spec.js"],
|
||||
data = [
|
||||
"//packages/compiler-cli/test/ngtsc/fake_core:npm_package",
|
||||
|
@ -5,25 +5,29 @@
|
||||
* 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 {AbsoluteFsPath, resolve} from '@angular/compiler-cli/src/ngtsc/file_system';
|
||||
import {runInEachFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing';
|
||||
import {AbsoluteSourceSpan, IdentifierKind} from '@angular/compiler-cli/src/ngtsc/indexer';
|
||||
import {ParseSourceFile} from '@angular/compiler/src/compiler';
|
||||
import * as path from 'path';
|
||||
|
||||
import {NgtscTestEnvironment} from './env';
|
||||
|
||||
describe('ngtsc component indexing', () => {
|
||||
let env !: NgtscTestEnvironment;
|
||||
runInEachFileSystem(() => {
|
||||
describe('ngtsc component indexing', () => {
|
||||
let env !: NgtscTestEnvironment;
|
||||
let testSourceFile: AbsoluteFsPath;
|
||||
let testTemplateFile: AbsoluteFsPath;
|
||||
|
||||
function testPath(testFile: string): string { return path.posix.join(env.basePath, testFile); }
|
||||
beforeEach(() => {
|
||||
env = NgtscTestEnvironment.setup();
|
||||
env.tsconfig();
|
||||
testSourceFile = resolve(env.basePath, 'test.ts');
|
||||
testTemplateFile = resolve(env.basePath, 'test.html');
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
env = NgtscTestEnvironment.setup();
|
||||
env.tsconfig();
|
||||
});
|
||||
|
||||
describe('indexing metadata', () => {
|
||||
it('should generate component metadata', () => {
|
||||
const componentContent = `
|
||||
describe('indexing metadata', () => {
|
||||
it('should generate component metadata', () => {
|
||||
const componentContent = `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
@ -32,22 +36,22 @@ describe('ngtsc component indexing', () => {
|
||||
})
|
||||
export class TestCmp {}
|
||||
`;
|
||||
env.write('test.ts', componentContent);
|
||||
const indexed = env.driveIndexer();
|
||||
expect(indexed.size).toBe(1);
|
||||
env.write(testSourceFile, componentContent);
|
||||
const indexed = env.driveIndexer();
|
||||
expect(indexed.size).toBe(1);
|
||||
|
||||
const [[decl, indexedComp]] = Array.from(indexed.entries());
|
||||
const [[decl, indexedComp]] = Array.from(indexed.entries());
|
||||
|
||||
expect(decl.getText()).toContain('export class TestCmp {}');
|
||||
expect(indexedComp).toEqual(jasmine.objectContaining({
|
||||
name: 'TestCmp',
|
||||
selector: 'test-cmp',
|
||||
file: new ParseSourceFile(componentContent, testPath('test.ts')),
|
||||
}));
|
||||
});
|
||||
expect(decl.getText()).toContain('export class TestCmp {}');
|
||||
expect(indexedComp).toEqual(jasmine.objectContaining({
|
||||
name: 'TestCmp',
|
||||
selector: 'test-cmp',
|
||||
file: new ParseSourceFile(componentContent, testSourceFile),
|
||||
}));
|
||||
});
|
||||
|
||||
it('should index inline templates', () => {
|
||||
const componentContent = `
|
||||
it('should index inline templates', () => {
|
||||
const componentContent = `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
@ -56,25 +60,25 @@ describe('ngtsc component indexing', () => {
|
||||
})
|
||||
export class TestCmp { foo = 0; }
|
||||
`;
|
||||
env.write('test.ts', componentContent);
|
||||
const indexed = env.driveIndexer();
|
||||
const [[_, indexedComp]] = Array.from(indexed.entries());
|
||||
const template = indexedComp.template;
|
||||
env.write(testSourceFile, componentContent);
|
||||
const indexed = env.driveIndexer();
|
||||
const [[_, indexedComp]] = Array.from(indexed.entries());
|
||||
const template = indexedComp.template;
|
||||
|
||||
expect(template).toEqual({
|
||||
identifiers: new Set([{
|
||||
name: 'foo',
|
||||
kind: IdentifierKind.Property,
|
||||
span: new AbsoluteSourceSpan(127, 130),
|
||||
}]),
|
||||
usedComponents: new Set(),
|
||||
isInline: true,
|
||||
file: new ParseSourceFile(componentContent, testPath('test.ts')),
|
||||
expect(template).toEqual({
|
||||
identifiers: new Set([{
|
||||
name: 'foo',
|
||||
kind: IdentifierKind.Property,
|
||||
span: new AbsoluteSourceSpan(127, 130),
|
||||
}]),
|
||||
usedComponents: new Set(),
|
||||
isInline: true,
|
||||
file: new ParseSourceFile(componentContent, testSourceFile),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should index external templates', () => {
|
||||
env.write('test.ts', `
|
||||
it('should index external templates', () => {
|
||||
env.write(testSourceFile, `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
@ -83,29 +87,29 @@ describe('ngtsc component indexing', () => {
|
||||
})
|
||||
export class TestCmp { foo = 0; }
|
||||
`);
|
||||
env.write('test.html', '{{foo}}');
|
||||
const indexed = env.driveIndexer();
|
||||
const [[_, indexedComp]] = Array.from(indexed.entries());
|
||||
const template = indexedComp.template;
|
||||
env.write(testTemplateFile, '{{foo}}');
|
||||
const indexed = env.driveIndexer();
|
||||
const [[_, indexedComp]] = Array.from(indexed.entries());
|
||||
const template = indexedComp.template;
|
||||
|
||||
expect(template).toEqual({
|
||||
identifiers: new Set([{
|
||||
name: 'foo',
|
||||
kind: IdentifierKind.Property,
|
||||
span: new AbsoluteSourceSpan(2, 5),
|
||||
}]),
|
||||
usedComponents: new Set(),
|
||||
isInline: false,
|
||||
file: new ParseSourceFile('{{foo}}', testPath('test.html')),
|
||||
});
|
||||
});
|
||||
|
||||
it('should index templates compiled without preserving whitespace', () => {
|
||||
env.tsconfig({
|
||||
preserveWhitespaces: false,
|
||||
expect(template).toEqual({
|
||||
identifiers: new Set([{
|
||||
name: 'foo',
|
||||
kind: IdentifierKind.Property,
|
||||
span: new AbsoluteSourceSpan(2, 5),
|
||||
}]),
|
||||
usedComponents: new Set(),
|
||||
isInline: false,
|
||||
file: new ParseSourceFile('{{foo}}', testTemplateFile),
|
||||
});
|
||||
});
|
||||
|
||||
env.write('test.ts', `
|
||||
it('should index templates compiled without preserving whitespace', () => {
|
||||
env.tsconfig({
|
||||
preserveWhitespaces: false,
|
||||
});
|
||||
|
||||
env.write(testSourceFile, `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
@ -114,25 +118,25 @@ describe('ngtsc component indexing', () => {
|
||||
})
|
||||
export class TestCmp { foo = 0; }
|
||||
`);
|
||||
env.write('test.html', '<div> \n {{foo}}</div>');
|
||||
const indexed = env.driveIndexer();
|
||||
const [[_, indexedComp]] = Array.from(indexed.entries());
|
||||
const template = indexedComp.template;
|
||||
env.write(testTemplateFile, '<div> \n {{foo}}</div>');
|
||||
const indexed = env.driveIndexer();
|
||||
const [[_, indexedComp]] = Array.from(indexed.entries());
|
||||
const template = indexedComp.template;
|
||||
|
||||
expect(template).toEqual({
|
||||
identifiers: new Set([{
|
||||
name: 'foo',
|
||||
kind: IdentifierKind.Property,
|
||||
span: new AbsoluteSourceSpan(12, 15),
|
||||
}]),
|
||||
usedComponents: new Set(),
|
||||
isInline: false,
|
||||
file: new ParseSourceFile('<div> \n {{foo}}</div>', testPath('test.html')),
|
||||
expect(template).toEqual({
|
||||
identifiers: new Set([{
|
||||
name: 'foo',
|
||||
kind: IdentifierKind.Property,
|
||||
span: new AbsoluteSourceSpan(12, 15),
|
||||
}]),
|
||||
usedComponents: new Set(),
|
||||
isInline: false,
|
||||
file: new ParseSourceFile('<div> \n {{foo}}</div>', testTemplateFile),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should generated information about used components', () => {
|
||||
env.write('test.ts', `
|
||||
it('should generate information about used components', () => {
|
||||
env.write(testSourceFile, `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
@ -141,8 +145,8 @@ describe('ngtsc component indexing', () => {
|
||||
})
|
||||
export class TestCmp {}
|
||||
`);
|
||||
env.write('test.html', '<div></div>');
|
||||
env.write('test_import.ts', `
|
||||
env.write(testTemplateFile, '<div></div>');
|
||||
env.write('test_import.ts', `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
import {TestCmp} from './test';
|
||||
|
||||
@ -160,21 +164,22 @@ describe('ngtsc component indexing', () => {
|
||||
})
|
||||
export class TestModule {}
|
||||
`);
|
||||
env.write('test_import.html', '<test-cmp></test-cmp>');
|
||||
const indexed = env.driveIndexer();
|
||||
expect(indexed.size).toBe(2);
|
||||
env.write('test_import.html', '<test-cmp></test-cmp>');
|
||||
const indexed = env.driveIndexer();
|
||||
expect(indexed.size).toBe(2);
|
||||
|
||||
const indexedComps = Array.from(indexed.values());
|
||||
const testComp = indexedComps.find(comp => comp.name === 'TestCmp');
|
||||
const testImportComp = indexedComps.find(cmp => cmp.name === 'TestImportCmp');
|
||||
expect(testComp).toBeDefined();
|
||||
expect(testImportComp).toBeDefined();
|
||||
const indexedComps = Array.from(indexed.values());
|
||||
const testComp = indexedComps.find(comp => comp.name === 'TestCmp');
|
||||
const testImportComp = indexedComps.find(cmp => cmp.name === 'TestImportCmp');
|
||||
expect(testComp).toBeDefined();
|
||||
expect(testImportComp).toBeDefined();
|
||||
|
||||
expect(testComp !.template.usedComponents.size).toBe(0);
|
||||
expect(testImportComp !.template.usedComponents.size).toBe(1);
|
||||
expect(testComp !.template.usedComponents.size).toBe(0);
|
||||
expect(testImportComp !.template.usedComponents.size).toBe(1);
|
||||
|
||||
const [usedComp] = Array.from(testImportComp !.template.usedComponents);
|
||||
expect(indexed.get(usedComp)).toEqual(testComp);
|
||||
const [usedComp] = Array.from(testImportComp !.template.usedComponents);
|
||||
expect(indexed.get(usedComp)).toEqual(testComp);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -7,32 +7,17 @@
|
||||
*/
|
||||
|
||||
import {CustomTransformers, Program} from '@angular/compiler-cli';
|
||||
import {IndexedComponent} from '@angular/compiler-cli/src/ngtsc/indexer';
|
||||
import {NgtscProgram} from '@angular/compiler-cli/src/ngtsc/program';
|
||||
import {setWrapHostForTest} from '@angular/compiler-cli/src/transformers/compiler_host';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {createCompilerHost, createProgram} from '../../ngtools2';
|
||||
import {main, mainDiagnosticsForTest, readNgcCommandLineAndConfiguration} from '../../src/main';
|
||||
import {AbsoluteFsPath, FileSystem, NgtscCompilerHost, absoluteFrom, getFileSystem} from '../../src/ngtsc/file_system';
|
||||
import {Folder, MockFileSystem} from '../../src/ngtsc/file_system/testing';
|
||||
import {IndexedComponent} from '../../src/ngtsc/indexer';
|
||||
import {NgtscProgram} from '../../src/ngtsc/program';
|
||||
import {LazyRoute} from '../../src/ngtsc/routing';
|
||||
import {resolveNpmTreeArtifact} from '../runfile_helpers';
|
||||
import {TestSupport, setup} from '../test_support';
|
||||
import {setWrapHostForTest} from '../../src/transformers/compiler_host';
|
||||
|
||||
function setupFakeCore(support: TestSupport): void {
|
||||
if (!process.env.TEST_SRCDIR) {
|
||||
throw new Error('`setupFakeCore` must be run within a Bazel test');
|
||||
}
|
||||
|
||||
const fakeNpmPackageDir =
|
||||
resolveNpmTreeArtifact('angular/packages/compiler-cli/test/ngtsc/fake_core/npm_package');
|
||||
|
||||
const nodeModulesPath = path.join(support.basePath, 'node_modules');
|
||||
const angularCoreDirectory = path.join(nodeModulesPath, '@angular/core');
|
||||
|
||||
fs.symlinkSync(fakeNpmPackageDir, angularCoreDirectory, 'junction');
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages a temporary testing directory structure and environment for testing ngtsc by feeding it
|
||||
@ -43,24 +28,24 @@ export class NgtscTestEnvironment {
|
||||
private oldProgram: Program|null = null;
|
||||
private changedResources: Set<string>|undefined = undefined;
|
||||
|
||||
private constructor(private support: TestSupport, readonly outDir: string) {}
|
||||
|
||||
get basePath(): string { return this.support.basePath; }
|
||||
private constructor(
|
||||
private fs: FileSystem, readonly outDir: AbsoluteFsPath, readonly basePath: AbsoluteFsPath) {}
|
||||
|
||||
/**
|
||||
* Set up a new testing environment.
|
||||
*/
|
||||
static setup(): NgtscTestEnvironment {
|
||||
const support = setup();
|
||||
const outDir = path.posix.join(support.basePath, 'built');
|
||||
process.chdir(support.basePath);
|
||||
static setup(files?: Folder): NgtscTestEnvironment {
|
||||
const fs = getFileSystem();
|
||||
if (files !== undefined && fs instanceof MockFileSystem) {
|
||||
fs.init(files);
|
||||
}
|
||||
|
||||
setupFakeCore(support);
|
||||
setWrapHostForTest(null);
|
||||
const host = new AugmentedCompilerHost(fs);
|
||||
setWrapHostForTest(makeWrapHost(host));
|
||||
|
||||
const env = new NgtscTestEnvironment(support, outDir);
|
||||
const env = new NgtscTestEnvironment(fs, fs.resolve('/built'), absoluteFrom('/'));
|
||||
|
||||
env.write('tsconfig-base.json', `{
|
||||
env.write(absoluteFrom('/tsconfig-base.json'), `{
|
||||
"compilerOptions": {
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
@ -91,26 +76,26 @@ export class NgtscTestEnvironment {
|
||||
}
|
||||
|
||||
assertExists(fileName: string) {
|
||||
if (!fs.existsSync(path.resolve(this.outDir, fileName))) {
|
||||
if (!this.fs.exists(this.fs.resolve(this.outDir, fileName))) {
|
||||
throw new Error(`Expected ${fileName} to be emitted (outDir: ${this.outDir})`);
|
||||
}
|
||||
}
|
||||
|
||||
assertDoesNotExist(fileName: string) {
|
||||
if (fs.existsSync(path.resolve(this.outDir, fileName))) {
|
||||
if (this.fs.exists(this.fs.resolve(this.outDir, fileName))) {
|
||||
throw new Error(`Did not expect ${fileName} to be emitted (outDir: ${this.outDir})`);
|
||||
}
|
||||
}
|
||||
|
||||
getContents(fileName: string): string {
|
||||
this.assertExists(fileName);
|
||||
const modulePath = path.resolve(this.outDir, fileName);
|
||||
return fs.readFileSync(modulePath, 'utf8');
|
||||
const modulePath = this.fs.resolve(this.outDir, fileName);
|
||||
return this.fs.readFile(modulePath);
|
||||
}
|
||||
|
||||
enableMultipleCompilations(): void {
|
||||
this.changedResources = new Set();
|
||||
this.multiCompileHostExt = new MultiCompileHostExt();
|
||||
this.multiCompileHostExt = new MultiCompileHostExt(this.fs);
|
||||
setWrapHostForTest(makeWrapHost(this.multiCompileHostExt));
|
||||
}
|
||||
|
||||
@ -126,31 +111,31 @@ export class NgtscTestEnvironment {
|
||||
if (this.multiCompileHostExt === null) {
|
||||
throw new Error(`Not tracking written files - call enableMultipleCompilations()`);
|
||||
}
|
||||
const outDir = path.posix.join(this.support.basePath, 'built');
|
||||
const writtenFiles = new Set<string>();
|
||||
this.multiCompileHostExt.getFilesWrittenSinceLastFlush().forEach(rawFile => {
|
||||
if (rawFile.startsWith(outDir)) {
|
||||
writtenFiles.add(rawFile.substr(outDir.length));
|
||||
if (rawFile.startsWith(this.outDir)) {
|
||||
writtenFiles.add(rawFile.substr(this.outDir.length));
|
||||
}
|
||||
});
|
||||
return writtenFiles;
|
||||
}
|
||||
|
||||
write(fileName: string, content: string) {
|
||||
const absFilePath = this.fs.resolve(this.basePath, fileName);
|
||||
if (this.multiCompileHostExt !== null) {
|
||||
const absFilePath = path.resolve(this.support.basePath, fileName).replace(/\\/g, '/');
|
||||
this.multiCompileHostExt.invalidate(absFilePath);
|
||||
this.changedResources !.add(absFilePath);
|
||||
}
|
||||
this.support.write(fileName, content);
|
||||
this.fs.ensureDir(this.fs.dirname(absFilePath));
|
||||
this.fs.writeFile(absFilePath, content);
|
||||
}
|
||||
|
||||
invalidateCachedFile(fileName: string): void {
|
||||
const absFilePath = this.fs.resolve(this.basePath, fileName);
|
||||
if (this.multiCompileHostExt === null) {
|
||||
throw new Error(`Not caching files - call enableMultipleCompilations()`);
|
||||
}
|
||||
const fullFile = path.posix.join(this.support.basePath, fileName);
|
||||
this.multiCompileHostExt.invalidate(fullFile);
|
||||
this.multiCompileHostExt.invalidate(absFilePath);
|
||||
}
|
||||
|
||||
tsconfig(extraOpts: {[key: string]: string | boolean} = {}, extraRootDirs?: string[]): void {
|
||||
@ -166,7 +151,7 @@ export class NgtscTestEnvironment {
|
||||
this.write('tsconfig.json', JSON.stringify(tsconfig, null, 2));
|
||||
|
||||
if (extraOpts['_useHostForImportGeneration'] === true) {
|
||||
setWrapHostForTest(makeWrapHost(new FileNameToModuleNameHost()));
|
||||
setWrapHostForTest(makeWrapHost(new FileNameToModuleNameHost(this.fs)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -214,18 +199,15 @@ export class NgtscTestEnvironment {
|
||||
}
|
||||
}
|
||||
|
||||
class AugmentedCompilerHost {
|
||||
class AugmentedCompilerHost extends NgtscCompilerHost {
|
||||
delegate !: ts.CompilerHost;
|
||||
}
|
||||
|
||||
class FileNameToModuleNameHost extends AugmentedCompilerHost {
|
||||
// CWD must be initialized lazily as `this.delegate` is not set until later.
|
||||
private cwd: string|null = null;
|
||||
fileNameToModuleName(importedFilePath: string): string {
|
||||
if (this.cwd === null) {
|
||||
this.cwd = this.delegate.getCurrentDirectory();
|
||||
}
|
||||
return 'root' + importedFilePath.substr(this.cwd.length).replace(/(\.d)?.ts$/, '');
|
||||
const relativeFilePath = this.fs.relative(this.fs.pwd(), this.fs.resolve(importedFilePath));
|
||||
const rootedPath = this.fs.join('root', relativeFilePath);
|
||||
return rootedPath.replace(/(\.d)?.ts$/, '');
|
||||
}
|
||||
}
|
||||
|
||||
@ -239,8 +221,7 @@ class MultiCompileHostExt extends AugmentedCompilerHost implements Partial<ts.Co
|
||||
if (this.cache.has(fileName)) {
|
||||
return this.cache.get(fileName) !;
|
||||
}
|
||||
const sf =
|
||||
this.delegate.getSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile);
|
||||
const sf = super.getSourceFile(fileName, languageVersion);
|
||||
if (sf !== undefined) {
|
||||
this.cache.set(sf.fileName, sf);
|
||||
}
|
||||
@ -253,7 +234,7 @@ class MultiCompileHostExt extends AugmentedCompilerHost implements Partial<ts.Co
|
||||
fileName: string, data: string, writeByteOrderMark: boolean,
|
||||
onError: ((message: string) => void)|undefined,
|
||||
sourceFiles?: ReadonlyArray<ts.SourceFile>): void {
|
||||
this.delegate.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles);
|
||||
super.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles);
|
||||
this.writtenFiles.add(fileName);
|
||||
}
|
||||
|
||||
|
@ -6,26 +6,31 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AbsoluteFsPath} from '@angular/compiler-cli/src/ngtsc/path';
|
||||
import {runInEachFileSystem} from '../../src/ngtsc/file_system/testing';
|
||||
import {loadStandardTestFiles} from '../helpers/src/mock_file_loading';
|
||||
|
||||
import {NgtscTestEnvironment} from './env';
|
||||
|
||||
describe('ngtsc incremental compilation', () => {
|
||||
let env !: NgtscTestEnvironment;
|
||||
beforeEach(() => {
|
||||
env = NgtscTestEnvironment.setup();
|
||||
env.enableMultipleCompilations();
|
||||
env.tsconfig();
|
||||
});
|
||||
const testFiles = loadStandardTestFiles();
|
||||
|
||||
it('should skip unchanged services', () => {
|
||||
env.write('service.ts', `
|
||||
runInEachFileSystem(() => {
|
||||
describe('ngtsc incremental compilation', () => {
|
||||
let env !: NgtscTestEnvironment;
|
||||
|
||||
beforeEach(() => {
|
||||
env = NgtscTestEnvironment.setup(testFiles);
|
||||
env.enableMultipleCompilations();
|
||||
env.tsconfig();
|
||||
});
|
||||
|
||||
it('should skip unchanged services', () => {
|
||||
env.write('service.ts', `
|
||||
import {Injectable} from '@angular/core';
|
||||
|
||||
@Injectable()
|
||||
export class Service {}
|
||||
`);
|
||||
env.write('test.ts', `
|
||||
env.write('test.ts', `
|
||||
import {Component} from '@angular/core';
|
||||
import {Service} from './service';
|
||||
|
||||
@ -34,186 +39,186 @@ describe('ngtsc incremental compilation', () => {
|
||||
constructor(service: Service) {}
|
||||
}
|
||||
`);
|
||||
env.driveMain();
|
||||
env.flushWrittenFileTracking();
|
||||
env.driveMain();
|
||||
env.flushWrittenFileTracking();
|
||||
|
||||
// Pretend a change was made to test.ts.
|
||||
env.invalidateCachedFile('test.ts');
|
||||
env.driveMain();
|
||||
const written = env.getFilesWrittenSinceLastFlush();
|
||||
// Pretend a change was made to test.ts.
|
||||
env.invalidateCachedFile('test.ts');
|
||||
env.driveMain();
|
||||
const written = env.getFilesWrittenSinceLastFlush();
|
||||
|
||||
// The changed file should be recompiled, but not the service.
|
||||
expect(written).toContain('/test.js');
|
||||
expect(written).not.toContain('/service.js');
|
||||
});
|
||||
// The changed file should be recompiled, but not the service.
|
||||
expect(written).toContain('/test.js');
|
||||
expect(written).not.toContain('/service.js');
|
||||
});
|
||||
|
||||
it('should rebuild components that have changed', () => {
|
||||
env.write('component1.ts', `
|
||||
it('should rebuild components that have changed', () => {
|
||||
env.write('component1.ts', `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({selector: 'cmp', template: 'cmp'})
|
||||
export class Cmp1 {}
|
||||
`);
|
||||
env.write('component2.ts', `
|
||||
env.write('component2.ts', `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({selector: 'cmp2', template: 'cmp'})
|
||||
export class Cmp2 {}
|
||||
`);
|
||||
env.driveMain();
|
||||
env.driveMain();
|
||||
|
||||
// Pretend a change was made to Cmp1
|
||||
env.flushWrittenFileTracking();
|
||||
env.invalidateCachedFile('component1.ts');
|
||||
env.driveMain();
|
||||
const written = env.getFilesWrittenSinceLastFlush();
|
||||
expect(written).toContain('/component1.js');
|
||||
expect(written).not.toContain('/component2.js');
|
||||
});
|
||||
// Pretend a change was made to Cmp1
|
||||
env.flushWrittenFileTracking();
|
||||
env.invalidateCachedFile('component1.ts');
|
||||
env.driveMain();
|
||||
const written = env.getFilesWrittenSinceLastFlush();
|
||||
expect(written).toContain('/component1.js');
|
||||
expect(written).not.toContain('/component2.js');
|
||||
});
|
||||
|
||||
it('should rebuild components whose templates have changed', () => {
|
||||
env.write('component1.ts', `
|
||||
it('should rebuild components whose templates have changed', () => {
|
||||
env.write('component1.ts', `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({selector: 'cmp', templateUrl: './component1.template.html'})
|
||||
export class Cmp1 {}
|
||||
`);
|
||||
env.write('component1.template.html', 'cmp1');
|
||||
env.write('component2.ts', `
|
||||
env.write('component1.template.html', 'cmp1');
|
||||
env.write('component2.ts', `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({selector: 'cmp2', templateUrl: './component2.template.html'})
|
||||
export class Cmp2 {}
|
||||
`);
|
||||
env.write('component2.template.html', 'cmp2');
|
||||
env.write('component2.template.html', 'cmp2');
|
||||
|
||||
env.driveMain();
|
||||
env.driveMain();
|
||||
|
||||
// Make a change to Cmp1 template
|
||||
env.flushWrittenFileTracking();
|
||||
env.write('component1.template.html', 'changed');
|
||||
env.driveMain();
|
||||
const written = env.getFilesWrittenSinceLastFlush();
|
||||
expect(written).toContain('/component1.js');
|
||||
expect(written).not.toContain('/component2.js');
|
||||
});
|
||||
// Make a change to Cmp1 template
|
||||
env.flushWrittenFileTracking();
|
||||
env.write('component1.template.html', 'changed');
|
||||
env.driveMain();
|
||||
const written = env.getFilesWrittenSinceLastFlush();
|
||||
expect(written).toContain('/component1.js');
|
||||
expect(written).not.toContain('/component2.js');
|
||||
});
|
||||
|
||||
it('should rebuild components whose partial-evaluation dependencies have changed', () => {
|
||||
env.write('component1.ts', `
|
||||
it('should rebuild components whose partial-evaluation dependencies have changed', () => {
|
||||
env.write('component1.ts', `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({selector: 'cmp', template: 'cmp'})
|
||||
export class Cmp1 {}
|
||||
`);
|
||||
env.write('component2.ts', `
|
||||
env.write('component2.ts', `
|
||||
import {Component} from '@angular/core';
|
||||
import {SELECTOR} from './constants';
|
||||
|
||||
@Component({selector: SELECTOR, template: 'cmp'})
|
||||
export class Cmp2 {}
|
||||
`);
|
||||
env.write('constants.ts', `
|
||||
env.write('constants.ts', `
|
||||
export const SELECTOR = 'cmp';
|
||||
`);
|
||||
env.driveMain();
|
||||
env.driveMain();
|
||||
|
||||
// Pretend a change was made to SELECTOR
|
||||
env.flushWrittenFileTracking();
|
||||
env.invalidateCachedFile('constants.ts');
|
||||
env.driveMain();
|
||||
const written = env.getFilesWrittenSinceLastFlush();
|
||||
expect(written).toContain('/constants.js');
|
||||
expect(written).not.toContain('/component1.js');
|
||||
expect(written).toContain('/component2.js');
|
||||
// Pretend a change was made to SELECTOR
|
||||
env.flushWrittenFileTracking();
|
||||
env.invalidateCachedFile('constants.ts');
|
||||
env.driveMain();
|
||||
const written = env.getFilesWrittenSinceLastFlush();
|
||||
expect(written).toContain('/constants.js');
|
||||
expect(written).not.toContain('/component1.js');
|
||||
expect(written).toContain('/component2.js');
|
||||
});
|
||||
|
||||
it('should rebuild components whose imported dependencies have changed', () => {
|
||||
setupFooBarProgram(env);
|
||||
|
||||
// Pretend a change was made to BarDir.
|
||||
env.invalidateCachedFile('bar_directive.ts');
|
||||
env.driveMain();
|
||||
|
||||
let written = env.getFilesWrittenSinceLastFlush();
|
||||
expect(written).toContain('/bar_directive.js');
|
||||
expect(written).toContain('/bar_component.js');
|
||||
expect(written).toContain('/bar_module.js');
|
||||
expect(written).not.toContain('/foo_component.js');
|
||||
expect(written).not.toContain('/foo_pipe.js');
|
||||
expect(written).not.toContain('/foo_module.js');
|
||||
});
|
||||
|
||||
it('should rebuild components where their NgModule declared dependencies have changed', () => {
|
||||
setupFooBarProgram(env);
|
||||
|
||||
// Pretend a change was made to FooPipe.
|
||||
env.invalidateCachedFile('foo_pipe.ts');
|
||||
env.driveMain();
|
||||
const written = env.getFilesWrittenSinceLastFlush();
|
||||
expect(written).not.toContain('/bar_directive.js');
|
||||
expect(written).not.toContain('/bar_component.js');
|
||||
expect(written).not.toContain('/bar_module.js');
|
||||
expect(written).toContain('/foo_component.js');
|
||||
expect(written).toContain('/foo_pipe.js');
|
||||
expect(written).toContain('/foo_module.js');
|
||||
});
|
||||
|
||||
it('should rebuild components where their NgModule has changed', () => {
|
||||
setupFooBarProgram(env);
|
||||
|
||||
// Pretend a change was made to FooPipe.
|
||||
env.invalidateCachedFile('foo_module.ts');
|
||||
env.driveMain();
|
||||
const written = env.getFilesWrittenSinceLastFlush();
|
||||
expect(written).not.toContain('/bar_directive.js');
|
||||
expect(written).not.toContain('/bar_component.js');
|
||||
expect(written).not.toContain('/bar_module.js');
|
||||
expect(written).toContain('/foo_component.js');
|
||||
expect(written).toContain('/foo_pipe.js');
|
||||
expect(written).toContain('/foo_module.js');
|
||||
});
|
||||
|
||||
it('should rebuild everything if a typings file changes', () => {
|
||||
setupFooBarProgram(env);
|
||||
|
||||
// Pretend a change was made to a typings file.
|
||||
env.invalidateCachedFile('foo_selector.d.ts');
|
||||
env.driveMain();
|
||||
const written = env.getFilesWrittenSinceLastFlush();
|
||||
expect(written).toContain('/bar_directive.js');
|
||||
expect(written).toContain('/bar_component.js');
|
||||
expect(written).toContain('/bar_module.js');
|
||||
expect(written).toContain('/foo_component.js');
|
||||
expect(written).toContain('/foo_pipe.js');
|
||||
expect(written).toContain('/foo_module.js');
|
||||
});
|
||||
|
||||
it('should compile incrementally with template type-checking turned on', () => {
|
||||
env.tsconfig({ivyTemplateTypeCheck: true});
|
||||
env.write('main.ts', 'export class Foo {}');
|
||||
env.driveMain();
|
||||
env.invalidateCachedFile('main.ts');
|
||||
env.driveMain();
|
||||
// If program reuse were configured incorrectly (as was responsible for
|
||||
// https://github.com/angular/angular/issues/30079), this would have crashed.
|
||||
});
|
||||
});
|
||||
|
||||
it('should rebuild components whose imported dependencies have changed', () => {
|
||||
setupFooBarProgram(env);
|
||||
|
||||
// Pretend a change was made to BarDir.
|
||||
env.invalidateCachedFile('bar_directive.ts');
|
||||
env.driveMain();
|
||||
|
||||
let written = env.getFilesWrittenSinceLastFlush();
|
||||
expect(written).toContain('/bar_directive.js');
|
||||
expect(written).toContain('/bar_component.js');
|
||||
expect(written).toContain('/bar_module.js');
|
||||
expect(written).not.toContain('/foo_component.js');
|
||||
expect(written).not.toContain('/foo_pipe.js');
|
||||
expect(written).not.toContain('/foo_module.js');
|
||||
});
|
||||
|
||||
it('should rebuild components where their NgModule declared dependencies have changed', () => {
|
||||
setupFooBarProgram(env);
|
||||
|
||||
// Pretend a change was made to FooPipe.
|
||||
env.invalidateCachedFile('foo_pipe.ts');
|
||||
env.driveMain();
|
||||
const written = env.getFilesWrittenSinceLastFlush();
|
||||
expect(written).not.toContain('/bar_directive.js');
|
||||
expect(written).not.toContain('/bar_component.js');
|
||||
expect(written).not.toContain('/bar_module.js');
|
||||
expect(written).toContain('/foo_component.js');
|
||||
expect(written).toContain('/foo_pipe.js');
|
||||
expect(written).toContain('/foo_module.js');
|
||||
});
|
||||
|
||||
it('should rebuild components where their NgModule has changed', () => {
|
||||
setupFooBarProgram(env);
|
||||
|
||||
// Pretend a change was made to FooPipe.
|
||||
env.invalidateCachedFile('foo_module.ts');
|
||||
env.driveMain();
|
||||
const written = env.getFilesWrittenSinceLastFlush();
|
||||
expect(written).not.toContain('/bar_directive.js');
|
||||
expect(written).not.toContain('/bar_component.js');
|
||||
expect(written).not.toContain('/bar_module.js');
|
||||
expect(written).toContain('/foo_component.js');
|
||||
expect(written).toContain('/foo_pipe.js');
|
||||
expect(written).toContain('/foo_module.js');
|
||||
});
|
||||
|
||||
it('should rebuild everything if a typings file changes', () => {
|
||||
setupFooBarProgram(env);
|
||||
|
||||
// Pretend a change was made to a typings file.
|
||||
env.invalidateCachedFile('foo_selector.d.ts');
|
||||
env.driveMain();
|
||||
const written = env.getFilesWrittenSinceLastFlush();
|
||||
expect(written).toContain('/bar_directive.js');
|
||||
expect(written).toContain('/bar_component.js');
|
||||
expect(written).toContain('/bar_module.js');
|
||||
expect(written).toContain('/foo_component.js');
|
||||
expect(written).toContain('/foo_pipe.js');
|
||||
expect(written).toContain('/foo_module.js');
|
||||
});
|
||||
|
||||
it('should compile incrementally with template type-checking turned on', () => {
|
||||
env.tsconfig({ivyTemplateTypeCheck: true});
|
||||
env.write('main.ts', 'export class Foo {}');
|
||||
env.driveMain();
|
||||
env.invalidateCachedFile('main.ts');
|
||||
env.driveMain();
|
||||
// If program reuse were configured incorrectly (as was responsible for
|
||||
// https://github.com/angular/angular/issues/30079), this would have crashed.
|
||||
});
|
||||
});
|
||||
|
||||
function setupFooBarProgram(env: NgtscTestEnvironment) {
|
||||
env.write('foo_component.ts', `
|
||||
function setupFooBarProgram(env: NgtscTestEnvironment) {
|
||||
env.write('foo_component.ts', `
|
||||
import {Component} from '@angular/core';
|
||||
import {fooSelector} from './foo_selector';
|
||||
|
||||
@Component({selector: fooSelector, template: 'foo'})
|
||||
export class FooCmp {}
|
||||
`);
|
||||
env.write('foo_pipe.ts', `
|
||||
env.write('foo_pipe.ts', `
|
||||
import {Pipe} from '@angular/core';
|
||||
|
||||
@Pipe({name: 'foo'})
|
||||
export class FooPipe {}
|
||||
`);
|
||||
env.write('foo_module.ts', `
|
||||
env.write('foo_module.ts', `
|
||||
import {NgModule} from '@angular/core';
|
||||
import {FooCmp} from './foo_component';
|
||||
import {FooPipe} from './foo_pipe';
|
||||
@ -224,19 +229,19 @@ function setupFooBarProgram(env: NgtscTestEnvironment) {
|
||||
})
|
||||
export class FooModule {}
|
||||
`);
|
||||
env.write('bar_component.ts', `
|
||||
env.write('bar_component.ts', `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({selector: 'bar', template: 'bar'})
|
||||
export class BarCmp {}
|
||||
`);
|
||||
env.write('bar_directive.ts', `
|
||||
env.write('bar_directive.ts', `
|
||||
import {Directive} from '@angular/core';
|
||||
|
||||
@Directive({selector: '[bar]'})
|
||||
export class BarDir {}
|
||||
`);
|
||||
env.write('bar_module.ts', `
|
||||
env.write('bar_module.ts', `
|
||||
import {NgModule} from '@angular/core';
|
||||
import {BarCmp} from './bar_component';
|
||||
import {BarDir} from './bar_directive';
|
||||
@ -246,9 +251,10 @@ function setupFooBarProgram(env: NgtscTestEnvironment) {
|
||||
})
|
||||
export class BarModule {}
|
||||
`);
|
||||
env.write('foo_selector.d.ts', `
|
||||
env.write('foo_selector.d.ts', `
|
||||
export const fooSelector = 'foo';
|
||||
`);
|
||||
env.driveMain();
|
||||
env.flushWrittenFileTracking();
|
||||
}
|
||||
env.driveMain();
|
||||
env.flushWrittenFileTracking();
|
||||
}
|
||||
});
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -5,25 +5,29 @@
|
||||
* 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 * as ts from 'typescript';
|
||||
|
||||
import {ErrorCode, ngErrorCode} from '../../src/ngtsc/diagnostics';
|
||||
import {runInEachFileSystem} from '../../src/ngtsc/file_system/testing';
|
||||
import {loadStandardTestFiles} from '../helpers/src/mock_file_loading';
|
||||
|
||||
import {NgtscTestEnvironment} from './env';
|
||||
|
||||
describe('ngtsc module scopes', () => {
|
||||
let env !: NgtscTestEnvironment;
|
||||
beforeEach(() => {
|
||||
env = NgtscTestEnvironment.setup();
|
||||
env.tsconfig();
|
||||
});
|
||||
const testFiles = loadStandardTestFiles();
|
||||
|
||||
describe('diagnostics', () => {
|
||||
describe('imports', () => {
|
||||
it('should emit imports in a pure function call', () => {
|
||||
env.tsconfig();
|
||||
env.write('test.ts', `
|
||||
runInEachFileSystem(() => {
|
||||
describe('ngtsc module scopes', () => {
|
||||
let env !: NgtscTestEnvironment;
|
||||
|
||||
beforeEach(() => {
|
||||
env = NgtscTestEnvironment.setup(testFiles);
|
||||
env.tsconfig();
|
||||
});
|
||||
|
||||
describe('diagnostics', () => {
|
||||
describe('imports', () => {
|
||||
it('should emit imports in a pure function call', () => {
|
||||
env.write('test.ts', `
|
||||
import {NgModule} from '@angular/core';
|
||||
|
||||
@NgModule({})
|
||||
@ -33,22 +37,22 @@ describe('ngtsc module scopes', () => {
|
||||
export class TestModule {}
|
||||
`);
|
||||
|
||||
env.driveMain();
|
||||
env.driveMain();
|
||||
|
||||
const jsContents = env.getContents('test.js');
|
||||
expect(jsContents).toContain('i0.ɵɵdefineNgModule({ type: TestModule });');
|
||||
expect(jsContents)
|
||||
.toContain(
|
||||
'/*@__PURE__*/ i0.ɵɵsetNgModuleScope(TestModule, { imports: [OtherModule] });');
|
||||
const jsContents = env.getContents('test.js');
|
||||
expect(jsContents).toContain('i0.ɵɵdefineNgModule({ type: TestModule });');
|
||||
expect(jsContents)
|
||||
.toContain(
|
||||
'/*@__PURE__*/ i0.ɵɵsetNgModuleScope(TestModule, { imports: [OtherModule] });');
|
||||
|
||||
const dtsContents = env.getContents('test.d.ts');
|
||||
expect(dtsContents)
|
||||
.toContain(
|
||||
'static ngModuleDef: i0.ɵɵNgModuleDefWithMeta<TestModule, never, [typeof OtherModule], never>');
|
||||
});
|
||||
const dtsContents = env.getContents('test.d.ts');
|
||||
expect(dtsContents)
|
||||
.toContain(
|
||||
'static ngModuleDef: i0.ɵɵNgModuleDefWithMeta<TestModule, never, [typeof OtherModule], never>');
|
||||
});
|
||||
|
||||
it('should produce an error when an invalid class is imported', () => {
|
||||
env.write('test.ts', `
|
||||
it('should produce an error when an invalid class is imported', () => {
|
||||
env.write('test.ts', `
|
||||
import {NgModule} from '@angular/core';
|
||||
|
||||
class NotAModule {}
|
||||
@ -56,36 +60,35 @@ describe('ngtsc module scopes', () => {
|
||||
@NgModule({imports: [NotAModule]})
|
||||
class IsAModule {}
|
||||
`);
|
||||
const [error] = env.driveDiagnostics();
|
||||
expect(error).not.toBeUndefined();
|
||||
expect(error.messageText).toContain('IsAModule');
|
||||
expect(error.messageText).toContain('NgModule.imports');
|
||||
expect(error.code).toEqual(ngErrorCode(ErrorCode.NGMODULE_INVALID_IMPORT));
|
||||
expect(diagnosticToNode(error, ts.isIdentifier).text).toEqual('NotAModule');
|
||||
});
|
||||
const [error] = env.driveDiagnostics();
|
||||
expect(error).not.toBeUndefined();
|
||||
expect(error.messageText).toContain('IsAModule');
|
||||
expect(error.messageText).toContain('NgModule.imports');
|
||||
expect(error.code).toEqual(ngErrorCode(ErrorCode.NGMODULE_INVALID_IMPORT));
|
||||
expect(diagnosticToNode(error, ts.isIdentifier).text).toEqual('NotAModule');
|
||||
});
|
||||
|
||||
it('should produce an error when a non-class is imported from a .d.ts dependency', () => {
|
||||
env.write('dep.d.ts', `export declare let NotAClass: Function;`);
|
||||
env.write('test.ts', `
|
||||
it('should produce an error when a non-class is imported from a .d.ts dependency', () => {
|
||||
env.write('dep.d.ts', `export declare let NotAClass: Function;`);
|
||||
env.write('test.ts', `
|
||||
import {NgModule} from '@angular/core';
|
||||
import {NotAClass} from './dep';
|
||||
|
||||
@NgModule({imports: [NotAClass]})
|
||||
class IsAModule {}
|
||||
`);
|
||||
const [error] = env.driveDiagnostics();
|
||||
expect(error).not.toBeUndefined();
|
||||
expect(error.messageText).toContain('IsAModule');
|
||||
expect(error.messageText).toContain('NgModule.imports');
|
||||
expect(error.code).toEqual(ngErrorCode(ErrorCode.VALUE_HAS_WRONG_TYPE));
|
||||
expect(diagnosticToNode(error, ts.isIdentifier).text).toEqual('NotAClass');
|
||||
const [error] = env.driveDiagnostics();
|
||||
expect(error).not.toBeUndefined();
|
||||
expect(error.messageText).toContain('IsAModule');
|
||||
expect(error.messageText).toContain('NgModule.imports');
|
||||
expect(error.code).toEqual(ngErrorCode(ErrorCode.VALUE_HAS_WRONG_TYPE));
|
||||
expect(diagnosticToNode(error, ts.isIdentifier).text).toEqual('NotAClass');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('exports', () => {
|
||||
it('should emit exports in a pure function call', () => {
|
||||
env.tsconfig();
|
||||
env.write('test.ts', `
|
||||
describe('exports', () => {
|
||||
it('should emit exports in a pure function call', () => {
|
||||
env.write('test.ts', `
|
||||
import {NgModule} from '@angular/core';
|
||||
|
||||
@NgModule({})
|
||||
@ -95,22 +98,22 @@ describe('ngtsc module scopes', () => {
|
||||
export class TestModule {}
|
||||
`);
|
||||
|
||||
env.driveMain();
|
||||
env.driveMain();
|
||||
|
||||
const jsContents = env.getContents('test.js');
|
||||
expect(jsContents).toContain('i0.ɵɵdefineNgModule({ type: TestModule });');
|
||||
expect(jsContents)
|
||||
.toContain(
|
||||
'/*@__PURE__*/ i0.ɵɵsetNgModuleScope(TestModule, { exports: [OtherModule] });');
|
||||
const jsContents = env.getContents('test.js');
|
||||
expect(jsContents).toContain('i0.ɵɵdefineNgModule({ type: TestModule });');
|
||||
expect(jsContents)
|
||||
.toContain(
|
||||
'/*@__PURE__*/ i0.ɵɵsetNgModuleScope(TestModule, { exports: [OtherModule] });');
|
||||
|
||||
const dtsContents = env.getContents('test.d.ts');
|
||||
expect(dtsContents)
|
||||
.toContain(
|
||||
'static ngModuleDef: i0.ɵɵNgModuleDefWithMeta<TestModule, never, never, [typeof OtherModule]>');
|
||||
});
|
||||
const dtsContents = env.getContents('test.d.ts');
|
||||
expect(dtsContents)
|
||||
.toContain(
|
||||
'static ngModuleDef: i0.ɵɵNgModuleDefWithMeta<TestModule, never, never, [typeof OtherModule]>');
|
||||
});
|
||||
|
||||
it('should produce an error when a non-NgModule class is exported', () => {
|
||||
env.write('test.ts', `
|
||||
it('should produce an error when a non-NgModule class is exported', () => {
|
||||
env.write('test.ts', `
|
||||
import {NgModule} from '@angular/core';
|
||||
|
||||
class NotAModule {}
|
||||
@ -118,16 +121,16 @@ describe('ngtsc module scopes', () => {
|
||||
@NgModule({exports: [NotAModule]})
|
||||
class IsAModule {}
|
||||
`);
|
||||
const [error] = env.driveDiagnostics();
|
||||
expect(error).not.toBeUndefined();
|
||||
expect(error.messageText).toContain('IsAModule');
|
||||
expect(error.messageText).toContain('NgModule.exports');
|
||||
expect(error.code).toEqual(ngErrorCode(ErrorCode.NGMODULE_INVALID_EXPORT));
|
||||
expect(diagnosticToNode(error, ts.isIdentifier).text).toEqual('NotAModule');
|
||||
});
|
||||
const [error] = env.driveDiagnostics();
|
||||
expect(error).not.toBeUndefined();
|
||||
expect(error.messageText).toContain('IsAModule');
|
||||
expect(error.messageText).toContain('NgModule.exports');
|
||||
expect(error.code).toEqual(ngErrorCode(ErrorCode.NGMODULE_INVALID_EXPORT));
|
||||
expect(diagnosticToNode(error, ts.isIdentifier).text).toEqual('NotAModule');
|
||||
});
|
||||
|
||||
it('should produce a transitive error when an invalid NgModule is exported', () => {
|
||||
env.write('test.ts', `
|
||||
it('should produce a transitive error when an invalid NgModule is exported', () => {
|
||||
env.write('test.ts', `
|
||||
import {NgModule} from '@angular/core';
|
||||
|
||||
export class NotAModule {}
|
||||
@ -141,21 +144,21 @@ describe('ngtsc module scopes', () => {
|
||||
class IsAModule {}
|
||||
`);
|
||||
|
||||
// Find the diagnostic referencing InvalidModule, which should have come from IsAModule.
|
||||
const error = env.driveDiagnostics().find(
|
||||
error => diagnosticToNode(error, ts.isIdentifier).text === 'InvalidModule');
|
||||
if (error === undefined) {
|
||||
return fail('Expected to find a diagnostic referencing InvalidModule');
|
||||
}
|
||||
expect(error.messageText).toContain('IsAModule');
|
||||
expect(error.messageText).toContain('NgModule.exports');
|
||||
expect(error.code).toEqual(ngErrorCode(ErrorCode.NGMODULE_INVALID_EXPORT));
|
||||
// Find the diagnostic referencing InvalidModule, which should have come from IsAModule.
|
||||
const error = env.driveDiagnostics().find(
|
||||
error => diagnosticToNode(error, ts.isIdentifier).text === 'InvalidModule');
|
||||
if (error === undefined) {
|
||||
return fail('Expected to find a diagnostic referencing InvalidModule');
|
||||
}
|
||||
expect(error.messageText).toContain('IsAModule');
|
||||
expect(error.messageText).toContain('NgModule.exports');
|
||||
expect(error.code).toEqual(ngErrorCode(ErrorCode.NGMODULE_INVALID_EXPORT));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('re-exports', () => {
|
||||
it('should produce an error when a non-declared/imported class is re-exported', () => {
|
||||
env.write('test.ts', `
|
||||
describe('re-exports', () => {
|
||||
it('should produce an error when a non-declared/imported class is re-exported', () => {
|
||||
env.write('test.ts', `
|
||||
import {Directive, NgModule} from '@angular/core';
|
||||
|
||||
@Directive({selector: 'test'})
|
||||
@ -164,23 +167,24 @@ describe('ngtsc module scopes', () => {
|
||||
@NgModule({exports: [Dir]})
|
||||
class IsAModule {}
|
||||
`);
|
||||
const [error] = env.driveDiagnostics();
|
||||
expect(error).not.toBeUndefined();
|
||||
expect(error.messageText).toContain('IsAModule');
|
||||
expect(error.messageText).toContain('NgModule.exports');
|
||||
expect(error.code).toEqual(ngErrorCode(ErrorCode.NGMODULE_INVALID_REEXPORT));
|
||||
expect(diagnosticToNode(error, ts.isIdentifier).text).toEqual('Dir');
|
||||
const [error] = env.driveDiagnostics();
|
||||
expect(error).not.toBeUndefined();
|
||||
expect(error.messageText).toContain('IsAModule');
|
||||
expect(error.messageText).toContain('NgModule.exports');
|
||||
expect(error.code).toEqual(ngErrorCode(ErrorCode.NGMODULE_INVALID_REEXPORT));
|
||||
expect(diagnosticToNode(error, ts.isIdentifier).text).toEqual('Dir');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function diagnosticToNode<T extends ts.Node>(
|
||||
diag: ts.Diagnostic, guard: (node: ts.Node) => node is T): T {
|
||||
if (diag.file === undefined) {
|
||||
throw new Error(`Expected ts.Diagnostic to have a file source`);
|
||||
function diagnosticToNode<T extends ts.Node>(
|
||||
diag: ts.Diagnostic, guard: (node: ts.Node) => node is T): T {
|
||||
if (diag.file === undefined) {
|
||||
throw new Error(`Expected ts.Diagnostic to have a file source`);
|
||||
}
|
||||
const node = (ts as any).getTokenAtPosition(diag.file, diag.start) as ts.Node;
|
||||
expect(guard(node)).toBe(true);
|
||||
return node as T;
|
||||
}
|
||||
const node = (ts as any).getTokenAtPosition(diag.file, diag.start) as ts.Node;
|
||||
expect(guard(node)).toBe(true);
|
||||
return node as T;
|
||||
}
|
||||
});
|
||||
|
@ -5,7 +5,6 @@
|
||||
* 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 {MappingItem, SourceMapConsumer} from 'source-map';
|
||||
import {NgtscTestEnvironment} from './env';
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -8,10 +8,21 @@
|
||||
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {runInEachFileSystem} from '../../src/ngtsc/file_system/testing';
|
||||
import {loadStandardTestFiles} from '../helpers/src/mock_file_loading';
|
||||
|
||||
import {NgtscTestEnvironment} from './env';
|
||||
|
||||
function setupCommon(env: NgtscTestEnvironment): void {
|
||||
env.write('node_modules/@angular/common/index.d.ts', `
|
||||
const testFiles = loadStandardTestFiles();
|
||||
|
||||
runInEachFileSystem(() => {
|
||||
describe('ngtsc type checking', () => {
|
||||
let env !: NgtscTestEnvironment;
|
||||
|
||||
beforeEach(() => {
|
||||
env = NgtscTestEnvironment.setup(testFiles);
|
||||
env.tsconfig({fullTemplateTypeCheck: true});
|
||||
env.write('node_modules/@angular/common/index.d.ts', `
|
||||
import * as i0 from '@angular/core';
|
||||
|
||||
export declare class NgForOfContext<T> {
|
||||
@ -47,19 +58,10 @@ export declare class CommonModule {
|
||||
static ngModuleDef: i0.ɵɵNgModuleDefWithMeta<CommonModule, [typeof NgIf, typeof NgForOf, typeof IndexPipe], never, [typeof NgIf, typeof NgForOf, typeof IndexPipe]>;
|
||||
}
|
||||
`);
|
||||
}
|
||||
});
|
||||
|
||||
describe('ngtsc type checking', () => {
|
||||
let env !: NgtscTestEnvironment;
|
||||
|
||||
beforeEach(() => {
|
||||
env = NgtscTestEnvironment.setup();
|
||||
env.tsconfig({fullTemplateTypeCheck: true});
|
||||
setupCommon(env);
|
||||
});
|
||||
|
||||
it('should check a simple component', () => {
|
||||
env.write('test.ts', `
|
||||
it('should check a simple component', () => {
|
||||
env.write('test.ts', `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
@ -74,11 +76,11 @@ describe('ngtsc type checking', () => {
|
||||
class Module {}
|
||||
`);
|
||||
|
||||
env.driveMain();
|
||||
});
|
||||
env.driveMain();
|
||||
});
|
||||
|
||||
it('should check basic usage of NgIf', () => {
|
||||
env.write('test.ts', `
|
||||
it('should check basic usage of NgIf', () => {
|
||||
env.write('test.ts', `
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@ -97,11 +99,11 @@ describe('ngtsc type checking', () => {
|
||||
class Module {}
|
||||
`);
|
||||
|
||||
env.driveMain();
|
||||
});
|
||||
env.driveMain();
|
||||
});
|
||||
|
||||
it('should check usage of NgIf with explicit non-null guard', () => {
|
||||
env.write('test.ts', `
|
||||
it('should check usage of NgIf with explicit non-null guard', () => {
|
||||
env.write('test.ts', `
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@ -120,11 +122,11 @@ describe('ngtsc type checking', () => {
|
||||
class Module {}
|
||||
`);
|
||||
|
||||
env.driveMain();
|
||||
});
|
||||
env.driveMain();
|
||||
});
|
||||
|
||||
it('should check basic usage of NgFor', () => {
|
||||
env.write('test.ts', `
|
||||
it('should check basic usage of NgFor', () => {
|
||||
env.write('test.ts', `
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@ -143,11 +145,11 @@ describe('ngtsc type checking', () => {
|
||||
class Module {}
|
||||
`);
|
||||
|
||||
env.driveMain();
|
||||
});
|
||||
env.driveMain();
|
||||
});
|
||||
|
||||
it('should report an error inside the NgFor template', () => {
|
||||
env.write('test.ts', `
|
||||
it('should report an error inside the NgFor template', () => {
|
||||
env.write('test.ts', `
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@ -166,13 +168,13 @@ describe('ngtsc type checking', () => {
|
||||
export class Module {}
|
||||
`);
|
||||
|
||||
const diags = env.driveDiagnostics();
|
||||
expect(diags.length).toBe(1);
|
||||
expect(diags[0].messageText).toContain('does_not_exist');
|
||||
});
|
||||
const diags = env.driveDiagnostics();
|
||||
expect(diags.length).toBe(1);
|
||||
expect(diags[0].messageText).toContain('does_not_exist');
|
||||
});
|
||||
|
||||
it('should accept an NgFor iteration over an any-typed value', () => {
|
||||
env.write('test.ts', `
|
||||
it('should accept an NgFor iteration over an any-typed value', () => {
|
||||
env.write('test.ts', `
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@ -191,11 +193,11 @@ describe('ngtsc type checking', () => {
|
||||
export class Module {}
|
||||
`);
|
||||
|
||||
env.driveMain();
|
||||
});
|
||||
env.driveMain();
|
||||
});
|
||||
|
||||
it('should report an error with pipe bindings', () => {
|
||||
env.write('test.ts', `
|
||||
it('should report an error with pipe bindings', () => {
|
||||
env.write('test.ts', `
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@ -227,26 +229,27 @@ describe('ngtsc type checking', () => {
|
||||
class Module {}
|
||||
`);
|
||||
|
||||
const diags = env.driveDiagnostics();
|
||||
expect(diags.length).toBe(4);
|
||||
const diags = env.driveDiagnostics();
|
||||
expect(diags.length).toBe(4);
|
||||
|
||||
const allErrors = [
|
||||
`'does_not_exist' does not exist on type '{ name: string; }'`,
|
||||
`Expected 2 arguments, but got 3.`,
|
||||
`Argument of type '"test"' is not assignable to parameter of type 'number'`,
|
||||
`Argument of type '{ name: string; }' is not assignable to parameter of type '{}[]'`,
|
||||
];
|
||||
const allErrors = [
|
||||
`'does_not_exist' does not exist on type '{ name: string; }'`,
|
||||
`Expected 2 arguments, but got 3.`,
|
||||
`Argument of type '"test"' is not assignable to parameter of type 'number'`,
|
||||
`Argument of type '{ name: string; }' is not assignable to parameter of type '{}[]'`,
|
||||
];
|
||||
|
||||
for (const error of allErrors) {
|
||||
if (!diags.some(
|
||||
diag => ts.flattenDiagnosticMessageText(diag.messageText, '').indexOf(error) > -1)) {
|
||||
fail(`Expected a diagnostic message with text: ${error}`);
|
||||
for (const error of allErrors) {
|
||||
if (!diags.some(
|
||||
diag =>
|
||||
ts.flattenDiagnosticMessageText(diag.messageText, '').indexOf(error) > -1)) {
|
||||
fail(`Expected a diagnostic message with text: ${error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should constrain types using type parameter bounds', () => {
|
||||
env.write('test.ts', `
|
||||
it('should constrain types using type parameter bounds', () => {
|
||||
env.write('test.ts', `
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {Component, Input, NgModule} from '@angular/core';
|
||||
|
||||
@ -265,14 +268,14 @@ describe('ngtsc type checking', () => {
|
||||
class Module {}
|
||||
`);
|
||||
|
||||
const diags = env.driveDiagnostics();
|
||||
expect(diags.length).toBe(1);
|
||||
expect(diags[0].messageText).toContain('does_not_exist');
|
||||
});
|
||||
const diags = env.driveDiagnostics();
|
||||
expect(diags.length).toBe(1);
|
||||
expect(diags[0].messageText).toContain('does_not_exist');
|
||||
});
|
||||
|
||||
it('should property type-check a microsyntax variable with the same name as the expression',
|
||||
() => {
|
||||
env.write('test.ts', `
|
||||
it('should property type-check a microsyntax variable with the same name as the expression',
|
||||
() => {
|
||||
env.write('test.ts', `
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {Component, Input, NgModule} from '@angular/core';
|
||||
|
||||
@ -291,12 +294,12 @@ describe('ngtsc type checking', () => {
|
||||
export class Module {}
|
||||
`);
|
||||
|
||||
const diags = env.driveDiagnostics();
|
||||
expect(diags.length).toBe(0);
|
||||
});
|
||||
const diags = env.driveDiagnostics();
|
||||
expect(diags.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should properly type-check inherited directives', () => {
|
||||
env.write('test.ts', `
|
||||
it('should properly type-check inherited directives', () => {
|
||||
env.write('test.ts', `
|
||||
import {Component, Directive, Input, NgModule} from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
@ -325,15 +328,16 @@ describe('ngtsc type checking', () => {
|
||||
class Module {}
|
||||
`);
|
||||
|
||||
const diags = env.driveDiagnostics();
|
||||
expect(diags.length).toBe(2);
|
||||
const diags = env.driveDiagnostics();
|
||||
expect(diags.length).toBe(2);
|
||||
|
||||
// Error from the binding to [fromBase].
|
||||
expect(diags[0].messageText)
|
||||
.toBe(`Type 'number' is not assignable to type 'string | undefined'.`);
|
||||
// Error from the binding to [fromBase].
|
||||
expect(diags[0].messageText)
|
||||
.toBe(`Type 'number' is not assignable to type 'string | undefined'.`);
|
||||
|
||||
// Error from the binding to [fromChild].
|
||||
expect(diags[1].messageText)
|
||||
.toBe(`Type 'number' is not assignable to type 'boolean | undefined'.`);
|
||||
// Error from the binding to [fromChild].
|
||||
expect(diags[1].messageText)
|
||||
.toBe(`Type 'number' is not assignable to type 'boolean | undefined'.`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -5,12 +5,14 @@
|
||||
* 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
|
||||
*/
|
||||
|
||||
/// <reference types="node" />
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import * as ng from '../index';
|
||||
import {getAngularPackagesFromRunfiles, resolveNpmTreeArtifact} from './runfile_helpers';
|
||||
import {NodeJSFileSystem, setFileSystem} from '../src/ngtsc/file_system';
|
||||
import {getAngularPackagesFromRunfiles, resolveNpmTreeArtifact} from '../test/helpers';
|
||||
|
||||
// TEST_TMPDIR is always set by Bazel.
|
||||
const tmpdir = process.env.TEST_TMPDIR !;
|
||||
@ -143,6 +145,9 @@ export function setupBazelTo(tmpDirPath: string) {
|
||||
}
|
||||
|
||||
export function setup(): TestSupport {
|
||||
// // `TestSupport` provides its own file-system abstraction so we just use
|
||||
// // the native `NodeJSFileSystem` under the hood.
|
||||
setFileSystem(new NodeJSFileSystem());
|
||||
const tmpDirPath = makeTempDir();
|
||||
setupBazelTo(tmpDirPath);
|
||||
return createTestSupportFor(tmpDirPath);
|
||||
|
@ -5,12 +5,11 @@
|
||||
* 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
|
||||
*/
|
||||
|
||||
/// <reference types="node" />
|
||||
import * as ng from '@angular/compiler-cli';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {formatDiagnostics} from '../../src/perform_compile';
|
||||
import {CompilerHost, EmitFlags, LazyRoute} from '../../src/transformers/api';
|
||||
import {checkVersion, createSrcToOutPathMapper} from '../../src/transformers/program';
|
||||
|
Reference in New Issue
Block a user