Skip to content

Commit

Permalink
Merge pull request #53 from hypothesis/mount-utils
Browse files Browse the repository at this point in the history
Add utilities for mounting and un-mounting components
  • Loading branch information
robertknight authored Nov 26, 2024
2 parents ba7bdd2 + 0b51eb0 commit 22aa072
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 2 deletions.
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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(<MyWidget/>);

// Query component content etc.
});

it('should do something that requires component to be connected', () => {
const wrapper = mount(<MyWidget/>, { connected: true });

// Test behavior that relies on rendered component being part of the
// DOM tree under `document.body`.
});
});
```
5 changes: 3 additions & 2 deletions src/enzyme.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -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';
48 changes: 48 additions & 0 deletions src/mount.ts
Original file line number Diff line number Diff line change
@@ -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 = [];
}

0 comments on commit 22aa072

Please sign in to comment.