/** * @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 {StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, StaticSymbolResolverHost} from '@angular/compiler'; import {HostListener, Inject, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core'; import {MockStaticSymbolResolverHost, MockSummaryResolver} from './static_symbol_resolver_spec'; describe('StaticReflector', () => { let noContext: StaticSymbol; let host: StaticSymbolResolverHost; let symbolResolver: StaticSymbolResolver; let reflector: StaticReflector; function init( testData: {[key: string]: any} = DEFAULT_TEST_DATA, decorators: {name: string, filePath: string, ctor: any}[] = []) { const symbolCache = new StaticSymbolCache(); host = new MockStaticSymbolResolverHost(testData); symbolResolver = new StaticSymbolResolver(host, symbolCache, new MockSummaryResolver([])); reflector = new StaticReflector(symbolResolver, decorators); noContext = reflector.getStaticSymbol('', ''); } beforeEach(() => init()); function simplify(context: StaticSymbol, value: any) { return reflector.simplify(context, value); } it('should get annotations for NgFor', () => { const NgFor = reflector.findDeclaration('@angular/common/src/directives/ng_for', 'NgFor'); const annotations = reflector.annotations(NgFor); expect(annotations.length).toEqual(1); const annotation = annotations[0]; expect(annotation.selector).toEqual('[ngFor][ngForOf]'); expect(annotation.inputs).toEqual(['ngForTrackBy', 'ngForOf', 'ngForTemplate']); }); it('should get constructor for NgFor', () => { const NgFor = reflector.findDeclaration('@angular/common/src/directives/ng_for', 'NgFor'); const ViewContainerRef = reflector.findDeclaration( '@angular/core/src/linker/view_container_ref', 'ViewContainerRef'); const TemplateRef = reflector.findDeclaration('@angular/core/src/linker/template_ref', 'TemplateRef'); const IterableDiffers = reflector.findDeclaration( '@angular/core/src/change_detection/differs/iterable_differs', 'IterableDiffers'); const ChangeDetectorRef = reflector.findDeclaration( '@angular/core/src/change_detection/change_detector_ref', 'ChangeDetectorRef'); const parameters = reflector.parameters(NgFor); expect(parameters).toEqual([ [ViewContainerRef], [TemplateRef], [IterableDiffers], [ChangeDetectorRef] ]); }); 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 get and empty annotation list for an unknown class', () => { const UnknownClass = reflector.findDeclaration('src/app/app.component', 'UnknownClass'); const annotations = reflector.annotations(UnknownClass); expect(annotations).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([]); }); it('should get propMetadata for HeroDetailComponent', () => { const HeroDetailComponent = reflector.findDeclaration('src/app/hero-detail.component', 'HeroDetailComponent'); const props = reflector.propMetadata(HeroDetailComponent); expect(props['hero']).toBeTruthy(); expect(props['onMouseOver']).toEqual([new HostListener('mouseover', ['$event'])]); }); it('should get an empty object from propMetadata for an unknown class', () => { const UnknownClass = reflector.findDeclaration('src/app/app.component', 'UnknownClass'); const properties = reflector.propMetadata(UnknownClass); expect(properties).toEqual({}); }); it('should get empty parameters list for an unknown class ', () => { const UnknownClass = reflector.findDeclaration('src/app/app.component', 'UnknownClass'); const parameters = reflector.parameters(UnknownClass); expect(parameters).toEqual([]); }); it('should provide context for errors reported by the collector', () => { const SomeClass = reflector.findDeclaration('src/error-reporting', 'SomeClass'); expect(() => reflector.annotations(SomeClass)) .toThrow(new Error( 'Error encountered resolving symbol values statically. A reasonable error message (position 13:34 in the original .ts file), resolving symbol ErrorSym in /tmp/src/error-references.d.ts, resolving symbol Link2 in /tmp/src/error-references.d.ts, resolving symbol Link1 in /tmp/src/error-references.d.ts, resolving symbol SomeClass in /tmp/src/error-reporting.d.ts, resolving symbol SomeClass in /tmp/src/error-reporting.d.ts')); }); it('should simplify primitive into itself', () => { expect(simplify(noContext, 1)).toBe(1); expect(simplify(noContext, true)).toBe(true); expect(simplify(noContext, 'some value')).toBe('some value'); }); it('should simplify a static symbol into itself', () => { const staticSymbol = reflector.getStaticSymbol('', ''); expect(simplify(noContext, staticSymbol)).toBe(staticSymbol); }); it('should simplify an array into a copy of the array', () => { expect(simplify(noContext, [1, 2, 3])).toEqual([1, 2, 3]); }); it('should simplify an object to a copy of the object', () => { const expr = {a: 1, b: 2, c: 3}; expect(simplify(noContext, expr)).toEqual(expr); }); it('should simplify &&', () => { expect(simplify(noContext, ({__symbolic: 'binop', operator: '&&', left: true, right: true}))) .toBe(true); expect(simplify(noContext, ({__symbolic: 'binop', operator: '&&', left: true, right: false}))) .toBe(false); expect(simplify(noContext, ({__symbolic: 'binop', operator: '&&', left: false, right: true}))) .toBe(false); expect(simplify(noContext, ({__symbolic: 'binop', operator: '&&', left: false, right: false}))) .toBe(false); }); it('should simplify ||', () => { expect(simplify(noContext, ({__symbolic: 'binop', operator: '||', left: true, right: true}))) .toBe(true); expect(simplify(noContext, ({__symbolic: 'binop', operator: '||', left: true, right: false}))) .toBe(true); expect(simplify(noContext, ({__symbolic: 'binop', operator: '||', left: false, right: true}))) .toBe(true); expect(simplify(noContext, ({__symbolic: 'binop', operator: '||', left: false, right: false}))) .toBe(false); }); it('should simplify &', () => { expect(simplify(noContext, ({__symbolic: 'binop', operator: '&', left: 0x22, right: 0x0F}))) .toBe(0x22 & 0x0F); expect(simplify(noContext, ({__symbolic: 'binop', operator: '&', left: 0x22, right: 0xF0}))) .toBe(0x22 & 0xF0); }); it('should simplify |', () => { expect(simplify(noContext, ({__symbolic: 'binop', operator: '|', left: 0x22, right: 0x0F}))) .toBe(0x22 | 0x0F); expect(simplify(noContext, ({__symbolic: 'binop', operator: '|', left: 0x22, right: 0xF0}))) .toBe(0x22 | 0xF0); }); it('should simplify ^', () => { expect(simplify(noContext, ({__symbolic: 'binop', operator: '|', left: 0x22, right: 0x0F}))) .toBe(0x22 | 0x0F); expect(simplify(noContext, ({__symbolic: 'binop', operator: '|', left: 0x22, right: 0xF0}))) .toBe(0x22 | 0xF0); }); it('should simplify ==', () => { expect(simplify(noContext, ({__symbolic: 'binop', operator: '==', left: 0x22, right: 0x22}))) .toBe(0x22 == 0x22); expect(simplify(noContext, ({__symbolic: 'binop', operator: '==', left: 0x22, right: 0xF0}))) .toBe(0x22 as any == 0xF0); }); it('should simplify !=', () => { expect(simplify(noContext, ({__symbolic: 'binop', operator: '!=', left: 0x22, right: 0x22}))) .toBe(0x22 != 0x22); expect(simplify(noContext, ({__symbolic: 'binop', operator: '!=', left: 0x22, right: 0xF0}))) .toBe(0x22 as any != 0xF0); }); it('should simplify ===', () => { expect(simplify(noContext, ({__symbolic: 'binop', operator: '===', left: 0x22, right: 0x22}))) .toBe(0x22 === 0x22); expect(simplify(noContext, ({__symbolic: 'binop', operator: '===', left: 0x22, right: 0xF0}))) .toBe(0x22 as any === 0xF0); }); it('should simplify !==', () => { expect(simplify(noContext, ({__symbolic: 'binop', operator: '!==', left: 0x22, right: 0x22}))) .toBe(0x22 !== 0x22); expect(simplify(noContext, ({__symbolic: 'binop', operator: '!==', left: 0x22, right: 0xF0}))) .toBe(0x22 as any !== 0xF0); }); it('should simplify >', () => { expect(simplify(noContext, ({__symbolic: 'binop', operator: '>', left: 1, right: 1}))) .toBe(1 > 1); expect(simplify(noContext, ({__symbolic: 'binop', operator: '>', left: 1, right: 0}))) .toBe(1 > 0); expect(simplify(noContext, ({__symbolic: 'binop', operator: '>', left: 0, right: 1}))) .toBe(0 > 1); }); it('should simplify >=', () => { expect(simplify(noContext, ({__symbolic: 'binop', operator: '>=', left: 1, right: 1}))) .toBe(1 >= 1); expect(simplify(noContext, ({__symbolic: 'binop', operator: '>=', left: 1, right: 0}))) .toBe(1 >= 0); expect(simplify(noContext, ({__symbolic: 'binop', operator: '>=', left: 0, right: 1}))) .toBe(0 >= 1); }); it('should simplify <=', () => { expect(simplify(noContext, ({__symbolic: 'binop', operator: '<=', left: 1, right: 1}))) .toBe(1 <= 1); expect(simplify(noContext, ({__symbolic: 'binop', operator: '<=', left: 1, right: 0}))) .toBe(1 <= 0); expect(simplify(noContext, ({__symbolic: 'binop', operator: '<=', left: 0, right: 1}))) .toBe(0 <= 1); }); it('should simplify <', () => { expect(simplify(noContext, ({__symbolic: 'binop', operator: '<', left: 1, right: 1}))) .toBe(1 < 1); expect(simplify(noContext, ({__symbolic: 'binop', operator: '<', left: 1, right: 0}))) .toBe(1 < 0); expect(simplify(noContext, ({__symbolic: 'binop', operator: '<', left: 0, right: 1}))) .toBe(0 < 1); }); it('should simplify <<', () => { expect(simplify(noContext, ({__symbolic: 'binop', operator: '<<', left: 0x55, right: 2}))) .toBe(0x55 << 2); }); it('should simplify >>', () => { expect(simplify(noContext, ({__symbolic: 'binop', operator: '>>', left: 0x55, right: 2}))) .toBe(0x55 >> 2); }); it('should simplify +', () => { expect(simplify(noContext, ({__symbolic: 'binop', operator: '+', left: 0x55, right: 2}))) .toBe(0x55 + 2); }); it('should simplify -', () => { expect(simplify(noContext, ({__symbolic: 'binop', operator: '-', left: 0x55, right: 2}))) .toBe(0x55 - 2); }); it('should simplify *', () => { expect(simplify(noContext, ({__symbolic: 'binop', operator: '*', left: 0x55, right: 2}))) .toBe(0x55 * 2); }); it('should simplify /', () => { expect(simplify(noContext, ({__symbolic: 'binop', operator: '/', left: 0x55, right: 2}))) .toBe(0x55 / 2); }); it('should simplify %', () => { expect(simplify(noContext, ({__symbolic: 'binop', operator: '%', left: 0x55, right: 2}))) .toBe(0x55 % 2); }); it('should simplify prefix -', () => { expect(simplify(noContext, ({__symbolic: 'pre', operator: '-', operand: 2}))).toBe(-2); }); it('should simplify prefix ~', () => { expect(simplify(noContext, ({__symbolic: 'pre', operator: '~', operand: 2}))).toBe(~2); }); it('should simplify prefix !', () => { expect(simplify(noContext, ({__symbolic: 'pre', operator: '!', operand: true}))).toBe(!true); expect(simplify(noContext, ({__symbolic: 'pre', operator: '!', operand: false}))).toBe(!false); }); it('should simplify an array index', () => { expect(simplify(noContext, ({__symbolic: 'index', expression: [1, 2, 3], index: 2}))).toBe(3); }); it('should simplify an object index', () => { const expr = {__symbolic: 'select', expression: {a: 1, b: 2, c: 3}, member: 'b'}; expect(simplify(noContext, expr)).toBe(2); }); it('should simplify a file reference', () => { expect(simplify( reflector.getStaticSymbol('/src/cases', ''), reflector.getStaticSymbol('/src/extern.d.ts', 's'))) .toEqual('s'); }); it('should simplify a non existing reference as a static symbol', () => { expect(simplify( reflector.getStaticSymbol('/src/cases', ''), reflector.getStaticSymbol('/src/extern.d.ts', 'nonExisting'))) .toEqual(reflector.getStaticSymbol('/src/extern.d.ts', 'nonExisting')); }); it('should simplify a function reference as a static symbol', () => { expect(simplify( reflector.getStaticSymbol('/src/cases', 'myFunction'), ({__symbolic: 'function', parameters: ['a'], value: []}))) .toEqual(reflector.getStaticSymbol('/src/cases', 'myFunction')); }); 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); }); it('should error on direct recursive calls', () => { expect( () => simplify( reflector.getStaticSymbol('/tmp/src/function-reference.ts', ''), reflector.getStaticSymbol('/tmp/src/function-reference.ts', 'recursion'))) .toThrow(new Error( 'Recursion not supported, resolving symbol recursive in /tmp/src/function-recursive.d.ts, resolving symbol recursion in /tmp/src/function-reference.ts, resolving symbol in /tmp/src/function-reference.ts')); }); it('should throw a SyntaxError without stack trace when the required resource cannot be resolved', () => { expect( () => simplify( reflector.getStaticSymbol('/tmp/src/function-reference.ts', 'AppModule'), ({ __symbolic: 'error', message: 'Could not resolve ./does-not-exist.component relative to /tmp/src/function-reference.ts' }))) .toThrowError( 'Error encountered resolving symbol values statically. Could not resolve ./does-not-exist.component relative to /tmp/src/function-reference.ts, resolving symbol AppModule in /tmp/src/function-reference.ts'); }); it('should record data about the error in the exception', () => { let threw = false; try { const metadata = host.getMetadataFor('/tmp/src/invalid-metadata.ts'); expect(metadata).toBeDefined(); const moduleMetadata: any = metadata[0]['metadata']; expect(moduleMetadata).toBeDefined(); const classData: any = moduleMetadata['InvalidMetadata']; expect(classData).toBeDefined(); simplify( reflector.getStaticSymbol('/tmp/src/invalid-metadata.ts', ''), classData.decorators[0]); } catch (e) { expect(e.fileName).toBe('/tmp/src/invalid-metadata.ts'); threw = true; } expect(threw).toBe(true); }); it('should error on indirect recursive calls', () => { expect( () => simplify( reflector.getStaticSymbol('/tmp/src/function-reference.ts', ''), reflector.getStaticSymbol('/tmp/src/function-reference.ts', 'indirectRecursion'))) .toThrow(new Error( 'Recursion not supported, resolving symbol indirectRecursion2 in /tmp/src/function-recursive.d.ts, resolving symbol indirectRecursion1 in /tmp/src/function-recursive.d.ts, resolving symbol indirectRecursion in /tmp/src/function-reference.ts, resolving symbol in /tmp/src/function-reference.ts')); }); it('should simplify a spread expression', () => { expect(simplify( reflector.getStaticSymbol('/tmp/src/spread.ts', ''), reflector.getStaticSymbol('/tmp/src/spread.ts', 'spread'))) .toEqual([0, 1, 2, 3, 4, 5]); }); it('should be able to get metadata for a class containing a custom decorator', () => { const props = reflector.propMetadata( reflector.getStaticSymbol('/tmp/src/custom-decorator-reference.ts', 'Foo')); expect(props).toEqual({foo: []}); }); it('should read ctor parameters with forwardRef', () => { const src = '/tmp/src/forward-ref.ts'; const dep = reflector.getStaticSymbol(src, 'Dep'); const props = reflector.parameters(reflector.getStaticSymbol(src, 'Forward')); expect(props).toEqual([[dep, new Inject(dep)]]); }); it('should report an error for invalid function calls', () => { expect( () => reflector.annotations( reflector.getStaticSymbol('/tmp/src/invalid-calls.ts', 'MyComponent'))) .toThrow(new Error( `Error encountered resolving symbol values statically. Calling function 'someFunction', function calls are not supported. Consider replacing the function or lambda with a reference to an exported function, resolving symbol MyComponent in /tmp/src/invalid-calls.ts, resolving symbol MyComponent in /tmp/src/invalid-calls.ts`)); }); it('should be able to get metadata for a class containing a static method call', () => { const annotations = reflector.annotations( reflector.getStaticSymbol('/tmp/src/static-method-call.ts', 'MyComponent')); expect(annotations.length).toBe(1); expect(annotations[0].providers).toEqual({provider: 'a', useValue: 100}); }); it('should be able to get metadata for a class containing a static field reference', () => { const annotations = reflector.annotations( reflector.getStaticSymbol('/tmp/src/static-field-reference.ts', 'Foo')); expect(annotations.length).toBe(1); expect(annotations[0].providers).toEqual([{provider: 'a', useValue: 'Some string'}]); }); it('should be able to get the metadata for a class calling a method with a conditional expression', () => { const annotations = reflector.annotations( reflector.getStaticSymbol('/tmp/src/static-method-call.ts', 'MyCondComponent')); expect(annotations.length).toBe(1); expect(annotations[0].providers).toEqual([ [{provider: 'a', useValue: '1'}], [{provider: 'a', useValue: '2'}] ]); }); 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 be able to get the metadata for a class calling a method with default parameters', () => { const annotations = reflector.annotations( reflector.getStaticSymbol('/tmp/src/static-method-call.ts', 'MyDefaultsComponent')); expect(annotations.length).toBe(1); expect(annotations[0].providers).toEqual([['a', true, false]]); }); it('should be able to get metadata with a reference to a static method', () => { const annotations = reflector.annotations( reflector.getStaticSymbol('/tmp/src/static-method-ref.ts', 'MethodReference')); expect(annotations.length).toBe(1); expect(annotations[0].providers[0].useValue.members[0]).toEqual('staticMethod'); }); // #13605 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: "