angular-cn/packages/compiler/test/directive_resolver_spec.ts
Joey Perrott d1ea1f4c7f build: update license headers to reference Google LLC ()
Update the license headers throughout the repository to reference Google LLC
rather than Google Inc, for the required license headers.

PR Close 
2020-05-26 14:26:58 -04:00

452 lines
14 KiB
TypeScript

/**
* @license
* Copyright Google LLC 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 {core} from '@angular/compiler';
import {DirectiveResolver} from '@angular/compiler/src/directive_resolver';
import {Component, ContentChild, ContentChildren, Directive, HostBinding, HostListener, Input, Output, ViewChild, ViewChildren} from '@angular/core/src/metadata';
import {JitReflector} from '@angular/platform-browser-dynamic/src/compiler_reflector';
@Directive({selector: 'someDirective'})
class SomeDirective {
}
@Directive({selector: 'someDirective', inputs: ['c']})
class SomeDirectiveWithInputs {
@Input() a: any;
@Input('renamed') b: any;
c: any;
}
@Directive({selector: 'someDirective', outputs: ['c']})
class SomeDirectiveWithOutputs {
@Output() a: any;
@Output('renamed') b: any;
c: any;
}
@Directive({selector: 'someDirective'})
class SomeDirectiveWithSetterProps {
@Input('renamed')
set a(value: any) {
}
}
@Directive({selector: 'someDirective'})
class SomeDirectiveWithGetterOutputs {
@Output('renamed')
get a(): any {
return null;
}
}
@Directive({selector: 'someDirective', host: {'[c]': 'c'}})
class SomeDirectiveWithHostBindings {
@HostBinding() a: any;
@HostBinding('renamed') b: any;
c: any;
}
@Directive({selector: 'someDirective', host: {'(c)': 'onC()'}})
class SomeDirectiveWithHostListeners {
@HostListener('a')
onA() {
}
@HostListener('b', ['$event.value'])
onB(value: any) {
}
}
@Directive({selector: 'someDirective', queries: {'cs': new ContentChildren('c')}})
class SomeDirectiveWithContentChildren {
@ContentChildren('a') as: any;
c: any;
}
@Directive({selector: 'someDirective', queries: {'cs': new ViewChildren('c')}})
class SomeDirectiveWithViewChildren {
@ViewChildren('a') as: any;
c: any;
}
@Directive({selector: 'someDirective', queries: {'c': new ContentChild('c')}})
class SomeDirectiveWithContentChild {
@ContentChild('a') a: any;
c: any;
}
@Directive({selector: 'someDirective', queries: {'c': new ViewChild('c')}})
class SomeDirectiveWithViewChild {
@ViewChild('a') a: any;
c: any;
}
@Component({
selector: 'sample',
template: 'some template',
styles: ['some styles'],
preserveWhitespaces: true
})
class ComponentWithTemplate {
}
@Directive({
selector: 'someDirective',
host: {'[decorator]': 'decorator'},
inputs: ['decorator'],
})
class SomeDirectiveWithSameHostBindingAndInput {
@Input() @HostBinding() prop: any;
}
@Directive({selector: 'someDirective'})
class SomeDirectiveWithMalformedHostBinding1 {
@HostBinding('(a)')
onA() {
}
}
@Directive({selector: 'someDirective'})
class SomeDirectiveWithMalformedHostBinding2 {
@HostBinding('[a]')
onA() {
}
}
class SomeDirectiveWithoutMetadata {}
{
describe('DirectiveResolver', () => {
let resolver: DirectiveResolver;
beforeEach(() => {
resolver = new DirectiveResolver(new JitReflector());
});
it('should read out the Directive metadata', () => {
const directiveMetadata = resolver.resolve(SomeDirective);
expect(directiveMetadata).toEqual(core.createDirective({
selector: 'someDirective',
inputs: [],
outputs: [],
host: {},
queries: {},
guards: {},
exportAs: undefined,
providers: undefined
}));
});
it('should throw if not matching metadata is found', () => {
expect(() => {
resolver.resolve(SomeDirectiveWithoutMetadata);
}).toThrowError('No Directive annotation found on SomeDirectiveWithoutMetadata');
});
it('should support inheriting the Directive metadata', function() {
@Directive({selector: 'p'})
class Parent {
}
class ChildNoDecorator extends Parent {}
@Directive({selector: 'c'})
class ChildWithDecorator extends Parent {
}
expect(resolver.resolve(ChildNoDecorator)).toEqual(core.createDirective({
selector: 'p',
inputs: [],
outputs: [],
host: {},
queries: {},
guards: {},
exportAs: undefined,
providers: undefined
}));
expect(resolver.resolve(ChildWithDecorator)).toEqual(core.createDirective({
selector: 'c',
inputs: [],
outputs: [],
host: {},
queries: {},
guards: {},
exportAs: undefined,
providers: undefined
}));
});
describe('inputs', () => {
it('should append directive inputs', () => {
const directiveMetadata = resolver.resolve(SomeDirectiveWithInputs);
expect(directiveMetadata.inputs).toEqual(['c', 'a', 'b: renamed']);
});
it('should work with getters and setters', () => {
const directiveMetadata = resolver.resolve(SomeDirectiveWithSetterProps);
expect(directiveMetadata.inputs).toEqual(['a: renamed']);
});
it('should remove duplicate inputs', () => {
@Directive({selector: 'someDirective', inputs: ['a', 'a']})
class SomeDirectiveWithDuplicateInputs {
}
const directiveMetadata = resolver.resolve(SomeDirectiveWithDuplicateInputs);
expect(directiveMetadata.inputs).toEqual(['a']);
});
it('should use the last input if duplicate inputs (with rename)', () => {
@Directive({selector: 'someDirective', inputs: ['a', 'localA: a']})
class SomeDirectiveWithDuplicateInputs {
}
const directiveMetadata = resolver.resolve(SomeDirectiveWithDuplicateInputs);
expect(directiveMetadata.inputs).toEqual(['localA: a']);
});
it('should prefer @Input over @Directive.inputs', () => {
@Directive({selector: 'someDirective', inputs: ['a']})
class SomeDirectiveWithDuplicateInputs {
@Input('a') propA: any;
}
const directiveMetadata = resolver.resolve(SomeDirectiveWithDuplicateInputs);
expect(directiveMetadata.inputs).toEqual(['propA: a']);
});
it('should support inheriting inputs', () => {
@Directive({selector: 'p'})
class Parent {
@Input() p1: any;
@Input('p21') p2: any;
}
class Child extends Parent {
@Input('p22') p2: any;
@Input() p3: any;
}
const directiveMetadata = resolver.resolve(Child);
expect(directiveMetadata.inputs).toEqual(['p1', 'p2: p22', 'p3']);
});
});
describe('outputs', () => {
it('should append directive outputs', () => {
const directiveMetadata = resolver.resolve(SomeDirectiveWithOutputs);
expect(directiveMetadata.outputs).toEqual(['c', 'a', 'b: renamed']);
});
it('should work with getters and setters', () => {
const directiveMetadata = resolver.resolve(SomeDirectiveWithGetterOutputs);
expect(directiveMetadata.outputs).toEqual(['a: renamed']);
});
it('should remove duplicate outputs', () => {
@Directive({selector: 'someDirective', outputs: ['a', 'a']})
class SomeDirectiveWithDuplicateOutputs {
}
const directiveMetadata = resolver.resolve(SomeDirectiveWithDuplicateOutputs);
expect(directiveMetadata.outputs).toEqual(['a']);
});
it('should use the last output if duplicate outputs (with rename)', () => {
@Directive({selector: 'someDirective', outputs: ['a', 'localA: a']})
class SomeDirectiveWithDuplicateOutputs {
}
const directiveMetadata = resolver.resolve(SomeDirectiveWithDuplicateOutputs);
expect(directiveMetadata.outputs).toEqual(['localA: a']);
});
it('should prefer @Output over @Directive.outputs', () => {
@Directive({selector: 'someDirective', outputs: ['a']})
class SomeDirectiveWithDuplicateOutputs {
@Output('a') propA: any;
}
const directiveMetadata = resolver.resolve(SomeDirectiveWithDuplicateOutputs);
expect(directiveMetadata.outputs).toEqual(['propA: a']);
});
it('should support inheriting outputs', () => {
@Directive({selector: 'p'})
class Parent {
@Output() p1: any;
@Output('p21') p2: any;
}
class Child extends Parent {
@Output('p22') p2: any;
@Output() p3: any;
}
const directiveMetadata = resolver.resolve(Child);
expect(directiveMetadata.outputs).toEqual(['p1', 'p2: p22', 'p3']);
});
});
describe('host', () => {
it('should append host bindings', () => {
const directiveMetadata = resolver.resolve(SomeDirectiveWithHostBindings);
expect(directiveMetadata.host).toEqual({'[c]': 'c', '[a]': 'a', '[renamed]': 'b'});
});
it('should append host binding and input on the same property', () => {
const directiveMetadata = resolver.resolve(SomeDirectiveWithSameHostBindingAndInput);
expect(directiveMetadata.host).toEqual({'[decorator]': 'decorator', '[prop]': 'prop'});
expect(directiveMetadata.inputs).toEqual(['decorator', 'prop']);
});
it('should append host listeners', () => {
const directiveMetadata = resolver.resolve(SomeDirectiveWithHostListeners);
expect(directiveMetadata.host)
.toEqual({'(c)': 'onC()', '(a)': 'onA()', '(b)': 'onB($event.value)'});
});
it('should throw when @HostBinding name starts with "("', () => {
expect(() => resolver.resolve(SomeDirectiveWithMalformedHostBinding1))
.toThrowError('@HostBinding can not bind to events. Use @HostListener instead.');
});
it('should throw when @HostBinding name starts with "["', () => {
expect(() => resolver.resolve(SomeDirectiveWithMalformedHostBinding2))
.toThrowError(
`@HostBinding parameter should be a property name, 'class.<name>', or 'attr.<name>'.`);
});
it('should support inheriting host bindings', () => {
@Directive({selector: 'p'})
class Parent {
@HostBinding() p1: any;
@HostBinding('p21') p2: any;
}
class Child extends Parent {
@HostBinding('p22') p2: any;
@HostBinding() p3: any;
}
const directiveMetadata = resolver.resolve(Child);
expect(directiveMetadata.host)
.toEqual({'[p1]': 'p1', '[p21]': 'p2', '[p22]': 'p2', '[p3]': 'p3'});
});
it('should support inheriting host listeners', () => {
@Directive({selector: 'p'})
class Parent {
@HostListener('p1')
p1() {
}
@HostListener('p21')
p2() {
}
}
class Child extends Parent {
@HostListener('p22')
p2() {
}
@HostListener('p3')
p3() {
}
}
const directiveMetadata = resolver.resolve(Child);
expect(directiveMetadata.host)
.toEqual({'(p1)': 'p1()', '(p21)': 'p2()', '(p22)': 'p2()', '(p3)': 'p3()'});
});
it('should combine host bindings and listeners during inheritance', () => {
@Directive({selector: 'p'})
class Parent {
@HostListener('p11')
@HostListener('p12')
p1() {
}
@HostBinding('p21') @HostBinding('p22') p2: any;
}
class Child extends Parent {
@HostListener('c1')
p1() {
}
@HostBinding('c2') p2: any;
}
const directiveMetadata = resolver.resolve(Child);
expect(directiveMetadata.host).toEqual({
'(p11)': 'p1()',
'(p12)': 'p1()',
'(c1)': 'p1()',
'[p21]': 'p2',
'[p22]': 'p2',
'[c2]': 'p2'
});
});
});
describe('queries', () => {
it('should append ContentChildren', () => {
const directiveMetadata = resolver.resolve(SomeDirectiveWithContentChildren);
expect(directiveMetadata.queries)
.toEqual({'cs': new ContentChildren('c'), 'as': new ContentChildren('a')});
});
it('should append ViewChildren', () => {
const directiveMetadata = resolver.resolve(SomeDirectiveWithViewChildren);
expect(directiveMetadata.queries)
.toEqual({'cs': new ViewChildren('c'), 'as': new ViewChildren('a')});
});
it('should append ContentChild', () => {
const directiveMetadata = resolver.resolve(SomeDirectiveWithContentChild);
expect(directiveMetadata.queries)
.toEqual({'c': new ContentChild('c'), 'a': new ContentChild('a')});
});
it('should append ViewChild', () => {
const directiveMetadata = resolver.resolve(SomeDirectiveWithViewChild);
expect(directiveMetadata.queries)
.toEqual({'c': new ViewChild('c'), 'a': new ViewChild('a')});
});
it('should support inheriting queries', () => {
@Directive({selector: 'p'})
class Parent {
@ContentChild('p1') p1: any;
@ContentChild('p21') p2: any;
}
class Child extends Parent {
@ContentChild('p22') p2: any;
@ContentChild('p3') p3: any;
}
const directiveMetadata = resolver.resolve(Child);
expect(directiveMetadata.queries).toEqual({
'p1': new ContentChild('p1'),
'p2': new ContentChild('p22'),
'p3': new ContentChild('p3')
});
});
});
describe('Component', () => {
it('should read out the template related metadata from the Component metadata', () => {
const compMetadata: Component = resolver.resolve(ComponentWithTemplate);
expect(compMetadata.template).toEqual('some template');
expect(compMetadata.styles).toEqual(['some styles']);
expect(compMetadata.preserveWhitespaces).toBe(true);
});
});
});
}