| 
									
										
										
										
											2017-01-26 09:35:49 -08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @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
 | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-14 09:16:15 -07:00
										 |  |  | import {AotCompiler, AotCompilerHost, AotCompilerOptions, GeneratedFile, createAotCompiler} from '@angular/compiler'; | 
					
						
							| 
									
										
										
										
											2017-02-17 12:55:55 -08:00
										 |  |  | import {RenderComponentType, ɵReflectionCapabilities as ReflectionCapabilities, ɵreflector as reflector} from '@angular/core'; | 
					
						
							| 
									
										
										
										
											2017-03-15 15:50:30 -07:00
										 |  |  | import {async} from '@angular/core/testing'; | 
					
						
							| 
									
										
										
										
											2017-02-15 13:30:40 -08:00
										 |  |  | import {MetadataBundler, MetadataCollector, ModuleMetadata, privateEntriesToIndex} from '@angular/tsc-wrapped'; | 
					
						
							| 
									
										
										
										
											2017-01-26 09:35:49 -08:00
										 |  |  | import * as ts from 'typescript'; | 
					
						
							| 
									
										
										
										
											2017-03-14 09:16:15 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | import {extractSourceMap, originalPositionFor} from '../output/source_map_util'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import {EmittingCompilerHost, MockAotCompilerHost, MockCompilerHost, MockData, MockDirectory, MockMetadataBundlerHost, settings} from './test_util'; | 
					
						
							| 
									
										
										
										
											2017-01-26 09:35:49 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | const DTS = /\.d\.ts$/; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-14 13:33:06 -08:00
										 |  |  | const minCoreIndex = `
 | 
					
						
							| 
									
										
										
										
											2017-02-15 13:30:40 -08:00
										 |  |  |   export * from './src/application_module'; | 
					
						
							|  |  |  |   export * from './src/change_detection'; | 
					
						
							| 
									
										
										
										
											2017-02-14 13:33:06 -08:00
										 |  |  |   export * from './src/metadata'; | 
					
						
							|  |  |  |   export * from './src/di/metadata'; | 
					
						
							|  |  |  |   export * from './src/di/injector'; | 
					
						
							|  |  |  |   export * from './src/di/injection_token'; | 
					
						
							|  |  |  |   export * from './src/linker'; | 
					
						
							|  |  |  |   export * from './src/render'; | 
					
						
							|  |  |  |   export * from './src/codegen_private_exports'; | 
					
						
							|  |  |  | `;
 | 
					
						
							| 
									
										
										
										
											2017-01-26 09:35:49 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-15 13:30:40 -08:00
										 |  |  | describe('compiler (unbundled Angular)', () => { | 
					
						
							| 
									
										
										
										
											2017-01-26 09:35:49 -08:00
										 |  |  |   let angularFiles: Map<string, string>; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   beforeAll(() => { | 
					
						
							| 
									
										
										
										
											2017-02-14 13:33:06 -08:00
										 |  |  |     const emittingHost = new EmittingCompilerHost([], {emitMetadata: true}); | 
					
						
							|  |  |  |     emittingHost.addScript('@angular/core/index.ts', minCoreIndex); | 
					
						
							| 
									
										
										
										
											2017-01-26 09:35:49 -08:00
										 |  |  |     const emittingProgram = ts.createProgram(emittingHost.scripts, settings, emittingHost); | 
					
						
							|  |  |  |     emittingProgram.emit(); | 
					
						
							| 
									
										
										
										
											2017-02-14 13:33:06 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-26 09:35:49 -08:00
										 |  |  |     angularFiles = emittingHost.written; | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-14 09:16:15 -07:00
										 |  |  |   // Restore reflector since AoT compiler will update it with a new static reflector
 | 
					
						
							|  |  |  |   afterEach(() => { reflector.updateCapabilities(new ReflectionCapabilities()); }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-26 09:35:49 -08:00
										 |  |  |   describe('Quickstart', () => { | 
					
						
							|  |  |  |     let host: MockCompilerHost; | 
					
						
							|  |  |  |     let aotHost: MockAotCompilerHost; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     beforeEach(() => { | 
					
						
							|  |  |  |       host = new MockCompilerHost(QUICKSTART, FILES, angularFiles); | 
					
						
							|  |  |  |       aotHost = new MockAotCompilerHost(host); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should compile', | 
					
						
							|  |  |  |        async(() => compile(host, aotHost, expectNoDiagnostics).then(generatedFiles => { | 
					
						
							|  |  |  |          expect(generatedFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl))) | 
					
						
							| 
									
										
										
										
											2017-02-15 13:30:40 -08:00
										 |  |  |              .toBeDefined(); | 
					
						
							| 
									
										
										
										
											2017-01-26 09:35:49 -08:00
										 |  |  |          expect(generatedFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl))) | 
					
						
							| 
									
										
										
										
											2017-02-15 13:30:40 -08:00
										 |  |  |              .toBeDefined(); | 
					
						
							| 
									
										
										
										
											2017-01-26 09:35:49 -08:00
										 |  |  |        }))); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should compile using summaries', | 
					
						
							|  |  |  |        async(() => summaryCompile(host, aotHost).then(generatedFiles => { | 
					
						
							|  |  |  |          expect(generatedFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl))) | 
					
						
							| 
									
										
										
										
											2017-02-15 13:30:40 -08:00
										 |  |  |              .toBeDefined(); | 
					
						
							| 
									
										
										
										
											2017-01-26 09:35:49 -08:00
										 |  |  |          expect(generatedFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl))) | 
					
						
							| 
									
										
										
										
											2017-02-15 13:30:40 -08:00
										 |  |  |              .toBeDefined(); | 
					
						
							|  |  |  |        }))); | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2017-03-14 09:16:15 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |   describe('aot source mapping', () => { | 
					
						
							|  |  |  |     const componentPath = '/app/app.component.ts'; | 
					
						
							| 
									
										
										
										
											2017-03-16 17:33:17 -07:00
										 |  |  |     const ngComponentPath = 'ng:///app/app.component.ts' | 
					
						
							| 
									
										
										
										
											2017-03-14 09:16:15 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     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}; | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-15 15:50:30 -07:00
										 |  |  |     function compileApp(): Promise<GeneratedFile> { | 
					
						
							|  |  |  |       return new Promise((resolve, reject) => { | 
					
						
							|  |  |  |         const host = new MockCompilerHost(['/app/app.module.ts'], rootDir, angularFiles); | 
					
						
							|  |  |  |         const aotHost = new MockAotCompilerHost(host); | 
					
						
							|  |  |  |         let result: GeneratedFile[]; | 
					
						
							|  |  |  |         let error: Error; | 
					
						
							|  |  |  |         resolve(compile(host, aotHost, expectNoDiagnostics, expectNoDiagnostics) | 
					
						
							|  |  |  |                     .then( | 
					
						
							|  |  |  |                         (files) => files.find( | 
					
						
							|  |  |  |                             genFile => genFile.srcFileUrl === componentPath && | 
					
						
							|  |  |  |                                 genFile.genFileUrl.endsWith('.ts')))); | 
					
						
							|  |  |  |       }); | 
					
						
							| 
									
										
										
										
											2017-03-14 09:16:15 -07:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     function findLineAndColumn(file: string, token: string): {line: number, column: number} { | 
					
						
							|  |  |  |       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', () => { | 
					
						
							| 
									
										
										
										
											2017-03-16 17:33:17 -07:00
										 |  |  |       const ngUrl = `${ngComponentPath}.AppComponent.html`; | 
					
						
							| 
									
										
										
										
											2017-03-14 09:16:15 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |       function templateDecorator(template: string) { return `template: \`${template}\`,`; } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-16 17:33:17 -07:00
										 |  |  |       declareTests({ngUrl, templateDecorator}); | 
					
						
							| 
									
										
										
										
											2017-03-14 09:16:15 -07:00
										 |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     describe('external templates', () => { | 
					
						
							| 
									
										
										
										
											2017-03-16 17:33:17 -07:00
										 |  |  |       const ngUrl = 'ng:///app/app.component.html'; | 
					
						
							| 
									
										
										
										
											2017-03-14 09:16:15 -07:00
										 |  |  |       const templateUrl = '/app/app.component.html'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       function templateDecorator(template: string) { | 
					
						
							|  |  |  |         appDir['app.component.html'] = template; | 
					
						
							|  |  |  |         return `templateUrl: 'app.component.html',`; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-16 17:33:17 -07:00
										 |  |  |       declareTests({ngUrl, templateDecorator}); | 
					
						
							| 
									
										
										
										
											2017-03-14 09:16:15 -07:00
										 |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-16 17:33:17 -07:00
										 |  |  |     function declareTests({ngUrl, templateDecorator}: | 
					
						
							|  |  |  |                               {ngUrl: string, templateDecorator: (template: string) => string}) { | 
					
						
							| 
									
										
										
										
											2017-03-15 15:50:30 -07:00
										 |  |  |       it('should use the right source url in html parse errors', async(() => { | 
					
						
							| 
									
										
										
										
											2017-03-14 09:16:15 -07:00
										 |  |  |            appDir['app.component.ts'] = | 
					
						
							|  |  |  |                createComponentSource(templateDecorator('<div>\n  </error>')); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-15 15:50:30 -07:00
										 |  |  |            expectPromiseToThrow( | 
					
						
							| 
									
										
										
										
											2017-03-16 17:33:17 -07:00
										 |  |  |                compileApp(), new RegExp(`Template parse errors[\\s\\S]*${ngUrl}@1:2`)); | 
					
						
							| 
									
										
										
										
											2017-03-14 09:16:15 -07:00
										 |  |  |          })); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-15 15:50:30 -07:00
										 |  |  |       it('should use the right source url in template parse errors', async(() => { | 
					
						
							| 
									
										
										
										
											2017-03-14 09:16:15 -07:00
										 |  |  |            appDir['app.component.ts'] = createComponentSource( | 
					
						
							|  |  |  |                templateDecorator('<div>\n  <div unknown="{{ctxProp}}"></div>')); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-15 15:50:30 -07:00
										 |  |  |            expectPromiseToThrow( | 
					
						
							| 
									
										
										
										
											2017-03-16 17:33:17 -07:00
										 |  |  |                compileApp(), new RegExp(`Template parse errors[\\s\\S]*${ngUrl}@1:7`)); | 
					
						
							| 
									
										
										
										
											2017-03-14 09:16:15 -07:00
										 |  |  |          })); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-15 15:50:30 -07:00
										 |  |  |       it('should create a sourceMap for the template', async(() => { | 
					
						
							| 
									
										
										
										
											2017-03-14 09:16:15 -07:00
										 |  |  |            const template = 'Hello World!'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |            appDir['app.component.ts'] = createComponentSource(templateDecorator(template)); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-15 15:50:30 -07:00
										 |  |  |            compileApp().then((genFile) => { | 
					
						
							|  |  |  |              const sourceMap = extractSourceMap(genFile.source); | 
					
						
							|  |  |  |              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, ...)
 | 
					
						
							| 
									
										
										
										
											2017-03-16 17:33:17 -07:00
										 |  |  |              const templateIndex = sourceMap.sources.indexOf(ngUrl); | 
					
						
							| 
									
										
										
										
											2017-03-15 15:50:30 -07:00
										 |  |  |              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.
 | 
					
						
							| 
									
										
										
										
											2017-03-16 17:33:17 -07:00
										 |  |  |              const sourceIndex = sourceMap.sources.indexOf(ngComponentPath); | 
					
						
							| 
									
										
										
										
											2017-03-17 09:44:27 -07:00
										 |  |  |              expect(sourceMap.sourcesContent[sourceIndex]).toBe(' '); | 
					
						
							| 
									
										
										
										
											2017-03-15 15:50:30 -07:00
										 |  |  |            }); | 
					
						
							| 
									
										
										
										
											2017-03-14 09:16:15 -07:00
										 |  |  |          })); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-15 15:50:30 -07:00
										 |  |  |       it('should map elements correctly to the source', async(() => { | 
					
						
							| 
									
										
										
										
											2017-03-14 09:16:15 -07:00
										 |  |  |            const template = '<div>\n   <span></span></div>'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |            appDir['app.component.ts'] = createComponentSource(templateDecorator(template)); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-15 15:50:30 -07:00
										 |  |  |            compileApp().then((genFile) => { | 
					
						
							|  |  |  |              const sourceMap = extractSourceMap(genFile.source); | 
					
						
							|  |  |  |              expect(originalPositionFor(sourceMap, findLineAndColumn(genFile.source, `'span'`))) | 
					
						
							| 
									
										
										
										
											2017-03-16 17:33:17 -07:00
										 |  |  |                  .toEqual({line: 2, column: 3, source: ngUrl}); | 
					
						
							| 
									
										
										
										
											2017-03-15 15:50:30 -07:00
										 |  |  |            }); | 
					
						
							| 
									
										
										
										
											2017-03-14 09:16:15 -07:00
										 |  |  |          })); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-15 15:50:30 -07:00
										 |  |  |       it('should map bindings correctly to the source', async(() => { | 
					
						
							| 
									
										
										
										
											2017-03-14 09:16:15 -07:00
										 |  |  |            const template = `<div>\n   <span [title]="someMethod()"></span></div>`; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |            appDir['app.component.ts'] = createComponentSource(templateDecorator(template)); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-15 15:50:30 -07:00
										 |  |  |            compileApp().then((genFile) => { | 
					
						
							|  |  |  |              const sourceMap = extractSourceMap(genFile.source); | 
					
						
							|  |  |  |              expect( | 
					
						
							|  |  |  |                  originalPositionFor(sourceMap, findLineAndColumn(genFile.source, `someMethod()`))) | 
					
						
							| 
									
										
										
										
											2017-03-16 17:33:17 -07:00
										 |  |  |                  .toEqual({line: 2, column: 9, source: ngUrl}); | 
					
						
							| 
									
										
										
										
											2017-03-15 15:50:30 -07:00
										 |  |  |            }); | 
					
						
							| 
									
										
										
										
											2017-03-14 09:16:15 -07:00
										 |  |  |          })); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-15 15:50:30 -07:00
										 |  |  |       it('should map events correctly to the source', async(() => { | 
					
						
							| 
									
										
										
										
											2017-03-14 09:16:15 -07:00
										 |  |  |            const template = `<div>\n   <span (click)="someMethod()"></span></div>`; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |            appDir['app.component.ts'] = createComponentSource(templateDecorator(template)); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-15 15:50:30 -07:00
										 |  |  |            compileApp().then((genFile) => { | 
					
						
							|  |  |  |              const sourceMap = extractSourceMap(genFile.source); | 
					
						
							|  |  |  |              expect( | 
					
						
							|  |  |  |                  originalPositionFor(sourceMap, findLineAndColumn(genFile.source, `someMethod()`))) | 
					
						
							| 
									
										
										
										
											2017-03-16 17:33:17 -07:00
										 |  |  |                  .toEqual({line: 2, column: 9, source: ngUrl}); | 
					
						
							| 
									
										
										
										
											2017-03-15 15:50:30 -07:00
										 |  |  |            }); | 
					
						
							|  |  |  |          })); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it('should map non template parts to the source file', async(() => { | 
					
						
							|  |  |  |            appDir['app.component.ts'] = createComponentSource(templateDecorator('Hello World!')); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |            compileApp().then((genFile) => { | 
					
						
							|  |  |  |              const sourceMap = extractSourceMap(genFile.source); | 
					
						
							|  |  |  |              expect(originalPositionFor(sourceMap, {line: 1, column: 0})) | 
					
						
							| 
									
										
										
										
											2017-03-16 17:33:17 -07:00
										 |  |  |                  .toEqual({line: 1, column: 0, source: ngComponentPath}); | 
					
						
							| 
									
										
										
										
											2017-03-15 15:50:30 -07:00
										 |  |  |            }); | 
					
						
							| 
									
										
										
										
											2017-03-14 09:16:15 -07:00
										 |  |  |          })); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2017-03-13 14:39:34 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |   describe('errors', () => { | 
					
						
							|  |  |  |     it('should only warn if not all arguments of an @Injectable class can be resolved', | 
					
						
							| 
									
										
										
										
											2017-03-15 15:50:30 -07:00
										 |  |  |        async(() => { | 
					
						
							| 
									
										
										
										
											2017-03-13 14:39:34 -07:00
										 |  |  |          const FILES: MockData = { | 
					
						
							|  |  |  |            app: { | 
					
						
							|  |  |  |              'app.ts': `
 | 
					
						
							|  |  |  |                 import {Injectable} from '@angular/core'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 @Injectable() | 
					
						
							|  |  |  |                 export class MyService { | 
					
						
							|  |  |  |                   constructor(a: boolean) {} | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |               `
 | 
					
						
							|  |  |  |            } | 
					
						
							|  |  |  |          }; | 
					
						
							|  |  |  |          const host = new MockCompilerHost(['/app/app.ts'], FILES, angularFiles); | 
					
						
							|  |  |  |          const aotHost = new MockAotCompilerHost(host); | 
					
						
							|  |  |  |          const warnSpy = spyOn(console, 'warn'); | 
					
						
							| 
									
										
										
										
											2017-03-15 15:50:30 -07:00
										 |  |  |          compile(host, aotHost, expectNoDiagnostics).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`); | 
					
						
							|  |  |  |          }); | 
					
						
							| 
									
										
										
										
											2017-03-13 14:39:34 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |        })); | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2017-03-15 15:50:30 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |   it('should add the preamble to generated files', async(() => { | 
					
						
							|  |  |  |        const FILES: MockData = { | 
					
						
							|  |  |  |          app: { | 
					
						
							|  |  |  |            'app.ts': `
 | 
					
						
							|  |  |  |               import { NgModule, Component } from '@angular/core'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |               @Component({ template: '' }) | 
					
						
							|  |  |  |               export class AppComponent {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |               @NgModule({ declarations: [ AppComponent ] }) | 
					
						
							|  |  |  |               export class AppModule { } | 
					
						
							|  |  |  |             `
 | 
					
						
							|  |  |  |          } | 
					
						
							|  |  |  |        }; | 
					
						
							|  |  |  |        const host = new MockCompilerHost(['/app/app.ts'], FILES, angularFiles); | 
					
						
							|  |  |  |        const aotHost = new MockAotCompilerHost(host); | 
					
						
							|  |  |  |        const genFilePreamble = '/* Hello world! */'; | 
					
						
							|  |  |  |        compile(host, aotHost, expectNoDiagnostics, expectNoDiagnostics, {genFilePreamble}) | 
					
						
							|  |  |  |            .then((generatedFiles) => { | 
					
						
							|  |  |  |              const genFile = generatedFiles.find( | 
					
						
							|  |  |  |                  gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts')); | 
					
						
							|  |  |  |              expect(genFile.source.startsWith(genFilePreamble)).toBe(true); | 
					
						
							|  |  |  |            }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |      })); | 
					
						
							| 
									
										
										
										
											2017-03-14 14:54:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |   describe('ComponentFactories', () => { | 
					
						
							|  |  |  |     it('should include inputs, outputs and ng-content selectors in the component factory', | 
					
						
							|  |  |  |        async(() => { | 
					
						
							|  |  |  |          const FILES: MockData = { | 
					
						
							|  |  |  |            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 host = new MockCompilerHost(['/app/app.ts'], FILES, angularFiles); | 
					
						
							|  |  |  |          const aotHost = new MockAotCompilerHost(host); | 
					
						
							|  |  |  |          let generatedFiles: GeneratedFile[]; | 
					
						
							|  |  |  |          compile(host, aotHost, expectNoDiagnostics).then((generatedFiles) => { | 
					
						
							|  |  |  |            const genFile = generatedFiles.find(genFile => genFile.srcFileUrl === '/app/app.ts'); | 
					
						
							|  |  |  |            const createComponentFactoryCall = | 
					
						
							|  |  |  |                /ɵccf\([^)]*\)/m.exec(genFile.source)[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']`); | 
					
						
							|  |  |  |          }); | 
					
						
							|  |  |  |        })); | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2017-03-20 15:26:06 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |   describe('generated templates', () => { | 
					
						
							|  |  |  |     it('should not call `check` for directives without bindings nor ngDoCheck/ngOnInit', | 
					
						
							|  |  |  |        async(() => { | 
					
						
							|  |  |  |          const FILES: MockData = { | 
					
						
							|  |  |  |            app: { | 
					
						
							|  |  |  |              'app.ts': `
 | 
					
						
							|  |  |  |                 import { NgModule, Component } from '@angular/core'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 @Component({ template: '' }) | 
					
						
							|  |  |  |                 export class AppComponent {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 @NgModule({ declarations: [ AppComponent ] }) | 
					
						
							|  |  |  |                 export class AppModule { } | 
					
						
							|  |  |  |               `
 | 
					
						
							|  |  |  |            } | 
					
						
							|  |  |  |          }; | 
					
						
							|  |  |  |          const host = new MockCompilerHost(['/app/app.ts'], FILES, angularFiles); | 
					
						
							|  |  |  |          const aotHost = new MockAotCompilerHost(host); | 
					
						
							|  |  |  |          const genFilePreamble = '/* Hello world! */'; | 
					
						
							|  |  |  |          compile(host, aotHost, expectNoDiagnostics, expectNoDiagnostics, {genFilePreamble}) | 
					
						
							|  |  |  |              .then((generatedFiles) => { | 
					
						
							|  |  |  |                const genFile = generatedFiles.find( | 
					
						
							|  |  |  |                    gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts')); | 
					
						
							|  |  |  |                expect(genFile.source).not.toContain('check('); | 
					
						
							|  |  |  |              }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |        })); | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2017-02-15 13:30:40 -08:00
										 |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | describe('compiler (bundled Angular)', () => { | 
					
						
							|  |  |  |   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.written; | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   describe('Quickstart', () => { | 
					
						
							|  |  |  |     let host: MockCompilerHost; | 
					
						
							|  |  |  |     let aotHost: MockAotCompilerHost; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     beforeEach(() => { | 
					
						
							|  |  |  |       host = new MockCompilerHost(QUICKSTART, FILES, angularFiles); | 
					
						
							|  |  |  |       aotHost = new MockAotCompilerHost(host); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Restore reflector since AoT compiler will update it with a new static reflector
 | 
					
						
							|  |  |  |     afterEach(() => { reflector.updateCapabilities(new ReflectionCapabilities()); }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should compile', | 
					
						
							|  |  |  |        async(() => compile(host, aotHost, expectNoDiagnostics).then(generatedFiles => { | 
					
						
							|  |  |  |          expect(generatedFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl))) | 
					
						
							|  |  |  |              .toBeDefined(); | 
					
						
							|  |  |  |          expect(generatedFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl))) | 
					
						
							|  |  |  |              .toBeDefined(); | 
					
						
							| 
									
										
										
										
											2017-01-26 09:35:49 -08:00
										 |  |  |        }))); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function expectNoDiagnostics(program: ts.Program) { | 
					
						
							|  |  |  |   function fileInfo(diagnostic: ts.Diagnostic): string { | 
					
						
							|  |  |  |     if (diagnostic.file) { | 
					
						
							|  |  |  |       return `${diagnostic.file.fileName}(${diagnostic.start}): `; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return ''; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function chars(len: number, ch: string): string { return new Array(len).fill(ch).join(''); } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function lineNoOf(offset: number, text: string): number { | 
					
						
							|  |  |  |     let result = 1; | 
					
						
							|  |  |  |     for (let i = 0; i < offset; i++) { | 
					
						
							|  |  |  |       if (text[i] == '\n') result++; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return result; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function lineInfo(diagnostic: ts.Diagnostic): string { | 
					
						
							|  |  |  |     if (diagnostic.file) { | 
					
						
							|  |  |  |       const start = diagnostic.start; | 
					
						
							|  |  |  |       let end = diagnostic.start + diagnostic.length; | 
					
						
							|  |  |  |       const source = diagnostic.file.text; | 
					
						
							|  |  |  |       let lineStart = start; | 
					
						
							|  |  |  |       let lineEnd = end; | 
					
						
							|  |  |  |       while (lineStart > 0 && source[lineStart] != '\n') lineStart--; | 
					
						
							|  |  |  |       if (lineStart < start) lineStart++; | 
					
						
							|  |  |  |       while (lineEnd < source.length && source[lineEnd] != '\n') lineEnd++; | 
					
						
							|  |  |  |       let line = source.substring(lineStart, lineEnd); | 
					
						
							|  |  |  |       const lineIndex = line.indexOf('/n'); | 
					
						
							|  |  |  |       if (lineIndex > 0) { | 
					
						
							|  |  |  |         line = line.substr(0, lineIndex); | 
					
						
							|  |  |  |         end = start + lineIndex; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       const lineNo = lineNoOf(start, source) + ': '; | 
					
						
							|  |  |  |       return '\n' + lineNo + line + '\n' + chars(start - lineStart + lineNo.length, ' ') + | 
					
						
							|  |  |  |           chars(end - start, '^'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return ''; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function expectNoDiagnostics(diagnostics: ts.Diagnostic[]) { | 
					
						
							|  |  |  |     if (diagnostics && diagnostics.length) { | 
					
						
							|  |  |  |       throw new Error( | 
					
						
							|  |  |  |           'Errors from TypeScript:\n' + | 
					
						
							|  |  |  |           diagnostics.map(d => `${fileInfo(d)}${d.messageText}${lineInfo(d)}`).join(' \n')); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   expectNoDiagnostics(program.getOptionsDiagnostics()); | 
					
						
							|  |  |  |   expectNoDiagnostics(program.getSyntacticDiagnostics()); | 
					
						
							|  |  |  |   expectNoDiagnostics(program.getSemanticDiagnostics()); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function isDTS(fileName: string): boolean { | 
					
						
							|  |  |  |   return /\.d\.ts$/.test(fileName); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function isSource(fileName: string): boolean { | 
					
						
							|  |  |  |   return /\.ts$/.test(fileName); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function isFactory(fileName: string): boolean { | 
					
						
							|  |  |  |   return /\.ngfactory\./.test(fileName); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function summaryCompile( | 
					
						
							|  |  |  |     host: MockCompilerHost, aotHost: MockAotCompilerHost, | 
					
						
							|  |  |  |     preCompile?: (program: ts.Program) => void) { | 
					
						
							|  |  |  |   // First compile the program to generate the summary files.
 | 
					
						
							|  |  |  |   return compile(host, aotHost).then(generatedFiles => { | 
					
						
							|  |  |  |     // Remove generated files that were not generated from a DTS file
 | 
					
						
							|  |  |  |     host.remove(generatedFiles.filter(f => !isDTS(f.srcFileUrl)).map(f => f.genFileUrl)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Next compile the program shrowding metadata and only treating .ts files as source.
 | 
					
						
							|  |  |  |     aotHost.hideMetadata(); | 
					
						
							|  |  |  |     aotHost.tsFilesOnly(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return compile(host, aotHost); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function compile( | 
					
						
							|  |  |  |     host: MockCompilerHost, aotHost: AotCompilerHost, preCompile?: (program: ts.Program) => void, | 
					
						
							| 
									
										
										
										
											2017-02-17 08:56:49 -08:00
										 |  |  |     postCompile: (program: ts.Program) => void = expectNoDiagnostics, | 
					
						
							|  |  |  |     options: AotCompilerOptions = {}) { | 
					
						
							| 
									
										
										
										
											2017-02-14 13:33:06 -08:00
										 |  |  |   const scripts = host.scriptNames.slice(0); | 
					
						
							|  |  |  |   const program = ts.createProgram(scripts, settings, host); | 
					
						
							| 
									
										
										
										
											2017-01-26 09:35:49 -08:00
										 |  |  |   if (preCompile) preCompile(program); | 
					
						
							| 
									
										
										
										
											2017-02-17 08:56:49 -08:00
										 |  |  |   const {compiler, reflector} = createAotCompiler(aotHost, options); | 
					
						
							| 
									
										
										
										
											2017-01-26 09:35:49 -08:00
										 |  |  |   return compiler.compileAll(program.getSourceFiles().map(sf => sf.fileName)) | 
					
						
							|  |  |  |       .then(generatedFiles => { | 
					
						
							|  |  |  |         generatedFiles.forEach( | 
					
						
							|  |  |  |             file => isSource(file.genFileUrl) ? host.addScript(file.genFileUrl, file.source) : | 
					
						
							|  |  |  |                                                 host.override(file.genFileUrl, file.source)); | 
					
						
							| 
									
										
										
										
											2017-02-14 13:33:06 -08:00
										 |  |  |         const scripts = host.scriptNames.slice(0); | 
					
						
							|  |  |  |         const newProgram = ts.createProgram(scripts, settings, host); | 
					
						
							| 
									
										
										
										
											2017-01-26 09:35:49 -08:00
										 |  |  |         if (postCompile) postCompile(newProgram); | 
					
						
							|  |  |  |         return generatedFiles; | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const QUICKSTART = ['/quickstart/app/app.module.ts']; | 
					
						
							|  |  |  | const FILES: MockData = { | 
					
						
							|  |  |  |   quickstart: { | 
					
						
							|  |  |  |     app: { | 
					
						
							|  |  |  |       'app.component.ts': `
 | 
					
						
							| 
									
										
										
										
											2017-02-14 13:33:06 -08:00
										 |  |  |         import {Component} from '@angular/core'; | 
					
						
							| 
									
										
										
										
											2017-01-26 09:35:49 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         @Component({ | 
					
						
							|  |  |  |           template: '<h1>Hello {{name}}</h1>' | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |         export class AppComponent { | 
					
						
							|  |  |  |           name = 'Angular'; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       `,
 | 
					
						
							|  |  |  |       'app.module.ts': `
 | 
					
						
							| 
									
										
										
										
											2017-02-14 13:33:06 -08:00
										 |  |  |         import { NgModule }      from '@angular/core'; | 
					
						
							| 
									
										
										
										
											2017-01-26 09:35:49 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         import { AppComponent }  from './app.component'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         @NgModule({ | 
					
						
							|  |  |  |           declarations: [ AppComponent ], | 
					
						
							|  |  |  |           bootstrap:    [ AppComponent ] | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |         export class AppModule { } | 
					
						
							|  |  |  |       `
 | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | }; | 
					
						
							| 
									
										
										
										
											2017-03-15 15:50:30 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | function expectPromiseToThrow(p: Promise<any>, msg: RegExp) { | 
					
						
							|  |  |  |   p.then( | 
					
						
							|  |  |  |       () => { throw new Error('Expected to throw'); }, (e) => { expect(e.message).toMatch(msg); }); | 
					
						
							|  |  |  | } |