From fc46838b2797ba93a5928ad66364d7d56beb1505 Mon Sep 17 00:00:00 2001 From: Isaac Datlof Date: Mon, 5 Mar 2018 18:13:23 -0500 Subject: [PATCH] feat: support inputs and outputs from extended components --- lib/mock-component/mock-component.spec.ts | 20 ++++++++--- lib/mock-component/mock-component.ts | 33 ++++++++++--------- .../simple-component.component.ts | 13 ++++++-- 3 files changed, 44 insertions(+), 22 deletions(-) diff --git a/lib/mock-component/mock-component.spec.ts b/lib/mock-component/mock-component.spec.ts index cb576a23b4..0aa7055af8 100644 --- a/lib/mock-component/mock-component.spec.ts +++ b/lib/mock-component/mock-component.spec.ts @@ -11,7 +11,8 @@ import { SimpleComponent } from './test-components/simple-component.component'; template: ` + (someOutput1)="emitted = $event" + (someOutput2)="emitted = $event"> @@ -55,7 +56,7 @@ describe('MockComponent', () => { fixture.detectChanges(); const mockedComponent = fixture.debugElement .query(By.directive(MockComponent(SimpleComponent))) - .componentInstance as SimpleComponent; + .componentInstance; expect(mockedComponent.someInput).toEqual('hi'); expect(mockedComponent.someInput2).toEqual('bye'); }); @@ -64,11 +65,20 @@ describe('MockComponent', () => { fixture.detectChanges(); const mockedComponent = fixture.debugElement .query(By.directive(MockComponent(SimpleComponent))) - .componentInstance as SimpleComponent; + .componentInstance; mockedComponent.someOutput1.emit('hi'); expect(component.emitted).toEqual('hi'); }); + it('should trigger output bound behavior for extended outputs', () => { + fixture.detectChanges(); + const mockedComponent = fixture.debugElement + .query(By.directive(MockComponent(SimpleComponent))) + .componentInstance; + mockedComponent.someOutput2.emit('hi'); + expect(component.emitted).toEqual('hi'); + }); + it('the mock should have an ng-content body', () => { fixture.detectChanges(); const mockedComponent = fixture.debugElement.query(By.css('#ng-content-component')); @@ -78,8 +88,8 @@ describe('MockComponent', () => { it('should give each instance of a mocked component its own event emitter', () => { const mockedComponents = fixture.debugElement .queryAll(By.directive(MockComponent(SimpleComponent))); - const mockedComponent1 = mockedComponents[0].componentInstance as SimpleComponent; - const mockedComponent2 = mockedComponents[1].componentInstance as SimpleComponent; + const mockedComponent1 = mockedComponents[0].componentInstance; + const mockedComponent2 = mockedComponents[1].componentInstance; expect(mockedComponent1.someOutput1).not.toEqual(mockedComponent2.someOutput1); }); diff --git a/lib/mock-component/mock-component.ts b/lib/mock-component/mock-component.ts index e3ef022071..112263e8bd 100644 --- a/lib/mock-component/mock-component.ts +++ b/lib/mock-component/mock-component.ts @@ -3,6 +3,22 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; const cache = new Map, Type>(); +const metaReducer = (propertyMetaData: any) => + (acc: string[], meta: any): string[] => + acc.concat(propertyMetaData[meta].map((m: any): string => + [meta, m.bindingPropertyName || meta].join(':'))); + +function getInputsOrOutputs(component: Type, type: 'Input' | 'Output'): string[] { + if (!component) { + return []; + } + const propertyMetadata = (component as any).__prop__metadata__ || {}; + const outputs = Object.keys(propertyMetadata) + .filter((meta) => propertyMetadata[meta][0].ngMetadataName === type) + .reduce(metaReducer(propertyMetadata), []); + return outputs.concat(getInputsOrOutputs((component as any).__proto__, type)); +} + export function MockComponent(component: Type): Type { const cacheHit = cache.get(component); if (cacheHit) { @@ -10,16 +26,11 @@ export function MockComponent(component: Type): Type isInput(propertyMetadata[meta])) - .map((meta) => [meta, propertyMetadata[meta][0].bindingPropertyName || meta].join(':')), - outputs: Object.keys(propertyMetadata) - .filter((meta) => isOutput(propertyMetadata[meta])) - .map((meta) => [meta, propertyMetadata[meta][0].bindingPropertyName || meta].join(':')), + inputs: getInputsOrOutputs(component, 'Input'), + outputs: getInputsOrOutputs(component, 'Output'), providers: [{ multi: true, provide: NG_VALUE_ACCESSOR, @@ -51,11 +62,3 @@ export function MockComponent(component: Type): Type; +} + +/* tslint:disable:max-classes-per-file */ @Component({ exportAs: 'seeimple', selector: 'simple-component', template: 'some template' }) -export class SimpleComponent { +export class SimpleComponent extends BaseSimpleComponent { @Input() someInput: string; @Input('someOtherInput') someInput2: string; @Output() someOutput1: EventEmitter; - @Output() someOutput2: EventEmitter; } +/* tslint:enable:max-classes-per-file */