it('should be able to get metadata for a class with nested method calls', () => { const annotations = reflector.annotations( reflector.getStaticSymbol('/tmp/src/static-method-call.ts', 'MyFactoryComponent')); expect(annotations.length).toBe(1); expect(annotations[0].providers).toEqual({ provide: 'c', useFactory: reflector.getStaticSymbol('/tmp/src/static-method.ts', 'AnotherModule', ['someFactory']) }); });
it('should simplify values initialized with a function call', () => { expect(simplify( reflector.getStaticSymbol('/tmp/src/function-reference.ts', ''), reflector.getStaticSymbol('/tmp/src/function-reference.ts', 'one'))) .toEqual(['some-value']); expect(simplify( reflector.getStaticSymbol('/tmp/src/function-reference.ts', ''), reflector.getStaticSymbol('/tmp/src/function-reference.ts', 'three'))) .toEqual(3); });
function init( testData: {[key: string]: any} = DEFAULT_TEST_DATA, decorators: {name: string, filePath: string, ctor: any}[] = [], errorRecorder?: (error: any, fileName: string) => void, collectorOptions?: CollectorOptions) { const symbolCache = new StaticSymbolCache(); host = new MockStaticSymbolResolverHost(testData, collectorOptions); symbolResolver = new StaticSymbolResolver(host, symbolCache, new MockSummaryResolver([]), errorRecorder); reflector = new StaticReflector(symbolResolver, decorators, [], errorRecorder); noContext = reflector.getStaticSymbol('', ''); }
it('should quote identifiers quoted in the source', () => { const sourceText = ` import {Component} from '@angular/core'; @Component({ providers: [{ provide: 'SomeToken', useValue: {a: 1, 'b': 2, c: 3, 'd': 4}}] }) export class MyComponent {} `; const source = ts.createSourceFile('test.ts', sourceText, ts.ScriptTarget.Latest); const collector = new MetadataCollector({quotedNames: true}); const stubHost = new StubReflectorHost(); const symbolCache = new StaticSymbolCache(); const symbolResolver = new StaticSymbolResolver(stubHost, symbolCache, new MockSummaryResolver()); const reflector = new StaticReflector(symbolResolver); // Get the metadata from the above source const metadata = collector.getMetadata(source); const componentMetadata = metadata.metadata['MyComponent']; // Get the first argument of the decorator call which is passed to @Component expect(isClassMetadata(componentMetadata)).toBeTruthy(); if (!isClassMetadata(componentMetadata)) return; const decorators = componentMetadata.decorators; const firstDecorator = decorators[0]; expect(isMetadataSymbolicCallExpression(firstDecorator)).toBeTruthy(); if (!isMetadataSymbolicCallExpression(firstDecorator)) return; const firstArgument = firstDecorator.arguments[0]; // Simplify this value using the StaticReflector const context = reflector.getStaticSymbol('none', 'none'); const argumentValue = reflector.simplify(context, firstArgument); // Convert the value to an output AST const outputAst = convertValueToOutputAst(argumentValue); const statement = outputAst.toStmt(); // Convert the value to text using the typescript emitter const emitter = new TypeScriptEmitter(new StubImportResolver()); const text = emitter.emitStatements('module', [statement], []); // Expect the keys for 'b' and 'd' to be quoted but 'a' and 'c' not to be. expect(text).toContain('\'b\': 2'); expect(text).toContain('\'d\': 4'); expect(text).not.toContain('\'a\''); expect(text).not.toContain('\'c\''); });
it('should not throw on unknown decorators', () => { const data = Object.create(DEFAULT_TEST_DATA); const file = '/tmp/src/app.component.ts'; data[file] = ` import { Component } from '@angular/core'; export const enum TypeEnum { type } export function MyValidationDecorator(p1: any, p2: any): any { return null; } export function ValidationFunction(a1: any): any { return null; } @Component({ selector: 'my-app', template: "<h1>Hello {{name}}</h1>", }) export class AppComponent { name = 'Angular'; @MyValidationDecorator( TypeEnum.type, ValidationFunction({option: 'value'})) myClassProp: number; }`; init(data); const appComponent = reflector.getStaticSymbol(file, 'AppComponent'); expect(() => reflector.propMetadata(appComponent)).not.toThrow(); });
it('should get annotations for HeroDetailComponent', () => { const HeroDetailComponent = reflector.findDeclaration('src/app/hero-detail.component', 'HeroDetailComponent'); const annotations = reflector.annotations(HeroDetailComponent); expect(annotations.length).toEqual(1); const annotation = annotations[0]; expect(annotation.selector).toEqual('my-hero-detail'); expect(annotation.animations).toEqual([trigger('myAnimation', [ state('state1', style({'background': 'white'})), transition( '* => *', sequence([group([animate( '1s 0.5s', keyframes([style({'background': 'blue'}), style({'background': 'red'})]))])])) ])]); });
it('should continue to aggresively evaluate enum member accessors', () => { const data = Object.create(DEFAULT_TEST_DATA); const file = '/tmp/src/my_component.ts'; data[file] = ` import {Component} from '@angular/core'; import {intermediate} from './index'; @Component({ template: '<div></div>', providers: [{provide: 'foo', useValue: [...intermediate]}] }) export class MyComponent { } `; data['/tmp/src/intermediate.ts'] = ` import {MyEnum} from './indirect'; export const intermediate = [{ data: { c: [MyEnum.Value] } }];`; data['/tmp/src/index.ts'] = `export * from './intermediate';`; data['/tmp/src/indirect.ts'] = `export * from './consts';`; data['/tmp/src/consts.ts'] = ` export enum MyEnum { Value = 3 } `; init(data); expect(reflector.annotations(reflector.getStaticSymbol(file, 'MyComponent'))[0] .providers[0] .useValue) .toEqual([{data: {c: [3]}}]); });
it('should be able to inject a ctor parameter with a @Inject and a type expression', () => { const data = Object.create(DEFAULT_TEST_DATA); const file = '/tmp/src/invalid-component.ts'; data[file] = ` import {Injectable, Inject} from '@angular/core'; @Injectable() export class SomeClass { constructor (@Inject('some-token') a: {a: string, b: string}) {} } `; init(data); const someClass = reflector.getStaticSymbol(file, 'SomeClass'); const parameters = reflector.parameters(someClass); expect(compilerCore.createInject.isTypeOf(parameters[0][0])).toBe(true); });
it('should inherit property metadata', () => { initWithDecorator({ '/tmp/src/main.ts': ` import {PropDecorator} from './decorator'; export class A {} export class B {} export class C {} export class Parent { @PropDecorator('a') a: A; @PropDecorator('b1') b: B; } export class Child extends Parent { @PropDecorator('b2') b: B; @PropDecorator('c') c: C; } export class ChildInvalidParent extends a.InvalidParent {} ` }); // Check that metadata for Parent was not changed! expect(reflector.propMetadata(reflector.getStaticSymbol('/tmp/src/main.ts', 'Parent'))) .toEqual({ 'a': [new PropDecorator('a')], 'b': [new PropDecorator('b1')], }); expect(reflector.propMetadata(reflector.getStaticSymbol('/tmp/src/main.ts', 'Child'))) .toEqual({ 'a': [new PropDecorator('a')], 'b': [new PropDecorator('b1'), new PropDecorator('b2')], 'c': [new PropDecorator('c')] }); expect(reflector.propMetadata( reflector.getStaticSymbol('/tmp/src/main.ts', 'ChildInvalidParent'))) .toEqual({}); });
it('should get and empty annotation list for a symbol with null value', () => { init({ '/tmp/test.ts': ` export var x = null; ` }); const annotations = reflector.annotations(reflector.getStaticSymbol('/tmp/test.ts', 'x')); expect(annotations).toEqual([]); });