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 = [];
+}