1 | # `@shopify/jest-dom-mocks`
|
2 |
|
3 | [![Build Status](https://github.com/Shopify/quilt/workflows/Node-CI/badge.svg?branch=main)](https://github.com/Shopify/quilt/actions?query=workflow%3ANode-CI)
|
4 | [![Build Status](https://github.com/Shopify/quilt/workflows/Ruby-CI/badge.svg?branch=main)](https://github.com/Shopify/quilt/actions?query=workflow%3ARuby-CI)
|
5 | [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE.md) [![npm version](https://badge.fury.io/js/%40shopify%2Fjest-dom-mocks.svg)](https://badge.fury.io/js/%40shopify%2Fjest-dom-mocks)
|
6 |
|
7 | Jest mocking utilities for working with the DOM.
|
8 |
|
9 | ## Installation
|
10 |
|
11 | ```bash
|
12 | $ yarn add @shopify/jest-dom-mocks
|
13 | ```
|
14 |
|
15 | ## Setup
|
16 |
|
17 | This package provides two methods that should be included in the jest setup files:
|
18 |
|
19 | - `ensureMocksReset`
|
20 | - `installMockStorage`
|
21 |
|
22 | ### `ensureMocksReset`
|
23 |
|
24 | Should be called in the `afterEach` method of the jest `each-test` setup file. For example:
|
25 |
|
26 | ```ts
|
27 | import {ensureMocksReset} from '@shopify/jest-dom-mocks';
|
28 |
|
29 | afterEach(() => {
|
30 | ensureMocksReset();
|
31 | });
|
32 | ```
|
33 |
|
34 | this will ensure that appropriate error messages are shown if a DOM object is mocked without beign restored for the next test.
|
35 |
|
36 | ### `installMockStorage`
|
37 |
|
38 | Should be called in the jest `setup` file. For example:
|
39 |
|
40 | ```ts
|
41 | import {installMockStorage} from '@shopify/jest-dom-mocks';
|
42 |
|
43 | installMockStorage();
|
44 | ```
|
45 |
|
46 | this will install the `localStorage` and `sessionStorage` mocks onto the global `window` object.
|
47 |
|
48 | ## Example Usage
|
49 |
|
50 | In this example, we are testing a `NumberTransitioner` component using `Jest` and `Enzyme`. Note that parts of this file have been omitted in order to focus in on the relevant parts of the example.
|
51 |
|
52 | ```ts
|
53 | import {clock, animationFrame} from '@shopify/jest-dom-mocks';
|
54 |
|
55 | it('transitions to the next number after being updated', () => {
|
56 | clock.mock();
|
57 | animationFrame.mock();
|
58 |
|
59 | const duration = 1000;
|
60 | const rendered = mount(
|
61 | <NumberTransitioner duration={duration}>{100}</NumberTransitioner>,
|
62 | );
|
63 | rendered.setProps({children: 200});
|
64 |
|
65 | clock.tick(duration / 4);
|
66 | animationFrame.runFrame();
|
67 | expect(rendered.text()).toBe('125');
|
68 |
|
69 | clock.tick(duration / 2);
|
70 | animationFrame.runFrame();
|
71 | expect(rendered.text()).toBe('175');
|
72 |
|
73 | clock.restore();
|
74 | animationFrame.restore();
|
75 | });
|
76 | ```
|
77 |
|
78 | ## API Reference
|
79 |
|
80 | The mocks provided can be divided into 3 primary categories:
|
81 |
|
82 | - standard mocks
|
83 | - fetch mock
|
84 | - storage mocks
|
85 |
|
86 | ### Standard Mocks
|
87 |
|
88 | The following standard mocks are available:
|
89 |
|
90 | - `animationFrame`
|
91 | - `requestIdleCallback`
|
92 | - `clock`
|
93 | - `location`
|
94 | - `matchMedia`
|
95 | - `timer`
|
96 | - `promise`
|
97 | - `intersectionObserver`
|
98 | - `dimension`
|
99 | - `connection`
|
100 |
|
101 | Each of the standard mocks can be installed, for a given test, using `standardMock.mock()`, and must be restored before the end of the test using `standardMock.restore()`.
|
102 |
|
103 | For example:
|
104 |
|
105 | ```ts
|
106 | import {location} from '@shopify/jest-dom-mocks';
|
107 |
|
108 | beforeEach(() => {
|
109 | location.mock();
|
110 | });
|
111 |
|
112 | afterEach(() => {
|
113 | location.restore();
|
114 | });
|
115 |
|
116 | it('does a thing', () => {
|
117 | // run test code here
|
118 | });
|
119 | ```
|
120 |
|
121 | Or, if you just need to mock something for a single test:
|
122 |
|
123 | ```ts
|
124 | import {location} from '@shopify/jest-dom-mocks';
|
125 |
|
126 | it('does a thing', () => {
|
127 | location.mock();
|
128 |
|
129 | // run test code here
|
130 |
|
131 | location.restore();
|
132 | });
|
133 | ```
|
134 |
|
135 | Some of the standard mocks include additional features:
|
136 |
|
137 | #### `AnimationFrame.runFrame(): void`
|
138 |
|
139 | Executes all queued animation callbacks.
|
140 |
|
141 | #### `RequestIdleCallback.mockAsUnsupported(): void`
|
142 |
|
143 | Removes `window.requestIdleCallback` and `window.cancelIdleCallback`, which can be useful for testing features that should work with and without idle callbacks available.
|
144 |
|
145 | #### `RequestIdleCallback.runIdleCallbacks(timeRemaining?: number, didTimeout?: boolean): void`
|
146 |
|
147 | Runs all currently-scheduled idle callbacks. If provided, `timeRemaining`/ `didTimeout` will be used to construct the argument for these callbacks. Once called, all callbacks are removed from the queue.
|
148 |
|
149 | #### `RequestIdleCallback.cancelIdleCallbacks(): void`
|
150 |
|
151 | Cancels all currently-scheduled idle callbacks.
|
152 |
|
153 | #### `RequestIdleCallback.cancelIdleCallback(callback: any): void`
|
154 |
|
155 | Cancels the idle callback specified by the passed argument. This value should be the one returned from a call to `window.requestIdleCallback`.
|
156 |
|
157 | #### `Clock.mock(now: number | Date): void`
|
158 |
|
159 | In addition to the usual `.mock()` functionality (with no arguments), the `Clock` object can be `mock`ed by passing in a `number` or `Date` object to use as the current system time.
|
160 |
|
161 | #### `Clock.tick(time: number): void`
|
162 |
|
163 | Ticks the mocked `Clock` ahead by `time` milliseconds.
|
164 |
|
165 | #### `Clock.setTime(time: number): void`
|
166 |
|
167 | Sets the system time to the given `time`.
|
168 |
|
169 | #### `MatchMedia.mock(media?: MediaMatching): void`
|
170 |
|
171 | In addition to the usual `.mock()` functionality (with no arguments), the `MatchMedia` object can be `mock`ed by passing in a `MediaMatching` function to use as the implementation.
|
172 |
|
173 | The `MediaMatching` function has the following interface:
|
174 |
|
175 | ```ts
|
176 | interface MediaMatching {
|
177 | (mediaQuery: string): Partial<MediaQueryList>;
|
178 | }
|
179 | ```
|
180 |
|
181 | it takes a `mediaQuery` string as input and returns a partial `MediaQueryList` to use as the result of `window.matchMedia(mediaQuery)`. The partial result will be merged with the default values:
|
182 |
|
183 | ```ts
|
184 | {
|
185 | media: '',
|
186 | addListener: noop,
|
187 | removeListener: noop,
|
188 | matches: false
|
189 | }
|
190 | ```
|
191 |
|
192 | #### `MatchMedia.setMedia(media?: MediaMatching): void`
|
193 |
|
194 | Sets the implementation function for the mocked `MatchMedia` object. see above (`MatchMedia.mock(media?: MediaMatching): void`) for details on how `MediaMatching` works.
|
195 |
|
196 | You can also call `setMedia` with no arguments to restore the default implementation.
|
197 |
|
198 | #### `Timer.runAllTimers(): void`
|
199 |
|
200 | Runs all system timers to completion.
|
201 |
|
202 | #### `Timer.runTimersToTime(time: number): void`
|
203 |
|
204 | Runs all system timers to the given `time`.
|
205 |
|
206 | #### `Promise.runPending(): void`
|
207 |
|
208 | Runs all promise resolvers that have been queued.
|
209 |
|
210 | #### `IntersectionObserver.observers`
|
211 |
|
212 | Returns an array of records representing elements currently being observed with an `IntersectionObserver`. Each record contains a `target` (the element being observed), `callback` (the function used when constructing the observer), `options` (optional object used when constructing the observer), and a `source` (the fake `IntersectionObserver` instance that was used to observe).
|
213 |
|
214 | #### `IntersectionObserver.simulate(entry: Partial<IntersectionObserverEntry> | Partial<IntersectionObserverEntry>[]): void`
|
215 |
|
216 | Simulates a call on all matching observers. If you pass a `target` on the passed entry/ entries, only observers with a matching `target` element will be triggered. Otherwise, all observers will be triggered. If you do not provide a full `IntersectionObserverEntry` in any case, the missing fields will be filled out with sensible defaults.
|
217 |
|
218 | ### Fetch Mock
|
219 |
|
220 | We use a version of `fetch-mock` that is augmented to ensure that it is properly unmocked after each test run. See the [API of `fetch-mock`](http://www.wheresrhys.co.uk/fetch-mock) for more details.
|
221 |
|
222 | ### Storage mock
|
223 |
|
224 | The storage mocks are a bit different than the other mocks, because they serve primarily as a polyfill for the `localStorage` and `sessionStorage` APIs. The following standard API methods are implemented:
|
225 |
|
226 | - `getItem`
|
227 | - `setItem`
|
228 | - `removeItem`
|
229 | - `clear`
|
230 |
|
231 | Each of these are wrapped in a jest spy, which is automatically restored at the end of the test run.
|
232 |
|
233 | ### Dimension mocks
|
234 |
|
235 | The dimension mocks allow mocking the following DOM properties:
|
236 |
|
237 | - `scrollWidth`
|
238 | - `scrollHeight`
|
239 | - `offsetWidth`
|
240 | - `offsetHeight`
|
241 | - `innerWidth`
|
242 |
|
243 | Pass the dimension you want to mock and the value you want returned for all calls when calling `mock`:
|
244 |
|
245 | ```ts
|
246 | import {dimension} from '@shopify/jest-dom-mocks';
|
247 |
|
248 | beforeEach(() => {
|
249 | dimension.mock({
|
250 | scrollWidth: 100,
|
251 | offsetHeight: 200,
|
252 | });
|
253 | });
|
254 |
|
255 | afterEach(() => dimension.restore());
|
256 | ```
|
257 |
|
258 | You can also pass in a function as a mock that returns a number. The element is passed as the only argument to the function:
|
259 |
|
260 | ```tsx
|
261 | beforeEach(() => {
|
262 | dimension.mock({
|
263 | scrollWidth(element: HTMLElement) {
|
264 | return element.id === 'test-id' ? 200 : 0;
|
265 | },
|
266 | });
|
267 | });
|
268 |
|
269 | afterEach(() => dimension.restore());
|
270 |
|
271 | describe('DOM tests', () => {
|
272 | it('returns the element width', () => {
|
273 | function Component() {
|
274 | return <div id="some-id" />;
|
275 | }
|
276 | const element = mount(<Component />);
|
277 | const elementWidth =
|
278 | element.domNode == null ? undefined : element.domNode.scrollWidth;
|
279 |
|
280 | expect(elementWidth).toBe(200);
|
281 | });
|
282 | });
|
283 | ```
|