angular-docs-cn/packages/compiler-cli/test/ngtsc/component_indexing_spec.ts
Pete Bacon Darwin 7186f9c016 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
2019-06-25 16:25:24 -07:00

186 lines
5.9 KiB
TypeScript

/**
* @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
*/
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 {NgtscTestEnvironment} from './env';
runInEachFileSystem(() => {
describe('ngtsc component indexing', () => {
let env !: NgtscTestEnvironment;
let testSourceFile: AbsoluteFsPath;
let testTemplateFile: AbsoluteFsPath;
beforeEach(() => {
env = NgtscTestEnvironment.setup();
env.tsconfig();
testSourceFile = resolve(env.basePath, 'test.ts');
testTemplateFile = resolve(env.basePath, 'test.html');
});
describe('indexing metadata', () => {
it('should generate component metadata', () => {
const componentContent = `
import {Component} from '@angular/core';
@Component({
selector: 'test-cmp',
template: '<div></div>',
})
export class TestCmp {}
`;
env.write(testSourceFile, componentContent);
const indexed = env.driveIndexer();
expect(indexed.size).toBe(1);
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, testSourceFile),
}));
});
it('should index inline templates', () => {
const componentContent = `
import {Component} from '@angular/core';
@Component({
selector: 'test-cmp',
template: '{{foo}}',
})
export class TestCmp { foo = 0; }
`;
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, testSourceFile),
});
});
it('should index external templates', () => {
env.write(testSourceFile, `
import {Component} from '@angular/core';
@Component({
selector: 'test-cmp',
templateUrl: './test.html',
})
export class TestCmp { foo = 0; }
`);
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}}', testTemplateFile),
});
});
it('should index templates compiled without preserving whitespace', () => {
env.tsconfig({
preserveWhitespaces: false,
});
env.write(testSourceFile, `
import {Component} from '@angular/core';
@Component({
selector: 'test-cmp',
templateUrl: './test.html',
})
export class TestCmp { foo = 0; }
`);
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>', testTemplateFile),
});
});
it('should generate information about used components', () => {
env.write(testSourceFile, `
import {Component} from '@angular/core';
@Component({
selector: 'test-cmp',
templateUrl: './test.html',
})
export class TestCmp {}
`);
env.write(testTemplateFile, '<div></div>');
env.write('test_import.ts', `
import {Component, NgModule} from '@angular/core';
import {TestCmp} from './test';
@Component({
templateUrl: './test_import.html',
})
export class TestImportCmp {}
@NgModule({
declarations: [
TestCmp,
TestImportCmp,
],
bootstrap: [TestImportCmp]
})
export class TestModule {}
`);
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();
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);
});
});
});
});