164 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			164 lines
		
	
	
		
			5.7 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 {PartialModule} from '@angular/compiler';
 | |
| import * as o from '@angular/compiler/src/output/output_ast';
 | |
| import {MockAotCompilerHost} from '@angular/compiler/test/aot/test_util';
 | |
| import {initDomAdapter} from '@angular/platform-browser/src/browser';
 | |
| import * as ts from 'typescript';
 | |
| 
 | |
| import {getAngularClassTransformerFactory} from '../../src/transformers/r3_transform';
 | |
| import {Directory, MockAotContext, MockCompilerHost} from '../mocks';
 | |
| 
 | |
| const someGenFilePath = '/somePackage/someGenFile';
 | |
| const someGenFileName = someGenFilePath + '.ts';
 | |
| 
 | |
| describe('r3_transform_spec', () => {
 | |
|   let context: MockAotContext;
 | |
|   let host: MockCompilerHost;
 | |
| 
 | |
|   beforeEach(() => {
 | |
|     context = new MockAotContext('/', FILES);
 | |
|     host = new MockCompilerHost(context);
 | |
|   });
 | |
| 
 | |
|   it('should be able to generate a simple identity function', () => {
 | |
|     expect(emitStaticMethod(new o.ReturnStatement(o.variable('v')), ['v']))
 | |
|         .toContain('static someMethod(v) { return v; }');
 | |
|   });
 | |
| 
 | |
|   it('should be able to generate a static field declaration',
 | |
|      () => { expect(emitStaticField(o.literal(10))).toContain('SomeClass.someField = 10'); });
 | |
| 
 | |
|   it('should be able to import a symbol', () => {
 | |
|     expect(emitStaticMethod(new o.ReturnStatement(
 | |
|                o.importExpr(new o.ExternalReference('@angular/core', 'Component')))))
 | |
|         .toContain('static someMethod() { return i0.Component; } }');
 | |
|   });
 | |
| 
 | |
|   it('should be able to modify multiple classes in the same module', () => {
 | |
|     const result = emit(getAngularClassTransformerFactory([{
 | |
|       fileName: someGenFileName,
 | |
|       statements: [
 | |
|         classMethod(new o.ReturnStatement(o.variable('v')), ['v'], 'someMethod', 'SomeClass'),
 | |
|         classMethod(
 | |
|             new o.ReturnStatement(o.variable('v')), ['v'], 'someOtherMethod', 'SomeOtherClass')
 | |
|       ]
 | |
|     }]));
 | |
|     expect(result).toContain('static someMethod(v) { return v; }');
 | |
|     expect(result).toContain('static someOtherMethod(v) { return v; }');
 | |
|   });
 | |
| 
 | |
|   it('should insert imports after existing imports', () => {
 | |
|     context = context.override({
 | |
|       somePackage: {
 | |
|         'someGenFile.ts': `
 | |
|         import {Component} from '@angular/core';
 | |
| 
 | |
|         @Component({selector: 'some-class', template: 'hello!'})
 | |
|         export class SomeClass {}
 | |
| 
 | |
|         export class SomeOtherClass {}
 | |
|       `
 | |
|       }
 | |
|     });
 | |
|     host = new MockCompilerHost(context);
 | |
| 
 | |
|     expect(emitStaticMethod(new o.ReturnStatement(
 | |
|                o.importExpr(new o.ExternalReference('@angular/core', 'Component')))))
 | |
|         .toContain('const core_1 = require("@angular/core"); const i0 = require("@angular/core");');
 | |
|   });
 | |
| 
 | |
|   function emit(factory: ts.TransformerFactory<ts.SourceFile>): string {
 | |
|     let result: string = '';
 | |
|     const program = ts.createProgram(
 | |
|         [someGenFileName], {module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2017}, host);
 | |
|     const moduleSourceFile = program.getSourceFile(someGenFileName);
 | |
|     const transformers: ts.CustomTransformers = {before: [factory]};
 | |
|     const emitResult = program.emit(
 | |
|         moduleSourceFile, (fileName, data, writeByteOrderMark, onError, sourceFiles) => {
 | |
|           if (fileName.startsWith(someGenFilePath)) {
 | |
|             result = data;
 | |
|           }
 | |
|         }, undefined, undefined, transformers);
 | |
|     return normalizeResult(result);
 | |
|   }
 | |
| 
 | |
|   function emitStaticMethod(
 | |
|       stmt: o.Statement | o.Statement[], parameters: string[] = [],
 | |
|       methodName: string = 'someMethod', className: string = 'SomeClass'): string {
 | |
|     const module: PartialModule = {
 | |
|       fileName: someGenFileName,
 | |
|       statements: [classMethod(stmt, parameters, methodName, className)]
 | |
|     };
 | |
|     return emit(getAngularClassTransformerFactory([module]));
 | |
|   }
 | |
| 
 | |
|   function emitStaticField(
 | |
|       initializer: o.Expression, fieldName: string = 'someField',
 | |
|       className: string = 'SomeClass'): string {
 | |
|     const module: PartialModule = {
 | |
|       fileName: someGenFileName,
 | |
|       statements: [classField(initializer, fieldName, className)]
 | |
|     };
 | |
|     return emit(getAngularClassTransformerFactory([module]));
 | |
|   }
 | |
| });
 | |
| 
 | |
| const FILES: Directory = {
 | |
|   somePackage: {
 | |
|     'someGenFile.ts': `
 | |
| 
 | |
|   export class SomeClass {}
 | |
| 
 | |
|   export class SomeOtherClass {}
 | |
| `
 | |
|   }
 | |
| };
 | |
| 
 | |
| function classMethod(
 | |
|     stmt: o.Statement | o.Statement[], parameters: string[] = [], methodName: string = 'someMethod',
 | |
|     className: string = 'SomeClass'): o.ClassStmt {
 | |
|   const statements = Array.isArray(stmt) ? stmt : [stmt];
 | |
|   return new o.ClassStmt(
 | |
|       /* name */ className,
 | |
|       /* parent */ null,
 | |
|       /* fields */[],
 | |
|       /* getters */[],
 | |
|       /* constructorMethod */ new o.ClassMethod(null, [], []),
 | |
|       /* methods */[new o.ClassMethod(
 | |
|           methodName, parameters.map(name => new o.FnParam(name)), statements, null,
 | |
|           [o.StmtModifier.Static])]);
 | |
| }
 | |
| 
 | |
| function classField(
 | |
|     initializer: o.Expression, fieldName: string = 'someField',
 | |
|     className: string = 'SomeClass'): o.ClassStmt {
 | |
|   return new o.ClassStmt(
 | |
|       /* name */ className,
 | |
|       /* parent */ null,
 | |
|       /* fields */[new o.ClassField(fieldName, null, [o.StmtModifier.Static], initializer)],
 | |
|       /* getters */[],
 | |
|       /* constructorMethod */ new o.ClassMethod(null, [], []),
 | |
|       /* methods */[]);
 | |
| }
 | |
| 
 | |
| function normalizeResult(result: string): string {
 | |
|   // Remove TypeScript prefixes
 | |
|   // Remove new lines
 | |
|   // Squish adjacent spaces
 | |
|   // Remove prefix and postfix spaces
 | |
|   return result.replace('"use strict";', ' ')
 | |
|       .replace('exports.__esModule = true;', ' ')
 | |
|       .replace('Object.defineProperty(exports, "__esModule", { value: true });', ' ')
 | |
|       .replace(/\n/g, ' ')
 | |
|       .replace(/ +/g, ' ')
 | |
|       .replace(/^ /g, '')
 | |
|       .replace(/ $/g, '');
 | |
| }
 |