Skip to content

Commit

Permalink
Code refactoring (#36)
Browse files Browse the repository at this point in the history
* Make `ComponentTestBed` extend to `CommonTestBed` and update tests

* Refactor

* Refactor demo

* Rename folder

* Move shared.model.ts

* Rename folders

* Move resolver model

* Add lib index

* Add missing exports

* Update `InjectionStore` import path

* Rename `CommonTestBedFactory` to `CustomTestBedFactory`
  • Loading branch information
remscodes authored Jan 21, 2024
1 parent 0078abd commit 0758a91
Show file tree
Hide file tree
Showing 81 changed files with 200 additions and 295 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,12 @@ import { Page2Component } from './page-2.component';
describe('Page2Component', () => {
const tb = componentTestBed(Page2Component);

beforeEach(() => tb.compile());

tb.shouldCreate()
tb.compileEach();
tb.shouldCreate();

it('should click', tb(({ component, action }) => {
expect(component.clicked).toBeFalse();

action.click('#my-span');

expect(component.clicked).toBeTrue();
}));
});
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { CanMatchFn, Route, UrlSegment } from '@angular/router';
import { CanMatchFn } from '@angular/router';
import { of } from 'rxjs';

export function loadLazyGuard(): CanMatchFn {
return (route: Route, segments: UrlSegment[]) => {
return of(true);
};
return () => of(true);
}
6 changes: 2 additions & 4 deletions projects/ngx-testing-tools-demo/src/app/guards/login.guard.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { ActivatedRouteSnapshot, CanActivateFn, RouterStateSnapshot } from '@angular/router';
import { CanActivateFn } from '@angular/router';
import { of } from 'rxjs';

export function loginGuard(): CanActivateFn {
return (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
return of(true);
};
return () => of(true);
}
11 changes: 2 additions & 9 deletions projects/ngx-testing-tools-demo/src/app/guards/no-back.guard.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
import { ActivatedRouteSnapshot, CanDeactivateFn, RouterStateSnapshot } from '@angular/router';
import { CanDeactivateFn } from '@angular/router';
import { of } from 'rxjs';

export function noBackGuard<T>(): CanDeactivateFn<T> {
return (
component: T,
currentRoute: ActivatedRouteSnapshot,
currentState: RouterStateSnapshot,
nextState: RouterStateSnapshot,
) => {
return of(true);
};
return () => of(true);
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Type } from '@angular/core';
import { Nullable } from '../../models/shared.model';
import { Nullable } from '../../shared.model';

type MetadataName =
| 'Component'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Component, Type } from '@angular/core';
import { Nullable } from '../../models/shared.model';
import { Nullable } from '../../shared.model';
import { getAnnotation } from './annotation';

export function getComponentAnnotation<T>(ComponentCtor: Type<T>): Nullable<Component> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Directive, Type } from '@angular/core';
import { Nullable } from '../../models/shared.model';
import { Nullable } from '../../shared.model';
import { getAnnotation } from './annotation';

export function getDirectiveAnnotation<T>(DirectiveCtor: Type<T>): Nullable<Directive> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Injectable, Type } from '@angular/core';
import { Nullable } from '../../models/shared.model';
import { Nullable } from '../../shared.model';
import { getAnnotation } from './annotation';

export function getInjectableAnnotation<T>(InjectableCtor: Type<T>): Nullable<Injectable> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { NgModule, Type } from '@angular/core';
import { Nullable } from '../../models/shared.model';
import { Nullable } from '../../shared.model';
import { getAnnotation } from './annotation';

export function getNgModuleAnnotation<T>(NgModuleCtor: Type<T>): Nullable<NgModule> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Pipe, Type } from '@angular/core';
import { Nullable } from '../../models/shared.model';
import { Nullable } from '../../shared.model';
import { getAnnotation } from './annotation';

export function getPipeAnnotation<T>(PipeCtor: Type<T>): Nullable<Pipe> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { ProviderToken, Type } from '@angular/core';
import { TestBed, TestBedStatic } from '@angular/core/testing';
import { AnyProvider, Declaration, Importation } from '../../components/test-bed/models/metadata-type.model';
import { MaybeArray, NonEmptyString, PrettyMerge } from '../../models/shared.model';
import { AnyProvider, Importation } from '../../component/test-bed/models/metadata-type.model';
import { MaybeArray, NonEmptyString, PrettyMerge } from '../../shared.model';
import { makeArray } from '../../util/array.util';
import { EnhancedJasmineCallback } from './models/enhanced-jasmine-callback.model';
import { InjectionStore } from './store';
import { InjectionStore } from './store/models/injected-store.model';

export abstract class CommonTestBedFactory<Instance, Store extends InjectionStore = InjectionStore> {
export abstract class CustomTestBedFactory<Instance, Store extends InjectionStore = InjectionStore> {

protected constructor(
protected described: Type<Instance>,
Expand All @@ -15,17 +15,16 @@ export abstract class CommonTestBedFactory<Instance, Store extends InjectionStor
protected testBed: TestBedStatic = TestBed;

protected imports: Set<Importation> = new Set();
protected declarations: Set<Declaration> = new Set();
protected providers: Set<AnyProvider> = new Set();

protected injectedMap: Map<string, ProviderToken<any>> = new Map();

/**
* Import one module or one standalone component / directive / pipe into the custom test bed.
* Imports one module or one standalone component / directive / pipe into the custom test bed.
*/
public import(importation: Importation): this
/**
* Import many modules or many standalone components / directives / pipes into the custom test bed.
* Imports many modules or many standalone components / directives / pipes into the custom test bed.
*/
public import(imports: Importation[]): this
public import(oneOrManyImports: MaybeArray<Importation>): this {
Expand All @@ -34,62 +33,59 @@ export abstract class CommonTestBedFactory<Instance, Store extends InjectionStor
}

/**
* Add one provider into the custom test bed.
* Adds one provider into the custom test bed.
*/
public provide(provider: AnyProvider): this
/**
* Add many providers into the custom test bed.
* Adds many providers into the custom test bed.
*/
public provide(providers: AnyProvider[]): this
public provide(oneOrManyProviders: MaybeArray<AnyProvider>): this {
makeArray(oneOrManyProviders).forEach(v => this.providers.add(v));
return this;
}

private configureModule(): void {
this.testBed.configureTestingModule({
imports: [...this.imports.values()],
declarations: [...this.declarations.values()],
providers: [...this.providers.values()],
});
}

/**
* Inject an instance by token into the custom test bed.
* Injects an instance by token into the custom test bed.
*
* Retrieve it into the `ComponentTools.injected` by autocompletion.
* @param name the key to access the instance.
* @param token the provider token.
*/
public inject<key extends string, T>(name: NonEmptyString<key>, token: ProviderToken<T>): CommonTestBedFactory<Instance, InjectionStore<PrettyMerge<Store['injected'] & { [k in key]: T }>>> {
public inject<key extends string, T>(name: NonEmptyString<key>, token: ProviderToken<T>): CustomTestBedFactory<Instance, InjectionStore<PrettyMerge<Store['injected'] & { [k in key]: T }>>> {
this.injectedMap.set(name, token);
return this;
}

/**
* Compile the test bed before each test.
* Compiles the custom test bed before each test.
* **To be called outside jasmine `beforeEach` callback.**
* @see compile
*/
public compileEach(): void {
beforeEach(() => this.compile());
}

/**
* Compile the custom test bed to make enhanced tools available.
* Compiles the custom test bed to make enhanced tools available.
*/
public async compile(): Promise<void> {
this.configureModule();
this.testBed.configureTestingModule({
imports: [...this.imports.values()],
providers: [...this.providers.values()],
});
}

/**
* Setups extra stuffs using the enhanced tools.
* Sets up extra stuffs using the enhanced tools.
*
* **Works only for `beforeEach` and `afterEach`**.
*/
public abstract setup<Action extends EnhancedJasmineCallback<any>>(action: Action): jasmine.ImplementationCallback

/**
* Set up the redondant "should create" test generated by Angular schematic.
* Invokes the redondant "should create" test generated by Angular schematic.
*
* **To be called outside jasmine `it` callback.**
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { CommonTestBedFactory } from './common-test-bed-factory';
import { CustomTestBedFactory } from './custom-test-bed-factory';

export function mergeFactoryToTestBed<T, F extends CommonTestBedFactory<T>>(factory: F, tb: F) {
export function mergeFactoryToTestBed<T, F extends CustomTestBedFactory<T>>(factory: F, tb: F) {
tb.import = (imports: any) => {
factory.import(imports);
return tb;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { PrettyMerge } from '../../../models/shared.model';
import { CommonTestBedFactory } from '../common-test-bed-factory';
import { PrettyMerge } from '../../../shared.model';
import { CustomTestBedFactory } from '../custom-test-bed-factory';

export type CustomTestBed<
Fn extends (...args: any[]) => jasmine.ImplementationCallback,
Factory extends CommonTestBedFactory<any>
Factory extends CustomTestBedFactory<any>
> = PrettyMerge<Fn & Factory>

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Injector } from '@angular/core';
import { InjectionStore } from '../store/models/injected-store.model';

export interface CustomTools<I extends {} = {}> extends InjectionStore<I> {
/**
* The root injector.
*/
injector: Injector;
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { CommonTools } from './common-tools.model';
import { CustomTools } from './custom-tools.model';

export type EnhancedJasmineCallback<Tools extends CommonTools> = (tools: Tools, done: DoneFn) => ReturnType<jasmine.ImplementationCallback>
export type EnhancedJasmineCallback<Tools extends CustomTools> = (tools: Tools, done: DoneFn) => ReturnType<jasmine.ImplementationCallback>

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Type } from '@angular/core';
import { ComponentFixture } from '@angular/core/testing';
import { findDebugElement } from '../element';
import { findDebugElement } from '../query';

export function emitEvent(fixture: ComponentFixture<any>, selectorOrDirective: string | Type<any>, name: string, value?: any): void {
findDebugElement(fixture, selectorOrDirective)
Expand Down
3 changes: 3 additions & 0 deletions projects/ngx-testing-tools/src/lib/component/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './query';
export * from './action';
export * from './test-bed';
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { DebugElement, Type } from '@angular/core';
import { ComponentFixture } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { Nullable } from '../../models/shared.model';
import { Nullable } from '../../shared.model';
import { throwCannotFind } from './cannot-find';

export function findDebugElement(fixture: ComponentFixture<any>, selector: string): DebugElement
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Component, Type } from '@angular/core';
import { getComponentAnnotation, isComponentAnnotation } from '../../../common/annotation/component-annotation';
import { Nullable } from '../../../shared.model';

export function assertComponent(ComponentCtor: Type<any>): void {
const annotation: Nullable<Component> = getComponentAnnotation(ComponentCtor);
if (!annotation || !isComponentAnnotation(annotation))
throw new Error(`The provided "${ComponentCtor.name ?? ComponentCtor}" is not a Component. The ComponentTestBed cannot be created.`);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export function assertComponentFixture(fixture: unknown): void {
if (!fixture)
throw new Error('ComponentFixture is falsy. You need to use `testBed.compile()` before running expectation.');
throw new Error('ComponentFixture is falsy. You need to use `beforeEach(() => tb.compile());` of `tb.compileEach();` before running expectations.');
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ComponentFixture } from '@angular/core/testing';
import { click, emitOutput } from '../event';
import { click, emitOutput } from '../action';
import { ComponentActionTools } from './models/component-action-tools.model';

export function buildComponentActionTools(fixture: ComponentFixture<unknown>): ComponentActionTools {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ComponentFixture } from '@angular/core/testing';
import { findAllComponents, findAllDebugElements, findAllElements, findComponent, findDebugElement, findElement } from '../element';
import { findAllComponents, findAllDebugElements, findAllElements, findComponent, findDebugElement, findElement } from '../query';
import { ComponentQueryTools } from './models/component-query-tools.model';

export function buildComponentQueryTools(fixture: ComponentFixture<unknown>): ComponentQueryTools {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { ProviderToken, Type } from '@angular/core';
import { ComponentFixture } from '@angular/core/testing';
import { getComponentAnnotation } from '../../common/annotation/component-annotation';
import { shouldCreate } from '../../common/expectation/should-create';
import { CustomTestBedFactory } from '../../common/test-bed/custom-test-bed-factory';
import { InjectionStore } from '../../common/test-bed/store/models/injected-store.model';
import { MaybeArray, NonEmptyString, PrettyMerge } from '../../shared.model';
import { makeArray } from '../../util/array.util';
import { assertComponent } from './assertions/assert-component';
import { assertComponentFixture } from './assertions/assert-fixture';
import { buildComponentTools } from './component-tools';
import { ComponentTestBed } from './models';
import { ComponentSetup } from './models/component-setup.model';
import { Declaration } from './models/metadata-type.model';

export class ComponentTestBedFactory<ComponentType, Store extends InjectionStore = InjectionStore> extends CustomTestBedFactory<ComponentType, Store> {

public constructor(rootComponent: Type<ComponentType>) {
assertComponent(rootComponent);
super(rootComponent);
(getComponentAnnotation(rootComponent)?.standalone)
? this.import(this.described)
: this.declare(this.described);
}

private fixture: ComponentFixture<ComponentType> = null!;

private declarations: Set<Declaration> = new Set();

/**
* Declares one non-standalone component, directive or pipe into the `ComponentTestBed`.
*/
public declare(declaration: Declaration): this
/**
* Declares many non-standalone components, directives and pipes into `ComponentTestBed`.
*/
public declare(declarations: Declaration[]): this
public declare(oneOrManyDeclarations: MaybeArray<Declaration>): this {
makeArray(oneOrManyDeclarations).forEach(v => this.declarations.add(v));
return this;
}

public override inject<S extends string, T>(name: NonEmptyString<S>, token: ProviderToken<T>): ComponentTestBed<ComponentType, InjectionStore<PrettyMerge<Store['injected'] & { [K in S]: T }>>> {
return super.inject(name, token) as any;
}

public override async compile(): Promise<void> {
await super.compile();
this.testBed.configureTestingModule({
declarations: [...this.declarations.values()],
});
await this.testBed.compileComponents();
this.fixture = this.testBed.createComponent(this.described);
}

public override setup(action: ComponentSetup<ComponentType, Store['injected']>): jasmine.ImplementationCallback {
return (action.length > 1)
? (done: DoneFn) => action(buildComponentTools(this), done)
: () => action(buildComponentTools(this), null!);
}

public override shouldCreate(): void {
shouldCreate(() => {
assertComponentFixture(this.fixture);
return this.fixture.componentInstance;
});
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ProviderToken } from '@angular/core';
import { ComponentFixture } from '@angular/core/testing';
import { InjectionStore } from '../../common/test-bed/store';
import { buildInjected } from '../../common/test-bed/store/injected';
import { InjectionStore } from '../../common/test-bed/store/models/injected-store.model';
import { assertComponentFixture } from './assertions/assert-fixture';
import { buildComponentActionTools } from './component-action-tools';
import { buildComponentQueryTools } from './component-query-tools';
Expand Down
Loading

0 comments on commit 0758a91

Please sign in to comment.