Skip to content

Commit

Permalink
feat(MockDirective): added the ability to use ViewChild/ViewChildren …
Browse files Browse the repository at this point in the history
…etc with MockDirective

Also ensured that methods of the component are defined in the mock component, to allow spying.
  • Loading branch information
kenjiqq authored and ike18t committed Oct 17, 2018
1 parent 69ebb9c commit 8853e87
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 4 deletions.
26 changes: 24 additions & 2 deletions lib/mock-directive/mock-directive.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, Directive, EventEmitter, Input, Output } from '@angular/core';
import { Component, Directive, EventEmitter, Input, Output, ViewChild } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { FormControl, FormControlDirective } from '@angular/forms';
import { By } from '@angular/platform-browser';
Expand All @@ -13,6 +13,10 @@ export class ExampleDirective {
@Input() exampleDirective: string;
@Output() someOutput = new EventEmitter<boolean>();
@Input('bah') something: string;

performAction(s: string) {
return this;
}
}

@Directive({
Expand All @@ -34,12 +38,18 @@ export class ExampleStructuralDirective {
`
})
export class ExampleComponentContainer {
@ViewChild(ExampleDirective) childDirective: ExampleDirective;
emitted = false;
foo = new FormControl('');

performActionOnChild(s: string): void {
this.childDirective.performAction(s);
}
}
// tslint:enable:max-classes-per-file

describe('MockDirective', () => {
let component: ExampleComponentContainer;
let fixture: ComponentFixture<ExampleComponentContainer>;

beforeEach(async(() => {
Expand All @@ -56,6 +66,7 @@ describe('MockDirective', () => {

beforeEach(() => {
fixture = TestBed.createComponent(ExampleComponentContainer);
component = fixture.componentInstance;
fixture.detectChanges();
});

Expand All @@ -76,7 +87,7 @@ describe('MockDirective', () => {
const element = debugElement.injector.get(MockDirective(ExampleDirective)); // tslint:disable-line

element.someOutput.emit(true);
expect(fixture.componentInstance.emitted).toEqual(true);
expect(component.emitted).toEqual(true);
});

it('should memoize the return value by argument', () => {
Expand All @@ -97,4 +108,15 @@ describe('MockDirective', () => {
const debugElement = fixture.debugElement.query(By.css('#example-structural-directive'));
expect(debugElement.nativeElement.innerHTML).toContain('hi');
});

it('should set ViewChild directives correctly', () => {
fixture.detectChanges();
expect(component.childDirective).toBeTruthy();
});

it('should allow spying of viewchild directive methods', () => {
const spy = spyOn(component.childDirective, 'performAction');
component.performActionOnChild('test');
expect(spy).toHaveBeenCalledWith('test');
});
});
23 changes: 21 additions & 2 deletions lib/mock-directive/mock-directive.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Directive, EventEmitter, Inject, Optional, TemplateRef, Type, ViewContainerRef } from '@angular/core';
import {
Directive, EventEmitter, forwardRef, Inject, Optional, TemplateRef, Type, ViewContainerRef
} from '@angular/core';
import { MockOf } from '../common';
import { directiveResolver } from '../common/reflect';

Expand All @@ -15,12 +17,29 @@ export function MockDirective<TDirective>(directive: Type<TDirective>): Type<TDi
}

const { selector, exportAs, inputs, outputs } = directiveResolver.resolve(directive);

// tslint:disable:no-unnecessary-class
@MockOf(directive)
@Directive({ exportAs, inputs, outputs, selector })
@Directive({
exportAs,
inputs,
outputs,
providers: [{
provide: directive,
useExisting: forwardRef(() => DirectiveMock)
}],
selector
})
class DirectiveMock {
constructor(@Optional() @Inject(TemplateRef) templateRef?: TemplateRef<any>,
@Optional() @Inject(ViewContainerRef) viewContainer?: ViewContainerRef) {

Object.keys(directive.prototype).forEach((method) => {
if (!(this as any)[method]) {
(this as any)[method] = () => {};
}
});

(outputs || []).forEach((output) => {
(this as any)[output.split(':')[0]] = new EventEmitter<any>();
});
Expand Down

0 comments on commit 8853e87

Please sign in to comment.