feat(ivy): support inheriting input/output from bare base class (#25094)

PR Close #25094
This commit is contained in:
Ben Lesh 2018-07-23 17:01:22 -07:00 committed by Igor Minar
parent 6e2a1877ab
commit 64516da6b0
9 changed files with 378 additions and 62 deletions

View File

@ -11,6 +11,7 @@ import {Provider} from '../di';
import {R3_COMPILE_COMPONENT, R3_COMPILE_DIRECTIVE, R3_COMPILE_PIPE} from '../ivy_switch'; import {R3_COMPILE_COMPONENT, R3_COMPILE_DIRECTIVE, R3_COMPILE_PIPE} from '../ivy_switch';
import {Type} from '../type'; import {Type} from '../type';
import {TypeDecorator, makeDecorator, makePropDecorator} from '../util/decorators'; import {TypeDecorator, makeDecorator, makePropDecorator} from '../util/decorators';
import {fillProperties} from '../util/property';
import {ViewEncapsulation} from './view'; import {ViewEncapsulation} from './view';
@ -734,7 +735,7 @@ export interface Input {
* selector: 'bank-account', * selector: 'bank-account',
* template: ` * template: `
* Bank Name: {{bankName}} * Bank Name: {{bankName}}
* Account Id: {{id}} * Account Id: {{id}}
* ` * `
* }) * })
* class BankAccount { * class BankAccount {
@ -761,12 +762,47 @@ export interface Input {
bindingPropertyName?: string; bindingPropertyName?: string;
} }
const initializeBaseDef = (target: any): void => {
const constructor = target.constructor;
const inheritedBaseDef = constructor.ngBaseDef;
const baseDef = constructor.ngBaseDef = {
inputs: {},
outputs: {},
declaredInputs: {},
};
if (inheritedBaseDef) {
fillProperties(baseDef.inputs, inheritedBaseDef.inputs);
fillProperties(baseDef.outputs, inheritedBaseDef.outputs);
fillProperties(baseDef.declaredInputs, inheritedBaseDef.declaredInputs);
}
};
/**
* Does the work of creating the `ngBaseDef` property for the @Input and @Output decorators.
* @param key "inputs" or "outputs"
*/
const updateBaseDefFromIOProp = (getProp: (baseDef: {inputs?: any, outputs?: any}) => any) =>
(target: any, name: string, ...args: any[]) => {
const constructor = target.constructor;
if (!constructor.hasOwnProperty('ngBaseDef')) {
initializeBaseDef(target);
}
const baseDef = constructor.ngBaseDef;
const defProp = getProp(baseDef);
defProp[name] = args[0];
};
/** /**
* *
* @Annotation * @Annotation
*/ */
export const Input: InputDecorator = export const Input: InputDecorator = makePropDecorator(
makePropDecorator('Input', (bindingPropertyName?: string) => ({bindingPropertyName})); 'Input', (bindingPropertyName?: string) => ({bindingPropertyName}), undefined,
updateBaseDefFromIOProp(baseDef => baseDef.inputs || {}));
/** /**
* Type of the Output decorator / constructor function. * Type of the Output decorator / constructor function.
@ -800,8 +836,10 @@ export interface Output { bindingPropertyName?: string; }
* *
* @Annotation * @Annotation
*/ */
export const Output: OutputDecorator = export const Output: OutputDecorator = makePropDecorator(
makePropDecorator('Output', (bindingPropertyName?: string) => ({bindingPropertyName})); 'Output', (bindingPropertyName?: string) => ({bindingPropertyName}), undefined,
updateBaseDefFromIOProp(baseDef => baseDef.outputs || {}));
/** /**

View File

@ -16,7 +16,7 @@ import {Type} from '../type';
import {resolveRendererType2} from '../view/util'; import {resolveRendererType2} from '../view/util';
import {diPublic} from './di'; import {diPublic} from './di';
import {ComponentDefFeature, ComponentDefInternal, ComponentQuery, ComponentTemplate, ComponentType, DirectiveDefFeature, DirectiveDefInternal, DirectiveDefListOrFactory, DirectiveType, DirectiveTypesOrFactory, PipeDefInternal, PipeType, PipeTypesOrFactory} from './interfaces/definition'; import {BaseDef, ComponentDefFeature, ComponentDefInternal, ComponentQuery, ComponentTemplate, ComponentType, DirectiveDefFeature, DirectiveDefInternal, DirectiveDefListOrFactory, DirectiveType, DirectiveTypesOrFactory, PipeDefInternal, PipeType, PipeTypesOrFactory} from './interfaces/definition';
import {CssSelectorList, SelectorFlags} from './interfaces/projection'; import {CssSelectorList, SelectorFlags} from './interfaces/projection';
@ -353,6 +353,84 @@ function invertObject(obj: any, secondary?: any): any {
return newLookup; return newLookup;
} }
/**
* Create a base definition
*
* # Example
* ```
* class ShouldBeInherited {
* static ngBaseDef = defineBase({
* ...
* })
* }
* @param baseDefinition The base definition parameters
*/
export function defineBase<T>(baseDefinition: {
/**
* A map of input names.
*
* The format is in: `{[actualPropertyName: string]:(string|[string, string])}`.
*
* Given:
* ```
* class MyComponent {
* @Input()
* publicInput1: string;
*
* @Input('publicInput2')
* declaredInput2: string;
* }
* ```
*
* is described as:
* ```
* {
* publicInput1: 'publicInput1',
* declaredInput2: ['declaredInput2', 'publicInput2'],
* }
* ```
*
* Which the minifier may translate to:
* ```
* {
* minifiedPublicInput1: 'publicInput1',
* minifiedDeclaredInput2: [ 'declaredInput2', 'publicInput2'],
* }
* ```
*
* This allows the render to re-construct the minified, public, and declared names
* of properties.
*
* NOTE:
* - Because declared and public name are usually same we only generate the array
* `['declared', 'public']` format when they differ.
* - The reason why this API and `outputs` API is not the same is that `NgOnChanges` has
* inconsistent behavior in that it uses declared names rather than minified or public. For
* this reason `NgOnChanges` will be deprecated and removed in future version and this
* API will be simplified to be consistent with `outputs`.
*/
inputs?: {[P in keyof T]?: string | [string, string]};
/**
* A map of output names.
*
* The format is in: `{[actualPropertyName: string]:string}`.
*
* Which the minifier may translate to: `{[minifiedPropertyName: string]:string}`.
*
* This allows the render to re-construct the minified and non-minified names
* of properties.
*/
outputs?: {[P in keyof T]?: string};
}): BaseDef<T> {
const declaredInputs: {[P in keyof T]: P} = {} as any;
return {
inputs: invertObject(baseDefinition.inputs, declaredInputs),
declaredInputs: declaredInputs,
outputs: invertObject(baseDefinition.outputs),
};
}
/** /**
* Create a directive definition object. * Create a directive definition object.
* *

View File

@ -7,22 +7,10 @@
*/ */
import {Type} from '../../type'; import {Type} from '../../type';
import {ComponentDefInternal, ComponentType, DirectiveDefFeature, DirectiveDefInternal} from '../interfaces/definition'; import {fillProperties} from '../../util/property';
import {ComponentDefInternal, DirectiveDefFeature, DirectiveDefInternal} from '../interfaces/definition';
/**
* Sets properties on a target object from a source object, but only if
* the property doesn't already exist on the target object.
* @param target The target to set properties on
* @param source The source of the property keys and values to set
*/
function fillProperties(target: {[key: string]: string}, source: {[key: string]: string}) {
for (const key in source) {
if (source.hasOwnProperty(key) && !target.hasOwnProperty(key)) {
target[key] = source[key];
}
}
}
/** /**
* Determines if a definition is a {@link ComponentDefInternal} or a {@link DirectiveDefInternal} * Determines if a definition is a {@link ComponentDefInternal} or a {@link DirectiveDefInternal}
* @param definition The definition to examine * @param definition The definition to examine
@ -45,9 +33,9 @@ function getSuperType(type: Type<any>): Type<any>&
export function InheritDefinitionFeature( export function InheritDefinitionFeature(
definition: DirectiveDefInternal<any>| ComponentDefInternal<any>): void { definition: DirectiveDefInternal<any>| ComponentDefInternal<any>): void {
let superType = getSuperType(definition.type); let superType = getSuperType(definition.type);
let superDef: DirectiveDefInternal<any>|ComponentDefInternal<any>|undefined = undefined;
while (superType && !superDef) { while (superType) {
let superDef: DirectiveDefInternal<any>|ComponentDefInternal<any>|undefined = undefined;
if (isComponentDef(definition)) { if (isComponentDef(definition)) {
superDef = superType.ngComponentDef || superType.ngDirectiveDef; superDef = superType.ngComponentDef || superType.ngDirectiveDef;
} else { } else {
@ -57,12 +45,15 @@ export function InheritDefinitionFeature(
superDef = superType.ngDirectiveDef; superDef = superType.ngDirectiveDef;
} }
if (superDef) { const baseDef = (superType as any).ngBaseDef;
if (baseDef) {
// Merge inputs and outputs // Merge inputs and outputs
fillProperties(definition.inputs, superDef.inputs); fillProperties(definition.inputs, baseDef.inputs);
fillProperties(definition.declaredInputs, superDef.declaredInputs); fillProperties(definition.declaredInputs, baseDef.declaredInputs);
fillProperties(definition.outputs, superDef.outputs); fillProperties(definition.outputs, baseDef.outputs);
}
if (superDef) {
// Merge hostBindings // Merge hostBindings
const prevHostBindings = definition.hostBindings; const prevHostBindings = definition.hostBindings;
const superHostBindings = superDef.hostBindings; const superHostBindings = superDef.hostBindings;
@ -77,6 +68,11 @@ export function InheritDefinitionFeature(
} }
} }
// Merge inputs and outputs
fillProperties(definition.inputs, superDef.inputs);
fillProperties(definition.declaredInputs, superDef.declaredInputs);
fillProperties(definition.outputs, superDef.outputs);
// Inherit hooks // Inherit hooks
// Assume super class inheritance feature has already run. // Assume super class inheritance feature has already run.
definition.afterContentChecked = definition.afterContentChecked =
@ -97,6 +93,8 @@ export function InheritDefinitionFeature(
} }
} }
} }
break;
} else { } else {
// Even if we don't have a definition, check the type for the hooks and use those if need be // Even if we don't have a definition, check the type for the hooks and use those if need be
const superPrototype = superType.prototype; const superPrototype = superType.prototype;

View File

@ -7,7 +7,7 @@
*/ */
import {LifecycleHooksFeature, getHostElement, getRenderedText, renderComponent, whenRendered} from './component'; import {LifecycleHooksFeature, getHostElement, getRenderedText, renderComponent, whenRendered} from './component';
import {defineComponent, defineDirective, defineNgModule, definePipe} from './definition'; import {defineBase, defineComponent, defineDirective, defineNgModule, definePipe} from './definition';
import {InheritDefinitionFeature} from './features/inherit_definition_feature'; import {InheritDefinitionFeature} from './features/inherit_definition_feature';
import {NgOnChangesFeature} from './features/ng_onchanges_feature'; import {NgOnChangesFeature} from './features/ng_onchanges_feature';
import {PublicFeature} from './features/public_feature'; import {PublicFeature} from './features/public_feature';
@ -164,6 +164,7 @@ export {
defineComponent, defineComponent,
defineDirective, defineDirective,
defineNgModule, defineNgModule,
defineBase,
definePipe, definePipe,
getHostElement, getHostElement,
getRenderedText, getRenderedText,

View File

@ -67,29 +67,15 @@ export interface PipeType<T> extends Type<T> { ngPipeDef: never; }
export type DirectiveDefInternal<T> = DirectiveDef<T, string>; export type DirectiveDefInternal<T> = DirectiveDef<T, string>;
/** /**
* Runtime link information for Directives. * Runtime information for classes that are inherited by components or directives
* that aren't defined as components or directives.
* *
* This is internal data structure used by the render to link * This is an internal data structure used by the render to determine what inputs
* directives into templates. * and outputs should be inherited.
* *
* NOTE: Always use `defineDirective` function to create this object, * See: {@link defineBase}
* never create the object directly since the shape of this object
* can change between versions.
*
* @param Selector type metadata specifying the selector of the directive or component
*
* See: {@link defineDirective}
*/ */
export interface DirectiveDef<T, Selector extends string> { export interface BaseDef<T> {
/** Token representing the directive. Used by DI. */
type: Type<T>;
/** Function that makes a directive public to the DI system. */
diPublic: ((def: DirectiveDef<T, string>) => void)|null;
/** The selectors that will be used to match nodes to this directive. */
selectors: CssSelectorList;
/** /**
* A dictionary mapping the inputs' minified property names to their public API names, which * A dictionary mapping the inputs' minified property names to their public API names, which
* are their aliases if any, or their original unminified property names * are their aliases if any, or their original unminified property names
@ -109,6 +95,31 @@ export interface DirectiveDef<T, Selector extends string> {
* (as in `@Output('alias') propertyName: any;`). * (as in `@Output('alias') propertyName: any;`).
*/ */
readonly outputs: {[P in keyof T]: P}; readonly outputs: {[P in keyof T]: P};
}
/**
* Runtime link information for Directives.
*
* This is internal data structure used by the render to link
* directives into templates.
*
* NOTE: Always use `defineDirective` function to create this object,
* never create the object directly since the shape of this object
* can change between versions.
*
* @param Selector type metadata specifying the selector of the directive or component
*
* See: {@link defineDirective}
*/
export interface DirectiveDef<T, Selector extends string> extends BaseDef<T> {
/** Token representing the directive. Used by DI. */
type: Type<T>;
/** Function that makes a directive public to the DI system. */
diPublic: ((def: DirectiveDef<T, string>) => void)|null;
/** The selectors that will be used to match nodes to this directive. */
selectors: CssSelectorList;
/** /**
* Name under which the directive is exported (for use with local references in template) * Name under which the directive is exported (for use with local references in template)

View File

@ -41,31 +41,34 @@ export const PROP_METADATA = '__prop__metadata__';
/** /**
* @suppress {globalThis} * @suppress {globalThis}
*/ */
export function makeDecorator( export function makeDecorator<T>(
name: string, props?: (...args: any[]) => any, parentClass?: any, name: string, props?: (...args: any[]) => any, parentClass?: any,
chainFn?: (fn: Function) => void, typeFn?: (type: Type<any>, ...args: any[]) => void): additionalProcessing?: (type: Type<T>) => void,
typeFn?: (type: Type<T>, ...args: any[]) => void):
{new (...args: any[]): any; (...args: any[]): any; (...args: any[]): (cls: any) => any;} { {new (...args: any[]): any; (...args: any[]): any; (...args: any[]): (cls: any) => any;} {
const metaCtor = makeMetadataCtor(props); const metaCtor = makeMetadataCtor(props);
function DecoratorFactory(...args: any[]): (cls: any) => any { function DecoratorFactory(...args: any[]): (cls: Type<T>) => any {
if (this instanceof DecoratorFactory) { if (this instanceof DecoratorFactory) {
metaCtor.call(this, ...args); metaCtor.call(this, ...args);
return this; return this;
} }
const annotationInstance = new (<any>DecoratorFactory)(...args); const annotationInstance = new (DecoratorFactory as any)(...args);
const TypeDecorator: TypeDecorator = <TypeDecorator>function TypeDecorator(cls: Type<any>) { return function TypeDecorator(cls: Type<T>) {
typeFn && typeFn(cls, ...args); if (typeFn) typeFn(cls, ...args);
// Use of Object.defineProperty is important since it creates non-enumerable property which // Use of Object.defineProperty is important since it creates non-enumerable property which
// prevents the property is copied during subclassing. // prevents the property is copied during subclassing.
const annotations = cls.hasOwnProperty(ANNOTATIONS) ? const annotations = cls.hasOwnProperty(ANNOTATIONS) ?
(cls as any)[ANNOTATIONS] : (cls as any)[ANNOTATIONS] :
Object.defineProperty(cls, ANNOTATIONS, {value: []})[ANNOTATIONS]; Object.defineProperty(cls, ANNOTATIONS, {value: []})[ANNOTATIONS];
annotations.push(annotationInstance); annotations.push(annotationInstance);
if (additionalProcessing) additionalProcessing(cls);
return cls; return cls;
}; };
if (chainFn) chainFn(TypeDecorator);
return TypeDecorator;
} }
if (parentClass) { if (parentClass) {
@ -73,7 +76,7 @@ export function makeDecorator(
} }
DecoratorFactory.prototype.ngMetadataName = name; DecoratorFactory.prototype.ngMetadataName = name;
(<any>DecoratorFactory).annotationCls = DecoratorFactory; (DecoratorFactory as any).annotationCls = DecoratorFactory;
return DecoratorFactory as any; return DecoratorFactory as any;
} }
@ -127,7 +130,8 @@ export function makeParamDecorator(
} }
export function makePropDecorator( export function makePropDecorator(
name: string, props?: (...args: any[]) => any, parentClass?: any): any { name: string, props?: (...args: any[]) => any, parentClass?: any,
additionalProcessing?: (target: any, name: string, ...args: any[]) => void): any {
const metaCtor = makeMetadataCtor(props); const metaCtor = makeMetadataCtor(props);
function PropDecoratorFactory(...args: any[]): any { function PropDecoratorFactory(...args: any[]): any {
@ -138,7 +142,7 @@ export function makePropDecorator(
const decoratorInstance = new (<any>PropDecoratorFactory)(...args); const decoratorInstance = new (<any>PropDecoratorFactory)(...args);
return function PropDecorator(target: any, name: string) { function PropDecorator(target: any, name: string) {
const constructor = target.constructor; const constructor = target.constructor;
// Use of Object.defineProperty is important since it creates non-enumerable property which // Use of Object.defineProperty is important since it creates non-enumerable property which
// prevents the property is copied during subclassing. // prevents the property is copied during subclassing.
@ -147,7 +151,11 @@ export function makePropDecorator(
Object.defineProperty(constructor, PROP_METADATA, {value: {}})[PROP_METADATA]; Object.defineProperty(constructor, PROP_METADATA, {value: {}})[PROP_METADATA];
meta[name] = meta.hasOwnProperty(name) && meta[name] || []; meta[name] = meta.hasOwnProperty(name) && meta[name] || [];
meta[name].unshift(decoratorInstance); meta[name].unshift(decoratorInstance);
};
if (additionalProcessing) additionalProcessing(target, name, ...args);
}
return PropDecorator;
} }
if (parentClass) { if (parentClass) {

View File

@ -14,3 +14,17 @@ export function getClosureSafeProperty<T>(objWithPropertyToExtract: T, target: a
} }
throw Error('Could not find renamed property on target object.'); throw Error('Could not find renamed property on target object.');
} }
/**
* Sets properties on a target object from a source object, but only if
* the property doesn't already exist on the target object.
* @param target The target to set properties on
* @param source The source of the property keys and values to set
*/
export function fillProperties(target: {[key: string]: string}, source: {[key: string]: string}) {
for (const key in source) {
if (source.hasOwnProperty(key) && !target.hasOwnProperty(key)) {
target[key] = source[key];
}
}
}

View File

@ -6,9 +6,9 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {DoCheck, EventEmitter, Input, OnChanges, Output, SimpleChange, SimpleChanges} from '../../src/core'; import {EventEmitter, Output} from '../../src/core';
import {InheritDefinitionFeature} from '../../src/render3/features/inherit_definition_feature'; import {InheritDefinitionFeature} from '../../src/render3/features/inherit_definition_feature';
import {DirectiveDefInternal, NgOnChangesFeature, defineComponent, defineDirective} from '../../src/render3/index'; import {DirectiveDefInternal, defineBase, defineComponent, defineDirective} from '../../src/render3/index';
describe('InheritDefinitionFeature', () => { describe('InheritDefinitionFeature', () => {
it('should inherit lifecycle hooks', () => { it('should inherit lifecycle hooks', () => {
@ -131,6 +131,147 @@ describe('InheritDefinitionFeature', () => {
}); });
}); });
it('should inherit inputs from ngBaseDefs along the way', () => {
class Class5 {
input5 = 'data, so data';
static ngBaseDef = defineBase({
inputs: {
input5: 'input5',
},
});
}
// tslint:disable-next-line:class-as-namespace
class Class4 extends Class5 {
input4 = 'hehe';
static ngDirectiveDef = defineDirective({
inputs: {
input4: 'input4',
},
type: Class4,
selectors: [['', 'superDir', '']],
factory: () => new Class4(),
features: [InheritDefinitionFeature],
});
}
class Class3 extends Class4 {}
class Class2 extends Class3 {
input3 = 'wee';
static ngBaseDef = defineBase({
inputs: {
input3: ['alias3', 'input3'],
}
}) as any;
}
// tslint:disable-next-line:class-as-namespace
class Class1 extends Class2 {
input1 = 'test';
input2 = 'whatever';
static ngDirectiveDef = defineDirective({
type: Class1,
inputs: {
input1: 'input1',
input2: 'input2',
},
selectors: [['', 'subDir', '']],
factory: () => new Class1(),
features: [InheritDefinitionFeature],
});
}
const subDef = Class1.ngDirectiveDef as DirectiveDefInternal<any>;
expect(subDef.inputs).toEqual({
input1: 'input1',
input2: 'input2',
alias3: 'input3',
input4: 'input4',
input5: 'input5',
});
expect(subDef.declaredInputs).toEqual({
input1: 'input1',
input2: 'input2',
input3: 'input3',
input4: 'input4',
input5: 'input5',
});
});
it('should inherit outputs from ngBaseDefs along the way', () => {
class Class5 {
output5 = 'data, so data';
static ngBaseDef = defineBase({
outputs: {
output5: 'alias5',
},
});
}
// tslint:disable-next-line:class-as-namespace
class Class4 extends Class5 {
output4 = 'hehe';
static ngDirectiveDef = defineDirective({
outputs: {
output4: 'alias4',
},
type: Class4,
selectors: [['', 'superDir', '']],
factory: () => new Class4(),
features: [InheritDefinitionFeature],
});
}
class Class3 extends Class4 {}
class Class2 extends Class3 {
output3 = 'wee';
static ngBaseDef = defineBase({
outputs: {
output3: 'alias3',
}
}) as any;
}
// tslint:disable-next-line:class-as-namespace
class Class1 extends Class2 {
output1 = 'test';
output2 = 'whatever';
static ngDirectiveDef = defineDirective({
type: Class1,
outputs: {
output1: 'alias1',
output2: 'alias2',
},
selectors: [['', 'subDir', '']],
factory: () => new Class1(),
features: [InheritDefinitionFeature],
});
}
const subDef = Class1.ngDirectiveDef as DirectiveDefInternal<any>;
expect(subDef.outputs).toEqual({
alias1: 'output1',
alias2: 'output2',
alias3: 'output3',
alias4: 'output4',
alias5: 'output5',
});
});
it('should compose hostBindings', () => { it('should compose hostBindings', () => {
const log: Array<[string, number, number]> = []; const log: Array<[string, number, number]> = [];

View File

@ -12,10 +12,11 @@ import {InjectorDef, defineInjectable} from '@angular/core/src/di/defs';
import {Injectable} from '@angular/core/src/di/injectable'; import {Injectable} from '@angular/core/src/di/injectable';
import {inject, setCurrentInjector} from '@angular/core/src/di/injector'; import {inject, setCurrentInjector} from '@angular/core/src/di/injector';
import {ivyEnabled} from '@angular/core/src/ivy_switch'; import {ivyEnabled} from '@angular/core/src/ivy_switch';
import {Component, HostBinding, HostListener, Pipe} from '@angular/core/src/metadata/directives'; import {Component, HostBinding, HostListener, Input, Output, Pipe} from '@angular/core/src/metadata/directives';
import {NgModule, NgModuleDefInternal} from '@angular/core/src/metadata/ng_module'; import {NgModule, NgModuleDefInternal} from '@angular/core/src/metadata/ng_module';
import {ComponentDefInternal, PipeDefInternal} from '@angular/core/src/render3/interfaces/definition'; import {ComponentDefInternal, PipeDefInternal} from '@angular/core/src/render3/interfaces/definition';
ivyEnabled && describe('render3 jit', () => { ivyEnabled && describe('render3 jit', () => {
let injector: any; let injector: any;
beforeAll(() => { injector = setCurrentInjector(null); }); beforeAll(() => { injector = setCurrentInjector(null); });
@ -233,6 +234,32 @@ ivyEnabled && describe('render3 jit', () => {
const pipeDef = (P as any).ngPipeDef as PipeDefInternal<P>; const pipeDef = (P as any).ngPipeDef as PipeDefInternal<P>;
expect(pipeDef.pure).toBe(true, 'pipe should be pure'); expect(pipeDef.pure).toBe(true, 'pipe should be pure');
}); });
it('should add ngBaseDef to types with @Input properties', () => {
class C {
@Input('alias1')
prop1 = 'test';
@Input('alias2')
prop2 = 'test';
}
expect((C as any).ngBaseDef).toBeDefined();
expect((C as any).ngBaseDef.inputs).toEqual({prop1: 'alias1', prop2: 'alias2'});
});
it('should add ngBaseDef to types with @Output properties', () => {
class C {
@Output('alias1')
prop1 = 'test';
@Output('alias2')
prop2 = 'test';
}
expect((C as any).ngBaseDef).toBeDefined();
expect((C as any).ngBaseDef.outputs).toEqual({prop1: 'alias1', prop2: 'alias2'});
});
}); });
it('ensure at least one spec exists', () => {}); it('ensure at least one spec exists', () => {});