refactor(compiler): allow sync AOT compilation (#16832).

AOT compilation can be executed synchronously now,
if the `ReosurceLoader` returns a string directly
(and no `Promise`).
This commit is contained in:
Tobias Bosch 2017-05-17 15:39:08 -07:00 committed by Chuck Jazdzewski
parent 255d7226d1
commit 5af143e8e4
15 changed files with 577 additions and 643 deletions

View File

@ -38,7 +38,7 @@ export class CodeGenerator {
codegen(): Promise<any> {
return this.compiler
.compileAll(this.program.getSourceFiles().map(
.compileAllAsync(this.program.getSourceFiles().map(
sf => this.ngCompilerHost.getCanonicalFileName(sf.fileName)))
.then(generatedModules => {
generatedModules.forEach(generatedModule => {

View File

@ -17,7 +17,11 @@ import {DiagnosticTemplateInfo} from '../../src/diagnostics/expression_diagnosti
import {getClassFromStaticSymbol, getClassMembers, getPipesTable, getSymbolQuery} from '../../src/diagnostics/typescript_symbols';
import {Directory, MockAotContext} from '../mocks';
const packages = path.join(__dirname, '../../../../../packages');
function calcRootPath() {
const moduleFilename = module.filename.replace(/\\/g, '/');
const distIndex = moduleFilename.indexOf('/dist/all');
return moduleFilename.substr(0, distIndex);
}
const realFiles = new Map<string, string>();
@ -40,7 +44,7 @@ export class MockLanguageServiceHost implements ts.LanguageServiceHost, Compiler
strictNullChecks: true,
baseUrl: currentDirectory,
lib: ['lib.es2015.d.ts', 'lib.dom.d.ts'],
paths: {'@angular/*': [packages + '/*']}
paths: {'@angular/*': [calcRootPath() + '/packages/*']}
};
this.context = new MockAotContext(currentDirectory, files)
}

View File

@ -39,7 +39,7 @@ export class AotCompiler {
clearCache() { this._metadataResolver.clearCache(); }
compileAll(rootFiles: string[]): Promise<GeneratedFile[]> {
compileAllAsync(rootFiles: string[]): Promise<GeneratedFile[]> {
const programSymbols = extractProgramSymbols(this._symbolResolver, rootFiles, this._host);
const {ngModuleByPipeOrDirective, files, ngModules} =
analyzeAndValidateNgModules(programSymbols, this._host, this._metadataResolver);
@ -56,6 +56,20 @@ export class AotCompiler {
});
}
compileAllSync(rootFiles: string[]): GeneratedFile[] {
const programSymbols = extractProgramSymbols(this._symbolResolver, rootFiles, this._host);
const {ngModuleByPipeOrDirective, files, ngModules} =
analyzeAndValidateNgModules(programSymbols, this._host, this._metadataResolver);
ngModules.forEach(
ngModule => this._metadataResolver.loadNgModuleDirectiveAndPipeMetadata(
ngModule.type.reference, true));
const sourceModules = files.map(
file => this._compileSrcFile(
file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.pipes, file.ngModules,
file.injectables));
return flatten(sourceModules);
}
private _compileSrcFile(
srcFileUrl: string, ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
directives: StaticSymbol[], pipes: StaticSymbol[], ngModules: StaticSymbol[],

View File

@ -17,5 +17,5 @@ export interface AotCompilerHost extends StaticSymbolResolverHost, AotSummaryRes
/**
* Loads a resource (e.g. html / css)
*/
loadResource(path: string): Promise<string>;
loadResource(path: string): Promise<string>|string;
}

View File

@ -18,7 +18,7 @@ import {ResourceLoader} from './resource_loader';
import {extractStyleUrls, isStyleUrlResolvable} from './style_url_resolver';
import {PreparsedElementType, preparseElement} from './template_parser/template_preparser';
import {UrlResolver} from './url_resolver';
import {SyncAsyncResult, isDefined, syntaxError} from './util';
import {SyncAsync, isDefined, syntaxError} from './util';
export interface PrenormalizedTemplateMetadata {
ngModuleType: any;
@ -35,7 +35,7 @@ export interface PrenormalizedTemplateMetadata {
@CompilerInjectable()
export class DirectiveNormalizer {
private _resourceLoaderCache = new Map<string, Promise<string>>();
private _resourceLoaderCache = new Map<string, SyncAsync<string>>();
constructor(
private _resourceLoader: ResourceLoader, private _urlResolver: UrlResolver,
@ -53,19 +53,17 @@ export class DirectiveNormalizer {
(stylesheet) => { this._resourceLoaderCache.delete(stylesheet.moduleUrl !); });
}
private _fetch(url: string): Promise<string> {
private _fetch(url: string): SyncAsync<string> {
let result = this._resourceLoaderCache.get(url);
if (!result) {
result = this._resourceLoader.get(url) !;
result = this._resourceLoader.get(url);
this._resourceLoaderCache.set(url, result);
}
return result;
}
normalizeTemplate(prenormData: PrenormalizedTemplateMetadata):
SyncAsyncResult<CompileTemplateMetadata> {
let normalizedTemplateSync: CompileTemplateMetadata = null !;
let normalizedTemplateAsync: Promise<CompileTemplateMetadata> = undefined !;
SyncAsync<CompileTemplateMetadata> {
if (isDefined(prenormData.template)) {
if (isDefined(prenormData.templateUrl)) {
throw syntaxError(
@ -75,39 +73,33 @@ export class DirectiveNormalizer {
throw syntaxError(
`The template specified for component ${stringify(prenormData.componentType)} is not a string`);
}
normalizedTemplateSync = this.normalizeTemplateSync(prenormData);
normalizedTemplateAsync = Promise.resolve(normalizedTemplateSync !);
} else if (isDefined(prenormData.templateUrl)) {
if (typeof prenormData.templateUrl !== 'string') {
throw syntaxError(
`The templateUrl specified for component ${stringify(prenormData.componentType)} is not a string`);
}
normalizedTemplateAsync = this.normalizeTemplateAsync(prenormData);
} else {
throw syntaxError(
`No template specified for component ${stringify(prenormData.componentType)}`);
}
return SyncAsync.then(
this.normalizeTemplateOnly(prenormData),
(result: CompileTemplateMetadata) => this.normalizeExternalStylesheets(result));
}
if (normalizedTemplateSync && normalizedTemplateSync.styleUrls.length === 0) {
// sync case
return new SyncAsyncResult(normalizedTemplateSync);
normalizeTemplateOnly(prenomData: PrenormalizedTemplateMetadata):
SyncAsync<CompileTemplateMetadata> {
let template: SyncAsync<string>;
let templateUrl: string;
if (prenomData.template != null) {
template = prenomData.template;
templateUrl = prenomData.moduleUrl;
} else {
// async case
return new SyncAsyncResult(
null, normalizedTemplateAsync.then(
(normalizedTemplate) => this.normalizeExternalStylesheets(normalizedTemplate)));
templateUrl = this._urlResolver.resolve(prenomData.moduleUrl, prenomData.templateUrl !);
template = this._fetch(templateUrl);
}
}
normalizeTemplateSync(prenomData: PrenormalizedTemplateMetadata): CompileTemplateMetadata {
return this.normalizeLoadedTemplate(prenomData, prenomData.template !, prenomData.moduleUrl);
}
normalizeTemplateAsync(prenomData: PrenormalizedTemplateMetadata):
Promise<CompileTemplateMetadata> {
const templateUrl = this._urlResolver.resolve(prenomData.moduleUrl, prenomData.templateUrl !);
return this._fetch(templateUrl)
.then((value) => this.normalizeLoadedTemplate(prenomData, value, templateUrl));
return SyncAsync.then(
template, (template) => this.normalizeLoadedTemplate(prenomData, template, templateUrl));
}
normalizeLoadedTemplate(
@ -162,37 +154,42 @@ export class DirectiveNormalizer {
}
normalizeExternalStylesheets(templateMeta: CompileTemplateMetadata):
Promise<CompileTemplateMetadata> {
return this._loadMissingExternalStylesheets(templateMeta.styleUrls)
.then((externalStylesheets) => new CompileTemplateMetadata({
encapsulation: templateMeta.encapsulation,
template: templateMeta.template,
templateUrl: templateMeta.templateUrl,
styles: templateMeta.styles,
styleUrls: templateMeta.styleUrls,
externalStylesheets: externalStylesheets,
ngContentSelectors: templateMeta.ngContentSelectors,
animations: templateMeta.animations,
interpolation: templateMeta.interpolation,
isInline: templateMeta.isInline,
}));
SyncAsync<CompileTemplateMetadata> {
return SyncAsync.then(
this._loadMissingExternalStylesheets(templateMeta.styleUrls),
(externalStylesheets) => new CompileTemplateMetadata({
encapsulation: templateMeta.encapsulation,
template: templateMeta.template,
templateUrl: templateMeta.templateUrl,
styles: templateMeta.styles,
styleUrls: templateMeta.styleUrls,
externalStylesheets: externalStylesheets,
ngContentSelectors: templateMeta.ngContentSelectors,
animations: templateMeta.animations,
interpolation: templateMeta.interpolation,
isInline: templateMeta.isInline,
}));
}
private _loadMissingExternalStylesheets(
styleUrls: string[],
loadedStylesheets:
Map<string, CompileStylesheetMetadata> = new Map<string, CompileStylesheetMetadata>()):
Promise<CompileStylesheetMetadata[]> {
return Promise
.all(styleUrls.filter((styleUrl) => !loadedStylesheets.has(styleUrl))
.map(styleUrl => this._fetch(styleUrl).then((loadedStyle) => {
const stylesheet = this.normalizeStylesheet(
new CompileStylesheetMetadata({styles: [loadedStyle], moduleUrl: styleUrl}));
loadedStylesheets.set(styleUrl, stylesheet);
return this._loadMissingExternalStylesheets(
stylesheet.styleUrls, loadedStylesheets);
})))
.then((_) => Array.from(loadedStylesheets.values()));
SyncAsync<CompileStylesheetMetadata[]> {
return SyncAsync.then(
SyncAsync.all(styleUrls.filter((styleUrl) => !loadedStylesheets.has(styleUrl))
.map(
styleUrl => SyncAsync.then(
this._fetch(styleUrl),
(loadedStyle) => {
const stylesheet =
this.normalizeStylesheet(new CompileStylesheetMetadata(
{styles: [loadedStyle], moduleUrl: styleUrl}));
loadedStylesheets.set(styleUrl, stylesheet);
return this._loadMissingExternalStylesheets(
stylesheet.styleUrls, loadedStylesheets);
}))),
(_) => Array.from(loadedStylesheets.values()));
}
normalizeStylesheet(stylesheet: CompileStylesheetMetadata): CompileStylesheetMetadata {

View File

@ -19,7 +19,7 @@ import {jitStatements} from '../output/output_jit';
import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
import {SummaryResolver} from '../summary_resolver';
import {TemplateParser} from '../template_parser/template_parser';
import {OutputContext, SyncAsyncResult} from '../util';
import {OutputContext, SyncAsync} from '../util';
import {ViewCompiler} from '../view_compiler/view_compiler';
@ -51,20 +51,20 @@ export class JitCompiler implements Compiler {
get injector(): Injector { return this._injector; }
compileModuleSync<T>(moduleType: Type<T>): NgModuleFactory<T> {
return this._compileModuleAndComponents(moduleType, true).syncResult !;
return SyncAsync.assertSync(this._compileModuleAndComponents(moduleType, true));
}
compileModuleAsync<T>(moduleType: Type<T>): Promise<NgModuleFactory<T>> {
return this._compileModuleAndComponents(moduleType, false).asyncResult !;
return Promise.resolve(this._compileModuleAndComponents(moduleType, false));
}
compileModuleAndAllComponentsSync<T>(moduleType: Type<T>): ModuleWithComponentFactories<T> {
return this._compileModuleAndAllComponents(moduleType, true).syncResult !;
return SyncAsync.assertSync(this._compileModuleAndAllComponents(moduleType, true));
}
compileModuleAndAllComponentsAsync<T>(moduleType: Type<T>):
Promise<ModuleWithComponentFactories<T>> {
return this._compileModuleAndAllComponents(moduleType, false).asyncResult !;
return Promise.resolve(this._compileModuleAndAllComponents(moduleType, false));
}
getNgContentSelectors(component: Type<any>): string[] {
@ -97,36 +97,24 @@ export class JitCompiler implements Compiler {
}
private _compileModuleAndComponents<T>(moduleType: Type<T>, isSync: boolean):
SyncAsyncResult<NgModuleFactory<T>> {
const loadingPromise = this._loadModules(moduleType, isSync);
const createResult = () => {
SyncAsync<NgModuleFactory<T>> {
return SyncAsync.then(this._loadModules(moduleType, isSync), () => {
this._compileComponents(moduleType, null);
return this._compileModule(moduleType);
};
if (isSync) {
return new SyncAsyncResult(createResult());
} else {
return new SyncAsyncResult(null, loadingPromise.then(createResult));
}
});
}
private _compileModuleAndAllComponents<T>(moduleType: Type<T>, isSync: boolean):
SyncAsyncResult<ModuleWithComponentFactories<T>> {
const loadingPromise = this._loadModules(moduleType, isSync);
const createResult = () => {
SyncAsync<ModuleWithComponentFactories<T>> {
return SyncAsync.then(this._loadModules(moduleType, isSync), () => {
const componentFactories: ComponentFactory<any>[] = [];
this._compileComponents(moduleType, componentFactories);
return new ModuleWithComponentFactories(this._compileModule(moduleType), componentFactories);
};
if (isSync) {
return new SyncAsyncResult(createResult());
} else {
return new SyncAsyncResult(null, loadingPromise.then(createResult));
}
});
}
private _loadModules(mainModule: any, isSync: boolean): Promise<any> {
const loadingPromises: Promise<any>[] = [];
private _loadModules(mainModule: any, isSync: boolean): SyncAsync<any> {
const loading: Promise<any>[] = [];
const mainNgModule = this._metadataResolver.getNgModuleMetadata(mainModule) !;
// Note: for runtime compilation, we want to transitively compile all modules,
// so we also need to load the declared directives / pipes for all nested modules.
@ -137,13 +125,13 @@ export class JitCompiler implements Compiler {
const promise =
this._metadataResolver.loadDirectiveMetadata(moduleMeta.type.reference, ref, isSync);
if (promise) {
loadingPromises.push(promise);
loading.push(promise);
}
});
this._filterJitIdentifiers(moduleMeta.declaredPipes)
.forEach((ref) => this._metadataResolver.getOrLoadPipeMetadata(ref));
});
return Promise.all(loadingPromises);
return SyncAsync.all(loading);
}
private _compileModule<T>(moduleType: Type<T>): NgModuleFactory<T> {

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Attribute, ChangeDetectionStrategy, Component, ComponentFactory, Directive, Host, Inject, Injectable, InjectionToken, ModuleWithProviders, Optional, Provider, Query, RendererType2, SchemaMetadata, Self, SkipSelf, Type, resolveForwardRef, ɵConsole as Console, ɵERROR_COMPONENT_TYPE, ɵccf as createComponentFactory, ɵstringify as stringify} from '@angular/core';
import {Attribute, ChangeDetectionStrategy, Component, ComponentFactory, Directive, Host, Inject, Injectable, InjectionToken, ModuleWithProviders, Optional, Provider, Query, RendererType2, SchemaMetadata, Self, SkipSelf, Type, resolveForwardRef, ɵConsole as Console, ɵERROR_COMPONENT_TYPE, ɵccf as createComponentFactory, ɵisPromise as isPromise, ɵstringify as stringify} from '@angular/core';
import {StaticSymbol, StaticSymbolCache} from './aot/static_symbol';
import {ngfactoryFilePath} from './aot/util';
@ -24,7 +24,7 @@ import {PipeResolver} from './pipe_resolver';
import {ElementSchemaRegistry} from './schema/element_schema_registry';
import {SummaryResolver} from './summary_resolver';
import {getUrlScheme} from './url_resolver';
import {MODULE_SUFFIX, ValueTransformer, noUndefined, syntaxError, visitValue} from './util';
import {MODULE_SUFFIX, SyncAsync, ValueTransformer, noUndefined, syntaxError, visitValue} from './util';
export type ErrorCollector = (error: any, type?: any) => void;
export const ERROR_COLLECTOR_TOKEN = new InjectionToken('ErrorCollector');
@ -170,7 +170,7 @@ export class CompileMetadataResolver {
return typeSummary && typeSummary.summaryKind === kind ? typeSummary : null;
}
loadDirectiveMetadata(ngModuleType: any, directiveType: any, isSync: boolean): Promise<any>|null {
loadDirectiveMetadata(ngModuleType: any, directiveType: any, isSync: boolean): SyncAsync<null> {
if (this._directiveCache.has(directiveType)) {
return null;
}
@ -205,7 +205,7 @@ export class CompileMetadataResolver {
}
this._directiveCache.set(directiveType, normalizedDirMeta);
this._summaryCache.set(directiveType, normalizedDirMeta.toSummary());
return normalizedDirMeta;
return null;
};
if (metadata.isComponent) {
@ -222,16 +222,11 @@ export class CompileMetadataResolver {
animations: template.animations,
interpolation: template.interpolation
});
if (templateMeta.syncResult) {
createDirectiveMetadata(templateMeta.syncResult);
if (isPromise(templateMeta) && isSync) {
this._reportError(componentStillLoadingError(directiveType), directiveType);
return null;
} else {
if (isSync) {
this._reportError(componentStillLoadingError(directiveType), directiveType);
return null;
}
return templateMeta.asyncResult !.then(createDirectiveMetadata);
}
return SyncAsync.then(templateMeta, createDirectiveMetadata);
} else {
// directive
createDirectiveMetadata(null);

View File

@ -11,5 +11,5 @@
* to load templates.
*/
export class ResourceLoader {
get(url: string): Promise<string>|null { return null; }
get(url: string): Promise<string>|string { return ''; }
}

View File

@ -6,6 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ɵisPromise as isPromise} from '@angular/core';
import * as o from './output/output_ast';
export const MODULE_SUFFIX = '';
@ -80,19 +82,28 @@ export class ValueTransformer implements ValueVisitor {
visitOther(value: any, context: any): any { return value; }
}
export class SyncAsyncResult<T> {
constructor(public syncResult: T|null, public asyncResult: Promise<T>|null = null) {
if (!asyncResult) {
this.asyncResult = Promise.resolve(syncResult);
export type SyncAsync<T> = T | Promise<T>;
export const SyncAsync = {
assertSync: <T>(value: SyncAsync<T>): T => {
if (isPromise(value)) {
throw new Error(`Illegal state: value cannot be a promise`);
}
return value;
},
then: <T, R>(value: SyncAsync<T>, cb: (value: T) => R | Promise<R>| SyncAsync<R>):
SyncAsync<R> => { return isPromise(value) ? value.then(cb) : cb(value);},
all: <T>(syncAsyncValues: SyncAsync<T>[]): SyncAsync<T[]> => {
return syncAsyncValues.some(isPromise) ? Promise.all(syncAsyncValues) : syncAsyncValues as T[];
}
}
export function syntaxError(msg: string): Error {
const error = Error(msg);
(error as any)[ERROR_SYNTAX_ERROR] = true;
return error;
}
export function syntaxError(msg: string):
Error {
const error = Error(msg);
(error as any)[ERROR_SYNTAX_ERROR] = true;
return error;
}
const ERROR_SYNTAX_ERROR = 'ngSyntaxError';

View File

@ -8,7 +8,6 @@
import {GeneratedFile, toTypeScript} from '@angular/compiler';
import {NodeFlags} from '@angular/core/src/view/index';
import {async} from '@angular/core/testing';
import {MetadataBundler, MetadataCollector, ModuleMetadata, privateEntriesToIndex} from '@angular/tsc-wrapped';
import * as ts from 'typescript';
@ -20,11 +19,11 @@ describe('compiler (unbundled Angular)', () => {
let angularFiles = setup();
describe('Quickstart', () => {
it('should compile', async(() => compile([QUICKSTART, angularFiles]).then(({genFiles}) => {
expect(genFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl)))
.toBeDefined();
expect(genFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl))).toBeDefined();
})));
it('should compile', () => {
const {genFiles} = compile([QUICKSTART, angularFiles]);
expect(genFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl))).toBeDefined();
expect(genFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl))).toBeDefined();
});
});
describe('aot source mapping', () => {
@ -51,12 +50,10 @@ describe('compiler (unbundled Angular)', () => {
rootDir = {'app': appDir};
});
function compileApp(): Promise<GeneratedFile> {
return compile([rootDir, angularFiles])
.then(
({genFiles}) => {return genFiles.find(
genFile =>
genFile.srcFileUrl === componentPath && genFile.genFileUrl.endsWith('.ts'))});
function compileApp(): GeneratedFile {
const {genFiles} = compile([rootDir, angularFiles]);
return genFiles.find(
genFile => genFile.srcFileUrl === componentPath && genFile.genFileUrl.endsWith('.ts'));
}
function findLineAndColumn(
@ -106,102 +103,95 @@ describe('compiler (unbundled Angular)', () => {
function declareTests({ngUrl, templateDecorator}:
{ngUrl: string, templateDecorator: (template: string) => string}) {
it('should use the right source url in html parse errors', async(() => {
appDir['app.component.ts'] =
createComponentSource(templateDecorator('<div>\n </error>'));
it('should use the right source url in html parse errors', () => {
appDir['app.component.ts'] = createComponentSource(templateDecorator('<div>\n </error>'));
expectPromiseToThrow(
compileApp(), new RegExp(`Template parse errors[\\s\\S]*${ngUrl}@1:2`));
}));
expect(() => compileApp())
.toThrowError(new RegExp(`Template parse errors[\\s\\S]*${ngUrl}@1:2`));
});
it('should use the right source url in template parse errors', async(() => {
appDir['app.component.ts'] = createComponentSource(
templateDecorator('<div>\n <div unknown="{{ctxProp}}"></div>'));
it('should use the right source url in template parse errors', () => {
appDir['app.component.ts'] =
createComponentSource(templateDecorator('<div>\n <div unknown="{{ctxProp}}"></div>'));
expectPromiseToThrow(
compileApp(), new RegExp(`Template parse errors[\\s\\S]*${ngUrl}@1:7`));
}));
expect(() => compileApp())
.toThrowError(new RegExp(`Template parse errors[\\s\\S]*${ngUrl}@1:7`));
});
it('should create a sourceMap for the template', async(() => {
const template = 'Hello World!';
it('should create a sourceMap for the template', () => {
const template = 'Hello World!';
appDir['app.component.ts'] = createComponentSource(templateDecorator(template));
appDir['app.component.ts'] = createComponentSource(templateDecorator(template));
compileApp().then((genFile) => {
const genSource = toTypeScript(genFile);
const sourceMap = extractSourceMap(genSource) !;
expect(sourceMap.file).toEqual(genFile.genFileUrl);
const genFile = compileApp();
const genSource = toTypeScript(genFile);
const sourceMap = extractSourceMap(genSource) !;
expect(sourceMap.file).toEqual(genFile.genFileUrl);
// the generated file contains code that is not mapped to
// the template but rather to the original source file (e.g. import statements, ...)
const templateIndex = sourceMap.sources.indexOf(ngUrl);
expect(sourceMap.sourcesContent[templateIndex]).toEqual(template);
// the generated file contains code that is not mapped to
// the template but rather to the original source file (e.g. import statements, ...)
const templateIndex = sourceMap.sources.indexOf(ngUrl);
expect(sourceMap.sourcesContent[templateIndex]).toEqual(template);
// for the mapping to the original source file we don't store the source code
// as we want to keep whatever TypeScript / ... produced for them.
const sourceIndex = sourceMap.sources.indexOf(ngComponentPath);
expect(sourceMap.sourcesContent[sourceIndex]).toBe(' ');
});
}));
// for the mapping to the original source file we don't store the source code
// as we want to keep whatever TypeScript / ... produced for them.
const sourceIndex = sourceMap.sources.indexOf(ngComponentPath);
expect(sourceMap.sourcesContent[sourceIndex]).toBe(' ');
});
it('should map elements correctly to the source', async(() => {
const template = '<div>\n <span></span></div>';
it('should map elements correctly to the source', () => {
const template = '<div>\n <span></span></div>';
appDir['app.component.ts'] = createComponentSource(templateDecorator(template));
appDir['app.component.ts'] = createComponentSource(templateDecorator(template));
compileApp().then((genFile) => {
const genSource = toTypeScript(genFile);
const sourceMap = extractSourceMap(genSource) !;
expect(originalPositionFor(sourceMap, findLineAndColumn(genSource, `'span'`)))
.toEqual({line: 2, column: 3, source: ngUrl});
});
}));
const genFile = compileApp();
const genSource = toTypeScript(genFile);
const sourceMap = extractSourceMap(genSource) !;
expect(originalPositionFor(sourceMap, findLineAndColumn(genSource, `'span'`)))
.toEqual({line: 2, column: 3, source: ngUrl});
});
it('should map bindings correctly to the source', async(() => {
const template = `<div>\n <span [title]="someMethod()"></span></div>`;
it('should map bindings correctly to the source', () => {
const template = `<div>\n <span [title]="someMethod()"></span></div>`;
appDir['app.component.ts'] = createComponentSource(templateDecorator(template));
appDir['app.component.ts'] = createComponentSource(templateDecorator(template));
compileApp().then((genFile) => {
const genSource = toTypeScript(genFile);
const sourceMap = extractSourceMap(genSource) !;
expect(originalPositionFor(sourceMap, findLineAndColumn(genSource, `someMethod()`)))
.toEqual({line: 2, column: 9, source: ngUrl});
});
}));
const genFile = compileApp();
const genSource = toTypeScript(genFile);
const sourceMap = extractSourceMap(genSource) !;
expect(originalPositionFor(sourceMap, findLineAndColumn(genSource, `someMethod()`)))
.toEqual({line: 2, column: 9, source: ngUrl});
});
it('should map events correctly to the source', async(() => {
const template = `<div>\n <span (click)="someMethod()"></span></div>`;
it('should map events correctly to the source', () => {
const template = `<div>\n <span (click)="someMethod()"></span></div>`;
appDir['app.component.ts'] = createComponentSource(templateDecorator(template));
appDir['app.component.ts'] = createComponentSource(templateDecorator(template));
compileApp().then((genFile) => {
const genSource = toTypeScript(genFile);
const sourceMap = extractSourceMap(genSource) !;
expect(originalPositionFor(sourceMap, findLineAndColumn(genSource, `someMethod()`)))
.toEqual({line: 2, column: 9, source: ngUrl});
});
}));
const genFile = compileApp();
const genSource = toTypeScript(genFile);
const sourceMap = extractSourceMap(genSource) !;
expect(originalPositionFor(sourceMap, findLineAndColumn(genSource, `someMethod()`)))
.toEqual({line: 2, column: 9, source: ngUrl});
});
it('should map non template parts to the source file', async(() => {
appDir['app.component.ts'] = createComponentSource(templateDecorator('Hello World!'));
it('should map non template parts to the source file', () => {
appDir['app.component.ts'] = createComponentSource(templateDecorator('Hello World!'));
compileApp().then((genFile) => {
const genSource = toTypeScript(genFile);
const sourceMap = extractSourceMap(genSource) !;
expect(originalPositionFor(sourceMap, {line: 1, column: 0}))
.toEqual({line: 1, column: 0, source: ngComponentPath});
});
}));
const genFile = compileApp();
const genSource = toTypeScript(genFile);
const sourceMap = extractSourceMap(genSource) !;
expect(originalPositionFor(sourceMap, {line: 1, column: 0}))
.toEqual({line: 1, column: 0, source: ngComponentPath});
});
}
});
describe('errors', () => {
it('should only warn if not all arguments of an @Injectable class can be resolved',
async(() => {
const FILES: MockDirectory = {
app: {
'app.ts': `
it('should only warn if not all arguments of an @Injectable class can be resolved', () => {
const FILES: MockDirectory = {
app: {
'app.ts': `
import {Injectable} from '@angular/core';
@Injectable()
@ -209,20 +199,19 @@ describe('compiler (unbundled Angular)', () => {
constructor(a: boolean) {}
}
`
}
};
const warnSpy = spyOn(console, 'warn');
compile([FILES, angularFiles]).then(() => {
expect(warnSpy).toHaveBeenCalledWith(
`Warning: Can't resolve all parameters for MyService in /app/app.ts: (?). This will become an error in Angular v5.x`);
});
}
};
const warnSpy = spyOn(console, 'warn');
compile([FILES, angularFiles]);
expect(warnSpy).toHaveBeenCalledWith(
`Warning: Can't resolve all parameters for MyService in /app/app.ts: (?). This will become an error in Angular v5.x`);
}));
});
it('should be able to supress a null access', async(() => {
const FILES: MockDirectory = {
app: {
'app.ts': `
it('should be able to supress a null access', () => {
const FILES: MockDirectory = {
app: {
'app.ts': `
import {Component, NgModule} from '@angular/core';
interface Person { name: string; }
@ -240,16 +229,16 @@ describe('compiler (unbundled Angular)', () => {
})
export class MyModule {}
`
}
};
compile([FILES, angularFiles], {postCompile: expectNoDiagnostics});
}));
}
};
compile([FILES, angularFiles], {postCompile: expectNoDiagnostics});
});
});
it('should add the preamble to generated files', async(() => {
const FILES: MockDirectory = {
app: {
'app.ts': `
it('should add the preamble to generated files', () => {
const FILES: MockDirectory = {
app: {
'app.ts': `
import { NgModule, Component } from '@angular/core';
@Component({ template: '' })
@ -258,24 +247,21 @@ describe('compiler (unbundled Angular)', () => {
@NgModule({ declarations: [ AppComponent ] })
export class AppModule { }
`
}
};
const genFilePreamble = '/* Hello world! */';
compile([FILES, angularFiles]).then(({genFiles}) => {
const genFile =
genFiles.find(gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts'));
const genSource = toTypeScript(genFile, genFilePreamble);
expect(genSource.startsWith(genFilePreamble)).toBe(true);
});
}));
}
};
const genFilePreamble = '/* Hello world! */';
const {genFiles} = compile([FILES, angularFiles]);
const genFile =
genFiles.find(gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts'));
const genSource = toTypeScript(genFile, genFilePreamble);
expect(genSource.startsWith(genFilePreamble)).toBe(true);
});
describe('ComponentFactories', () => {
it('should include inputs, outputs and ng-content selectors in the component factory',
async(() => {
const FILES: MockDirectory = {
app: {
'app.ts': `
it('should include inputs, outputs and ng-content selectors in the component factory', () => {
const FILES: MockDirectory = {
app: {
'app.ts': `
import {Component, NgModule, Input, Output} from '@angular/core';
@Component({
@ -295,31 +281,28 @@ describe('compiler (unbundled Angular)', () => {
})
export class MyModule {}
`
}
};
compile([FILES, angularFiles]).then(({genFiles}) => {
const genFile = genFiles.find(genFile => genFile.srcFileUrl === '/app/app.ts');
const genSource = toTypeScript(genFile);
const createComponentFactoryCall =
/ɵccf\([^)]*\)/m.exec(genSource) ![0].replace(/\s*/g, '');
// selector
expect(createComponentFactoryCall).toContain('my-comp');
// inputs
expect(createComponentFactoryCall).toContain(`{aInputProp:'aInputName'}`);
// outputs
expect(createComponentFactoryCall).toContain(`{aOutputProp:'aOutputName'}`);
// ngContentSelectors
expect(createComponentFactoryCall).toContain(`['*','child']`);
});
}));
}
};
const {genFiles} = compile([FILES, angularFiles]);
const genFile = genFiles.find(genFile => genFile.srcFileUrl === '/app/app.ts');
const genSource = toTypeScript(genFile);
const createComponentFactoryCall = /ɵccf\([^)]*\)/m.exec(genSource) ![0].replace(/\s*/g, '');
// selector
expect(createComponentFactoryCall).toContain('my-comp');
// inputs
expect(createComponentFactoryCall).toContain(`{aInputProp:'aInputName'}`);
// outputs
expect(createComponentFactoryCall).toContain(`{aOutputProp:'aOutputName'}`);
// ngContentSelectors
expect(createComponentFactoryCall).toContain(`['*','child']`);
});
});
describe('generated templates', () => {
it('should not call `check` for directives without bindings nor ngDoCheck/ngOnInit',
async(() => {
const FILES: MockDirectory = {
app: {
'app.ts': `
it('should not call `check` for directives without bindings nor ngDoCheck/ngOnInit', () => {
const FILES: MockDirectory = {
app: {
'app.ts': `
import { NgModule, Component } from '@angular/core';
@Component({ template: '' })
@ -328,16 +311,15 @@ describe('compiler (unbundled Angular)', () => {
@NgModule({ declarations: [ AppComponent ] })
export class AppModule { }
`
}
};
compile([FILES, angularFiles]).then(({genFiles}) => {
const genFile = genFiles.find(
gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts'));
const genSource = toTypeScript(genFile);
expect(genSource).not.toContain('check(');
});
}
};
const {genFiles} = compile([FILES, angularFiles]);
const genFile =
genFiles.find(gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts'));
const genSource = toTypeScript(genFile);
expect(genSource).not.toContain('check(');
}));
});
});
describe('inheritance with summaries', () => {
@ -376,16 +358,15 @@ describe('compiler (unbundled Angular)', () => {
}
};
return compile([libInput, angularFiles], {useSummaries: true})
.then(({outDir}) => compile([outDir, appInput, angularFiles], {useSummaries: true}))
.then(({genFiles}) => genFiles.find(gf => gf.srcFileUrl === '/app/main.ts'));
const {outDir: libOutDir} = compile([libInput, angularFiles], {useSummaries: true});
const {genFiles} = compile([libOutDir, appInput, angularFiles], {useSummaries: true});
return genFiles.find(gf => gf.srcFileUrl === '/app/main.ts');
}
it('should inherit ctor and lifecycle hooks from classes in other compilation units',
async(() => {
const libInput: MockDirectory = {
'lib': {
'base.ts': `
it('should inherit ctor and lifecycle hooks from classes in other compilation units', () => {
const libInput: MockDirectory = {
'lib': {
'base.ts': `
export class AParam {}
export class Base {
@ -393,11 +374,11 @@ describe('compiler (unbundled Angular)', () => {
ngOnDestroy() {}
}
`
}
};
const appInput: MockDirectory = {
'app': {
'main.ts': `
}
};
const appInput: MockDirectory = {
'app': {
'main.ts': `
import {NgModule, Component} from '@angular/core';
import {Base} from '../lib/base';
@ -409,21 +390,19 @@ describe('compiler (unbundled Angular)', () => {
})
export class MyModule {}
`
}
};
}
};
compile([libInput, angularFiles], {useSummaries: true})
.then(({outDir}) => compile([outDir, appInput, angularFiles], {useSummaries: true}))
.then(({genFiles}) => {
const mainNgFactory = genFiles.find(gf => gf.srcFileUrl === '/app/main.ts');
const flags = NodeFlags.TypeDirective | NodeFlags.Component | NodeFlags.OnDestroy;
expect(toTypeScript(mainNgFactory))
.toContain(`${flags},(null as any),0,i1.Extends,[i2.AParam]`);
});
}));
const {outDir: libOutDir} = compile([libInput, angularFiles], {useSummaries: true});
const {genFiles} = compile([libOutDir, appInput, angularFiles], {useSummaries: true});
const mainNgFactory = genFiles.find(gf => gf.srcFileUrl === '/app/main.ts');
const flags = NodeFlags.TypeDirective | NodeFlags.Component | NodeFlags.OnDestroy;
expect(toTypeScript(mainNgFactory))
.toContain(`${flags},(null as any),0,i1.Extends,[i2.AParam]`);
});
it('should inherit ctor and lifecycle hooks from classes in other compilation units over 2 levels',
async(() => {
() => {
const lib1Input: MockDirectory = {
'lib1': {
'base.ts': `
@ -463,135 +442,134 @@ describe('compiler (unbundled Angular)', () => {
`
}
};
compile([lib1Input, angularFiles], {useSummaries: true})
.then(({outDir}) => compile([outDir, lib2Input, angularFiles], {useSummaries: true}))
.then(({outDir}) => compile([outDir, appInput, angularFiles], {useSummaries: true}))
.then(({genFiles}) => {
const mainNgFactory = genFiles.find(gf => gf.srcFileUrl === '/app/main.ts');
const flags = NodeFlags.TypeDirective | NodeFlags.Component | NodeFlags.OnDestroy;
expect(toTypeScript(mainNgFactory))
.toContain(`${flags},(null as any),0,i1.Extends,[i2.AParam_2]`);
});
}));
const {outDir: lib1OutDir} = compile([lib1Input, angularFiles], {useSummaries: true});
const {outDir: lib2OutDir} =
compile([lib1OutDir, lib2Input, angularFiles], {useSummaries: true});
const {genFiles} = compile([lib2OutDir, appInput, angularFiles], {useSummaries: true});
const mainNgFactory = genFiles.find(gf => gf.srcFileUrl === '/app/main.ts');
const flags = NodeFlags.TypeDirective | NodeFlags.Component | NodeFlags.OnDestroy;
expect(toTypeScript(mainNgFactory))
.toContain(`${flags},(null as any),0,i1.Extends,[i2.AParam_2]`);
});
describe('Injectable', () => {
it('should allow to inherit', async(() => {
compileParentAndChild({
parentClassDecorator: '@Injectable()',
parentModuleDecorator: '@NgModule({providers: [Base]})',
childClassDecorator: '@Injectable()',
childModuleDecorator: '@NgModule({providers: [Extends]})',
}).then((mainNgFactory) => { expect(mainNgFactory).toBeTruthy(); });
}));
it('should allow to inherit', () => {
const mainNgFactory = compileParentAndChild({
parentClassDecorator: '@Injectable()',
parentModuleDecorator: '@NgModule({providers: [Base]})',
childClassDecorator: '@Injectable()',
childModuleDecorator: '@NgModule({providers: [Extends]})',
});
expect(mainNgFactory).toBeTruthy();
});
it('should error if the child class has no matching decorator', async(() => {
compileParentAndChild({
parentClassDecorator: '@Injectable()',
parentModuleDecorator: '@NgModule({providers: [Base]})',
childClassDecorator: '',
childModuleDecorator: '@NgModule({providers: [Extends]})',
}).then(fail, (e) => {
expect(e.message).toContain(
'Class Extends in /app/main.ts extends from a Injectable in another compilation unit without duplicating the decorator. ' +
'Please add a Injectable or Pipe or Directive or Component or NgModule decorator to the class.');
});
}));
it('should error if the child class has no matching decorator', () => {
expect(() => compileParentAndChild({
parentClassDecorator: '@Injectable()',
parentModuleDecorator: '@NgModule({providers: [Base]})',
childClassDecorator: '',
childModuleDecorator: '@NgModule({providers: [Extends]})',
}))
.toThrowError(
'Class Extends in /app/main.ts extends from a Injectable in another compilation unit without duplicating the decorator. ' +
'Please add a Injectable or Pipe or Directive or Component or NgModule decorator to the class.');
});
});
describe('Component', () => {
it('should allow to inherit', async(() => {
compileParentAndChild({
parentClassDecorator: `@Component({template: ''})`,
parentModuleDecorator: '@NgModule({declarations: [Base]})',
childClassDecorator: `@Component({template: ''})`,
childModuleDecorator: '@NgModule({declarations: [Extends]})',
}).then((mainNgFactory) => { expect(mainNgFactory).toBeTruthy(); });
}));
it('should allow to inherit', () => {
const mainNgFactory = compileParentAndChild({
parentClassDecorator: `@Component({template: ''})`,
parentModuleDecorator: '@NgModule({declarations: [Base]})',
childClassDecorator: `@Component({template: ''})`,
childModuleDecorator: '@NgModule({declarations: [Extends]})'
});
expect(mainNgFactory).toBeTruthy();
});
it('should error if the child class has no matching decorator', async(() => {
compileParentAndChild({
parentClassDecorator: `@Component({template: ''})`,
parentModuleDecorator: '@NgModule({declarations: [Base]})',
childClassDecorator: '',
childModuleDecorator: '@NgModule({declarations: [Extends]})',
}).then(fail, (e) => {
expect(e.message).toContain(
'Class Extends in /app/main.ts extends from a Directive in another compilation unit without duplicating the decorator. ' +
'Please add a Directive or Component decorator to the class.');
});
}));
it('should error if the child class has no matching decorator', () => {
expect(() => compileParentAndChild({
parentClassDecorator: `@Component({template: ''})`,
parentModuleDecorator: '@NgModule({declarations: [Base]})',
childClassDecorator: '',
childModuleDecorator: '@NgModule({declarations: [Extends]})',
}))
.toThrowError(
'Class Extends in /app/main.ts extends from a Directive in another compilation unit without duplicating the decorator. ' +
'Please add a Directive or Component decorator to the class.');
});
});
describe('Directive', () => {
it('should allow to inherit', async(() => {
compileParentAndChild({
parentClassDecorator: `@Directive({selector: '[someDir]'})`,
parentModuleDecorator: '@NgModule({declarations: [Base]})',
childClassDecorator: `@Directive({selector: '[someDir]'})`,
childModuleDecorator: '@NgModule({declarations: [Extends]})',
}).then((mainNgFactory) => { expect(mainNgFactory).toBeTruthy(); });
}));
it('should allow to inherit', () => {
const mainNgFactory = compileParentAndChild({
parentClassDecorator: `@Directive({selector: '[someDir]'})`,
parentModuleDecorator: '@NgModule({declarations: [Base]})',
childClassDecorator: `@Directive({selector: '[someDir]'})`,
childModuleDecorator: '@NgModule({declarations: [Extends]})',
});
expect(mainNgFactory).toBeTruthy();
});
it('should error if the child class has no matching decorator', async(() => {
compileParentAndChild({
parentClassDecorator: `@Directive({selector: '[someDir]'})`,
parentModuleDecorator: '@NgModule({declarations: [Base]})',
childClassDecorator: '',
childModuleDecorator: '@NgModule({declarations: [Extends]})',
}).then(fail, (e) => {
expect(e.message).toContain(
'Class Extends in /app/main.ts extends from a Directive in another compilation unit without duplicating the decorator. ' +
'Please add a Directive or Component decorator to the class.');
});
}));
it('should error if the child class has no matching decorator', () => {
expect(() => compileParentAndChild({
parentClassDecorator: `@Directive({selector: '[someDir]'})`,
parentModuleDecorator: '@NgModule({declarations: [Base]})',
childClassDecorator: '',
childModuleDecorator: '@NgModule({declarations: [Extends]})',
}))
.toThrowError(
'Class Extends in /app/main.ts extends from a Directive in another compilation unit without duplicating the decorator. ' +
'Please add a Directive or Component decorator to the class.');
});
});
describe('Pipe', () => {
it('should allow to inherit', async(() => {
compileParentAndChild({
parentClassDecorator: `@Pipe({name: 'somePipe'})`,
parentModuleDecorator: '@NgModule({declarations: [Base]})',
childClassDecorator: `@Pipe({name: 'somePipe'})`,
childModuleDecorator: '@NgModule({declarations: [Extends]})',
}).then((mainNgFactory) => { expect(mainNgFactory).toBeTruthy(); });
}));
it('should allow to inherit', () => {
const mainNgFactory = compileParentAndChild({
parentClassDecorator: `@Pipe({name: 'somePipe'})`,
parentModuleDecorator: '@NgModule({declarations: [Base]})',
childClassDecorator: `@Pipe({name: 'somePipe'})`,
childModuleDecorator: '@NgModule({declarations: [Extends]})',
});
expect(mainNgFactory).toBeTruthy();
});
it('should error if the child class has no matching decorator', async(() => {
compileParentAndChild({
parentClassDecorator: `@Pipe({name: 'somePipe'})`,
parentModuleDecorator: '@NgModule({declarations: [Base]})',
childClassDecorator: '',
childModuleDecorator: '@NgModule({declarations: [Extends]})',
}).then(fail, (e) => {
expect(e.message).toContain(
'Class Extends in /app/main.ts extends from a Pipe in another compilation unit without duplicating the decorator. ' +
'Please add a Pipe decorator to the class.');
});
}));
it('should error if the child class has no matching decorator', () => {
expect(() => compileParentAndChild({
parentClassDecorator: `@Pipe({name: 'somePipe'})`,
parentModuleDecorator: '@NgModule({declarations: [Base]})',
childClassDecorator: '',
childModuleDecorator: '@NgModule({declarations: [Extends]})',
}))
.toThrowError(
'Class Extends in /app/main.ts extends from a Pipe in another compilation unit without duplicating the decorator. ' +
'Please add a Pipe decorator to the class.');
});
});
describe('NgModule', () => {
it('should allow to inherit', async(() => {
compileParentAndChild({
parentClassDecorator: `@NgModule()`,
parentModuleDecorator: '',
childClassDecorator: `@NgModule()`,
childModuleDecorator: '',
}).then((mainNgFactory) => { expect(mainNgFactory).toBeTruthy(); });
}));
it('should allow to inherit', () => {
const mainNgFactory = compileParentAndChild({
parentClassDecorator: `@NgModule()`,
parentModuleDecorator: '',
childClassDecorator: `@NgModule()`,
childModuleDecorator: '',
});
expect(mainNgFactory).toBeTruthy();
});
it('should error if the child class has no matching decorator', async(() => {
compileParentAndChild({
parentClassDecorator: `@NgModule()`,
parentModuleDecorator: '',
childClassDecorator: '',
childModuleDecorator: '',
}).then(fail, (e) => {
expect(e.message).toContain(
'Class Extends in /app/main.ts extends from a NgModule in another compilation unit without duplicating the decorator. ' +
'Please add a NgModule decorator to the class.');
});
}));
it('should error if the child class has no matching decorator', () => {
expect(() => compileParentAndChild({
parentClassDecorator: `@NgModule()`,
parentModuleDecorator: '',
childClassDecorator: '',
childModuleDecorator: '',
}))
.toThrowError(
'Class Extends in /app/main.ts extends from a NgModule in another compilation unit without duplicating the decorator. ' +
'Please add a NgModule decorator to the class.');
});
});
});
});
@ -624,11 +602,11 @@ describe('compiler (bundled Angular)', () => {
});
describe('Quickstart', () => {
it('should compile', async(() => compile([QUICKSTART, angularFiles]).then(({genFiles}) => {
expect(genFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl)))
.toBeDefined();
expect(genFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl))).toBeDefined();
})));
it('should compile', () => {
const {genFiles} = compile([QUICKSTART, angularFiles]);
expect(genFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl))).toBeDefined();
expect(genFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl))).toBeDefined();
});
});
describe('Bundled library', () => {
@ -662,7 +640,7 @@ describe('compiler (bundled Angular)', () => {
({fileName, content}) => ({fileName: `/node_modules${fileName}`, content})));
});
it('should compile', async(() => compile([LIBRARY_USING_APP, libraryFiles, angularFiles])));
it('should compile', () => compile([LIBRARY_USING_APP, libraryFiles, angularFiles]));
});
});
@ -767,8 +745,3 @@ const LIBRARY_USING_APP: MockDirectory = {
}
}
};
function expectPromiseToThrow(p: Promise<any>, msg: RegExp) {
p.then(
() => { throw new Error('Expected to throw'); }, (e) => { expect(e.message).toMatch(msg); });
}

View File

@ -7,7 +7,6 @@
*/
import {AotCompiler, AotCompilerHost, AotCompilerOptions, CompileSummaryKind, GeneratedFile, createAotCompiler, toTypeScript} from '@angular/compiler';
import {fakeAsync, tick} from '@angular/core/testing';
import {MockDirectory, compile, setup} from './test_util';
@ -16,19 +15,12 @@ describe('aot summaries for jit', () => {
function compileApp(rootDir: MockDirectory, options: {useSummaries?: boolean} = {}):
{genFiles: GeneratedFile[], outDir: MockDirectory} {
let result: {genFiles: GeneratedFile[], outDir: MockDirectory} = null !;
let error: Error|null = null;
compile([rootDir, angularFiles], options).then((r) => result = r, (e) => error = e);
tick();
if (error) {
throw error;
}
return result;
return compile([rootDir, angularFiles], options);
}
it('should create @Injectable summaries', fakeAsync(() => {
const appDir = {
'app.module.ts': `
it('should create @Injectable summaries', () => {
const appDir = {
'app.module.ts': `
import { Injectable } from '@angular/core';
export class Dep {}
@ -38,23 +30,23 @@ describe('aot summaries for jit', () => {
constructor(d: Dep) {}
}
`
};
const rootDir = {'app': appDir};
};
const rootDir = {'app': appDir};
const genFile =
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts');
const genFile =
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts');
const genSource = toTypeScript(genFile);
const genSource = toTypeScript(genFile);
expect(genSource).toContain(`import * as i0 from '/app/app.module'`);
expect(genSource).toContain('export function MyServiceNgSummary()');
// Note: CompileSummaryKind.Injectable = 3
expect(genSource).toMatch(/summaryKind:3,\s*type:\{\s*reference:i0.MyService/);
expect(genSource).toContain('token:{identifier:{reference:i0.Dep}}');
}));
expect(genSource).toContain(`import * as i0 from '/app/app.module'`);
expect(genSource).toContain('export function MyServiceNgSummary()');
// Note: CompileSummaryKind.Injectable = 3
expect(genSource).toMatch(/summaryKind:3,\s*type:\{\s*reference:i0.MyService/);
expect(genSource).toContain('token:{identifier:{reference:i0.Dep}}');
});
it('should create @Pipe summaries', fakeAsync(() => {
const appDir = {
'app.module.ts': `
it('should create @Pipe summaries', () => {
const appDir = {
'app.module.ts': `
import { Pipe, NgModule } from '@angular/core';
export class Dep {}
@ -67,23 +59,23 @@ describe('aot summaries for jit', () => {
@NgModule({declarations: [MyPipe]})
export class MyModule {}
`
};
const rootDir = {'app': appDir};
};
const rootDir = {'app': appDir};
const genFile =
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts');
const genFile =
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts');
const genSource = toTypeScript(genFile);
const genSource = toTypeScript(genFile);
expect(genSource).toContain(`import * as i0 from '/app/app.module'`);
expect(genSource).toContain('export function MyPipeNgSummary()');
// Note: CompileSummaryKind.Pipe = 1
expect(genSource).toMatch(/summaryKind:0,\s*type:\{\s*reference:i0.MyPipe/);
expect(genSource).toContain('token:{identifier:{reference:i0.Dep}}');
}));
expect(genSource).toContain(`import * as i0 from '/app/app.module'`);
expect(genSource).toContain('export function MyPipeNgSummary()');
// Note: CompileSummaryKind.Pipe = 1
expect(genSource).toMatch(/summaryKind:0,\s*type:\{\s*reference:i0.MyPipe/);
expect(genSource).toContain('token:{identifier:{reference:i0.Dep}}');
});
it('should create @Directive summaries', fakeAsync(() => {
const appDir = {
'app.module.ts': `
it('should create @Directive summaries', () => {
const appDir = {
'app.module.ts': `
import { Directive, NgModule } from '@angular/core';
export class Dep {}
@ -96,23 +88,23 @@ describe('aot summaries for jit', () => {
@NgModule({declarations: [MyDirective]})
export class MyModule {}
`
};
const rootDir = {'app': appDir};
};
const rootDir = {'app': appDir};
const genFile =
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts');
const genFile =
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts');
const genSource = toTypeScript(genFile);
const genSource = toTypeScript(genFile);
expect(genSource).toContain(`import * as i0 from '/app/app.module'`);
expect(genSource).toContain('export function MyDirectiveNgSummary()');
// Note: CompileSummaryKind.Directive = 1
expect(genSource).toMatch(/summaryKind:1,\s*type:\{\s*reference:i0.MyDirective/);
expect(genSource).toContain('token:{identifier:{reference:i0.Dep}}');
}));
expect(genSource).toContain(`import * as i0 from '/app/app.module'`);
expect(genSource).toContain('export function MyDirectiveNgSummary()');
// Note: CompileSummaryKind.Directive = 1
expect(genSource).toMatch(/summaryKind:1,\s*type:\{\s*reference:i0.MyDirective/);
expect(genSource).toContain('token:{identifier:{reference:i0.Dep}}');
});
it('should create @NgModule summaries', fakeAsync(() => {
const appDir = {
'app.module.ts': `
it('should create @NgModule summaries', () => {
const appDir = {
'app.module.ts': `
import { NgModule } from '@angular/core';
export class Dep {}
@ -122,23 +114,23 @@ describe('aot summaries for jit', () => {
constructor(d: Dep) {}
}
`
};
const rootDir = {'app': appDir};
};
const rootDir = {'app': appDir};
const genFile =
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts');
const genFile =
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts');
const genSource = toTypeScript(genFile);
const genSource = toTypeScript(genFile);
expect(genSource).toContain(`import * as i0 from '/app/app.module'`);
expect(genSource).toContain('export function MyModuleNgSummary()');
// Note: CompileSummaryKind.NgModule = 2
expect(genSource).toMatch(/summaryKind:2,\s*type:\{\s*reference:i0.MyModule/);
expect(genSource).toContain('token:{identifier:{reference:i0.Dep}}');
}));
expect(genSource).toContain(`import * as i0 from '/app/app.module'`);
expect(genSource).toContain('export function MyModuleNgSummary()');
// Note: CompileSummaryKind.NgModule = 2
expect(genSource).toMatch(/summaryKind:2,\s*type:\{\s*reference:i0.MyModule/);
expect(genSource).toContain('token:{identifier:{reference:i0.Dep}}');
});
it('should embed useClass provider summaries in @Directive summaries', fakeAsync(() => {
const appDir = {
'app.service.ts': `
it('should embed useClass provider summaries in @Directive summaries', () => {
const appDir = {
'app.service.ts': `
import { Injectable } from '@angular/core';
export class Dep {}
@ -148,7 +140,7 @@ describe('aot summaries for jit', () => {
constructor(d: Dep) {}
}
`,
'app.module.ts': `
'app.module.ts': `
import { Directive, NgModule } from '@angular/core';
import { MyService } from './app.service';
@ -161,22 +153,22 @@ describe('aot summaries for jit', () => {
@NgModule({declarations: [MyDirective]})
export class MyModule {}
`
};
const rootDir = {'app': appDir};
};
const rootDir = {'app': appDir};
const genFile =
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts');
const genFile =
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts');
const genSource = toTypeScript(genFile);
const genSource = toTypeScript(genFile);
expect(genSource).toMatch(/useClass:\{\s*reference:i1.MyService/);
// Note: CompileSummaryKind.Injectable = 3
expect(genSource).toMatch(/summaryKind:3,\s*type:\{\s*reference:i1.MyService/);
expect(genSource).toContain('token:{identifier:{reference:i1.Dep}}');
}));
expect(genSource).toMatch(/useClass:\{\s*reference:i1.MyService/);
// Note: CompileSummaryKind.Injectable = 3
expect(genSource).toMatch(/summaryKind:3,\s*type:\{\s*reference:i1.MyService/);
expect(genSource).toContain('token:{identifier:{reference:i1.Dep}}');
});
it('should embed useClass provider summaries into @NgModule summaries', fakeAsync(() => {
const appDir = {
'app.service.ts': `
it('should embed useClass provider summaries into @NgModule summaries', () => {
const appDir = {
'app.service.ts': `
import { Injectable } from '@angular/core';
export class Dep {}
@ -186,7 +178,7 @@ describe('aot summaries for jit', () => {
constructor(d: Dep) {}
}
`,
'app.module.ts': `
'app.module.ts': `
import { NgModule } from '@angular/core';
import { MyService } from './app.service';
@ -195,23 +187,22 @@ describe('aot summaries for jit', () => {
})
export class MyModule {}
`
};
const rootDir = {'app': appDir};
};
const rootDir = {'app': appDir};
const genFile =
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts');
const genFile =
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts');
const genSource = toTypeScript(genFile);
const genSource = toTypeScript(genFile);
expect(genSource).toMatch(/useClass:\{\s*reference:i1.MyService/);
// Note: CompileSummaryKind.Injectable = 3
expect(genSource).toMatch(/summaryKind:3,\s*type:\{\s*reference:i1.MyService/);
expect(genSource).toContain('token:{identifier:{reference:i1.Dep}}');
}));
expect(genSource).toMatch(/useClass:\{\s*reference:i1.MyService/);
// Note: CompileSummaryKind.Injectable = 3
expect(genSource).toMatch(/summaryKind:3,\s*type:\{\s*reference:i1.MyService/);
expect(genSource).toContain('token:{identifier:{reference:i1.Dep}}');
});
it('should reference declared @Directive and @Pipe summaries in @NgModule summaries',
fakeAsync(() => {
const appDir = {
'app.module.ts': `
it('should reference declared @Directive and @Pipe summaries in @NgModule summaries', () => {
const appDir = {
'app.module.ts': `
import { Directive, Pipe, NgModule } from '@angular/core';
@Directive({selector: '[myDir]'})
@ -223,20 +214,20 @@ describe('aot summaries for jit', () => {
@NgModule({declarations: [MyDirective, MyPipe]})
export class MyModule {}
`
};
const rootDir = {'app': appDir};
};
const rootDir = {'app': appDir};
const genFile =
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts');
const genFile =
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts');
const genSource = toTypeScript(genFile);
const genSource = toTypeScript(genFile);
expect(genSource).toMatch(
/export function MyModuleNgSummary()[^;]*,\s*MyDirectiveNgSummary,\s*MyPipeNgSummary\s*\]\s*;/);
}));
expect(genSource).toMatch(
/export function MyModuleNgSummary()[^;]*,\s*MyDirectiveNgSummary,\s*MyPipeNgSummary\s*\]\s*;/);
});
it('should reference imported @NgModule summaries in @NgModule summaries', fakeAsync(() => {
const appDir = {
'app.module.ts': `
it('should reference imported @NgModule summaries in @NgModule summaries', () => {
const appDir = {
'app.module.ts': `
import { NgModule } from '@angular/core';
@NgModule()
@ -245,20 +236,20 @@ describe('aot summaries for jit', () => {
@NgModule({imports: [MyImportedModule]})
export class MyModule {}
`
};
const rootDir = {'app': appDir};
};
const rootDir = {'app': appDir};
const genFile =
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts');
const genFile =
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts');
const genSource = toTypeScript(genFile);
const genSource = toTypeScript(genFile);
expect(genSource).toMatch(
/export function MyModuleNgSummary()[^;]*,\s*MyImportedModuleNgSummary\s*\]\s*;/);
}));
expect(genSource).toMatch(
/export function MyModuleNgSummary()[^;]*,\s*MyImportedModuleNgSummary\s*\]\s*;/);
});
it('should create and use reexports for imported NgModules ' +
'accross compilation units',
fakeAsync(() => {
() => {
const lib1In = {
'lib1': {
'module.ts': `
@ -348,5 +339,5 @@ describe('aot summaries for jit', () => {
expect(toTypeScript(lib3ReexportNgSummary))
.toContain(
`export {ReexportModule_2NgSummary as ReexportModule_3NgSummary} from '/lib2/reexport.ngsummary'`);
}));
});
});

View File

@ -5,16 +5,15 @@
* 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 {async} from '@angular/core/testing';
import {MockDirectory, compile, expectNoDiagnostics, setup} from './test_util';
describe('regressions', () => {
let angularFiles = setup();
it('should compile components with empty templates', async(() => {
const appDir = {
'app.module.ts': `
it('should compile components with empty templates', () => {
const appDir = {
'app.module.ts': `
import { Component, NgModule } from '@angular/core';
@Component({template: ''})
@ -23,14 +22,11 @@ describe('regressions', () => {
@NgModule({declarations: [EmptyComp]})
export class MyModule {}
`
};
const rootDir = {'app': appDir};
compile([rootDir, angularFiles], {postCompile: expectNoDiagnostics}, {
noUnusedLocals: true,
noUnusedParameters: true
}).then((result) => {
expect(result.genFiles.find((f) => f.genFileUrl === '/app/app.module.ngfactory.ts'))
.toBeTruthy();
});
}));
};
const rootDir = {'app': appDir};
const {genFiles} = compile(
[rootDir, angularFiles], {postCompile: expectNoDiagnostics},
{noUnusedLocals: true, noUnusedParameters: true});
expect(genFiles.find((f) => f.genFileUrl === '/app/app.module.ngfactory.ts')).toBeTruthy();
});
});

View File

@ -399,11 +399,11 @@ export class MockAotCompilerHost implements AotCompilerHost {
return importedFile.replace(EXT, '');
}
loadResource(path: string): Promise<string> {
loadResource(path: string): string {
if (this.tsHost.fileExists(path)) {
return Promise.resolve(this.tsHost.readFile(path));
return this.tsHost.readFile(path);
} else {
return Promise.reject(new Error(`Resource ${path} not found.`))
throw new Error(`Resource ${path} not found.`);
}
}
}
@ -604,47 +604,43 @@ export function compile(
preCompile?: (program: ts.Program) => void,
postCompile?: (program: ts.Program) => void,
}& AotCompilerOptions = {},
tsOptions: ts.CompilerOptions = {}):
Promise<{genFiles: GeneratedFile[], outDir: MockDirectory}> {
// Make sure we always return errors via the promise...
return Promise.resolve(null).then(() => {
// when using summaries, always emit so the next step can use the results.
const emit = options.emit || options.useSummaries;
const preCompile = options.preCompile || expectNoDiagnostics;
const postCompile = options.postCompile || expectNoDiagnostics;
const rootDirArr = toMockFileArray(rootDirs);
const scriptNames = rootDirArr.map(entry => entry.fileName).filter(isSource);
tsOptions: ts.CompilerOptions = {}): {genFiles: GeneratedFile[], outDir: MockDirectory} {
// when using summaries, always emit so the next step can use the results.
const emit = options.emit || options.useSummaries;
const preCompile = options.preCompile || expectNoDiagnostics;
const postCompile = options.postCompile || expectNoDiagnostics;
const rootDirArr = toMockFileArray(rootDirs);
const scriptNames = rootDirArr.map(entry => entry.fileName).filter(isSource);
const host = new MockCompilerHost(scriptNames, arrayToMockDir(rootDirArr));
const aotHost = new MockAotCompilerHost(host);
if (options.useSummaries) {
aotHost.hideMetadata();
aotHost.tsFilesOnly();
const host = new MockCompilerHost(scriptNames, arrayToMockDir(rootDirArr));
const aotHost = new MockAotCompilerHost(host);
if (options.useSummaries) {
aotHost.hideMetadata();
aotHost.tsFilesOnly();
}
const tsSettings = {...settings, ...tsOptions};
const program = ts.createProgram(host.scriptNames.slice(0), tsSettings, host);
if (preCompile) preCompile(program);
const {compiler, reflector} = createAotCompiler(aotHost, options);
const genFiles = compiler.compileAllSync(program.getSourceFiles().map(sf => sf.fileName));
genFiles.forEach((file) => {
const source = file.source || toTypeScript(file);
if (isSource(file.genFileUrl)) {
host.addScript(file.genFileUrl, source);
} else {
host.override(file.genFileUrl, source);
}
const tsSettings = {...settings, ...tsOptions};
const scripts = host.scriptNames.slice(0);
const program = ts.createProgram(scripts, tsSettings, host);
if (preCompile) preCompile(program);
const {compiler, reflector} = createAotCompiler(aotHost, options);
return compiler.compileAll(program.getSourceFiles().map(sf => sf.fileName)).then(genFiles => {
genFiles.forEach((file) => {
const source = file.source || toTypeScript(file);
isSource(file.genFileUrl) ? host.addScript(file.genFileUrl, source) :
host.override(file.genFileUrl, source);
});
const scripts = host.scriptNames.slice(0);
const newProgram = ts.createProgram(scripts, tsSettings, host);
if (postCompile) postCompile(newProgram);
if (emit) {
newProgram.emit();
}
let outDir: MockDirectory = {};
if (emit) {
outDir = arrayToMockDir(toMockFileArray([
host.writtenFiles, host.overrides
]).filter((entry) => !isSource(entry.fileName)));
}
return {genFiles, outDir};
});
});
const newProgram = ts.createProgram(host.scriptNames.slice(0), tsSettings, host);
if (postCompile) postCompile(newProgram);
if (emit) {
newProgram.emit();
}
let outDir: MockDirectory = {};
if (emit) {
outDir = arrayToMockDir(toMockFileArray([
host.writtenFiles, host.overrides
]).filter((entry) => !isSource(entry.fileName)));
}
return {genFiles, outDir};
}

View File

@ -16,7 +16,7 @@ import {ViewEncapsulation} from '@angular/core/src/metadata/view';
import {TestBed} from '@angular/core/testing';
import {AsyncTestCompleter, beforeEach, describe, expect, inject, it} from '@angular/core/testing/src/testing_internal';
import {SyncAsyncResult, noUndefined} from '../src/util';
import {noUndefined} from '../src/util';
import {SpyResourceLoader} from './spies';
@ -46,7 +46,7 @@ function normalizeTemplate(normalizer: DirectiveNormalizer, o: {
});
}
function normalizeTemplateAsync(normalizer: DirectiveNormalizer, o: {
function normalizeTemplateOnly(normalizer: DirectiveNormalizer, o: {
ngModuleType?: any; componentType?: any; moduleUrl?: string; template?: string | null;
templateUrl?: string | null;
styles?: string[];
@ -55,30 +55,7 @@ function normalizeTemplateAsync(normalizer: DirectiveNormalizer, o: {
encapsulation?: ViewEncapsulation | null;
animations?: CompileAnimationEntryMetadata[];
}) {
return normalizer.normalizeTemplateAsync({
ngModuleType: noUndefined(o.ngModuleType),
componentType: noUndefined(o.componentType),
moduleUrl: noUndefined(o.moduleUrl),
template: noUndefined(o.template),
templateUrl: noUndefined(o.templateUrl),
styles: noUndefined(o.styles),
styleUrls: noUndefined(o.styleUrls),
interpolation: noUndefined(o.interpolation),
encapsulation: noUndefined(o.encapsulation),
animations: noUndefined(o.animations)
});
}
function normalizeTemplateSync(normalizer: DirectiveNormalizer, o: {
ngModuleType?: any; componentType?: any; moduleUrl?: string; template?: string | null;
templateUrl?: string | null;
styles?: string[];
styleUrls?: string[];
interpolation?: [string, string] | null;
encapsulation?: ViewEncapsulation | null;
animations?: CompileAnimationEntryMetadata[];
}): CompileTemplateMetadata {
return normalizer.normalizeTemplateSync({
return normalizer.normalizeTemplateOnly({
ngModuleType: noUndefined(o.ngModuleType),
componentType: noUndefined(o.componentType),
moduleUrl: noUndefined(o.moduleUrl),
@ -194,10 +171,10 @@ export function main() {
}));
});
describe('normalizeTemplateSync', () => {
describe('normalizeTemplateOnly sync', () => {
it('should store the template',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
const template = normalizeTemplateSync(normalizer, {
const template = <CompileTemplateMetadata>normalizeTemplateOnly(normalizer, {
ngModuleType: null,
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
@ -214,7 +191,7 @@ export function main() {
it('should resolve styles on the annotation against the moduleUrl',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
const template = normalizeTemplateSync(normalizer, {
const template = <CompileTemplateMetadata>normalizeTemplateOnly(normalizer, {
ngModuleType: null,
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
@ -229,7 +206,7 @@ export function main() {
it('should resolve styles in the template against the moduleUrl',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
const template = normalizeTemplateSync(normalizer, {
const template = <CompileTemplateMetadata>normalizeTemplateOnly(normalizer, {
ngModuleType: null,
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
@ -244,7 +221,7 @@ export function main() {
it('should use ViewEncapsulation.Emulated by default',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
const template = normalizeTemplateSync(normalizer, {
const template = <CompileTemplateMetadata>normalizeTemplateOnly(normalizer, {
ngModuleType: null,
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
@ -262,7 +239,7 @@ export function main() {
[CompilerConfig, DirectiveNormalizer],
(config: CompilerConfig, normalizer: DirectiveNormalizer) => {
config.defaultEncapsulation = ViewEncapsulation.None;
const template = normalizeTemplateSync(normalizer, {
const template = <CompileTemplateMetadata>normalizeTemplateOnly(normalizer, {
ngModuleType: null,
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
@ -284,7 +261,7 @@ export function main() {
(async: AsyncTestCompleter, normalizer: DirectiveNormalizer,
resourceLoader: MockResourceLoader) => {
resourceLoader.expect('package:some/module/sometplurl.html', 'a');
normalizeTemplateAsync(normalizer, {
(<Promise<CompileTemplateMetadata>>normalizeTemplateOnly(normalizer, {
ngModuleType: null,
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
@ -293,7 +270,7 @@ export function main() {
templateUrl: 'sometplurl.html',
styles: [],
styleUrls: ['test.css']
}).then((template: CompileTemplateMetadata) => {
})).then((template) => {
expect(template.template).toEqual('a');
expect(template.templateUrl).toEqual('package:some/module/sometplurl.html');
expect(template.isInline).toBe(false);
@ -308,7 +285,7 @@ export function main() {
(async: AsyncTestCompleter, normalizer: DirectiveNormalizer,
resourceLoader: MockResourceLoader) => {
resourceLoader.expect('package:some/module/tpl/sometplurl.html', '');
normalizeTemplateAsync(normalizer, {
(<Promise<CompileTemplateMetadata>>normalizeTemplateOnly(normalizer, {
ngModuleType: null,
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
@ -317,7 +294,7 @@ export function main() {
templateUrl: 'tpl/sometplurl.html',
styles: [],
styleUrls: ['test.css']
}).then((template: CompileTemplateMetadata) => {
})).then((template) => {
expect(template.styleUrls).toEqual(['package:some/module/test.css']);
async.done();
});
@ -331,7 +308,7 @@ export function main() {
resourceLoader: MockResourceLoader) => {
resourceLoader.expect(
'package:some/module/tpl/sometplurl.html', '<style>@import test.css</style>');
normalizeTemplateAsync(normalizer, {
(<Promise<CompileTemplateMetadata>>normalizeTemplateOnly(normalizer, {
ngModuleType: null,
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
@ -340,7 +317,7 @@ export function main() {
templateUrl: 'tpl/sometplurl.html',
styles: [],
styleUrls: []
}).then((template: CompileTemplateMetadata) => {
})).then((template) => {
expect(template.styleUrls).toEqual(['package:some/module/tpl/test.css']);
async.done();
});
@ -362,13 +339,13 @@ export function main() {
(async: AsyncTestCompleter, normalizer: DirectiveNormalizer,
resourceLoader: SpyResourceLoader) => {
programResourceLoaderSpy(resourceLoader, {'package:some/module/test.css': 'a'});
normalizer
.normalizeExternalStylesheets(compileTemplateMetadata({
template: '',
templateUrl: '',
styleUrls: ['package:some/module/test.css']
}))
.then((template: CompileTemplateMetadata) => {
(<Promise<CompileTemplateMetadata>>normalizer.normalizeExternalStylesheets(
compileTemplateMetadata({
template: '',
templateUrl: '',
styleUrls: ['package:some/module/test.css']
})))
.then((template) => {
expect(template.externalStylesheets.length).toBe(1);
expect(template.externalStylesheets[0]).toEqual(new CompileStylesheetMetadata({
moduleUrl: 'package:some/module/test.css',
@ -388,13 +365,13 @@ export function main() {
'package:some/module/test.css': 'a@import "test2.css"',
'package:some/module/test2.css': 'b'
});
normalizer
.normalizeExternalStylesheets(compileTemplateMetadata({
template: '',
templateUrl: '',
styleUrls: ['package:some/module/test.css']
}))
.then((template: CompileTemplateMetadata) => {
(<Promise<CompileTemplateMetadata>>normalizer.normalizeExternalStylesheets(
compileTemplateMetadata({
template: '',
templateUrl: '',
styleUrls: ['package:some/module/test.css']
})))
.then((template) => {
expect(template.externalStylesheets.length).toBe(2);
expect(template.externalStylesheets[0]).toEqual(new CompileStylesheetMetadata({
moduleUrl: 'package:some/module/test.css',
@ -426,8 +403,8 @@ export function main() {
};
Promise
.all([
normalizeTemplateAsync(normalizer, prenormMeta),
normalizeTemplateAsync(normalizer, prenormMeta)
normalizeTemplateOnly(normalizer, prenormMeta),
normalizeTemplateOnly(normalizer, prenormMeta)
])
.then((templates: CompileTemplateMetadata[]) => {
expect(templates[0].template).toEqual('a');

View File

@ -7,18 +7,10 @@
*/
import {fakeAsync} from '@angular/core/testing/src/fake_async';
import {SyncAsyncResult, escapeRegExp, splitAtColon, utf8Encode} from '../src/util';
import {SyncAsync, escapeRegExp, splitAtColon, utf8Encode} from '../src/util';
export function main() {
describe('util', () => {
describe('SyncAsyncResult', () => {
it('async value should default to Promise.resolve(syncValue)', fakeAsync(() => {
const syncValue = {};
const sar = new SyncAsyncResult(syncValue);
sar.asyncResult !.then((v: any) => expect(v).toBe(syncValue));
}));
});
describe('splitAtColon', () => {
it('should split when a single ":" is present', () => {
expect(splitAtColon('a:b', [])).toEqual(['a', 'b']);