diff --git a/cleanup-after-each.js b/cleanup-after-each.js new file mode 100644 index 0000000..8f2e439 --- /dev/null +++ b/cleanup-after-each.js @@ -0,0 +1 @@ +afterEach(require('./dist').cleanup); diff --git a/other/cheat-sheet.pdf b/other/cheat-sheet.pdf index 0210ae3..0df2ce2 100644 Binary files a/other/cheat-sheet.pdf and b/other/cheat-sheet.pdf differ diff --git a/package.json b/package.json index 34bb196..d75660c 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "files": [ "dist", "typings", + "cleanup-after-each.js", "jest-preset.js" ], "engines": { diff --git a/src/__tests__/act.js b/src/__tests__/act.js index 84791c7..ac9ce61 100644 --- a/src/__tests__/act.js +++ b/src/__tests__/act.js @@ -2,7 +2,9 @@ import React from 'react'; import 'jest-native/extend-expect'; import { Button } from 'react-native'; -import { render, fireEvent } from '../'; +import { render, fireEvent, cleanup } from '../'; + +afterEach(cleanup); test('render calls useEffect immediately', () => { const effectCb = jest.fn(); diff --git a/src/__tests__/bugs.js b/src/__tests__/bugs.js index 30e61e1..063d862 100644 --- a/src/__tests__/bugs.js +++ b/src/__tests__/bugs.js @@ -1,7 +1,9 @@ import React from 'react'; import { Text, View } from 'react-native'; -import { render, queryAllByProp } from '../'; +import { render, queryAllByProp, cleanup } from '../'; + +afterEach(cleanup); // This is to ensure custom queries can be passed to render. In most cases, you // wouldn't/shouldn't need to do this, but we do allow it so we'll test to diff --git a/src/__tests__/debug.js b/src/__tests__/debug.js index 3220ca4..231154d 100644 --- a/src/__tests__/debug.js +++ b/src/__tests__/debug.js @@ -1,13 +1,14 @@ import React from 'react'; import { Text } from 'react-native'; -import { render } from '../'; +import { cleanup, render } from '../'; beforeEach(() => { jest.spyOn(console, 'log').mockImplementation(() => {}); }); afterEach(() => { + cleanup(); console.log.mockRestore(); }); diff --git a/src/__tests__/end-to-end.js b/src/__tests__/end-to-end.js index 455b06e..5a1b6f8 100644 --- a/src/__tests__/end-to-end.js +++ b/src/__tests__/end-to-end.js @@ -2,7 +2,9 @@ import React from 'react'; import 'jest-native/extend-expect'; import { Text } from 'react-native'; -import { render, wait } from '../'; +import { cleanup, render, wait } from '../'; + +afterEach(cleanup); const fetchAMessage = () => new Promise(resolve => { diff --git a/src/__tests__/events.js b/src/__tests__/events.js index 0f0c21c..5f03421 100644 --- a/src/__tests__/events.js +++ b/src/__tests__/events.js @@ -2,7 +2,9 @@ import React from 'react'; import 'jest-native/extend-expect'; import { Button, Image, Text, TextInput, TouchableHighlight } from 'react-native'; -import { render, fireEvent, eventMap, NativeTestEvent, getEventHandlerName, wait } from '../'; +import { render, fireEvent, eventMap, getEventHandlerName, wait, cleanup } from '../'; + +afterEach(cleanup); Object.keys(eventMap).forEach(key => { describe(`${key} events`, () => { diff --git a/src/__tests__/fetch.js b/src/__tests__/fetch.js index febc65f..c118e02 100644 --- a/src/__tests__/fetch.js +++ b/src/__tests__/fetch.js @@ -2,7 +2,9 @@ import React from 'react'; import 'jest-native/extend-expect'; import { TouchableOpacity, Text, View } from 'react-native'; -import { render, fireEvent, wait } from '../'; +import { render, fireEvent, wait, cleanup } from '../'; + +afterEach(cleanup); global.fetch = require('jest-fetch-mock'); diff --git a/src/__tests__/forms.js b/src/__tests__/forms.js index 4e5b64b..af6641c 100644 --- a/src/__tests__/forms.js +++ b/src/__tests__/forms.js @@ -1,6 +1,8 @@ import React from 'react'; import { Button, TextInput, View } from 'react-native'; -import { render, fireEvent } from '../'; +import { render, fireEvent, cleanup } from '../'; + +afterEach(cleanup); function Login({ onSubmit, user }) { return ( diff --git a/src/__tests__/misc.js b/src/__tests__/misc.js index 385caf7..51191bb 100644 --- a/src/__tests__/misc.js +++ b/src/__tests__/misc.js @@ -2,7 +2,9 @@ import React from 'react'; import { Button, Picker, Text, View } from 'react-native'; import { toMatchDiffSnapshot } from 'snapshot-diff'; -import { fireEvent, render } from '../'; +import { cleanup, fireEvent, render } from '../'; + +afterEach(cleanup); test(' works', () => { function Wrapper() { @@ -32,15 +34,15 @@ test('fragments can show diffs', () => { expect.extend({ toMatchDiffSnapshot }); - const { getByText, asFragment } = render(); - const firstRender = asFragment(); + const { getByText, asJSON } = render(); + const firstRender = asJSON(); fireEvent.press(getByText(/Click to increase/)); // This will snapshot only the difference between the first render, and the // state of the DOM after the click event. // See https://github.com/jest-community/snapshot-diff - expect(firstRender).toMatchDiffSnapshot(asFragment()); + expect(firstRender).toMatchDiffSnapshot(asJSON()); }); test('finds only valid children', () => { diff --git a/src/__tests__/render.js b/src/__tests__/render.js index 6e83a35..fab3b7c 100644 --- a/src/__tests__/render.js +++ b/src/__tests__/render.js @@ -1,7 +1,9 @@ import React from 'react'; import { Text, SafeAreaView, View } from 'react-native'; -import { render, toJSON } from '../'; +import { cleanup, render } from '../'; + +afterEach(cleanup); test('renders View', () => { const { container } = render(); @@ -24,10 +26,10 @@ it('supports fragments', () => { } } - const { asFragment, unmount } = render(); - expect(asFragment()).toMatchSnapshot(); + const { asJSON, unmount } = render(); + expect(asJSON()).toMatchSnapshot(); unmount(); - expect(asFragment()).toBeNull(); + expect(asJSON()).toBeNull(); }); test('renders options.wrapper around node', () => { diff --git a/src/__tests__/rerender.js b/src/__tests__/rerender.js index 6631909..2539528 100644 --- a/src/__tests__/rerender.js +++ b/src/__tests__/rerender.js @@ -2,7 +2,9 @@ import React from 'react'; import 'jest-native/extend-expect'; import { Text } from 'react-native'; -import { render } from '../'; +import { cleanup, render } from '../'; + +afterEach(cleanup); test('rerender will re-render the element', () => { const Greeting = props => {props.message}; diff --git a/src/__tests__/stopwatch.js b/src/__tests__/stopwatch.js index 4b6bbc0..5dd86dc 100644 --- a/src/__tests__/stopwatch.js +++ b/src/__tests__/stopwatch.js @@ -1,7 +1,9 @@ import React from 'react'; import { Button, Text, View } from 'react-native'; -import { render, fireEvent, prettyPrint } from '../'; +import { render, fireEvent, prettyPrint, cleanup } from '../'; + +afterEach(cleanup); class StopWatch extends React.Component { state = { lapse: 0, running: false }; diff --git a/src/index.js b/src/index.js index 2cf70eb..32f1a04 100644 --- a/src/index.js +++ b/src/index.js @@ -8,10 +8,12 @@ import { getQueriesForElement, NativeTestEvent, prettyPrint, - proxyElement as proxy, + proxyElement, } from './lib'; import act from './act-compat'; +const renderers = new Set(); + function render(ui, { options = {}, wrapper: WrapperComponent, queries } = {}) { const wrapUiIfNeeded = innerElement => WrapperComponent ? ( @@ -28,7 +30,9 @@ function render(ui, { options = {}, wrapper: WrapperComponent, queries } = {}) { testRenderer = TR.create(wrapUiIfNeeded(ui), options); }); - const wrappers = proxy(testRenderer.root).findAll(n => n.getProp('pointerEvents') === 'box-none'); + renderers.add(testRenderer); + + const wrappers = proxyElement(testRenderer.root).findAll(n => n.type === 'View'); const baseElement = wrappers[0]; // Includes YellowBox and your render const container = wrappers[1]; // Includes only your render @@ -42,13 +46,22 @@ function render(ui, { options = {}, wrapper: WrapperComponent, queries } = {}) { testRenderer.update(wrapUiIfNeeded(rerenderUi)); }); }, - asFragment: () => { + asJSON: () => { return toJSON(container); }, ...getQueriesForElement(baseElement, queries), }; } +function cleanup() { + renderers.forEach(cleanupRenderer); +} + +function cleanupRenderer(renderer) { + renderer.unmount(); + renderers.delete(renderer); +} + function fireEvent(...args) { let returnValue; act(() => { @@ -68,4 +81,4 @@ Object.keys(rntlFireEvent).forEach(typeArg => { }); export * from './lib'; -export { act, fireEvent, render, NativeTestEvent }; +export { act, cleanup, fireEvent, render, NativeTestEvent }; diff --git a/src/lib/__tests__/get-by-errors.js b/src/lib/__tests__/get-by-errors.js index 458dada..10adff0 100644 --- a/src/lib/__tests__/get-by-errors.js +++ b/src/lib/__tests__/get-by-errors.js @@ -2,7 +2,9 @@ import React from 'react'; import { Button, Text, TextInput, View } from 'react-native'; import cases from 'jest-in-case'; -import { render } from '../../'; +import { cleanup, render } from '../../'; + +afterEach(cleanup); cases( 'getBy* queries throw an error when there are multiple elements returned', diff --git a/src/lib/__tests__/misc.js b/src/lib/__tests__/misc.js index 6d12662..47a41bb 100644 --- a/src/lib/__tests__/misc.js +++ b/src/lib/__tests__/misc.js @@ -1,7 +1,9 @@ import React from 'react'; -import { Text, View } from 'react-native'; +import { View } from 'react-native'; -import { render, queryByProp, queryByTestId } from '../../'; +import { render, queryByProp, queryByTestId, cleanup } from '../../'; + +afterEach(cleanup); test('queryByProp', () => { const { container } = render( diff --git a/src/lib/__tests__/pretty-print.js b/src/lib/__tests__/pretty-print.js index 841ee94..886ca53 100644 --- a/src/lib/__tests__/pretty-print.js +++ b/src/lib/__tests__/pretty-print.js @@ -1,7 +1,9 @@ import React from 'react'; import { Text, View } from 'react-native'; -import { render, prettyPrint } from '../../'; +import { render, prettyPrint, cleanup } from '../../'; + +afterEach(cleanup); test('it prints correctly with no children', () => { const { container } = render(); diff --git a/src/lib/__tests__/queries.find.js b/src/lib/__tests__/queries.find.js index 1d31615..115d5a4 100644 --- a/src/lib/__tests__/queries.find.js +++ b/src/lib/__tests__/queries.find.js @@ -1,7 +1,9 @@ import React from 'react'; import { Button, Image, Text, TextInput, View } from 'react-native'; -import { render } from '../../'; +import { cleanup, render } from '../../'; + +afterEach(cleanup); test('find asynchronously finds elements', async () => { const { diff --git a/src/lib/__tests__/text-matchers.js b/src/lib/__tests__/text-matchers.js index fe41f0d..6413989 100644 --- a/src/lib/__tests__/text-matchers.js +++ b/src/lib/__tests__/text-matchers.js @@ -2,7 +2,9 @@ import React from 'react'; import cases from 'jest-in-case'; import { Button, Image, Text, TextInput, TouchableOpacity } from 'react-native'; -import { getDefaultNormalizer, render } from '../../'; +import { cleanup, getDefaultNormalizer, render } from '../../'; + +afterEach(cleanup); cases( 'matches find case-sensitive full strings by default', diff --git a/src/lib/__tests__/to-json.js b/src/lib/__tests__/to-json.js index ce63d09..cae532e 100644 --- a/src/lib/__tests__/to-json.js +++ b/src/lib/__tests__/to-json.js @@ -1,7 +1,9 @@ import React from 'react'; import { Text, View } from 'react-native'; -import { prettyPrint, render, toJSON } from '../../'; +import { cleanup, prettyPrint, render, toJSON } from '../../'; + +afterEach(cleanup); test('it converts to json', () => { function ParentComponent({ children }) { diff --git a/src/lib/__tests__/wait-for-element-to-be-removed.fake-timers.js b/src/lib/__tests__/wait-for-element-to-be-removed.fake-timers.js index bcc0646..0b8d688 100644 --- a/src/lib/__tests__/wait-for-element-to-be-removed.fake-timers.js +++ b/src/lib/__tests__/wait-for-element-to-be-removed.fake-timers.js @@ -1,7 +1,9 @@ import React from 'react'; import { View } from 'react-native'; -import { render, waitForElementToBeRemoved } from '../../'; +import { cleanup, render, waitForElementToBeRemoved } from '../../'; + +afterEach(cleanup); jest.useFakeTimers(); diff --git a/src/lib/__tests__/wait-for-element-to-be-removed.js b/src/lib/__tests__/wait-for-element-to-be-removed.js index 640ec9b..0536f25 100644 --- a/src/lib/__tests__/wait-for-element-to-be-removed.js +++ b/src/lib/__tests__/wait-for-element-to-be-removed.js @@ -1,7 +1,9 @@ import React from 'react'; import { View } from 'react-native'; -import { render, waitForElementToBeRemoved } from '../../'; +import { cleanup, render, waitForElementToBeRemoved } from '../../'; + +afterEach(cleanup); test('resolves only when the element is removed', async () => { class MutatedElement extends React.Component { diff --git a/src/lib/__tests__/wait-for-element.js b/src/lib/__tests__/wait-for-element.js index 91d362e..baab649 100644 --- a/src/lib/__tests__/wait-for-element.js +++ b/src/lib/__tests__/wait-for-element.js @@ -1,7 +1,9 @@ import React from 'react'; import { Text, View } from 'react-native'; -import { render, waitForElement } from '../../'; +import { cleanup, render, waitForElement } from '../../'; + +afterEach(cleanup); test('waits for element to appear', async () => { const { rerender, getByTestId } = render(); diff --git a/src/lib/__tests__/within.js b/src/lib/__tests__/within.js index adcae0f..55fc563 100644 --- a/src/lib/__tests__/within.js +++ b/src/lib/__tests__/within.js @@ -1,7 +1,9 @@ import React from 'react'; import { Button, View } from 'react-native'; -import { render, within } from '../../'; +import { cleanup, render, within } from '../../'; + +afterEach(cleanup); test('it works when scoping to a smaller set of elements', () => { const { getByTestId } = render( diff --git a/src/preset/configure.js b/src/preset/configure.js index e257c1e..385b308 100644 --- a/src/preset/configure.js +++ b/src/preset/configure.js @@ -1,6 +1,10 @@ import { asyncAct } from '../act-compat'; +import { NativeTestEvent } from '../lib/events'; import { configure as configureNTL } from '../lib'; +// Make this global for convenience, just like browser events +global.NativeTestEvent = NativeTestEvent; + configureNTL({ asyncWrapper: async cb => { let result; diff --git a/typings/index.d.ts b/typings/index.d.ts index f6cf069..9b58eef 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -1,10 +1,11 @@ import { ReactElement, ComponentType } from 'react'; import { ReactTestRenderer } from 'react-test-renderer'; -import { getQueriesForElement, BoundFunction } from './get-queries-for-element'; import * as queries from './queries'; import * as queryHelpers from './query-helpers'; import { NativeTestInstance } from './query-helpers'; +import { NativeTestInstanceJSON } from './to-json'; +import { getQueriesForElement, BoundFunction } from './get-queries-for-element'; declare const within: typeof getQueriesForElement; @@ -25,9 +26,10 @@ interface Queries { export type RenderResult = { baseElement: NativeTestInstance; container: NativeTestInstance; - debug: () => void; + debug: (element?: NativeTestInstance) => void; rerender: (ui: ReactElement) => void; unmount: () => void; + asFragment: () => NativeTestInstanceJSON; } & { [P in keyof Q]: BoundFunction }; export interface RenderOptions { @@ -46,6 +48,8 @@ export function render( options: RenderOptions, ): RenderResult; +export const cleanup: () => void; + export const act: (callback: () => void) => void; export { queries, queryHelpers, within };