Skip to content

Commit

Permalink
feat: Upgrade to angular 5 and pull in testbed for tests
Browse files Browse the repository at this point in the history
  • Loading branch information
ike18t committed Dec 28, 2017
1 parent 53d67cc commit 7df64a8
Show file tree
Hide file tree
Showing 8 changed files with 6,034 additions and 1,027 deletions.
34 changes: 34 additions & 0 deletions karma.conf.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Karma configuration
// Generated on Mon Dec 25 2017 20:41:30 GMT-0800 (PST)

module.exports = (config: any) => {
config.set({
frameworks: ['jasmine', 'karma-typescript'],
files: [
'node_modules/zone.js/dist/zone.js',
'node_modules/zone.js/dist/long-stack-trace-zone.js',
'node_modules/zone.js/dist/proxy.js',
'node_modules/zone.js/dist/sync-test.js',
'node_modules/zone.js/dist/jasmine-patch.js',
'node_modules/zone.js/dist/async-test.js',
'node_modules/zone.js/dist/fake-async-test.js',
{ pattern: 'lib/**/*.ts' }
],
preprocessors: {
'**/*.ts': ['karma-typescript']
},
reporters: ['dots', 'karma-typescript', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: false,
browsers: ['ChromeHeadless'],
singleRun: true,

karmaTypescriptConfig: {
compilerOptions: {
lib: ['ES2015', 'DOM']
}
}
})
}
149 changes: 77 additions & 72 deletions lib/mock_component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,97 +1,102 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import 'reflect-metadata';
import { Component } from '@angular/core';
import { async, ComponentFixture, getTestBed, TestBed } from '@angular/core/testing';
import { FormsModule } from '@angular/forms';
import { By } from '@angular/platform-browser';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
import { EmptyComponent } from './test_components/empty_component.component';
import { SimpleComponent } from './test_components/simple_component.component';
import { MockComponent } from './mock_component';

/* tslint:disable:max-classes-per-file */
@Component({
selector: 'example-component',
template: 'some template'
selector: 'example-component-container',
template: `
<simple-component [someInput]="\'hi\'" (someOutput1)="someOutputHasEmitted = true"></simple-component>
<simple-component [someInput]="\'hi again\'"></simple-component>
<empty-component></empty-component>
<empty-component id="ng-content-component">doh</empty-component>
<empty-component id="ngmodel-component" [(ngModel)]="someOutputHasEmitted"></empty-component>
`
})
export class ExampleComponent {
@Input() someInput: string;
@Output() someOutput: EventEmitter<boolean>;
export class ExampleComponentContainer {
someOutputHasEmitted = false;
}

@Component({
selector: 'empty-component',
template: 'some template'
})
export class EmptyComponent {}
/* tslint:enable:max-classes-per-file */

describe('MockComponent', () => {
let exampleComponent: any;
let component: ExampleComponentContainer;
let fixture: ComponentFixture<ExampleComponentContainer>;
const mockedSimpleComponent = MockComponent(SimpleComponent);
const mockedEmptyComponent = MockComponent(EmptyComponent);

beforeEach(() => {
exampleComponent = {
decorators: [
{
args: [{
selector: 'example-component',
template: 'some template',
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);

}],
type: Component
}
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
ExampleComponentContainer,
mockedEmptyComponent,
mockedSimpleComponent
],
propDecorators: {
someInput: [{ type: Input }],
someOutput: [{ type: Output }]
}
};
});
imports: [
FormsModule
]
})
.compileComponents();
}));

it('the mock should have the same selector as the passed in component', () => {
const mockedComponent = MockComponent(ExampleComponent);
const annotations = Reflect.getMetadata('annotations', mockedComponent)[0];
expect(annotations.selector).toEqual('example-component');
beforeEach(() => {
fixture = TestBed.createComponent(ExampleComponentContainer);
component = fixture.componentInstance;
});

it('the mock should have the same inputs and outputs as the passed in component', () => {
const mockedComponent = MockComponent(ExampleComponent);
const annotations = Reflect.getMetadata('annotations', mockedComponent)[0];
expect(annotations.inputs).toEqual(['someInput']);
expect(annotations.outputs).toEqual(['someOutput']);
it('should have use the original component\'s selector', () => {
fixture.detectChanges();
const mockedComponent = fixture.debugElement.query(By.css('simple-component'));
expect(mockedComponent).not.toBeNull();
});

it('the mock should have an ng-content body', () => {
const mockedComponent = MockComponent(ExampleComponent);
const annotations = Reflect.getMetadata('annotations', mockedComponent)[0];
expect(annotations.template).toEqual('<ng-content></ng-content>');
it('should have the input set on the mock component', () => {
fixture.detectChanges();
const mockedComponent = fixture.debugElement
.query(By.directive(mockedSimpleComponent))
.componentInstance as SimpleComponent;
expect(mockedComponent.someInput).toEqual('hi');
});

// make pass when testbed is brought in
xit('each instance of a mocked component should have its own event emitter', () => {
const mockedComponent1 = MockComponent(ExampleComponent);
const mockedComponent2 = MockComponent(ExampleComponent);
expect((mockedComponent1 as any).someOutput).not.toEqual((mockedComponent2 as any).someOutput);
it('should trigger output bound behavior', () => {
fixture.detectChanges();
const mockedComponent = fixture.debugElement
.query(By.directive(mockedSimpleComponent))
.componentInstance as SimpleComponent;
mockedComponent.someOutput1.emit();
expect(component.someOutputHasEmitted).toBeTruthy();
});

describe('sometimes components are built like this, not sure why', () => {
it('the mock should have the same selector as the passed in component', () => {
const mockedComponent = MockComponent(exampleComponent);
const annotations = Reflect.getMetadata('annotations', mockedComponent)[0];
expect(annotations.selector).toEqual('example-component');
});
it('the mock should have an ng-content body', () => {
fixture.detectChanges();
const mockedComponent = fixture.debugElement.query(By.css('#ng-content-component'));
expect(mockedComponent.nativeElement.innerText).toContain('doh');
});

it('the mock should have the same inputs and outputs as the passed in component', () => {
const mockedComponent = MockComponent(exampleComponent);
const annotations = Reflect.getMetadata('annotations', mockedComponent)[0];
expect(annotations.inputs).toEqual(['someInput']);
expect(annotations.outputs).toEqual(['someOutput']);
});
it('should give each instance of a mocked component its own event emitter', () => {
const mockedComponents = fixture.debugElement.queryAll(By.directive(mockedSimpleComponent));
const mockedComponent1 = mockedComponents[0].componentInstance as SimpleComponent;
const mockedComponent2 = mockedComponents[1].componentInstance as SimpleComponent;
expect(mockedComponent1.someOutput1).not.toEqual(mockedComponent2.someOutput1);
});

it('the mock should have an ng-content body', () => {
const mockedComponent = MockComponent(exampleComponent);
const annotations = Reflect.getMetadata('annotations', mockedComponent)[0];
expect(annotations.template).toEqual('<ng-content></ng-content>');
});
it('should work with components w/o inputs or outputs', () => {
const mockedComponent = fixture.debugElement.query(By.directive(mockedEmptyComponent));
expect(mockedComponent).not.toBeNull();
});

it('should work with a component w/o inputs or outputs', () => {
const mockedComponent = MockComponent(EmptyComponent);
const annotations = Reflect.getMetadata('annotations', mockedComponent)[0];
expect(annotations.inputs).toEqual([]);
expect(annotations.outputs).toEqual([]);
it('should allow ngModel bindings', () => {
const mockedComponent = fixture.debugElement.query(By.css('#ngmodel-component'));
expect(mockedComponent).not.toBeNull();
});
});
24 changes: 6 additions & 18 deletions lib/mock_component.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { Component, EventEmitter, forwardRef, Input, Output, Type } from '@angular/core';
import { Component, EventEmitter, forwardRef, Type } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

declare var Reflect: any;

export function MockComponent<TComponent>(component: Type<TComponent>): Type<TComponent> {
const propertyMetadata = getPropertyMetadata(component);

Expand Down Expand Up @@ -41,30 +39,20 @@ export function MockComponent<TComponent>(component: Type<TComponent>): Type<TCo
}

function isInput(propertyMetadata: any): boolean {
return propertyMetadata[0].type === Input || propertyMetadata[0].toString() === '@Input';
return propertyMetadata[0].ngMetadataName === 'Input';
}

function isOutput(propertyMetadata: any): boolean {
return propertyMetadata[0].type === Output || propertyMetadata[0].toString() === '@Output';
return propertyMetadata[0].ngMetadataName === 'Output';
}

function getComponentSelector(component: any): string {
if (component.decorators) {
return component.decorators[0].args[0].selector;
}
if (Reflect.hasMetadata('annotations', component)) {
const metadata = Reflect.getMetadata('annotations', component);
return metadata[0].selector;
if (component.__annotations__) {
return component.__annotations__[0].selector;
}
throw new Error('No annotation or decoration metadata on your component');
}

function getPropertyMetadata(component: any): any {
if (component.propDecorators) {
return component.propDecorators;
}
if (Reflect.hasMetadata('propMetadata', component)) {
return Reflect.getMetadata('propMetadata', component);
}
return {};
return component.__prop__metadata__ || {};
}
7 changes: 7 additions & 0 deletions lib/test_components/empty_component.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Component } from '@angular/core';

@Component({
selector: 'empty-component',
template: 'some template'
})
export class EmptyComponent {}
11 changes: 11 additions & 0 deletions lib/test_components/simple_component.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';

@Component({
selector: 'simple-component',
template: 'some template'
})
export class SimpleComponent {
@Input() someInput: string;
@Output() someOutput1: EventEmitter<string>;
@Output() someOutput2: EventEmitter<string>;
}
Loading

0 comments on commit 7df64a8

Please sign in to comment.