UNPKG

@shopify/jest-dom-mocks

Version:
286 lines (189 loc) 8.82 kB
# `@shopify/jest-dom-mocks` [![Build Status](https://github.com/Shopify/quilt/workflows/Node-CI/badge.svg?branch=main)](https://github.com/Shopify/quilt/actions?query=workflow%3ANode-CI) [![Build Status](https://github.com/Shopify/quilt/workflows/Ruby-CI/badge.svg?branch=main)](https://github.com/Shopify/quilt/actions?query=workflow%3ARuby-CI) [![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) Jest mocking utilities for working with the DOM. ## Installation ```bash yarn add @shopify/jest-dom-mocks ``` ## Setup This package provides two methods that should be included in the jest setup files: - `ensureMocksReset` - `installMockStorage` ### `ensureMocksReset` Should be called in the `afterEach` method of the jest `each-test` setup file. For example: ```ts import {ensureMocksReset} from '@shopify/jest-dom-mocks'; afterEach(() => { ensureMocksReset(); }); ``` this will ensure that appropriate error messages are shown if a DOM object is mocked without being restored for the next test. ### `installMockStorage` Should be called in the jest `setup` file. For example: ```ts import {installMockStorage} from '@shopify/jest-dom-mocks'; installMockStorage(); ``` this will install the `localStorage` and `sessionStorage` mocks onto the global `window` object. ## Example Usage 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. ```ts import {clock, animationFrame} from '@shopify/jest-dom-mocks'; it('transitions to the next number after being updated', () => { clock.mock(); animationFrame.mock(); const duration = 1000; const rendered = mount( <NumberTransitioner duration={duration}>{100}</NumberTransitioner>, ); rendered.setProps({children: 200}); clock.tick(duration / 4); animationFrame.runFrame(); expect(rendered.text()).toBe('125'); clock.tick(duration / 2); animationFrame.runFrame(); expect(rendered.text()).toBe('175'); clock.restore(); animationFrame.restore(); }); ``` ## API Reference The mocks provided can be divided into 3 primary categories: - standard mocks - fetch mock - storage mocks ### Standard Mocks The following standard mocks are available: - `animationFrame` - `requestIdleCallback` - `clock` (Deprecated, use `jest.useFakeTimers()` instead) - `location` - `matchMedia` - `timer` (Deprecated, use `jest.useFakeTimers()` instead) - `promise` - `intersectionObserver` - `dimension` - `connection` 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()`. For example: ```ts import {location} from '@shopify/jest-dom-mocks'; beforeEach(() => { location.mock(); }); afterEach(() => { location.restore(); }); it('does a thing', () => { // run test code here }); ``` Or, if you just need to mock something for a single test: ```ts import {location} from '@shopify/jest-dom-mocks'; it('does a thing', () => { location.mock(); // run test code here location.restore(); }); ``` Some of the standard mocks include additional features: #### `AnimationFrame.runFrame(): void` Executes all queued animation callbacks. #### `RequestIdleCallback.mockAsUnsupported(): void` Removes `window.requestIdleCallback` and `window.cancelIdleCallback`, which can be useful for testing features that should work with and without idle callbacks available. #### `RequestIdleCallback.runIdleCallbacks(timeRemaining?: number, didTimeout?: boolean): void` 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. #### `RequestIdleCallback.cancelIdleCallbacks(): void` Cancels all currently-scheduled idle callbacks. #### `RequestIdleCallback.cancelIdleCallback(callback: any): void` Cancels the idle callback specified by the passed argument. This value should be the one returned from a call to `window.requestIdleCallback`. #### `Clock.mock(now: number | Date): void` 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. Deprecated - use `jest.useFakeTimers({now})` instead. #### `Clock.tick(time: number): void` Ticks the mocked `Clock` ahead by `time` milliseconds. Deprecated - use `jest.advanceTimersByTime()` instead. #### `Clock.setTime(time: number): void` Sets the system time to the given `time`. Deprecated - use `jest.setSystemTime()` instead. #### `MatchMedia.mock(media?: MediaMatching): void` 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. The `MediaMatching` function has the following interface: ```ts interface MediaMatching { (mediaQuery: string): Partial<MediaQueryList>; } ``` 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: ```ts { media: '', addListener: noop, removeListener: noop, matches: false } ``` #### `MatchMedia.setMedia(media?: MediaMatching): void` Sets the implementation function for the mocked `MatchMedia` object. see above (`MatchMedia.mock(media?: MediaMatching): void`) for details on how `MediaMatching` works. You can also call `setMedia` with no arguments to restore the default implementation. #### `Timer.runAllTimers(): void` Runs all system timers to completion. Deprecated - use `jest.runAllTimers()` instead. #### `Timer.runTimersToTime(time: number): void` Runs all system timers to the given `time`. Deprecated - use `jest.advanceTimersByTime()` instead. #### `Promise.runPending(): void` Runs all promise resolvers that have been queued. #### `IntersectionObserver.observers` 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). #### `IntersectionObserver.simulate(entry: Partial<IntersectionObserverEntry> | Partial<IntersectionObserverEntry>[]): void` 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. ### Fetch Mock 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. ### Storage mock 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: - `getItem` - `setItem` - `removeItem` - `clear` - `length` - `key` Each of these are wrapped in a jest spy, which is automatically restored at the end of the test run. ### Dimension mocks The dimension mocks allow mocking the following DOM properties: - `scrollWidth` - `scrollHeight` - `offsetWidth` - `offsetHeight` - `innerWidth` Pass the dimension you want to mock and the value you want returned for all calls when calling `mock`: ```ts import {dimension} from '@shopify/jest-dom-mocks'; beforeEach(() => { dimension.mock({ scrollWidth: 100, offsetHeight: 200, }); }); afterEach(() => dimension.restore()); ``` 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: ```tsx beforeEach(() => { dimension.mock({ scrollWidth(element: HTMLElement) { return element.id === 'test-id' ? 200 : 0; }, }); }); afterEach(() => dimension.restore()); describe('DOM tests', () => { it('returns the element width', () => { function Component() { return <div id="some-id" />; } const element = mount(<Component />); const elementWidth = element.domNode == null ? undefined : element.domNode.scrollWidth; expect(elementWidth).toBe(200); }); }); ```