angular-cn/packages/compiler/test/aot/compiler_spec.ts
Tobias Bosch 01f711281c fix(compiler): don’t use ng:// in AOT source maps, and never point to the original source file
This is important to not confuse users nor downstream tools that
consume our source maps. For generated content for which we don’t
have an original source file, we use the generated file now.

Fixes #19538
2017-10-04 16:20:55 -07:00

947 lines
33 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 {AotSummaryResolver, GeneratedFile, StaticSymbolCache, StaticSymbolResolver, toTypeScript} from '@angular/compiler';
import {MetadataBundler} from '@angular/compiler-cli/src/metadata/bundler';
import {privateEntriesToIndex} from '@angular/compiler-cli/src/metadata/index_writer';
import {NodeFlags} from '@angular/core/src/view/index';
import * as ts from 'typescript';
import {extractSourceMap, originalPositionFor} from '../output/source_map_util';
import {EmittingCompilerHost, MockAotCompilerHost, MockCompilerHost, MockDirectory, MockMetadataBundlerHost, arrayToMockDir, compile, expectNoDiagnostics, settings, setup, toMockFileArray} from './test_util';
describe('compiler (unbundled Angular)', () => {
let angularFiles = setup();
describe('Quickstart', () => {
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', () => {
const componentPath = '/app/app.component.ts';
const ngFactoryPath = '/app/app.component.ngfactory.ts';
let rootDir: MockDirectory;
let appDir: MockDirectory;
beforeEach(() => {
appDir = {
'app.module.ts': `
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
@NgModule({
declarations: [ AppComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule { }
`
};
rootDir = {'app': appDir};
});
function compileApp(): GeneratedFile {
const {genFiles} = compile([rootDir, angularFiles]);
return genFiles.find(
genFile => genFile.srcFileUrl === componentPath && genFile.genFileUrl.endsWith('.ts'));
}
function findLineAndColumn(
file: string, token: string): {line: number | null, column: number | null} {
const index = file.indexOf(token);
if (index === -1) {
return {line: null, column: null};
}
const linesUntilToken = file.slice(0, index).split('\n');
const line = linesUntilToken.length;
const column = linesUntilToken[linesUntilToken.length - 1].length;
return {line, column};
}
function createComponentSource(componentDecorator: string) {
return `
import { NgModule, Component } from '@angular/core';
@Component({
${componentDecorator}
})
export class AppComponent {
someMethod() {}
}
`;
}
describe('inline templates', () => {
const ngUrl = `${componentPath}.AppComponent.html`;
function templateDecorator(template: string) { return `template: \`${template}\`,`; }
declareTests({ngUrl, templateDecorator});
});
describe('external templates', () => {
const ngUrl = '/app/app.component.html';
const templateUrl = '/app/app.component.html';
function templateDecorator(template: string) {
appDir['app.component.html'] = template;
return `templateUrl: 'app.component.html',`;
}
declareTests({ngUrl, templateDecorator});
});
function declareTests({ngUrl, templateDecorator}:
{ngUrl: string, templateDecorator: (template: string) => string}) {
it('should use the right source url in html parse errors', () => {
appDir['app.component.ts'] = createComponentSource(templateDecorator('<div>\n </error>'));
expect(() => compileApp())
.toThrowError(new RegExp(`Template parse errors[\\s\\S]*${ngUrl}@1:2`));
});
it('should use the right source url in template parse errors', () => {
appDir['app.component.ts'] =
createComponentSource(templateDecorator('<div>\n <div unknown="{{ctxProp}}"></div>'));
expect(() => compileApp())
.toThrowError(new RegExp(`Template parse errors[\\s\\S]*${ngUrl}@1:7`));
});
it('should create a sourceMap for the template', () => {
const template = 'Hello World!';
appDir['app.component.ts'] = createComponentSource(templateDecorator(template));
const genFile = compileApp();
const genSource = toTypeScript(genFile);
const sourceMap = extractSourceMap(genSource) !;
expect(sourceMap.file).toEqual(genFile.genFileUrl);
// Note: the generated file also contains code that is not mapped to
// the template (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(ngFactoryPath);
expect(sourceMap.sourcesContent[sourceIndex]).toBe(' ');
});
it('should map elements correctly to the source', () => {
const template = '<div>\n <span></span></div>';
appDir['app.component.ts'] = createComponentSource(templateDecorator(template));
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', () => {
const template = `<div>\n <span [title]="someMethod()"></span></div>`;
appDir['app.component.ts'] = createComponentSource(templateDecorator(template));
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', () => {
const template = `<div>\n <span (click)="someMethod()"></span></div>`;
appDir['app.component.ts'] = createComponentSource(templateDecorator(template));
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 factory file', () => {
appDir['app.component.ts'] = createComponentSource(templateDecorator('Hello World!'));
const genFile = compileApp();
const genSource = toTypeScript(genFile);
const sourceMap = extractSourceMap(genSource) !;
expect(originalPositionFor(sourceMap, {line: 1, column: 0}))
.toEqual({line: 1, column: 0, source: ngFactoryPath});
});
}
});
describe('errors', () => {
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()
export class MyService {
constructor(a: boolean) {}
}
`
}
};
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 v6.x`);
});
it('should error if not all arguments of an @Injectable class can be resolved if strictInjectionParamters is true',
() => {
const FILES: MockDirectory = {
app: {
'app.ts': `
import {Injectable} from '@angular/core';
@Injectable()
export class MyService {
constructor(a: boolean) {}
}
`
}
};
const warnSpy = spyOn(console, 'warn');
expect(() => compile([FILES, angularFiles], {strictInjectionParameters: true}))
.toThrowError(`Can't resolve all parameters for MyService in /app/app.ts: (?).`);
expect(warnSpy).not.toHaveBeenCalled();
});
it('should be able to suppress a null access', () => {
const FILES: MockDirectory = {
app: {
'app.ts': `
import {Component, NgModule} from '@angular/core';
interface Person { name: string; }
@Component({
selector: 'my-comp',
template: '{{maybe_person!.name}}'
})
export class MyComp {
maybe_person?: Person;
}
@NgModule({
declarations: [MyComp]
})
export class MyModule {}
`
}
};
compile([FILES, angularFiles], {postCompile: expectNoDiagnostics});
});
it('should not contain a self import in factory', () => {
const FILES: MockDirectory = {
app: {
'app.ts': `
import {Component, NgModule} from '@angular/core';
interface Person { name: string; }
@Component({
selector: 'my-comp',
template: '{{person.name}}'
})
export class MyComp {
person: Person;
}
@NgModule({
declarations: [MyComp]
})
export class MyModule {}
`
}
};
compile([FILES, angularFiles], {
postCompile: program => {
const factorySource = program.getSourceFile('/app/app.ngfactory.ts');
expect(factorySource.text).not.toContain('\'/app/app.ngfactory\'');
}
});
});
});
it('should report when a component is declared in any module', () => {
const FILES: MockDirectory = {
app: {
'app.ts': `
import {Component, NgModule} from '@angular/core';
@Component({selector: 'my-comp', template: ''})
export class MyComp {}
@NgModule({})
export class MyModule {}
`
}
};
expect(() => compile([FILES, angularFiles]))
.toThrowError(/Cannot determine the module for class MyComp/);
});
it('should add the preamble to generated files', () => {
const FILES: MockDirectory = {
app: {
'app.ts': `
import { NgModule, Component } from '@angular/core';
@Component({ template: '' })
export class AppComponent {}
@NgModule({ declarations: [ AppComponent ] })
export class AppModule { }
`
}
};
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);
});
it('should be able to use animation macro methods', () => {
const FILES = {
app: {
'app.ts': `
import {Component, NgModule} from '@angular/core';
import {trigger, state, style, transition, animate} from '@angular/animations';
export const EXPANSION_PANEL_ANIMATION_TIMING = '225ms cubic-bezier(0.4,0.0,0.2,1)';
@Component({
selector: 'app-component',
template: '<div></div>',
animations: [
trigger('bodyExpansion', [
state('collapsed', style({height: '0px'})),
state('expanded', style({height: '*'})),
transition('expanded <=> collapsed', animate(EXPANSION_PANEL_ANIMATION_TIMING)),
]),
trigger('displayMode', [
state('collapsed', style({margin: '0'})),
state('default', style({margin: '16px 0'})),
state('flat', style({margin: '0'})),
transition('flat <=> collapsed, default <=> collapsed, flat <=> default',
animate(EXPANSION_PANEL_ANIMATION_TIMING)),
]),
],
})
export class AppComponent { }
@NgModule({ declarations: [ AppComponent ] })
export class AppModule { }
`
}
};
compile([FILES, angularFiles]);
});
it('should detect an entry component via an indirection', () => {
const FILES = {
app: {
'app.ts': `
import {NgModule, ANALYZE_FOR_ENTRY_COMPONENTS} from '@angular/core';
import {AppComponent} from './app.component';
import {COMPONENT_VALUE, MyComponent} from './my-component';
@NgModule({
declarations: [ AppComponent, MyComponent ],
bootstrap: [ AppComponent ],
providers: [{
provide: ANALYZE_FOR_ENTRY_COMPONENTS,
multi: true,
useValue: COMPONENT_VALUE
}],
})
export class AppModule { }
`,
'app.component.ts': `
import {Component} from '@angular/core';
@Component({
selector: 'app-component',
template: '<div></div>',
})
export class AppComponent { }
`,
'my-component.ts': `
import {Component} from '@angular/core';
@Component({
selector: 'my-component',
template: '<div></div>',
})
export class MyComponent {}
export const COMPONENT_VALUE = [{a: 'b', component: MyComponent}];
`
}
};
const result = compile([FILES, angularFiles]);
const appModuleFactory =
result.genFiles.find(f => /my-component\.ngfactory/.test(f.genFileUrl));
expect(appModuleFactory).toBeDefined();
if (appModuleFactory) {
expect(toTypeScript(appModuleFactory)).toContain('MyComponentNgFactory');
}
});
describe('ComponentFactories', () => {
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({
selector: 'my-comp',
template: '<ng-content></ng-content><ng-content select="child"></ng-content>'
})
export class MyComp {
@Input('aInputName')
aInputProp: string;
@Output('aOutputName')
aOutputProp: any;
}
@NgModule({
declarations: [MyComp]
})
export class MyModule {}
`
}
};
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', () => {
const FILES: MockDirectory = {
app: {
'app.ts': `
import { NgModule, Component } from '@angular/core';
@Component({ template: '' })
export class AppComponent {}
@NgModule({ declarations: [ AppComponent ] })
export class AppModule { }
`
}
};
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('summaries', () => {
let angularSummaryFiles: MockDirectory;
beforeAll(() => {
angularSummaryFiles = compile(angularFiles, {useSummaries: false, emit: true}).outDir;
});
inheritanceWithSummariesSpecs(() => angularSummaryFiles);
it('should not reexport type symbols mentioned in constructors', () => {
const libInput: MockDirectory = {
'lib': {
'base.ts': `
export type AType = {};
export class AClass {
constructor(a: AType) {}
}
`
}
};
const appInput: MockDirectory = {
'app': {
'main.ts': `
export * from '../lib/base';
`
}
};
const {outDir: libOutDir} = compile([libInput, angularSummaryFiles], {useSummaries: true});
const {genFiles: appGenFiles} =
compile([appInput, libOutDir, angularSummaryFiles], {useSummaries: true});
const appNgFactory = appGenFiles.find((f) => f.genFileUrl === '/app/main.ngfactory.ts');
expect(toTypeScript(appNgFactory)).not.toContain('AType');
});
});
function inheritanceWithSummariesSpecs(getAngularSummaryFiles: () => MockDirectory) {
function compileParentAndChild(
{parentClassDecorator, parentModuleDecorator, childClassDecorator, childModuleDecorator}: {
parentClassDecorator: string,
parentModuleDecorator: string,
childClassDecorator: string,
childModuleDecorator: string
}) {
const libInput: MockDirectory = {
'lib': {
'base.ts': `
import {Injectable, Pipe, Directive, Component, NgModule} from '@angular/core';
${parentClassDecorator}
export class Base {}
${parentModuleDecorator}
export class BaseModule {}
`
}
};
const appInput: MockDirectory = {
'app': {
'main.ts': `
import {Injectable, Pipe, Directive, Component, NgModule} from '@angular/core';
import {Base} from '../lib/base';
${childClassDecorator}
export class Extends extends Base {}
${childModuleDecorator}
export class MyModule {}
`
}
};
const {outDir: libOutDir} =
compile([libInput, getAngularSummaryFiles()], {useSummaries: true});
const {genFiles} =
compile([libOutDir, appInput, getAngularSummaryFiles()], {useSummaries: true});
return genFiles.find(gf => gf.srcFileUrl === '/app/main.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 {
constructor(a: AParam) {}
ngOnDestroy() {}
}
`
}
};
const appInput: MockDirectory = {
'app': {
'main.ts': `
import {NgModule, Component} from '@angular/core';
import {Base} from '../lib/base';
@Component({template: ''})
export class Extends extends Base {}
@NgModule({
declarations: [Extends]
})
export class MyModule {}
`
}
};
const {outDir: libOutDir} =
compile([libInput, getAngularSummaryFiles()], {useSummaries: true});
const {genFiles} =
compile([libOutDir, appInput, getAngularSummaryFiles()], {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',
() => {
const lib1Input: MockDirectory = {
'lib1': {
'base.ts': `
export class AParam {}
export class Base {
constructor(a: AParam) {}
ngOnDestroy() {}
}
`
}
};
const lib2Input: MockDirectory = {
'lib2': {
'middle.ts': `
import {Base} from '../lib1/base';
export class Middle extends Base {}
`
}
};
const appInput: MockDirectory = {
'app': {
'main.ts': `
import {NgModule, Component} from '@angular/core';
import {Middle} from '../lib2/middle';
@Component({template: ''})
export class Extends extends Middle {}
@NgModule({
declarations: [Extends]
})
export class MyModule {}
`
}
};
const {outDir: lib1OutDir} =
compile([lib1Input, getAngularSummaryFiles()], {useSummaries: true});
const {outDir: lib2OutDir} =
compile([lib1OutDir, lib2Input, getAngularSummaryFiles()], {useSummaries: true});
const {genFiles} =
compile([lib2OutDir, appInput, getAngularSummaryFiles()], {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', () => {
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', () => {
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', () => {
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', () => {
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', () => {
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', () => {
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', () => {
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', () => {
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', () => {
const mainNgFactory = compileParentAndChild({
parentClassDecorator: `@NgModule()`,
parentModuleDecorator: '',
childClassDecorator: `@NgModule()`,
childModuleDecorator: '',
});
expect(mainNgFactory).toBeTruthy();
});
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.');
});
});
}
});
describe('compiler (bundled Angular)', () => {
setup({compileAngular: false, compileAnimations: false});
let angularFiles: Map<string, string>;
beforeAll(() => {
const emittingHost = new EmittingCompilerHost(['@angular/core/index'], {emitMetadata: false});
// Create the metadata bundled
const indexModule = emittingHost.effectiveName('@angular/core/index');
const bundler = new MetadataBundler(
indexModule, '@angular/core', new MockMetadataBundlerHost(emittingHost));
const bundle = bundler.getMetadataBundle();
const metadata = JSON.stringify(bundle.metadata, null, ' ');
const bundleIndexSource = privateEntriesToIndex('./index', bundle.privates);
emittingHost.override('@angular/core/bundle_index.ts', bundleIndexSource);
emittingHost.addWrittenFile(
'@angular/core/package.json', JSON.stringify({typings: 'bundle_index.d.ts'}));
emittingHost.addWrittenFile('@angular/core/bundle_index.metadata.json', metadata);
// Emit the sources
const bundleIndexName = emittingHost.effectiveName('@angular/core/bundle_index.ts');
const emittingProgram = ts.createProgram([bundleIndexName], settings, emittingHost);
emittingProgram.emit();
angularFiles = emittingHost.writtenAngularFiles();
});
describe('Quickstart', () => {
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', () => {
let libraryFiles: MockDirectory;
beforeAll(() => {
// Emit the library bundle
const emittingHost =
new EmittingCompilerHost(['/bolder/index.ts'], {emitMetadata: false, mockData: LIBRARY});
// Create the metadata bundled
const indexModule = '/bolder/public-api';
const bundler =
new MetadataBundler(indexModule, 'bolder', new MockMetadataBundlerHost(emittingHost));
const bundle = bundler.getMetadataBundle();
const metadata = JSON.stringify(bundle.metadata, null, ' ');
const bundleIndexSource = privateEntriesToIndex('./public-api', bundle.privates);
emittingHost.override('/bolder/index.ts', bundleIndexSource);
emittingHost.addWrittenFile('/bolder/index.metadata.json', metadata);
// Emit the sources
const emittingProgram = ts.createProgram(['/bolder/index.ts'], settings, emittingHost);
emittingProgram.emit();
const libFiles = emittingHost.written;
// Copy the .html file
const htmlFileName = '/bolder/src/bolder.component.html';
libFiles.set(htmlFileName, emittingHost.readFile(htmlFileName));
libraryFiles = arrayToMockDir(toMockFileArray(libFiles).map(
({fileName, content}) => ({fileName: `/node_modules${fileName}`, content})));
});
it('should compile', () => compile([LIBRARY_USING_APP, libraryFiles, angularFiles]));
});
});
const QUICKSTART: MockDirectory = {
quickstart: {
app: {
'app.component.ts': `
import {Component} from '@angular/core';
@Component({
template: '<h1>Hello {{name}}</h1>'
})
export class AppComponent {
name = 'Angular';
}
`,
'app.module.ts': `
import { NgModule } from '@angular/core';
import { toString } from './utils';
import { AppComponent } from './app.component';
@NgModule({
declarations: [ AppComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule { }
`,
// #15420
'utils.ts': `
export function toString(value: any): string {
return '';
}
`
}
}
};
const LIBRARY: MockDirectory = {
bolder: {
'public-api.ts': `
export * from './src/bolder.component';
export * from './src/bolder.module';
export {BolderModule as ReExportedModule} from './src/bolder.module';
`,
src: {
'bolder.component.ts': `
import {Component, Input} from '@angular/core';
@Component({
selector: 'bolder',
templateUrl: './bolder.component.html'
})
export class BolderComponent {
@Input() data: string;
}
`,
'bolder.component.html': `
<b>{{data}}</b>
`,
'bolder.module.ts': `
import {NgModule} from '@angular/core';
import {BolderComponent} from './bolder.component';
@NgModule({
declarations: [BolderComponent],
exports: [BolderComponent]
})
export class BolderModule {}
`
}
}
};
const LIBRARY_USING_APP: MockDirectory = {
'lib-user': {
app: {
'app.component.ts': `
import {Component} from '@angular/core';
@Component({
template: '<h1>Hello <bolder [data]="name"></bolder></h1>'
})
export class AppComponent {
name = 'Angular';
}
`,
'app.module.ts': `
import { NgModule } from '@angular/core';
import { BolderModule } from 'bolder';
import { AppComponent } from './app.component';
@NgModule({
declarations: [ AppComponent ],
bootstrap: [ AppComponent ],
imports: [ BolderModule ]
})
export class AppModule { }
`
}
}
};