Skip to content

Commit

Permalink
feat: memoize function by arg
Browse files Browse the repository at this point in the history
  • Loading branch information
ike18t committed Feb 3, 2018
1 parent 78fc8e3 commit cac00b3
Show file tree
Hide file tree
Showing 3 changed files with 28 additions and 14 deletions.
7 changes: 2 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,11 @@ import { TestedComponent } from './tested.component';

describe('TestedComponent', () => {
let fixture: ComponentFixture<TestedComponent>;
const mockedDependencyComponent = MockComponent(DependencyComponent); // do this if you want to select By.directive

beforeEach(async() => {
TestBed.configureTestingModule({
declarations: [
mockedDependencyComponent, // to add your already mocked component if you went the by directive route
// or
MockComponent(DependencyComponent) // you can do this if you don't mind selecting by css selector
MockComponent(DependencyComponent)
]
})
.compileComponents();
Expand All @@ -53,7 +50,7 @@ describe('TestedComponent', () => {

it('should do something when the dependency component emits on its output', () => {
const mockedComponent = fixture.debugElement
.query(By.directive(mockedDependencyComponent))
.query(By.directive(MockComponent(DependencyComponent)))
.componentInstance as DependencyComponent; // casting to retain type safety
// again, let's pretend DependencyComponent has an output called 'someOutput'
// emit on the output that MockComponent setup when generating the mock of Dependency Component
Expand Down
22 changes: 14 additions & 8 deletions lib/mock_component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ export class ExampleComponentContainer {
describe('MockComponent', () => {
let component: ExampleComponentContainer;
let fixture: ComponentFixture<ExampleComponentContainer>;
const mockedSimpleComponent = MockComponent(SimpleComponent);
const mockedEmptyComponent = MockComponent(EmptyComponent);

getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
Expand All @@ -42,8 +40,8 @@ describe('MockComponent', () => {
TestBed.configureTestingModule({
declarations: [
ExampleComponentContainer,
mockedEmptyComponent,
mockedSimpleComponent
MockComponent(EmptyComponent),
MockComponent(SimpleComponent)
],
imports: [
FormsModule
Expand All @@ -65,7 +63,7 @@ describe('MockComponent', () => {
it('should have the input set on the mock component', () => {
fixture.detectChanges();
const mockedComponent = fixture.debugElement
.query(By.directive(mockedSimpleComponent))
.query(By.directive(MockComponent(SimpleComponent)))
.componentInstance as SimpleComponent;
expect(mockedComponent.someInput).toEqual('hi');
expect(mockedComponent.someInput2).toEqual('bye');
Expand All @@ -74,7 +72,7 @@ describe('MockComponent', () => {
it('should trigger output bound behavior', () => {
fixture.detectChanges();
const mockedComponent = fixture.debugElement
.query(By.directive(mockedSimpleComponent))
.query(By.directive(MockComponent(SimpleComponent)))
.componentInstance as SimpleComponent;
mockedComponent.someOutput1.emit('hi');
expect(component.emitted).toEqual('hi');
Expand All @@ -87,19 +85,27 @@ describe('MockComponent', () => {
});

it('should give each instance of a mocked component its own event emitter', () => {
const mockedComponents = fixture.debugElement.queryAll(By.directive(mockedSimpleComponent));
const mockedComponents = fixture.debugElement
.queryAll(By.directive(MockComponent(SimpleComponent)));
const mockedComponent1 = mockedComponents[0].componentInstance as SimpleComponent;
const mockedComponent2 = mockedComponents[1].componentInstance as SimpleComponent;
expect(mockedComponent1.someOutput1).not.toEqual(mockedComponent2.someOutput1);
});

it('should work with components w/o inputs or outputs', () => {
const mockedComponent = fixture.debugElement.query(By.directive(mockedEmptyComponent));
const mockedComponent = fixture.debugElement
.query(By.directive(MockComponent(EmptyComponent)));
expect(mockedComponent).not.toBeNull();
});

it('should allow ngModel bindings', () => {
const mockedComponent = fixture.debugElement.query(By.css('#ngmodel-component'));
expect(mockedComponent).not.toBeNull();
});

it('should memoize the return value by argument', () => {
expect(MockComponent(EmptyComponent)).toBe(MockComponent(EmptyComponent));
expect(MockComponent(SimpleComponent)).toBe(MockComponent(SimpleComponent));
expect(MockComponent(EmptyComponent)).not.toBe(MockComponent(SimpleComponent));
});
});
13 changes: 12 additions & 1 deletion lib/mock_component.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import { Component, EventEmitter, forwardRef, Type } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

const cache = new Map<Type<Component>, Type<Component>>();

export function MockComponent<TComponent>(component: Type<TComponent>): Type<TComponent> {
const cacheHit = cache.get(component);
if (cacheHit) {
return cacheHit as Type<TComponent>;
}

const annotations = (component as any).__annotations__[0] || {};
const propertyMetadata = (component as any).__prop__metadata__ || {};

Expand Down Expand Up @@ -37,8 +44,12 @@ export function MockComponent<TComponent>(component: Type<TComponent>): Type<TCo
}

/* tslint:disable:no-angle-bracket-type-assertion */
return Component(options)(<any> ComponentMock as Type<TComponent>);
const mockedComponent = Component(options)(<any> ComponentMock as Type<TComponent>);
/* tslint:enable:no-angle-bracket-type-assertion */

cache.set(component, mockedComponent);

return mockedComponent;
}

function isInput(propertyMetadata: any): boolean {
Expand Down

0 comments on commit cac00b3

Please sign in to comment.