fix(compiler): Added unit test to ReflectorHost and fixed issues (#9052)

Refactored ReflectorHost to allow it to be tested.
Fixed an issue where the .d.ts was findable but it wasn't used by the project
  (This happens when the .metadata.json file references a module that was not
   needed, such as it doesn't declare any types, and the reference to it was
   elided by TypeScript when writing the .d.ts file).
Added tests for ReflectorHost
This commit is contained in:
Chuck Jazdzewski
2016-06-09 14:51:53 -07:00
parent e178ee4ba0
commit 0658eb4429
6 changed files with 303 additions and 15 deletions

View File

@ -0,0 +1,118 @@
import * as ts from 'typescript';
import {ReflectorHost, ReflectorHostContext} from '../src/reflector_host';
export type Entry = string | Directory;
export interface Directory {
[name: string]: Entry;
}
export class MockContext implements ReflectorHostContext {
constructor (public currentDirectory: string, private files: Entry) {}
exists(fileName: string): boolean {
return this.getEntry(fileName) !== undefined;
}
read(fileName: string): string | undefined {
let data = this.getEntry(fileName);
if (typeof data === "string") {
return data;
}
return undefined;
}
write(fileName: string, data: string): void {
let parts = fileName.split('/');
let name = parts.pop();
let entry = this.getEntry(parts);
if (entry && typeof entry !== "string") {
entry[name] = data;
}
}
getEntry(fileName: string | string[]): Entry | undefined {
let parts = typeof fileName === "string" ? fileName.split('/') : fileName;
if (parts[0]) {
parts = this.currentDirectory.split('/').concat(parts);
}
parts.shift();
parts = normalize(parts);
let current = this.files;
while (parts.length) {
let part = parts.shift();
if (typeof current === "string") {
return undefined;
}
let next = (<Directory>current)[part];
if (next === undefined) {
return undefined;
}
current = next;
}
return current;
}
}
function normalize(parts: string[]): string[] {
let result: string[] = [];
while (parts.length) {
let part = parts.shift();
switch (part) {
case '.': break;
case '..': result.pop(); break;
default: result.push(part);
}
}
return result;
}
export class MockCompilerHost implements ts.CompilerHost {
constructor (private context: MockContext) {}
fileExists(fileName: string): boolean {
return this.context.exists(fileName);
}
readFile(fileName: string): string {
return this.context.read(fileName);
}
directoryExists(directoryName: string): boolean {
return this.context.exists(directoryName);
}
getSourceFile(fileName: string, languageVersion: ts.ScriptTarget, onError?: (message: string) => void): ts.SourceFile {
let sourceText = this.context.read(fileName);
if (sourceText) {
return ts.createSourceFile(fileName, sourceText, languageVersion);
} else {
return undefined;
}
}
getDefaultLibFileName(options: ts.CompilerOptions): string {
return ts.getDefaultLibFileName(options);
}
writeFile: ts.WriteFileCallback = (fileName, text) => {
this.context.write(fileName, text);
}
getCurrentDirectory(): string {
return this.context.currentDirectory;
}
getCanonicalFileName(fileName: string): string {
return fileName;
}
useCaseSensitiveFileNames(): boolean {
return false;
}
getNewLine(): string {
return '\n';
}
}

View File

@ -0,0 +1,138 @@
import * as ts from 'typescript';
import {
describe,
it,
iit,
expect,
ddescribe,
beforeEach
} from '@angular/core/testing/testing_internal';
import {ReflectorHost, ReflectorHostContext} from '../src/reflector_host';
import {Directory, Entry, MockContext, MockCompilerHost} from './mocks';
describe('reflector_host', () => {
var context: MockContext;
var host: ts.CompilerHost;
var program: ts.Program;
var reflectorHost: ReflectorHost;
beforeEach(() => {
context = new MockContext('/tmp/src', clone(FILES));
host = new MockCompilerHost(context)
program = ts.createProgram(['main.ts'], {
module: ts.ModuleKind.CommonJS,
}, host);
// Force a typecheck
let errors = program.getSemanticDiagnostics();
if (errors && errors.length) {
throw new Error('Expected no errors');
}
reflectorHost = new ReflectorHost(program, host, {
genDir: '/tmp/dist',
basePath: '/tmp/src',
skipMetadataEmit: false,
skipTemplateCodegen: false,
trace: false
}, context);
});
it('should provide the import locations for angular', () => {
let {coreDecorators, diDecorators, diMetadata, animationMetadata, provider} = reflectorHost.angularImportLocations();
expect(coreDecorators).toEqual('@angular/core/src/metadata');
expect(diDecorators).toEqual('@angular/core/src/di/decorators');
expect(diMetadata).toEqual('@angular/core/src/di/metadata');
expect(animationMetadata).toEqual('@angular/core/src/animation/metadata');
expect(provider).toEqual('@angular/core/src/di/provider');
});
it('should be able to produce an import from main @angular/core', () => {
expect(reflectorHost.getImportPath('main.ts', 'node_modules/@angular/core.d.ts')).toEqual('@angular/core');
});
it('should be ble to produce an import from main to a sub-directory', () => {
expect(reflectorHost.getImportPath('main.ts', 'lib/utils.ts')).toEqual('./lib/utils');
});
it('should be able to produce an import from to a peer file', () => {
expect(reflectorHost.getImportPath('lib/utils.ts', 'lib/collections.ts')).toEqual('./collections');
});
it('should be able to produce an import from to a sibling directory', () => {
expect(reflectorHost.getImportPath('lib2/utils2.ts', 'lib/utils.ts')).toEqual('../lib/utils');
});
it('should be able to produce a symbol for an exported symbol', () => {
expect(reflectorHost.findDeclaration('@angular/router', 'foo', 'main.ts')).toBeDefined();
});
it('should be able to produce a symbol for values space only reference', () => {
expect(reflectorHost.findDeclaration('@angular/router/src/providers', 'foo', 'main.ts')).toBeDefined();
});
it('should be produce the same symbol if asked twice', () => {
let foo1 = reflectorHost.getStaticSymbol('main.ts', 'foo');
let foo2 = reflectorHost.getStaticSymbol('main.ts', 'foo');
expect(foo1).toBe(foo2);
});
it('should be able to read a metadata file', () => {
expect(reflectorHost.getMetadataFor('node_modules/@angular/core.d.ts')).toEqual({
__symbolic: "module",
version: 1,
metadata: {
foo: {
__symbolic: "class"
}
}
})
});
});
const dummyModule = 'export let foo: any[];'
const FILES: Entry = {
'tmp': {
'src': {
'main.ts': `
import * as c from '@angular/core';
import * as r from '@angular/router';
import * as u from './lib/utils';
import * as cs from './lib/collections';
import * as u2 from './lib2/utils2';
`,
'lib': {
'utils.ts': dummyModule,
'collections.ts': dummyModule,
},
'lib2': {
'utils2.ts': dummyModule
},
'node_modules': {
'@angular': {
'core.d.ts': dummyModule,
'core.metadata.json': `{"__symbolic":"module", "version": 1, "metadata": {"foo": {"__symbolic": "class"}}}`,
'router': {
'index.d.ts': dummyModule,
'src': {
'providers.d.ts': dummyModule
}
}
}
}
}
}
}
function clone(entry: Entry): Entry {
if (typeof entry === "string") {
return entry;
} else {
let result: Directory = {};
for (let name in entry) {
result[name] = clone(entry[name]);
}
return result;
}
}