From 9e3b881876bb1947c808e6915ec8d290d5cda13e Mon Sep 17 00:00:00 2001 From: timdeschryver <28659384+timdeschryver@users.noreply.github.com> Date: Sat, 31 Aug 2019 17:08:39 +0200 Subject: [PATCH] docs: add inline docs --- README.md | 4 +- projects/jest-utils/src/lib/create-mock.ts | 2 +- projects/testing-library/src/lib/models.ts | 164 +++++++++++++++++- .../src/lib/user-events/index.ts | 26 +++ .../src/lib/user-events/type.ts | 22 ++- .../examples/05-component-provider.spec.ts | 62 +++++-- .../examples/07-with-ngrx-mock-store.spec.ts | 11 +- src/app/examples/07-with-ngrx-mock-store.ts | 6 +- 8 files changed, 270 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 8c3bd77c..e32272e3 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ export class CounterComponent { counter.component.spec.ts -```javascript +```typescript import { render } from '@testing-library/angular'; import CounterComponent from './counter.component.ts'; @@ -137,6 +137,8 @@ describe('Counter', () => { }); ``` +[See more examples](https://github.com/testing-library/angular-testing-library/tree/master/src/app/examples) + ## Installation This module is distributed via [npm][npm] which is bundled with [node][node] and diff --git a/projects/jest-utils/src/lib/create-mock.ts b/projects/jest-utils/src/lib/create-mock.ts index 3613abf1..1c1dea5c 100644 --- a/projects/jest-utils/src/lib/create-mock.ts +++ b/projects/jest-utils/src/lib/create-mock.ts @@ -32,6 +32,6 @@ export function createMock(type: Type): Mock { export function provideMock(type: Type): Provider { return { provide: type, - useFactory: () => createMock(type), + useValue: createMock(type), }; } diff --git a/projects/testing-library/src/lib/models.ts b/projects/testing-library/src/lib/models.ts index 7952363a..375b9e32 100644 --- a/projects/testing-library/src/lib/models.ts +++ b/projects/testing-library/src/lib/models.ts @@ -6,24 +6,184 @@ import { UserEvents } from './user-events'; export type RenderResultQueries = { [P in keyof Q]: BoundFunction }; export interface RenderResult extends RenderResultQueries, FireObject, UserEvents { + /** + * @description + * The HTML of the rendered component + */ container: HTMLElement; + /** + * @description + * Prints out the component's HTML with syntax highlighting + * + * @param + * element: The to be printed HTML element, if not provided it will log the whole component's HTML + */ debug: (element?: HTMLElement) => void; + /** + * @description + * The Angular `ComponentFixture` of the component + * For more info see https://angular.io/api/core/testing/ComponentFixture + */ fixture: ComponentFixture; } export interface RenderOptions { + /** + * @description + * Will call detectChanges when the component is compiled + * + * @default + * true + * + * @example + * const component = render(AppComponent, { + * detectChanges: false + * }) + */ detectChanges?: boolean; + /** + * @description + * A collection of components, directives and pipes needed to render the component, for example, nested components of the component + * For more info see https://angular.io/api/core/NgModule#declarations + * + * @default + * [] + * + * @example + * const component = render(AppComponent, { + * declarations: [ CustomerDetailComponent, ButtonComponent ] + * }) + */ declarations?: any[]; + /** + * @description + * A collection of providers needed to render the component via Dependency Injection, for example, injectable services or tokens + * For more info see https://angular.io/api/core/NgModule#providers + * + * @default + * [] + * + * @example + * const component = render(AppComponent, { + * providers: [ + * CustomersService, + * { + * provide: MAX_CUSTOMERS_TOKEN, + * useValue: 10 + * } + * ] + * }) + */ providers?: any[]; + /** + * @description + * A collection of imports needed to render the component, for example, shared modules + * For more info see https://angular.io/api/core/NgModule#imports + * + * @default + * Adds `NoopAnimationsModule` by default if `BrowserAnimationsModule` isn't added to the collection: + * `[NoopAnimationsModule]` + * + * @example + * const component = render(AppComponent, { + * providers: [ + * AppSharedModule, + * MaterialModule, + * ] + * }) + */ imports?: any[]; + /** + * @description + * A collection of schemas needed to render the component. + * Allowed value are `NO_ERRORS_SCHEMA` and `CUSTOM_ELEMENTS_SCHEMA`. + * For more info see https://angular.io/api/core/NgModule#schemas + * + * @default + * [] + * + * @example + * const component = render(AppComponent, { + * imports: [ + * NO_ERRORS_SCHEMA, + * ] + * }) + */ schemas?: any[]; + /** + * @description + * An object to set `@Input` and `@Output` properties of the component + * + * @default + * {} + * + * @example + * const component = render(AppComponent, { + * componentProperties: { + * counterValue: 10, + * send: (value) => { ... } + * } + * }) + */ componentProperties?: Partial; + /** + * @description + * A collection of providers to inject dependencies of the component + * For more info see https://angular.io/api/core/Directive#providers + * + * @default + * [] + * + * @example + * const component = render(AppComponent, { + * componentProviders: [ + * AppComponentService + * ] + * }) + */ componentProviders?: any[]; + /** + * @description + * Queries to bind. Overrides the default set from DOM Testing Library unless merged. + * + * @default + * undefined + * + * @example + * import * as customQueries from 'custom-queries' + * import { queries } from '@testing-library/angular' + * + * const component = render(AppComponent, { + * queries: { ...queries, ...customQueries } + * }) + */ queries?: Q; + /** + * @description + * An Angular component to wrap the component in + * + * @default + * `WrapperComponent`, an empty component that strips the `ng-version` attribute + * + * @example + * const component = render(AppComponent, { + * wrapper: CustomWrapperComponent + * }) + */ wrapper?: Type; /** - * Exclude the component to be automatically be added as a declaration - * This is needed when the component is declared in an imported module + * @description + * Exclude the component to be automatically be added as a declaration. + * This is needed when the component is declared in an imported module. + * + * @default + * false + * + * @example + * const component = render(AppComponent, { + * imports: [AppModule], // a module that includes AppComponent + * excludeComponentDeclaration: true + * }) */ excludeComponentDeclaration?: boolean; } diff --git a/projects/testing-library/src/lib/user-events/index.ts b/projects/testing-library/src/lib/user-events/index.ts index 06b9ff92..d915f647 100644 --- a/projects/testing-library/src/lib/user-events/index.ts +++ b/projects/testing-library/src/lib/user-events/index.ts @@ -3,7 +3,33 @@ import { createType } from './type'; import { createSelectOptions } from './selectOptions'; export interface UserEvents { + /** + * @description + * Types a value in an input field just like the user would do + * + * @argument + * element: HTMLElement - the form field to type in + * value: string - the value to type in + * + * @example + * component.type(component.getByLabelText('Firstname'), 'Tim') + * component.type(component.getByLabelText('Firstname'), 'Tim', { delay: 100 }) + * component.type(component.getByLabelText('Firstname'), 'Tim', { allAtOnce: true }) + */ type: ReturnType; + + /** + * @description + * Select an option(s) from a select just like the user would do + * + * @argument + * element: HTMLElement - the select box to select an option in + * matcher: Matcher | Matcher[] - the value(s) to select + * + * @example + * component.selectOptions(component.getByLabelText('Fruit'), 'Blueberry') + * component.selectOptions(component.getByLabelText('Fruit'), ['Blueberry'. 'Grape']) + */ selectOptions: ReturnType; } diff --git a/projects/testing-library/src/lib/user-events/type.ts b/projects/testing-library/src/lib/user-events/type.ts index c0b9848b..dcd5db5b 100644 --- a/projects/testing-library/src/lib/user-events/type.ts +++ b/projects/testing-library/src/lib/user-events/type.ts @@ -6,6 +6,25 @@ function wait(time) { }); } +export interface TypeOptions { + /** + * @description + * Write the text at once rather than on character at a time + * + * @default + * false + */ + allAtOnce?: boolean; + /** + * @description + * Number of milliseconds until the next character is typed + * + * @default + * 0 + */ + delay?: number; +} + // implementation from https://github.com/testing-library/user-event export function createType(fireEvent: FireFunction & FireObject) { function createFireChangeEvent(value: string) { @@ -17,7 +36,8 @@ export function createType(fireEvent: FireFunction & FireObject) { }; } - return async function type(element: HTMLElement, value: string, { allAtOnce = false, delay = 0 } = {}) { + return async function type(element: HTMLElement, value: string, options?: TypeOptions) { + const { allAtOnce = false, delay = 0 } = options || {}; const initialValue = (element as HTMLInputElement).value; if (allAtOnce) { diff --git a/src/app/examples/05-component-provider.spec.ts b/src/app/examples/05-component-provider.spec.ts index 0ab5fc28..2c487f3e 100644 --- a/src/app/examples/05-component-provider.spec.ts +++ b/src/app/examples/05-component-provider.spec.ts @@ -1,4 +1,6 @@ +import { TestBed } from '@angular/core/testing'; import { render } from '@testing-library/angular'; +import { provideMock, Mock, createMock } from '@testing-library/angular/jest-utils'; import { ComponentWithProviderComponent, CounterService } from './05-component-provider'; @@ -26,27 +28,49 @@ test('renders the current value and can increment and decrement', async () => { expect(valueControl.textContent).toBe('1'); }); -// test('renders the current value and can increment and decrement with a mocked jest-utils service', async () => { -// const component = await render(FixtureComponent, { -// componentProviders: [provideMock(CounterService)], -// }); +test('renders the current value and can increment and decrement with a mocked jest-utils service', async () => { + const counter = createMock(CounterService); + let fakeCounterValue = 50; + counter.increment.mockImplementation(() => (fakeCounterValue += 10)); + counter.decrement.mockImplementation(() => (fakeCounterValue -= 10)); + counter.value.mockImplementation(() => fakeCounterValue); -// const counter = TestBed.get(CounterService) as Mock; -// let fakeCounter = 50; -// counter.increment.mockImplementation(() => (fakeCounter += 10)); -// counter.decrement.mockImplementation(() => (fakeCounter -= 10)); -// counter.value.mockImplementation(() => fakeCounter); + const component = await render(ComponentWithProviderComponent, { + componentProviders: [ + { + provide: CounterService, + useValue: counter, + }, + ], + }); + + const incrementControl = component.getByText('Increment'); + const decrementControl = component.getByText('Decrement'); + const valueControl = component.getByTestId('value'); + + expect(valueControl.textContent).toBe('50'); + + component.click(incrementControl); + component.click(incrementControl); + expect(valueControl.textContent).toBe('70'); -// const incrementControl = component.getByText('Increment'); -// const decrementControl = component.getByText('Decrement'); -// const valueControl = component.getByTestId('value'); + component.click(decrementControl); + expect(valueControl.textContent).toBe('60'); +}); -// expect(valueControl.textContent).toBe('50'); +test('renders the current value and can increment and decrement with provideMocked from jest-utils', async () => { + const component = await render(ComponentWithProviderComponent, { + componentProviders: [provideMock(CounterService)], + }); -// component.click(incrementControl); -// component.click(incrementControl); -// expect(valueControl.textContent).toBe('70'); + const incrementControl = component.getByText('Increment'); + const decrementControl = component.getByText('Decrement'); + + component.click(incrementControl); + component.click(incrementControl); + component.click(decrementControl); -// component.click(decrementControl); -// expect(valueControl.textContent).toBe('60'); -// }); + const counterService = TestBed.get(CounterService) as Mock; + expect(counterService.increment).toHaveBeenCalledTimes(2); + expect(counterService.decrement).toHaveBeenCalledTimes(1); +}); diff --git a/src/app/examples/07-with-ngrx-mock-store.spec.ts b/src/app/examples/07-with-ngrx-mock-store.spec.ts index 9eab2519..3651bb86 100644 --- a/src/app/examples/07-with-ngrx-mock-store.spec.ts +++ b/src/app/examples/07-with-ngrx-mock-store.spec.ts @@ -1,7 +1,9 @@ import { render } from '@testing-library/angular'; -import { provideMockStore } from '@ngrx/store/testing'; +import { provideMockStore, MockStore } from '@ngrx/store/testing'; import { WithNgRxMockStoreComponent, selectItems } from './07-with-ngrx-mock-store'; +import { TestBed } from '@angular/core/testing'; +import { Store } from '@ngrx/store'; test('works with provideMockStore', async () => { const component = await render(WithNgRxMockStoreComponent, { @@ -17,6 +19,11 @@ test('works with provideMockStore', async () => { ], }); + const store = TestBed.get(Store) as MockStore; + store.dispatch = jest.fn(); + component.getByText('Four'); - component.getByText('Seven'); + component.click(component.getByText('Seven')); + + expect(store.dispatch).toBeCalledWith({ type: '[Item List] send', item: 'Seven' }); }); diff --git a/src/app/examples/07-with-ngrx-mock-store.ts b/src/app/examples/07-with-ngrx-mock-store.ts index dfe93162..491ae327 100644 --- a/src/app/examples/07-with-ngrx-mock-store.ts +++ b/src/app/examples/07-with-ngrx-mock-store.ts @@ -10,11 +10,15 @@ export const selectItems = createSelector( selector: 'app-fixture', template: `
    -
  • {{ item }}
  • +
  • {{ item }}
`, }) export class WithNgRxMockStoreComponent { items = this.store.pipe(select(selectItems)); constructor(private store: Store) {} + + send(item: string) { + this.store.dispatch({ type: '[Item List] send', item }); + } }