Skip to content

docs: add inline docs #43

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 1 commit into from
Aug 31, 2019
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
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion projects/jest-utils/src/lib/create-mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,6 @@ export function createMock<T>(type: Type<T>): Mock<T> {
export function provideMock<T>(type: Type<T>): Provider {
return {
provide: type,
useFactory: () => createMock(type),
useValue: createMock(type),
};
}
164 changes: 162 additions & 2 deletions projects/testing-library/src/lib/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,184 @@ import { UserEvents } from './user-events';
export type RenderResultQueries<Q extends Queries = typeof queries> = { [P in keyof Q]: BoundFunction<Q[P]> };

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<any>;
}

export interface RenderOptions<C, Q extends Queries = typeof queries> {
/**
* @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<C>;
/**
* @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<any>;
/**
* 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;
}
26 changes: 26 additions & 0 deletions projects/testing-library/src/lib/user-events/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof createType>;

/**
* @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<typeof createSelectOptions>;
}

Expand Down
22 changes: 21 additions & 1 deletion projects/testing-library/src/lib/user-events/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down
62 changes: 43 additions & 19 deletions src/app/examples/05-component-provider.spec.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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>(CounterService) as Mock<CounterService>;
// 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>(CounterService) as Mock<CounterService>;
expect(counterService.increment).toHaveBeenCalledTimes(2);
expect(counterService.decrement).toHaveBeenCalledTimes(1);
});
11 changes: 9 additions & 2 deletions src/app/examples/07-with-ngrx-mock-store.spec.ts
Original file line number Diff line number Diff line change
@@ -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, {
Expand All @@ -17,6 +19,11 @@ test('works with provideMockStore', async () => {
],
});

const store = TestBed.get(Store) as MockStore<any>;
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' });
});
6 changes: 5 additions & 1 deletion src/app/examples/07-with-ngrx-mock-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,15 @@ export const selectItems = createSelector(
selector: 'app-fixture',
template: `
<ul>
<li *ngFor="let item of items | async">{{ item }}</li>
<li *ngFor="let item of items | async" (click)="send(item)">{{ item }}</li>
</ul>
`,
})
export class WithNgRxMockStoreComponent {
items = this.store.pipe(select(selectItems));
constructor(private store: Store<any>) {}

send(item: string) {
this.store.dispatch({ type: '[Item List] send', item });
}
}