diff --git a/README.md b/README.md index 5783b9f..68cb32a 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ This package contains common utilities for testing UI components across Hypothesis frontend projects. It includes tools for: + - Rendering UI components and unmounting them once the test ends - Waiting for conditions to be met - Mocking UI components - Testing accessibility using [axe-core](https://github.com/dequelabs/axe-core) @@ -14,3 +15,36 @@ standard UI and UI testing stack, built on: - [Enzyme](https://github.com/enzymejs/enzyme) - [babel-plugin-mockable-imports](https://github.com/robertknight/babel-plugin-mockable-imports) +## API guide + +### Rendering components + +This package exports a wrapper around Enzyme's `mount` function to render +a component, query its output and interact with it. The function in this +package adds the wrapper to a global list of active wrappers which can then +be conveniently unmounted using `unmountAll` at the end of a test. + +```js +import { mount, unmountAll } from '@hypothesis/frontend-testing'; + +describe('MyWidget', () => { + afterEach(() => { + // Clean up by unmounting any wrappers mounted in the current test and + // removing associated DOM containers. + unmountAll(); + }); + + it('should render', () => { + const wrapper = mount(); + + // Query component content etc. + }); + + it('should do something that requires component to be connected', () => { + const wrapper = mount(, { connected: true }); + + // Test behavior that relies on rendered component being part of the + // DOM tree under `document.body`. + }); +}); +``` diff --git a/src/enzyme.d.ts b/src/enzyme.d.ts index 5a2f514..bf87019 100644 --- a/src/enzyme.d.ts +++ b/src/enzyme.d.ts @@ -4,10 +4,11 @@ declare module 'enzyme' { export class ReactWrapper { getDOMNode(): HTMLElement; + unmount(): void; } export function mount( elementOrWrapper: VNode | ReactWrapper, - { attachTo: HTMLElement }, - ); + options?: { attachTo?: HTMLElement }, + ): ReactWrapper; } diff --git a/src/index.ts b/src/index.ts index 1f34ec4..111853c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,7 @@ export type { Scenario } from './accessibility.js'; export { checkAccessibility } from './accessibility.js'; export { mockImportedComponents } from './mock-imported-components.js'; +export { mount, unmountAll } from './mount.js'; +export type { MountOptions } from './mount.js'; export type { TestTimeout, TimeoutSpec } from './wait.js'; export { delay, waitFor, waitForElement } from './wait.js'; diff --git a/src/mount.ts b/src/mount.ts new file mode 100644 index 0000000..461369b --- /dev/null +++ b/src/mount.ts @@ -0,0 +1,48 @@ +import * as enzyme from 'enzyme'; +import type { ReactWrapper } from 'enzyme'; +import type { VNode } from 'preact'; + +let containers: HTMLElement[] = []; +let wrappers: ReactWrapper[] = []; + +export type MountOptions = { + /** + * If true, the element will be rendered in a container element which is + * attached to `document.body`. + */ + connected?: boolean; +}; + +/** + * Render a Preact component using Enzyme and return a wrapper. + * + * The component can be unmounted by calling `wrapper.unmount()` or by calling + * {@link unmountAll} at the end of the test. + */ +export function mount(jsx: VNode, { connected = false }: MountOptions = {}) { + let wrapper; + if (connected) { + const container = document.createElement('div'); + container.setAttribute('data-enzyme-container', ''); + containers.push(container); + wrapper = enzyme.mount(jsx, { attachTo: container }); + } else { + wrapper = enzyme.mount(jsx); + } + + wrappers.push(wrapper); + + return wrapper; +} + +/** + * Unmount all Preact components rendered using {@link mount} and remove their + * parent container elements (if any) from the DOM. + */ +export function unmountAll() { + wrappers.forEach(w => w.unmount()); + wrappers = []; + + containers.forEach(c => c.remove()); + containers = []; +}