Skip to content

Commit

Permalink
feat: ngMocks.guts for easy start
Browse files Browse the repository at this point in the history
  • Loading branch information
satanTime committed Oct 22, 2020
1 parent c177201 commit d19f958
Show file tree
Hide file tree
Showing 22 changed files with 1,326 additions and 293 deletions.
183 changes: 183 additions & 0 deletions examples/ngMocksGuts/test.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import { CommonModule } from '@angular/common';
import {
Component,
Directive,
EventEmitter,
Inject,
Injectable,
InjectionToken,
Input,
NgModule,
NO_ERRORS_SCHEMA,
OnDestroy,
Output,
Pipe,
PipeTransform,
} from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MockDirective, MockModule, MockPipe, MockService, ngMocks } from 'ng-mocks';

const TARGET1 = new InjectionToken('TARGET1');

@Injectable()
class Target1Service {
public callback: () => void = () => undefined;

public touch(): void {
this.callback();
}
}

@Pipe({
name: 'target1',
})
class Target1Pipe implements PipeTransform {
protected readonly name = 'pipe1';
public transform(value: string): string {
return `${this.name}:${value}`;
}
}

@Component({
selector: 'target2',
template: `<ng-content></ng-content>`,
})
class Target2Component {}

@Component({
selector: 'target1',
template: `<div (target1)="update.emit()">
{{ greeting }} {{ greeting | target1 }} <target2>{{ target }}</target2>
</div>`,
})
class Target1Component {
@Input() public greeting: string | null = null;
public readonly target: string;
@Output() public readonly update: EventEmitter<void> = new EventEmitter();

constructor(@Inject(TARGET1) target: string) {
this.target = target;
}
}

@Directive({
selector: '[target1]',
})
class Target1Directive implements OnDestroy {
public readonly service: Target1Service;
@Output() public readonly target1: EventEmitter<void> = new EventEmitter();

constructor(service: Target1Service) {
this.service = service;
this.service.callback = () => this.target1.emit();
}

ngOnDestroy(): void {
this.service.callback = () => undefined;
}
}

@NgModule({
declarations: [Target2Component],
exports: [Target2Component],
providers: [
{
provide: TARGET1,
useValue: 'target1',
},
],
})
class Target2Module {}

@NgModule({
declarations: [Target1Pipe, Target1Component, Target1Directive],
imports: [CommonModule, Target2Module],
providers: [
Target1Service,
{
provide: TARGET1,
useValue: 'target1',
},
],
})
class Target1Module {}

describe('ngMocks.guts:NO_ERRORS_SCHEMA', () => {
let fixture: ComponentFixture<Target1Component>;
let component: Target1Component;

beforeEach(() => {
TestBed.configureTestingModule({
declarations: [Target1Component, Target1Pipe],
imports: [CommonModule],
providers: [
{
provide: TARGET1,
useValue: 'target1',
},
],
schemas: [NO_ERRORS_SCHEMA],
});
fixture = TestBed.createComponent(Target1Component);
component = fixture.componentInstance;
});

it('creates component', () => {
expect(component).toEqual(jasmine.any(Target1Component));
expect(fixture.nativeElement.innerHTML).toEqual('<div><target2></target2></div>');
component.greeting = 'hello';
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).toContain('hello');
});
});

describe('ngMocks.guts:legacy', () => {
let fixture: ComponentFixture<Target1Component>;
let component: Target1Component;

beforeEach(() => {
TestBed.configureTestingModule({
declarations: [MockPipe(Target1Pipe), MockDirective(Target1Directive), Target1Component],
imports: [CommonModule, MockModule(Target2Module)],
providers: [
{
provide: Target1Service,
useValue: MockService(Target1Service),
},
{
provide: TARGET1,
useValue: undefined,
},
],
});
fixture = TestBed.createComponent(Target1Component);
component = fixture.componentInstance;
});

it('creates component', () => {
expect(component).toEqual(jasmine.any(Target1Component));
expect(fixture.nativeElement.innerHTML).toEqual('<div><target2></target2></div>');
component.greeting = 'hello';
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).toContain('hello');
});
});

describe('ngMocks.guts:normal', () => {
let fixture: ComponentFixture<Target1Component>;
let component: Target1Component;

beforeEach(() => {
TestBed.configureTestingModule(ngMocks.guts(Target1Component, Target1Module));
fixture = TestBed.createComponent(Target1Component);
component = fixture.componentInstance;
});

it('creates component', () => {
expect(component).toEqual(jasmine.any(Target1Component));
expect(fixture.nativeElement.innerHTML).toEqual('<div><target2></target2></div>');
component.greeting = 'hello';
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).toContain('hello');
});
});
88 changes: 46 additions & 42 deletions lib/common/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,8 @@ export const extendClass = <I extends object>(base: Type<I>): Type<I> => {
return child;
};

export const isNgType = (object: Type<any>, type: string): boolean =>
jitReflector.annotations(object).some(annotation => annotation.ngMetadataName === type);
export const isNgType = (declaration: Type<any>, type: string): boolean =>
jitReflector.annotations(declaration).some(annotation => annotation.ngMetadataName === type);

/**
* Checks whether a class was decorated by a ng type.
Expand All @@ -119,14 +119,14 @@ export const isNgType = (object: Type<any>, type: string): boolean =>
* d - directive.
* p - pipe.
*/
export function isNgDef(object: any, ngType: 'm' | 'c' | 'd'): object is Type<any>;
export function isNgDef(object: any, ngType: 'p'): object is Type<PipeTransform>;
export function isNgDef(object: any): object is Type<any>;
export function isNgDef(object: any, ngType?: string): object is Type<any> {
const isModule = (!ngType || ngType === 'm') && isNgType(object, 'NgModule');
const isComponent = (!ngType || ngType === 'c') && isNgType(object, 'Component');
const isDirective = (!ngType || ngType === 'd') && isNgType(object, 'Directive');
const isPipe = (!ngType || ngType === 'p') && isNgType(object, 'Pipe');
export function isNgDef(declaration: any, ngType: 'm' | 'c' | 'd'): declaration is Type<any>;
export function isNgDef(declaration: any, ngType: 'p'): declaration is Type<PipeTransform>;
export function isNgDef(declaration: any): declaration is Type<any>;
export function isNgDef(declaration: any, ngType?: string): declaration is Type<any> {
const isModule = (!ngType || ngType === 'm') && isNgType(declaration, 'NgModule');
const isComponent = (!ngType || ngType === 'c') && isNgType(declaration, 'Component');
const isDirective = (!ngType || ngType === 'd') && isNgType(declaration, 'Directive');
const isPipe = (!ngType || ngType === 'p') && isNgType(declaration, 'Pipe');
return isModule || isComponent || isDirective || isPipe;
}

Expand Down Expand Up @@ -160,12 +160,12 @@ export function isMockedNgDefOf<T>(declaration: any, type: Type<T>, ngType?: any
);
}

export const isNgInjectionToken = (object: any): object is InjectionToken<any> =>
typeof object === 'object' && object.ngMetadataName === 'InjectionToken';
export const isNgInjectionToken = (token: any): token is InjectionToken<any> =>
typeof token === 'object' && token.ngMetadataName === 'InjectionToken';

// Checks if an object implements ModuleWithProviders.
export const isNgModuleDefWithProviders = (object: any): object is NgModuleWithProviders =>
object.ngModule !== undefined && isNgDef(object.ngModule, 'm');
export const isNgModuleDefWithProviders = (declaration: any): declaration is NgModuleWithProviders =>
declaration.ngModule !== undefined && isNgDef(declaration.ngModule, 'm');

/**
* Checks whether an object is an instance of a mocked class that was decorated by a ng type.
Expand All @@ -174,17 +174,21 @@ export const isNgModuleDefWithProviders = (object: any): object is NgModuleWithP
* d - directive.
* p - pipe.
*/
export function isMockOf<T>(object: any, type: Type<T>, ngType: 'm'): object is MockedModule<T>;
export function isMockOf<T>(object: any, type: Type<T>, ngType: 'c'): object is MockedComponent<T>;
export function isMockOf<T>(object: any, type: Type<T>, ngType: 'd'): object is MockedDirective<T>;
export function isMockOf<T extends PipeTransform>(object: any, type: Type<T>, ngType: 'p'): object is MockedPipe<T>;
export function isMockOf<T>(object: any, type: Type<T>): object is T;
export function isMockOf<T>(object: any, type: Type<T>, ngType?: any): object is T {
export function isMockOf<T>(instance: any, declaration: Type<T>, ngType: 'm'): instance is MockedModule<T>;
export function isMockOf<T>(instance: any, declaration: Type<T>, ngType: 'c'): instance is MockedComponent<T>;
export function isMockOf<T>(instance: any, declaration: Type<T>, ngType: 'd'): instance is MockedDirective<T>;
export function isMockOf<T extends PipeTransform>(
instance: any,
declaration: Type<T>,
ngType: 'p'
): instance is MockedPipe<T>;
export function isMockOf<T>(instance: any, declaration: Type<T>): instance is T;
export function isMockOf<T>(instance: any, declaration: Type<T>, ngType?: any): instance is T {
return (
typeof object === 'object' &&
object.__ngMocksMock &&
object.constructor === type &&
(ngType ? isNgDef(object.constructor, ngType) : isNgDef(object.constructor))
typeof instance === 'object' &&
instance.__ngMocksMock &&
instance.constructor === declaration &&
(ngType ? isNgDef(instance.constructor, ngType) : isNgDef(instance.constructor))
);
}

Expand All @@ -195,13 +199,13 @@ export function isMockOf<T>(object: any, type: Type<T>, ngType?: any): object is
* d - directive.
* p - pipe.
*/
export function getMockedNgDefOf<T>(type: Type<T>, ngType: 'm'): Type<MockedModule<T>>;
export function getMockedNgDefOf<T>(type: Type<T>, ngType: 'c'): Type<MockedComponent<T>>;
export function getMockedNgDefOf<T>(type: Type<T>, ngType: 'd'): Type<MockedDirective<T>>;
export function getMockedNgDefOf<T>(type: Type<T>, ngType: 'p'): Type<MockedPipe<T>>;
export function getMockedNgDefOf(type: Type<any>): Type<any>;
export function getMockedNgDefOf(type: any, ngType?: any): any {
const source = type.mockOf ? type.mockOf : type;
export function getMockedNgDefOf<T>(declaration: Type<T>, type: 'm'): Type<MockedModule<T>>;
export function getMockedNgDefOf<T>(declaration: Type<T>, type: 'c'): Type<MockedComponent<T>>;
export function getMockedNgDefOf<T>(declaration: Type<T>, type: 'd'): Type<MockedDirective<T>>;
export function getMockedNgDefOf<T>(declaration: Type<T>, type: 'p'): Type<MockedPipe<T>>;
export function getMockedNgDefOf(declaration: Type<any>): Type<any>;
export function getMockedNgDefOf(declaration: any, type?: any): any {
const source = declaration.mockOf ? declaration.mockOf : declaration;
const mocks = getTestBedInjection(NG_MOCKS);

let mock: any;
Expand All @@ -214,28 +218,28 @@ export function getMockedNgDefOf(type: any, ngType?: any): any {
}

// If we are not in the MockBuilder env we can rely on the current cache.
if (!mock && source !== type) {
mock = type;
if (!mock && source !== declaration) {
mock = declaration;
} else if (!mock && ngMocksUniverse.cacheMocks.has(source)) {
mock = ngMocksUniverse.cacheMocks.get(source);
}

if (mock && !ngType) {
if (mock && !type) {
return mock;
}
if (mock && ngType && isMockedNgDefOf(mock, source, ngType)) {
if (mock && type && isMockedNgDefOf(mock, source, type)) {
return mock;
}

// Looks like the def hasn't been mocked.
throw new Error(`There is no mock for ${source.name}`);
}

export function getSourceOfMock<T>(type: Type<MockedModule<T>>): Type<T>;
export function getSourceOfMock<T>(type: Type<MockedComponent<T>>): Type<T>;
export function getSourceOfMock<T>(type: Type<MockedDirective<T>>): Type<T>;
export function getSourceOfMock<T>(type: Type<MockedPipe<T>>): Type<T>;
export function getSourceOfMock<T>(type: Type<T>): Type<T>;
export function getSourceOfMock<T>(type: any): Type<T> {
return typeof type === 'function' && type.mockOf ? type.mockOf : type;
export function getSourceOfMock<T>(declaration: Type<MockedModule<T>>): Type<T>;
export function getSourceOfMock<T>(declaration: Type<MockedComponent<T>>): Type<T>;
export function getSourceOfMock<T>(declaration: Type<MockedDirective<T>>): Type<T>;
export function getSourceOfMock<T>(declaration: Type<MockedPipe<T>>): Type<T>;
export function getSourceOfMock<T>(declaration: Type<T>): Type<T>;
export function getSourceOfMock<T>(declaration: any): Type<T> {
return typeof declaration === 'function' && declaration.mockOf ? declaration.mockOf : declaration;
}
4 changes: 2 additions & 2 deletions lib/mock-builder/mock-builder.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { MetadataOverride, TestBed, TestModuleMetadata } from '@angular/core/testing';
import { ngMocksUniverse } from 'ng-mocks/dist/lib/common/ng-mocks-universe';

import { AnyType, flatten, isNgDef, mapEntries, NG_MOCKS, NG_MOCKS_OVERRIDES, Type } from '../common';
import { AnyType, flatten, isNgDef, mapEntries, NG_MOCKS, NG_MOCKS_OVERRIDES, Type } from '../common/lib';
import { ngMocksUniverse } from '../common/ng-mocks-universe';
import { ngMocks } from '../mock-helper/mock-helper';

import { MockBuilderPerformance } from './mock-builder-performance';
Expand Down
8 changes: 5 additions & 3 deletions lib/mock-declaration/mock-declaration.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AbstractType, isNgDef, Type } from '../common';
import { AnyType, isNgDef, Type } from '../common';
import { MockComponent, MockedComponent } from '../mock-component';
import { MockDirective, MockedDirective } from '../mock-directive';
import { MockedPipe, MockPipe } from '../mock-pipe';
Expand All @@ -7,9 +7,11 @@ export function MockDeclarations(...declarations: Array<Type<any>>): Array<Type<
return declarations.map(MockDeclaration);
}

export function MockDeclaration<T>(declaration: Type<T>): Type<MockedPipe<T> | MockedDirective<T> | MockedComponent<T>>;
export function MockDeclaration<T>(
declaration: AbstractType<T>
declaration: AnyType<T>
): Type<MockedPipe<T> | MockedDirective<T> | MockedComponent<T>>;
export function MockDeclaration<T>(
declaration: AnyType<T>
): Type<MockedPipe<T> | MockedDirective<T> | MockedComponent<T>>;
export function MockDeclaration<T>(
declaration: Type<T>
Expand Down
30 changes: 30 additions & 0 deletions lib/mock-helper/mock-helper.faster.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// tslint:disable:no-default-export no-default-import

import { getTestBed, TestBed } from '@angular/core/testing';

import { ngMocksUniverse } from '../common/ng-mocks-universe';

import flushTestBed from './mock-helper.flushTestBed';

export default () => {
beforeAll(() => {
if (ngMocksUniverse.global.has('bullet:customized')) {
TestBed.resetTestingModule();
}
ngMocksUniverse.global.set('bullet', true);
});

afterEach(() => {
flushTestBed();
for (const fixture of (getTestBed() as any)._activeFixtures || /* istanbul ignore next */ []) {
fixture.destroy();
}
});

afterAll(() => {
ngMocksUniverse.global.delete('bullet');
if (ngMocksUniverse.global.has('bullet:reset')) {
TestBed.resetTestingModule();
}
});
};
Loading

0 comments on commit d19f958

Please sign in to comment.