Skip to content

feat: add imports option to configure #116

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion jest.base.config.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
module.exports = {
preset: 'jest-preset-angular',
rootDir: '../',
setupFilesAfterEnv: ['<rootDir>/test.ts'],
transformIgnorePatterns: ['node_modules/(?!@ngrx)'],

snapshotSerializers: [
'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js',
'jest-preset-angular/build/AngularSnapshotSerializer.js',
Expand Down
5 changes: 5 additions & 0 deletions projects/jest.lib.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,9 @@ const baseConfig = require('../jest.base.config');
module.exports = {
...baseConfig,
roots: ['<rootDir>/projects'],
setupFilesAfterEnv: ['<rootDir>/projects/setupJest.ts'],
displayName: {
name: 'LIB',
color: 'magenta',
},
};
File renamed without changes.
24 changes: 24 additions & 0 deletions projects/testing-library/src/lib/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Config } from './models';

let config: Config = {
defaultImports: [],
dom: {},
};

export function configure(newConfig: Partial<Config> | ((config: Partial<Config>) => Partial<Config>)) {
if (typeof newConfig === 'function') {
// Pass the existing config out to the provided function
// and accept a delta in return
newConfig = newConfig(config);
}

// Merge the incoming config delta
config = {
...config,
...newConfig,
};
}

export function getConfig() {
return config;
}
15 changes: 14 additions & 1 deletion projects/testing-library/src/lib/models.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import { Type, DebugElement } from '@angular/core';
import { ComponentFixture } from '@angular/core/testing';
import { Routes } from '@angular/router';
import { BoundFunction, FireObject, Queries, queries, waitFor, waitForElementToBeRemoved } from '@testing-library/dom';
import {
BoundFunction,
FireObject,
Queries,
queries,
waitFor,
waitForElementToBeRemoved,
Config as dtlConfig,
} from '@testing-library/dom';
import { UserEvents } from './user-events';
import { OptionsReceived } from 'pretty-format';

Expand Down Expand Up @@ -304,3 +312,8 @@ export interface RenderDirectiveOptions<DirectiveType, WrapperType, Q extends Qu
wrapper?: Type<WrapperType>;
componentProperties?: Partial<any>;
}

export interface Config {
defaultImports: any[];
dom: Partial<dtlConfig>;
}
50 changes: 27 additions & 23 deletions projects/testing-library/src/lib/testing-library.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,11 @@ import {
configure as dtlConfigure,
} from '@testing-library/dom';
import { RenderComponentOptions, RenderDirectiveOptions, RenderResult } from './models';
import { getConfig } from './config';
import { createSelectOptions, createType, tab } from './user-events';

const mountedFixtures = new Set<ComponentFixture<any>>();

dtlConfigure({
eventWrapper: (cb) => {
const result = cb();
detectChangesForMountedFixtures();
return result;
},
});

export async function render<ComponentType>(
component: Type<ComponentType>,
renderOptions?: RenderComponentOptions<ComponentType>,
Expand Down Expand Up @@ -59,9 +52,20 @@ export async function render<SutType, WrapperType = SutType>(
removeAngularAttributes = false,
} = renderOptions as RenderDirectiveOptions<SutType, WrapperType>;

const config = getConfig();

dtlConfigure({
eventWrapper: (cb) => {
const result = cb();
detectChangesForMountedFixtures();
return result;
},
...config.dom,
});

TestBed.configureTestingModule({
declarations: addAutoDeclarations(sut, { declarations, excludeComponentDeclaration, template, wrapper }),
imports: addAutoImports({ imports, routes }),
imports: addAutoImports({ imports: imports.concat(config.defaultImports), routes }),
providers: [...providers],
schemas: [...schemas],
});
Expand Down Expand Up @@ -102,7 +106,7 @@ export async function render<SutType, WrapperType = SutType>(
// Call ngOnChanges on initial render
if (hasOnChangesHook(fixture.componentInstance)) {
const changes = getChangesObj(null, fixture.componentInstance);
fixture.componentInstance.ngOnChanges(changes)
fixture.componentInstance.ngOnChanges(changes);
}

if (detectChangesOnRender) {
Expand Down Expand Up @@ -224,20 +228,21 @@ function setComponentProperties<SutType>(
}

function hasOnChangesHook<SutType>(componentInstance: SutType): componentInstance is SutType & OnChanges {
return 'ngOnChanges' in componentInstance
&& typeof (componentInstance as SutType & OnChanges).ngOnChanges === 'function';
};
return (
'ngOnChanges' in componentInstance && typeof (componentInstance as SutType & OnChanges).ngOnChanges === 'function'
);
}

function getChangesObj<SutType>(
oldProps: Partial<SutType> | null,
newProps: Partial<SutType>
) {
function getChangesObj<SutType>(oldProps: Partial<SutType> | null, newProps: Partial<SutType>) {
const isFirstChange = oldProps === null;
return Object.keys(newProps).reduce<SimpleChanges>((changes, key) => ({
...changes,
[key]: new SimpleChange(isFirstChange ? null : oldProps[key], newProps[key], isFirstChange)
}), {});
};
return Object.keys(newProps).reduce<SimpleChanges>(
(changes, key) => ({
...changes,
[key]: new SimpleChange(isFirstChange ? null : oldProps[key], newProps[key], isFirstChange),
}),
{},
);
}

function addAutoDeclarations<SutType>(
component: Type<SutType>,
Expand Down Expand Up @@ -425,7 +430,6 @@ const userEvent = {
*/
export {
buildQueries,
configure,
getByLabelText,
getAllByLabelText,
queryByLabelText,
Expand Down
1 change: 1 addition & 0 deletions projects/testing-library/src/public_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@

export * from './lib/models';
export * from './lib/user-events';
export * from './lib/config';
export * from './lib/testing-library';
51 changes: 51 additions & 0 deletions projects/testing-library/tests/config.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Component } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { render, configure } from '../src/public_api';
import { ReactiveFormsModule, FormBuilder } from '@angular/forms';

@Component({
selector: 'app-fixture',
template: `
<form [formGroup]="form" name="form">
<div>
<label for="name">Name</label>
<input type="text" id="name" name="name" formControlName="name" />
</div>
</form>
`,
})
class FormsComponent {
form = this.formBuilder.group({
name: [''],
});

constructor(private formBuilder: FormBuilder) {}
}

let originalConfig;
beforeEach(() => {
// Grab the existing configuration so we can restore
// it at the end of the test
configure((existingConfig) => {
originalConfig = existingConfig;
// Don't change the existing config
return {};
});
});

afterEach(() => {
configure(originalConfig);
});

beforeEach(() => {
configure({
defaultImports: [ReactiveFormsModule],
});
});

test('adds default imports to the testbed', async () => {
await render(FormsComponent);

const reactive = TestBed.inject(ReactiveFormsModule);
expect(reactive).not.toBeNull();
});
59 changes: 30 additions & 29 deletions projects/testing-library/tests/render.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,20 @@ describe('removeAngularAttributes', () => {
});
});

@NgModule({
declarations: [FixtureComponent],
})
export class FixtureModule {}
describe('excludeComponentDeclaration', () => {
test('will throw if component is declared in an import', async () => {
await render(FixtureComponent, {
imports: [FixtureModule],
excludeComponentDeclaration: true,
describe('animationModule', () => {
@NgModule({
declarations: [FixtureComponent],
})
class FixtureModule {}
describe('excludeComponentDeclaration', () => {
test('will throw if component is declared in an import', async () => {
await render(FixtureComponent, {
imports: [FixtureModule],
excludeComponentDeclaration: true,
});
});
});
});

describe('animationModule', () => {
test('adds NoopAnimationsModule by default', async () => {
await render(FixtureComponent);
const noopAnimationsModule = TestBed.inject(NoopAnimationsModule);
Expand All @@ -72,28 +72,29 @@ describe('animationModule', () => {
});
});

@Component({
selector: 'fixture',
template: ` {{ name }} `,
})
class FixtureWithNgOnChangesComponent implements OnInit, OnChanges {
@Input() name = 'Sarah';
@Input() nameInitialized?: (name: string) => void;
@Input() nameChanged?: (name: string, isFirstChange: boolean) => void;

ngOnInit() {
if (this.nameInitialized) {
this.nameInitialized(this.name);
describe('Angular component life-cycle hooks', () => {
@Component({
selector: 'fixture',
template: ` {{ name }} `,
})
class FixtureWithNgOnChangesComponent implements OnInit, OnChanges {
@Input() name = 'Sarah';
@Input() nameInitialized?: (name: string) => void;
@Input() nameChanged?: (name: string, isFirstChange: boolean) => void;

ngOnInit() {
if (this.nameInitialized) {
this.nameInitialized(this.name);
}
}
}

ngOnChanges(changes: SimpleChanges) {
if (changes.name && this.nameChanged) {
this.nameChanged(changes.name.currentValue, changes.name.isFirstChange());
ngOnChanges(changes: SimpleChanges) {
if (changes.name && this.nameChanged) {
this.nameChanged(changes.name.currentValue, changes.name.isFirstChange());
}
}
}
}
describe('Angular component life-cycle hooks', () => {

test('will call ngOnInit on initial render', async () => {
const nameInitialized = jest.fn();
const componentProperties = { nameInitialized };
Expand Down
5 changes: 1 addition & 4 deletions src/app/examples/03-forms.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import { ReactiveFormsModule } from '@angular/forms';
import { render, screen, fireEvent } from '@testing-library/angular';
import userEvent from '@testing-library/user-event';

import { FormsComponent } from './03-forms';

test('is possible to fill in a form and verify error messages (with the help of jest-dom https://testing-library.com/docs/ecosystem-jest-dom)', async () => {
await render(FormsComponent, {
imports: [ReactiveFormsModule],
});
await render(FormsComponent);

const nameControl = screen.getByRole('textbox', { name: /name/i });
const scoreControl = screen.getByRole('spinbutton', { name: /score/i });
Expand Down
2 changes: 1 addition & 1 deletion src/app/examples/03-forms.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Component } from '@angular/core';
import { FormBuilder, Validators, ReactiveFormsModule, ValidationErrors } from '@angular/forms';
import { FormBuilder, Validators, ValidationErrors } from '@angular/forms';

@Component({
selector: 'app-fixture',
Expand Down
3 changes: 1 addition & 2 deletions src/app/examples/04-forms-with-material.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { ReactiveFormsModule } from '@angular/forms';
import { render, screen } from '@testing-library/angular';
import userEvent from '@testing-library/user-event';

Expand All @@ -7,7 +6,7 @@ import { MaterialFormsComponent } from './04-forms-with-material';

test('is possible to fill in a form and verify error messages (with the help of jest-dom https://testing-library.com/docs/ecosystem-jest-dom)', async () => {
const { fixture } = await render(MaterialFormsComponent, {
imports: [ReactiveFormsModule, MaterialModule],
imports: [MaterialModule],
});

const nameControl = screen.getByLabelText(/name/i);
Expand Down
2 changes: 1 addition & 1 deletion src/app/examples/04-forms-with-material.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Component } from '@angular/core';
import { FormBuilder, Validators, ReactiveFormsModule, ValidationErrors } from '@angular/forms';
import { FormBuilder, Validators, ValidationErrors } from '@angular/forms';

@Component({
selector: 'app-fixture',
Expand Down
6 changes: 6 additions & 0 deletions src/jest.app.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ const baseConfig = require('../jest.base.config');

module.exports = {
...baseConfig,

roots: ['<rootDir>/src'],
modulePaths: ['<rootDir>/dist'],
setupFilesAfterEnv: ['<rootDir>/src/setupJest.ts'],
displayName: {
name: 'EXAMPLE',
color: 'blue',
},
};
8 changes: 8 additions & 0 deletions src/setupJest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import 'jest-preset-angular';
import '@testing-library/jest-dom';
import { configure } from '@testing-library/angular';
import { ReactiveFormsModule } from '@angular/forms';

configure({
defaultImports: [ReactiveFormsModule],
});