angular-cn/packages/compiler-cli/test/transformers/program_spec.ts

236 lines
9.1 KiB
TypeScript
Raw Normal View History

/**
* @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 * as ng from '@angular/compiler-cli';
import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
import {CompilerHost} from '../../src/transformers/api';
import {GENERATED_FILES, StructureIsReused, tsStructureIsReused} from '../../src/transformers/util';
import {TestSupport, expectNoDiagnosticsInProgram, setup} from '../test_support';
describe('ng program', () => {
let testSupport: TestSupport;
let errorSpy: jasmine.Spy&((s: string) => void);
beforeEach(() => {
errorSpy = jasmine.createSpy('consoleError').and.callFake(console.error);
testSupport = setup();
});
function createModuleAndCompSource(prefix: string, template: string = prefix + 'template') {
const templateEntry =
template.endsWith('.html') ? `templateUrl: '${template}'` : `template: \`${template}\``;
return `
import {Component, NgModule} from '@angular/core';
@Component({selector: '${prefix}', ${templateEntry}})
export class ${prefix}Comp {}
@NgModule({declarations: [${prefix}Comp]})
export class ${prefix}Module {}
`;
}
describe('reuse of old program', () => {
function compileLib(libName: string) {
testSupport.writeFiles({
[`${libName}_src/index.ts`]: createModuleAndCompSource(libName),
});
const options = testSupport.createCompilerOptions({
skipTemplateCodegen: true,
});
const program = ng.createProgram({
rootNames: [path.resolve(testSupport.basePath, `${libName}_src/index.ts`)],
options,
host: ng.createCompilerHost({options}),
});
expectNoDiagnosticsInProgram(options, program);
fs.symlinkSync(
path.resolve(testSupport.basePath, 'built', `${libName}_src`),
path.resolve(testSupport.basePath, 'node_modules', libName));
program.emit({emitFlags: ng.EmitFlags.DTS | ng.EmitFlags.JS | ng.EmitFlags.Metadata});
}
function compile(oldProgram?: ng.Program): ng.Program {
const options = testSupport.createCompilerOptions();
const rootNames = [path.resolve(testSupport.basePath, 'src/index.ts')];
const program = ng.createProgram({
rootNames: rootNames,
options: testSupport.createCompilerOptions(),
host: ng.createCompilerHost({options}), oldProgram,
});
expectNoDiagnosticsInProgram(options, program);
program.emit();
return program;
}
it('should reuse generated code for libraries from old programs', () => {
compileLib('lib');
testSupport.writeFiles({
'src/main.ts': createModuleAndCompSource('main'),
'src/index.ts': `
export * from './main';
export * from 'lib/index';
`
});
const p1 = compile();
expect(p1.getTsProgram().getSourceFiles().some(
sf => /node_modules\/lib\/.*\.ngfactory\.ts$/.test(sf.fileName)))
.toBe(true);
expect(p1.getTsProgram().getSourceFiles().some(
sf => /node_modules\/lib2\/.*\.ngfactory.*$/.test(sf.fileName)))
.toBe(false);
const p2 = compile(p1);
expect(p2.getTsProgram().getSourceFiles().some(
sf => /node_modules\/lib\/.*\.ngfactory.*$/.test(sf.fileName)))
.toBe(false);
expect(p2.getTsProgram().getSourceFiles().some(
sf => /node_modules\/lib2\/.*\.ngfactory.*$/.test(sf.fileName)))
.toBe(false);
// import a library for which we didn't generate code before
compileLib('lib2');
testSupport.writeFiles({
'src/index.ts': `
export * from './main';
export * from 'lib/index';
export * from 'lib2/index';
`,
});
const p3 = compile(p2);
expect(p3.getTsProgram().getSourceFiles().some(
sf => /node_modules\/lib\/.*\.ngfactory.*$/.test(sf.fileName)))
.toBe(false);
expect(p3.getTsProgram().getSourceFiles().some(
sf => /node_modules\/lib2\/.*\.ngfactory\.ts$/.test(sf.fileName)))
.toBe(true);
const p4 = compile(p3);
expect(p4.getTsProgram().getSourceFiles().some(
sf => /node_modules\/lib\/.*\.ngfactory.*$/.test(sf.fileName)))
.toBe(false);
expect(p4.getTsProgram().getSourceFiles().some(
sf => /node_modules\/lib2\/.*\.ngfactory.*$/.test(sf.fileName)))
.toBe(false);
});
it('should reuse the old ts program completely if nothing changed', () => {
testSupport.writeFiles({'src/index.ts': createModuleAndCompSource('main')});
// Note: the second compile drops factories for library files,
// and therefore changes the structure again
const p1 = compile();
const p2 = compile(p1);
const p3 = compile(p2);
expect(tsStructureIsReused(p2.getTsProgram())).toBe(StructureIsReused.Completely);
});
it('should reuse the old ts program completely if a template or a ts file changed', () => {
testSupport.writeFiles({
'src/main.ts': createModuleAndCompSource('main', 'main.html'),
'src/main.html': `Some template`,
'src/util.ts': `export const x = 1`,
'src/index.ts': `
export * from './main';
export * from './util';
`
});
// Note: the second compile drops factories for library files,
// and therefore changes the structure again
const p1 = compile();
const p2 = compile(p1);
testSupport.writeFiles({
'src/main.html': `Another template`,
'src/util.ts': `export const x = 2`,
});
const p3 = compile(p2);
expect(tsStructureIsReused(p2.getTsProgram())).toBe(StructureIsReused.Completely);
});
it('should not reuse the old ts program if an import changed', () => {
testSupport.writeFiles({
'src/main.ts': createModuleAndCompSource('main'),
'src/util.ts': `export const x = 1`,
'src/index.ts': `
export * from './main';
export * from './util';
`
});
// Note: the second compile drops factories for library files,
// and therefore changes the structure again
const p1 = compile();
const p2 = compile(p1);
testSupport.writeFiles(
{'src/util.ts': `import {Injectable} from '@angular/core'; export const x = 1;`});
const p3 = compile(p2);
expect(tsStructureIsReused(p2.getTsProgram())).toBe(StructureIsReused.SafeModules);
});
});
it('should typecheck templates even if skipTemplateCodegen is set', () => {
testSupport.writeFiles({
'src/main.ts': createModuleAndCompSource('main', `{{nonExistent}}`),
});
const options = testSupport.createCompilerOptions({skipTemplateCodegen: true});
const host = ng.createCompilerHost({options});
const program = ng.createProgram(
{rootNames: [path.resolve(testSupport.basePath, 'src/main.ts')], options, host});
const diags = program.getNgSemanticDiagnostics();
expect(diags.length).toBe(1);
expect(diags[0].messageText).toBe(`Property 'nonExistent' does not exist on type 'mainComp'.`);
});
it('should be able to use asynchronously loaded resources', (done) => {
testSupport.writeFiles({
'src/main.ts': createModuleAndCompSource('main', 'main.html'),
// Note: we need to be able to resolve the template synchronously,
// only the content is delivered asynchronously.
'src/main.html': '',
});
const options = testSupport.createCompilerOptions();
const host = ng.createCompilerHost({options});
host.readResource = () => Promise.resolve('Hello world!');
const program = ng.createProgram(
{rootNames: [path.resolve(testSupport.basePath, 'src/main.ts')], options, host});
program.loadNgStructureAsync().then(() => {
program.emit();
const factory =
fs.readFileSync(path.resolve(testSupport.basePath, 'built/src/main.ngfactory.js'));
expect(factory).toContain('Hello world!');
done();
});
});
it('should work with noResolve', () => {
// create a temporary ts program to get the list of all files from angular...
testSupport.writeFiles({
'src/main.ts': createModuleAndCompSource('main'),
});
const preOptions = testSupport.createCompilerOptions();
const preHost = ts.createCompilerHost(preOptions);
// don't resolve symlinks
preHost.realpath = (f) => f;
const preProgram =
ts.createProgram([path.resolve(testSupport.basePath, 'src/main.ts')], preOptions, preHost);
const allRootNames = preProgram.getSourceFiles().map(sf => sf.fileName);
// now do the actual test with noResolve
const options = testSupport.createCompilerOptions({noResolve: true});
const host = ng.createCompilerHost({options});
const program = ng.createProgram({rootNames: allRootNames, options, host});
expectNoDiagnosticsInProgram(options, program);
program.emit();
testSupport.shouldExist('built/src/main.ngfactory.js');
testSupport.shouldExist('built/src/main.ngfactory.d.ts');
});
});