From c9c131e5e7348233c3207116f797a4f9b458121d Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Mon, 18 May 2020 13:00:18 +0200 Subject: [PATCH 01/16] fix(breaking): `getByTestId` should work only for native elements (#324) * Replaced getByTestId with fixed version * Added tests * Fixed flow-check * Updated docs * Fixed lint error * Used getByTestId in unit tests instead of queryByTestId * Updated negative checks in tests --- src/__tests__/getByApi.test.js | 37 ++++++++++++++++++++++++++++++++++ src/helpers/findByAPI.js | 4 ++-- src/helpers/getByAPI.js | 9 --------- website/docs/Queries.md | 9 ++------- 4 files changed, 41 insertions(+), 18 deletions(-) create mode 100644 src/__tests__/getByApi.test.js diff --git a/src/__tests__/getByApi.test.js b/src/__tests__/getByApi.test.js new file mode 100644 index 000000000..db66b1c69 --- /dev/null +++ b/src/__tests__/getByApi.test.js @@ -0,0 +1,37 @@ +// @flow +import React from 'react'; +import { View, Text, TextInput, Button } from 'react-native'; +import { render } from '..'; + +const MyComponent = () => { + return My Component; +}; + +test('getByTestId returns only native elements', () => { + const { getByTestId, getAllByTestId } = render( + + Text + + + + @@ -200,12 +200,12 @@ exports[`debug: shallow with message 1`] = ` underlineColorAndroid=\\"transparent\\" value=\\"I inspected freshie\\" /> - + diff --git a/src/__tests__/__snapshots__/shallow.test.js.snap b/src/__tests__/__snapshots__/shallow.test.js.snap deleted file mode 100644 index ac70b9743..000000000 --- a/src/__tests__/__snapshots__/shallow.test.js.snap +++ /dev/null @@ -1,21 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`shallow rendering React Test Instance 1`] = ` - - Press me - -`; - -exports[`shallow rendering React elements 1`] = ` - - - Press me - - -`; diff --git a/src/__tests__/render.test.js b/src/__tests__/render.test.js index 045ae88c4..617cd105b 100644 --- a/src/__tests__/render.test.js +++ b/src/__tests__/render.test.js @@ -17,7 +17,7 @@ const PLACEHOLDER_CHEF = 'Who inspected freshness?'; const INPUT_FRESHNESS = 'Custom Freshie'; const INPUT_CHEF = 'I inspected freshie'; -class Button extends React.Component { +class MyButton extends React.Component { render() { return ( @@ -68,9 +68,9 @@ class Banana extends React.Component { placeholder={PLACEHOLDER_CHEF} value={INPUT_CHEF} /> - + First Text Second Text {test} @@ -109,51 +109,18 @@ test('getAllByTestId, queryAllByTestId', () => { expect(queryAllByTestId('nonExistentTestId')).toHaveLength(0); }); -test('getByName, queryByName', () => { - const { getByText, getByName, queryByName } = render(); - const bananaFresh = getByText('not fresh'); - const button = getByName('Button'); - - button.props.onPress(); - - expect(bananaFresh.props.children).toBe('fresh'); - - const sameButton = getByName(Button); - sameButton.props.onPress(); - - expect(bananaFresh.props.children).toBe('not fresh'); - expect(() => getByName('InExistent')).toThrow('No instances found'); - expect(() => getByName(Text)).toThrow('Expected 1 but found 6'); - - expect(queryByName('Button')).toBe(button); - expect(queryByName('InExistent')).toBeNull(); -}); - -test('getAllByName, queryAllByName', () => { - const { getAllByName, queryAllByName } = render(); - const [text, status, button] = getAllByName('Text'); - - expect(text.props.children).toBe('Is the banana fresh?'); - expect(status.props.children).toBe('not fresh'); - expect(button.props.children).toBe('Change freshness!'); - expect(() => getAllByName('InExistent')).toThrow('No instances found'); - - expect(queryAllByName('Text')[1]).toBe(status); - expect(queryAllByName('InExistent')).toHaveLength(0); -}); - -test('getAllByType, queryAllByType', () => { - const { getAllByType, queryAllByType } = render(); - const [text, status, button] = getAllByType(Text); +test('UNSAFE_getAllByType, UNSAFE_queryAllByType', () => { + const { UNSAFE_getAllByType, UNSAFE_queryAllByType } = render(); + const [text, status, button] = UNSAFE_getAllByType(Text); const InExistent = () => null; expect(text.props.children).toBe('Is the banana fresh?'); expect(status.props.children).toBe('not fresh'); expect(button.props.children).toBe('Change freshness!'); - expect(() => getAllByType(InExistent)).toThrow('No instances found'); + expect(() => UNSAFE_getAllByType(InExistent)).toThrow('No instances found'); - expect(queryAllByType(Text)[1]).toBe(status); - expect(queryAllByType(InExistent)).toHaveLength(0); + expect(UNSAFE_queryAllByType(Text)[1]).toBe(status); + expect(UNSAFE_queryAllByType(InExistent)).toHaveLength(0); }); test('getByText, queryByText', () => { @@ -267,38 +234,37 @@ test('getAllByDisplayValue, queryAllByDisplayValue', () => { expect(queryAllByDisplayValue('no value')).toHaveLength(0); }); -test('getByProps, queryByProps', () => { - const { getByProps, queryByProps } = render(); - const primaryType = getByProps({ type: 'primary' }); +test('UNSAFE_getByProps, UNSAFE_queryByProps', () => { + const { UNSAFE_getByProps, UNSAFE_queryByProps } = render(); + const primaryType = UNSAFE_getByProps({ type: 'primary' }); expect(primaryType.props.children).toBe('Change freshness!'); - expect(() => getByProps({ type: 'inexistent' })).toThrow( + expect(() => UNSAFE_getByProps({ type: 'inexistent' })).toThrow( 'No instances found' ); - expect(queryByProps({ type: 'primary' })).toBe(primaryType); - expect(queryByProps({ type: 'inexistent' })).toBeNull(); + expect(UNSAFE_queryByProps({ type: 'primary' })).toBe(primaryType); + expect(UNSAFE_queryByProps({ type: 'inexistent' })).toBeNull(); }); -test('getAllByProp, queryAllByProps', () => { - const { getAllByProps, queryAllByProps } = render(); - const primaryTypes = getAllByProps({ type: 'primary' }); +test('UNSAFE_getAllByProp, UNSAFE_queryAllByProps', () => { + const { UNSAFE_getAllByProps, UNSAFE_queryAllByProps } = render(); + const primaryTypes = UNSAFE_getAllByProps({ type: 'primary' }); expect(primaryTypes).toHaveLength(1); - expect(() => getAllByProps({ type: 'inexistent' })).toThrow( + expect(() => UNSAFE_getAllByProps({ type: 'inexistent' })).toThrow( 'No instances found' ); - expect(queryAllByProps({ type: 'primary' })).toEqual(primaryTypes); - expect(queryAllByProps({ type: 'inexistent' })).toHaveLength(0); + expect(UNSAFE_queryAllByProps({ type: 'primary' })).toEqual(primaryTypes); + expect(UNSAFE_queryAllByProps({ type: 'inexistent' })).toHaveLength(0); }); test('update', () => { const fn = jest.fn(); - const { getByName, update, rerender } = render(); - const button = getByName('Button'); + const { getByText, update, rerender } = render(); - button.props.onPress(); + fireEvent.press(getByText('Change freshness!')); update(); rerender(); @@ -314,7 +280,7 @@ test('unmount', () => { }); test('toJSON', () => { - const { toJSON } = render(); + const { toJSON } = render(press me); expect(toJSON()).toMatchSnapshot(); }); @@ -345,9 +311,9 @@ test('debug', () => { test('debug changing component', () => { jest.spyOn(console, 'log').mockImplementation((x) => x); - const { getByProps, debug } = render(); + const { UNSAFE_getByProps, debug } = render(); - fireEvent.press(getByProps({ type: 'primary' })); + fireEvent.press(UNSAFE_getByProps({ type: 'primary' })); debug(); diff --git a/src/__tests__/shallow.test.js b/src/__tests__/shallow.test.js deleted file mode 100644 index a31af6c12..000000000 --- a/src/__tests__/shallow.test.js +++ /dev/null @@ -1,27 +0,0 @@ -// @flow -import React from 'react'; -import { View, Text } from 'react-native'; -import { shallow, render } from '..'; - -type Props = {| - +dummyID?: string, -|}; - -const TextPress = ({ dummyID }: Props) => ( - - Press me - -); - -test('shallow rendering React elements', () => { - const { output } = shallow(); - - expect(output).toMatchSnapshot(); -}); - -test('shallow rendering React Test Instance', () => { - const { getByText } = render(); - const { output } = shallow(getByText('Press me')); - - expect(output).toMatchSnapshot(); -}); diff --git a/src/__tests__/waitFor.test.js b/src/__tests__/waitFor.test.js index b05f1fe93..891c8457c 100644 --- a/src/__tests__/waitFor.test.js +++ b/src/__tests__/waitFor.test.js @@ -36,9 +36,9 @@ class BananaContainer extends React.Component<{}, any> { } test('waits for element until it stops throwing', async () => { - const { getByText, getByName, queryByText } = render(); + const { getByText, queryByText } = render(); - fireEvent.press(getByName('TouchableOpacity')); + fireEvent.press(getByText('Change freshness!')); expect(queryByText('Fresh')).toBeNull(); @@ -48,9 +48,9 @@ test('waits for element until it stops throwing', async () => { }); test('waits for element until timeout is met', async () => { - const { getByText, getByName } = render(); + const { getByText } = render(); - fireEvent.press(getByName('TouchableOpacity')); + fireEvent.press(getByText('Change freshness!')); await expect( waitFor(() => getByText('Fresh'), { timeout: 100 }) diff --git a/src/helpers/errors.js b/src/helpers/errors.js index edf108cbc..17a31de83 100644 --- a/src/helpers/errors.js +++ b/src/helpers/errors.js @@ -24,22 +24,7 @@ export const createQueryByError = (error: Error, callsite: Function) => { throw new ErrorWithStack(error.message, callsite); }; -const warned = { - getByName: false, - getAllByName: false, - queryByName: false, - queryAllByName: false, - - getByProps: false, - getAllByProps: false, - queryByProps: false, - queryAllByProps: false, - - getByType: false, - getAllByType: false, - queryByType: false, - queryAllByType: false, -}; +const warned = {}; export function printDeprecationWarning(functionName: string) { if (warned[functionName]) { @@ -67,3 +52,12 @@ export function printUnsafeWarning(functionName: string) { warned[functionName] = true; } + +export function throwRemovedFunctionError( + functionName: string, + docsRef: string +) { + throw new Error( + `${functionName} has been removed in version 2.0.\n\nPlease consult: https://callstack.github.io/react-native-testing-library/docs/${docsRef}` + ); +} diff --git a/src/helpers/getByAPI.js b/src/helpers/getByAPI.js index cc6079607..ea2342655 100644 --- a/src/helpers/getByAPI.js +++ b/src/helpers/getByAPI.js @@ -5,16 +5,11 @@ import { ErrorWithStack, createLibraryNotSupportedError, prepareErrorMessage, - printDeprecationWarning, - printUnsafeWarning, + throwRemovedFunctionError, } from './errors'; const filterNodeByType = (node, type) => node.type === type; -const filterNodeByName = (node, name) => - typeof node.type !== 'string' && - (node.type.displayName === name || node.type.name === name); - const getNodeByText = (node, text) => { try { // eslint-disable-next-line @@ -85,28 +80,6 @@ const getTextInputNodeByDisplayValue = (node, value) => { } }; -export const getByName = (instance: ReactTestInstance, warnFn?: Function) => - function getByNameFn(name: string | React.ComponentType) { - warnFn && warnFn('getByName'); - try { - return typeof name === 'string' - ? instance.find((node) => filterNodeByName(node, name)) - : instance.findByType(name); - } catch (error) { - throw new ErrorWithStack(prepareErrorMessage(error), getByNameFn); - } - }; - -export const getByType = (instance: ReactTestInstance, warnFn?: Function) => - function getByTypeFn(type: React.ComponentType) { - warnFn && warnFn('getByType'); - try { - return instance.findByType(type); - } catch (error) { - throw new ErrorWithStack(prepareErrorMessage(error), getByTypeFn); - } - }; - export const getByText = (instance: ReactTestInstance) => function getByTextFn(text: string | RegExp) { try { @@ -138,16 +111,6 @@ export const getByDisplayValue = (instance: ReactTestInstance) => } }; -export const getByProps = (instance: ReactTestInstance, warnFn?: Function) => - function getByPropsFn(props: { [propName: string]: any }) { - warnFn && warnFn('getByProps'); - try { - return instance.findByProps(props); - } catch (error) { - throw new ErrorWithStack(prepareErrorMessage(error), getByPropsFn); - } - }; - export const getByTestId = (instance: ReactTestInstance) => function getByTestIdFn(testID: string) { try { @@ -167,29 +130,6 @@ export const getByTestId = (instance: ReactTestInstance) => } }; -export const getAllByName = (instance: ReactTestInstance, warnFn?: Function) => - function getAllByNameFn(name: string | React.ComponentType) { - warnFn && warnFn('getAllByName'); - const results = - typeof name === 'string' - ? instance.findAll((node) => filterNodeByName(node, name)) - : instance.findAllByType(name); - if (results.length === 0) { - throw new ErrorWithStack('No instances found', getAllByNameFn); - } - return results; - }; - -export const getAllByType = (instance: ReactTestInstance, warnFn?: Function) => - function getAllByTypeFn(type: React.ComponentType) { - warnFn && warnFn('getAllByType'); - const results = instance.findAllByType(type); - if (results.length === 0) { - throw new ErrorWithStack('No instances found', getAllByTypeFn); - } - return results; - }; - export const getAllByText = (instance: ReactTestInstance) => function getAllByTextFn(text: string | RegExp) { const results = instance.findAll((node) => getNodeByText(node, text)); @@ -230,19 +170,6 @@ export const getAllByDisplayValue = (instance: ReactTestInstance) => return results; }; -export const getAllByProps = (instance: ReactTestInstance, warnFn?: Function) => - function getAllByPropsFn(props: { [propName: string]: any }) { - warnFn && warnFn('getAllByProps'); - const results = instance.findAllByProps(props); - if (results.length === 0) { - throw new ErrorWithStack( - `No instances found with props:\n${prettyFormat(props)}`, - getAllByPropsFn - ); - } - return results; - }; - export const getAllByTestId = (instance: ReactTestInstance) => function getAllByTestIdFn(testID: string): ReactTestInstance[] { const results = instance @@ -258,25 +185,75 @@ export const getAllByTestId = (instance: ReactTestInstance) => return results; }; +export const UNSAFE_getByType = (instance: ReactTestInstance) => + function getByTypeFn(type: React.ComponentType) { + try { + return instance.findByType(type); + } catch (error) { + throw new ErrorWithStack(prepareErrorMessage(error), getByTypeFn); + } + }; + +export const UNSAFE_getByProps = (instance: ReactTestInstance) => + function getByPropsFn(props: { [propName: string]: any }) { + try { + return instance.findByProps(props); + } catch (error) { + throw new ErrorWithStack(prepareErrorMessage(error), getByPropsFn); + } + }; + +export const UNSAFE_getAllByType = (instance: ReactTestInstance) => + function getAllByTypeFn(type: React.ComponentType) { + const results = instance.findAllByType(type); + if (results.length === 0) { + throw new ErrorWithStack('No instances found', getAllByTypeFn); + } + return results; + }; + +export const UNSAFE_getAllByProps = (instance: ReactTestInstance) => + function getAllByPropsFn(props: { [propName: string]: any }) { + const results = instance.findAllByProps(props); + if (results.length === 0) { + throw new ErrorWithStack( + `No instances found with props:\n${prettyFormat(props)}`, + getAllByPropsFn + ); + } + return results; + }; + export const getByAPI = (instance: ReactTestInstance) => ({ - getByTestId: getByTestId(instance), - getByName: getByName(instance, printDeprecationWarning), - getByType: getByType(instance, printUnsafeWarning), getByText: getByText(instance), getByPlaceholder: getByPlaceholder(instance), getByDisplayValue: getByDisplayValue(instance), - getByProps: getByProps(instance, printUnsafeWarning), - getAllByTestId: getAllByTestId(instance), - getAllByName: getAllByName(instance, printDeprecationWarning), - getAllByType: getAllByType(instance, printUnsafeWarning), + getByTestId: getByTestId(instance), getAllByText: getAllByText(instance), getAllByPlaceholder: getAllByPlaceholder(instance), getAllByDisplayValue: getAllByDisplayValue(instance), - getAllByProps: getAllByProps(instance, printUnsafeWarning), + getAllByTestId: getAllByTestId(instance), + + // Unsafe + UNSAFE_getByType: UNSAFE_getByType(instance), + UNSAFE_getAllByType: UNSAFE_getAllByType(instance), + UNSAFE_getByProps: UNSAFE_getByProps(instance), + UNSAFE_getAllByProps: UNSAFE_getAllByProps(instance), - // Unsafe aliases - UNSAFE_getByType: getByType(instance), - UNSAFE_getAllByType: getAllByType(instance), - UNSAFE_getByProps: getByProps(instance), - UNSAFE_getAllByProps: getAllByProps(instance), + // Removed + getByName: () => + throwRemovedFunctionError('getByName', 'migration-v2#removed-functions'), + getAllByName: () => + throwRemovedFunctionError('getAllByName', 'migration-v2#removed-functions'), + getByType: () => + throwRemovedFunctionError('getByType', 'migration-v2#removed-functions'), + getAllByType: () => + throwRemovedFunctionError('getAllByType', 'migration-v2#removed-functions'), + getByProps: () => + throwRemovedFunctionError('getByProps', 'migration-v2#removed-functions'), + getAllByProps: () => + throwRemovedFunctionError( + 'getAllByProps', + 'migration-v2#removed-functions' + ), }); diff --git a/src/helpers/queryByAPI.js b/src/helpers/queryByAPI.js index e2c06b0a0..5566e3e4d 100644 --- a/src/helpers/queryByAPI.js +++ b/src/helpers/queryByAPI.js @@ -2,45 +2,19 @@ import * as React from 'react'; import { getByTestId, - getByName, - getByType, getByText, getByPlaceholder, getByDisplayValue, - getByProps, getAllByTestId, - getAllByName, - getAllByType, getAllByText, getAllByPlaceholder, getAllByDisplayValue, - getAllByProps, + UNSAFE_getByType, + UNSAFE_getByProps, + UNSAFE_getAllByType, + UNSAFE_getAllByProps, } from './getByAPI'; -import { - createQueryByError, - printDeprecationWarning, - printUnsafeWarning, -} from './errors'; - -export const queryByName = (instance: ReactTestInstance, warnFn?: Function) => - function queryByNameFn(name: string | React.ComponentType) { - warnFn && warnFn('queryByName'); - try { - return getByName(instance)(name); - } catch (error) { - return createQueryByError(error, queryByNameFn); - } - }; - -export const queryByType = (instance: ReactTestInstance, warnFn?: Function) => - function queryByTypeFn(type: React.ComponentType) { - warnFn && warnFn('queryByType'); - try { - return getByType(instance)(type); - } catch (error) { - return createQueryByError(error, queryByTypeFn); - } - }; +import { createQueryByError, throwRemovedFunctionError } from './errors'; export const queryByText = (instance: ReactTestInstance) => function queryByTextFn(text: string | RegExp) { @@ -69,16 +43,6 @@ export const queryByDisplayValue = (instance: ReactTestInstance) => } }; -export const queryByProps = (instance: ReactTestInstance, warnFn?: Function) => - function queryByPropsFn(props: { [propName: string]: any }) { - warnFn && warnFn('queryByProps'); - try { - return getByProps(instance)(props); - } catch (error) { - return createQueryByError(error, queryByPropsFn); - } - }; - export const queryByTestId = (instance: ReactTestInstance) => function queryByTestIdFn(testID: string) { try { @@ -88,30 +52,6 @@ export const queryByTestId = (instance: ReactTestInstance) => } }; -export const queryAllByName = ( - instance: ReactTestInstance, - warnFn?: Function -) => (name: string | React.ComponentType) => { - warnFn && warnFn('queryAllByName'); - try { - return getAllByName(instance)(name); - } catch (error) { - return []; - } -}; - -export const queryAllByType = ( - instance: ReactTestInstance, - warnFn?: Function -) => (type: React.ComponentType) => { - warnFn && warnFn('queryAllByType'); - try { - return getAllByType(instance)(type); - } catch (error) { - return []; - } -}; - export const queryAllByText = (instance: ReactTestInstance) => ( text: string | RegExp ) => { @@ -142,23 +82,49 @@ export const queryAllByDisplayValue = (instance: ReactTestInstance) => ( } }; -export const queryAllByProps = ( - instance: ReactTestInstance, - warnFn?: Function -) => (props: { [propName: string]: any }) => { - warnFn && warnFn('queryAllByProps'); +export const queryAllByTestId = (instance: ReactTestInstance) => ( + testID: string +) => { try { - return getAllByProps(instance)(props); + return getAllByTestId(instance)(testID); } catch (error) { return []; } }; -export const queryAllByTestId = (instance: ReactTestInstance) => ( - testID: string +export const UNSAFE_queryByType = (instance: ReactTestInstance) => + function queryByTypeFn(type: React.ComponentType) { + try { + return UNSAFE_getByType(instance)(type); + } catch (error) { + return createQueryByError(error, queryByTypeFn); + } + }; + +export const UNSAFE_queryByProps = (instance: ReactTestInstance) => + function queryByPropsFn(props: { [propName: string]: any }) { + try { + return UNSAFE_getByProps(instance)(props); + } catch (error) { + return createQueryByError(error, queryByPropsFn); + } + }; + +export const UNSAFE_queryAllByType = (instance: ReactTestInstance) => ( + type: React.ComponentType ) => { try { - return getAllByTestId(instance)(testID); + return UNSAFE_getAllByType(instance)(type); + } catch (error) { + return []; + } +}; + +export const UNSAFE_queryAllByProps = (instance: ReactTestInstance) => (props: { + [propName: string]: any, +}) => { + try { + return UNSAFE_getAllByProps(instance)(props); } catch (error) { return []; } @@ -166,23 +132,40 @@ export const queryAllByTestId = (instance: ReactTestInstance) => ( export const queryByAPI = (instance: ReactTestInstance) => ({ queryByTestId: queryByTestId(instance), - queryByName: queryByName(instance, printDeprecationWarning), - queryByType: queryByType(instance, printUnsafeWarning), queryByText: queryByText(instance), queryByPlaceholder: queryByPlaceholder(instance), queryByDisplayValue: queryByDisplayValue(instance), - queryByProps: queryByProps(instance, printUnsafeWarning), queryAllByTestId: queryAllByTestId(instance), - queryAllByName: queryAllByName(instance, printDeprecationWarning), - queryAllByType: queryAllByType(instance, printUnsafeWarning), queryAllByText: queryAllByText(instance), queryAllByPlaceholder: queryAllByPlaceholder(instance), queryAllByDisplayValue: queryAllByDisplayValue(instance), - queryAllByProps: queryAllByProps(instance, printUnsafeWarning), - // Unsafe aliases - UNSAFE_queryByType: queryByType(instance), - UNSAFE_queryAllByType: queryAllByType(instance), - UNSAFE_queryByProps: queryByProps(instance), - UNSAFE_queryAllByProps: queryAllByProps(instance), + // Unsafe + UNSAFE_queryByType: UNSAFE_queryByType(instance), + UNSAFE_queryAllByType: UNSAFE_queryAllByType(instance), + UNSAFE_queryByProps: UNSAFE_queryByProps(instance), + UNSAFE_queryAllByProps: UNSAFE_queryAllByProps(instance), + + // Removed + queryByName: () => + throwRemovedFunctionError('queryByName', 'migration-v2#removed-functions'), + queryAllByName: () => + throwRemovedFunctionError( + 'queryAllByName', + 'migration-v2#removed-functions' + ), + queryByType: () => + throwRemovedFunctionError('queryByType', 'migration-v2#removed-functions'), + queryAllByType: () => + throwRemovedFunctionError( + 'queryAllByType', + 'migration-v2#removed-functions' + ), + queryByProps: () => + throwRemovedFunctionError('queryByProps', 'migration-v2#removed-functions'), + queryAllByProps: () => + throwRemovedFunctionError( + 'queryAllByProps', + 'migration-v2#removed-functions' + ), }); diff --git a/src/index.js b/src/index.js index 8500b3b26..534119714 100644 --- a/src/index.js +++ b/src/index.js @@ -5,7 +5,7 @@ import fireEvent from './fireEvent'; import flushMicrotasksQueue from './flushMicrotasksQueue'; import render from './render'; import shallow from './shallow'; -import waitFor from './waitFor'; +import waitFor, { waitForElement } from './waitFor'; import within from './within'; export { act }; @@ -14,5 +14,5 @@ export { fireEvent }; export { flushMicrotasksQueue }; export { render }; export { shallow }; -export { waitFor }; +export { waitFor, waitForElement }; export { within }; diff --git a/src/shallow.js b/src/shallow.js index 576ac913a..828f4dd61 100644 --- a/src/shallow.js +++ b/src/shallow.js @@ -1,7 +1,7 @@ // @flow import * as React from 'react'; import ShallowRenderer from 'react-test-renderer/shallow'; // eslint-disable-line import/no-extraneous-dependencies -import { printDeprecationWarning } from './helpers/errors'; +import { throwRemovedFunctionError } from './helpers/errors'; /** * Renders test component shallowly using react-test-renderer/shallow @@ -18,10 +18,9 @@ export function shallowInternal( }; } -export default function shallow( - instance: ReactTestInstance | React.Element -) { - printDeprecationWarning('shallow'); - - return shallowInternal(instance); +export default function shallow(_: ReactTestInstance | React.Element) { + throwRemovedFunctionError( + 'shallow', + 'migration-v2#removed-global-shallow-function' + ); } diff --git a/src/waitFor.js b/src/waitFor.js index e947f8631..5275643e5 100644 --- a/src/waitFor.js +++ b/src/waitFor.js @@ -1,5 +1,7 @@ // @flow +import { throwRemovedFunctionError } from './helpers/errors'; + const DEFAULT_TIMEOUT = 4500; const DEFAULT_INTERVAL = 50; @@ -35,3 +37,16 @@ export default function waitFor( setTimeout(runExpectation, 0); }); } + +export function waitForElement( + expectation: () => T, + _timeout: number = 4500, + _interval: number = 50 +): Promise { + throwRemovedFunctionError( + 'waitForElement', + 'migration-v2#waitfor-api-changes' + ); + + return Promise.reject(); +} diff --git a/typings/__tests__/index.test.tsx b/typings/__tests__/index.test.tsx index 2417ae8eb..9981ded7e 100644 --- a/typings/__tests__/index.test.tsx +++ b/typings/__tests__/index.test.tsx @@ -15,9 +15,9 @@ interface HasRequiredProp { requiredProp: string; } -const View = props => props.children; -const Text = props => props.children; -const TextInput = props => props.children; +const View = (props) => props.children; +const Text = (props) => props.children; +const TextInput = (props) => props.children; const ElementWithRequiredProps = (props: HasRequiredProp) => ( {props.requiredProp} ); @@ -32,10 +32,8 @@ const TestComponent = () => ( const tree = render(); // getByAPI tests -const getByNameString: ReactTestInstance = tree.getByName('View'); -const getByNameContructor: ReactTestInstance = tree.getByName(View); -const getByType: ReactTestInstance = tree.getByType(View); -const getByTypeWithRequiredProps: ReactTestInstance = tree.getByType( +const getByType: ReactTestInstance = tree.UNSAFE_getByType(View); +const getByTypeWithRequiredProps: ReactTestInstance = tree.UNSAFE_getByType( ElementWithRequiredProps ); const getByTextString: ReactTestInstance = tree.getByText(''); @@ -52,30 +50,24 @@ const getByDisplayValueString: ReactTestInstance = tree.getByDisplayValue( const getByDisplayValueRegExp: ReactTestInstance = tree.getByDisplayValue( /value/g ); -const getByProps: ReactTestInstance = tree.getByProps({ value: 2 }); +const getByProps: ReactTestInstance = tree.UNSAFE_getByProps({ value: 2 }); const getByTestId: ReactTestInstance = tree.getByTestId('test-id'); const getAllByTestId: ReactTestInstance[] = tree.getAllByTestId('test-id'); -const getAllByNameString: Array = tree.getAllByName('View'); -const getAllByNameConstructor: Array = tree.getAllByName( - View +const getAllByType: Array = tree.UNSAFE_getAllByType(View); +const getAllByTypeWithRequiredProps: Array = tree.UNSAFE_getAllByType( + ElementWithRequiredProps ); -const getAllByType: Array = tree.getAllByType(View); -const getAllByTypeWithRequiredProps: Array< - ReactTestInstance -> = tree.getAllByType(ElementWithRequiredProps); const getAllByTextString: Array = tree.getAllByText( '' ); const getAllByTextRegExp: Array = tree.getAllByText(/Text/g); -const getAllByProps: Array = tree.getAllByProps({ +const getAllByProps: Array = tree.UNSAFE_getAllByProps({ value: 2, }); // queuryByAPI tests -const queryByNameString: ReactTestInstance | null = tree.queryByName('View'); -const queryByNameConstructor: ReactTestInstance | null = tree.queryByName(View); -const queryByType: ReactTestInstance | null = tree.queryByType(View); -const queryByTypeWithRequiredProps: ReactTestInstance | null = tree.queryByType( +const queryByType: ReactTestInstance | null = tree.UNSAFE_queryByType(View); +const queryByTypeWithRequiredProps: ReactTestInstance | null = tree.UNSAFE_queryByType( ElementWithRequiredProps ); const queryByTextString: ReactTestInstance | null = tree.queryByText('View'); @@ -92,33 +84,31 @@ const queryByDisplayValueString: ReactTestInstance | null = tree.queryByDisplayV const queryByDisplayValueRegExp: ReactTestInstance | null = tree.queryByDisplayValue( /value/g ); -const queryByProps: ReactTestInstance | null = tree.queryByProps({ value: 2 }); +const queryByProps: ReactTestInstance | null = tree.UNSAFE_queryByProps({ + value: 2, +}); const queryByTestId: ReactTestInstance | null = tree.queryByTestId('test-id'); const queryAllByTestId: ReactTestInstance[] | null = tree.queryAllByTestId( 'test-id' ); -const queryAllByNameString: Array = tree.queryAllByName( - 'View' -); -const queryAllByNameConstructor: Array = tree.queryAllByName( +const queryAllByType: Array = tree.UNSAFE_queryAllByType( View ); -const queryAllByType: Array = tree.queryAllByType(View); -const queryAllByTypeWithRequiredProps: Array< - ReactTestInstance -> = tree.queryAllByType(ElementWithRequiredProps); +const queryAllByTypeWithRequiredProps: Array = tree.UNSAFE_queryAllByType( + ElementWithRequiredProps +); const queryAllByTextString: Array = tree.queryAllByText( 'View' ); const queryAllByTextRegExp: Array = tree.queryAllByText( /View/g ); -const queryAllByDisplayValueString: Array< - ReactTestInstance -> = tree.queryAllByDisplayValue('View'); -const queryAllByDisplayValueRegExp: Array< - ReactTestInstance -> = tree.queryAllByDisplayValue(/View/g); +const queryAllByDisplayValueString: Array = tree.queryAllByDisplayValue( + 'View' +); +const queryAllByDisplayValueRegExp: Array = tree.queryAllByDisplayValue( + /View/g +); // findBy API tests const findBy: Promise[] = [ @@ -200,33 +190,45 @@ const queryAllByA11yRole: Array = tree.queryAllByA11yRole( ); const getByA11yStates: ReactTestInstance = tree.getByA11yStates('selected'); -const getByA11yStatesArray: ReactTestInstance = tree.getByA11yStates(['selected']); +const getByA11yStatesArray: ReactTestInstance = tree.getByA11yStates([ + 'selected', +]); const getAllByA11yStates: Array = tree.getAllByA11yStates( 'selected' ); -const getAllByA11yStatesArray: Array< - ReactTestInstance -> = tree.getAllByA11yStates(['selected']); +const getAllByA11yStatesArray: Array = tree.getAllByA11yStates( + ['selected'] +); const queryByA11yStates: ReactTestInstance = tree.queryByA11yStates('selected'); const queryByA11yStatesArray: ReactTestInstance = tree.queryByA11yStates([ 'selected', ]); -const queryAllByA11yStates: Array< - ReactTestInstance -> = tree.queryAllByA11yStates('selected'); -const queryAllByA11yStatesArray: Array< - ReactTestInstance -> = tree.queryAllByA11yStates(['selected']); +const queryAllByA11yStates: Array = tree.queryAllByA11yStates( + 'selected' +); +const queryAllByA11yStatesArray: Array = tree.queryAllByA11yStates( + ['selected'] +); const getByA11yState: ReactTestInstance = tree.getByA11yState({ busy: true }); -const getAllByA11yState: Array = tree.getAllByA11yState({ busy: true }); -const queryByA11yState: ReactTestInstance = tree.queryByA11yState({ busy: true }); -const queryAllByA11yState: Array = tree.queryAllByA11yState({ busy: true }); +const getAllByA11yState: Array = tree.getAllByA11yState({ + busy: true, +}); +const queryByA11yState: ReactTestInstance = tree.queryByA11yState({ + busy: true, +}); +const queryAllByA11yState: Array = tree.queryAllByA11yState({ + busy: true, +}); const getByA11yValue: ReactTestInstance = tree.getByA11yValue({ min: 10 }); -const getAllByA11yValue: Array = tree.getAllByA11yValue({ min: 10 }); +const getAllByA11yValue: Array = tree.getAllByA11yValue({ + min: 10, +}); const queryByA11yValue: ReactTestInstance = tree.queryByA11yValue({ min: 10 }); -const queryAllByA11yValue: Array = tree.queryAllByA11yValue({ min: 10 }); +const queryAllByA11yValue: Array = tree.queryAllByA11yValue({ + min: 10, +}); const debugFn = tree.debug(); const debugFnWithMessage = tree.debug('my message'); @@ -236,12 +238,12 @@ tree.rerender(); tree.unmount(); // fireEvent API tests -fireEvent(getByNameString, 'press'); -fireEvent(getByNameString, 'press', 'data'); -fireEvent(getByNameString, 'press', 'param1', 'param2'); -fireEvent.press(getByNameString); -fireEvent.changeText(getByNameString, 'string'); -fireEvent.scroll(getByNameString, 'eventData'); +fireEvent(getByA11yLabel, 'press'); +fireEvent(getByA11yLabel, 'press', 'data'); +fireEvent(getByA11yLabel, 'press', 'param1', 'param2'); +fireEvent.press(getByA11yLabel); +fireEvent.changeText(getByA11yLabel, 'string'); +fireEvent.scroll(getByA11yLabel, 'eventData'); // shallow API const shallowTree: { output: React.ReactElement } = shallow( @@ -251,10 +253,10 @@ const shallowTree: { output: React.ReactElement } = shallow( const waitForFlush: Promise = flushMicrotasksQueue(); const waitBy: Promise = waitFor(() => - tree.getByName('View') + tree.getByA11yLabel('label') ); const waitByAll: Promise = waitFor( - () => tree.getAllByName('View'), + () => tree.getAllByA11yLabel('label'), { timeout: 1000, interval: 50 } ); diff --git a/typings/index.d.ts b/typings/index.d.ts index 4fd91e76d..d38f9c0c6 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -1,5 +1,9 @@ import * as React from 'react'; -import { AccessibilityState, AccessibilityStates, AccessibilityRole } from 'react-native'; +import { + AccessibilityState, + AccessibilityStates, + AccessibilityRole, +} from 'react-native'; import { ReactTestInstance, ReactTestRendererJSON } from 'react-test-renderer'; type GetReturn = ReactTestInstance; @@ -10,48 +14,62 @@ type FindReturn = Promise; type FindAllReturn = Promise; interface GetByAPI { - getByName: (name: React.ReactType | string) => ReactTestInstance; - getByType:

(type: React.ComponentType

) => ReactTestInstance; getByText: (text: string | RegExp) => ReactTestInstance; getByPlaceholder: (placeholder: string | RegExp) => ReactTestInstance; getByDisplayValue: (value: string | RegExp) => ReactTestInstance; - getByProps: (props: Record) => ReactTestInstance; getByTestId: (testID: string) => ReactTestInstance; getAllByTestId: (testID: string) => Array; - getAllByName: (name: React.ReactType | string) => Array; - getAllByType:

(type: React.ComponentType

) => Array; getAllByText: (text: string | RegExp) => Array; getAllByPlaceholder: ( placeholder: string | RegExp ) => Array; getAllByDisplayValue: (value: string | RegExp) => Array; - getAllByProps: (props: Record) => Array; - // Unsafe aliases - UNSAFE_getByType:

(type: React.ComponentType

) => ReactTestInstance, - UNSAFE_getAllByType:

(type: React.ComponentType

) => Array, - UNSAFE_getByProps: (props: Record) => ReactTestInstance, - UNSAFE_getAllByProps: (props: Record) => Array, + UNSAFE_getByType:

(type: React.ComponentType

) => ReactTestInstance; + UNSAFE_getAllByType:

( + type: React.ComponentType

+ ) => Array; + UNSAFE_getByProps: (props: Record) => ReactTestInstance; + UNSAFE_getAllByProps: ( + props: Record + ) => Array; + + // Removed + /** + * @deprecated This function has been removed. Please use other queries. + */ + getByName: (name: React.ReactType | string) => ReactTestInstance; + /** + * @deprecated This function has been renamed to `UNSAFE_getByType`. + */ + getByType:

(type: React.ComponentType

) => ReactTestInstance; + /** + * @deprecated This function has been renamed to `UNSAFE_getByProps`. + */ + getByProps: (props: Record) => ReactTestInstance; + /** + * @deprecated This function has been removed. Please use other queries. + */ + getAllByName: (name: React.ReactType | string) => Array; + /** + * @deprecated This function has been renamed to `UNSAFE_getAllByType`. + */ + getAllByType:

(type: React.ComponentType

) => Array; + /** + * @deprecated This function has been renamed to `UNSAFE_getAllByProps`. + */ + getAllByProps: (props: Record) => Array; } interface QueryByAPI { - queryByName: (name: React.ReactType | string) => ReactTestInstance | null; - queryByType:

(type: React.ComponentType

) => ReactTestInstance | null; queryByText: (name: string | RegExp) => ReactTestInstance | null; queryByPlaceholder: ( placeholder: string | RegExp ) => ReactTestInstance | null; queryByDisplayValue: (value: string | RegExp) => ReactTestInstance | null; - queryByProps: (props: Record) => ReactTestInstance | null; queryByTestId: (testID: string) => ReactTestInstance | null; queryAllByTestId: (testID: string) => Array | null; - queryAllByName: ( - name: React.ReactType | string - ) => Array | []; - queryAllByType:

( - type: React.ComponentType

- ) => Array | []; queryAllByText: (text: string | RegExp) => Array | []; queryAllByPlaceholder: ( placeholder: string | RegExp @@ -59,20 +77,50 @@ interface QueryByAPI { queryAllByDisplayValue: ( value: string | RegExp ) => Array | []; - queryAllByProps: ( - props: Record - ) => Array | []; // Unsafe aliases - UNSAFE_queryByType:

(type: React.ComponentType

) => ReactTestInstance | null, - UNSAFE_queryAllByType:

(type: React.ComponentType

) => Array | [], - UNSAFE_queryByProps: (props: Record) => ReactTestInstance | null, - UNSAFE_queryAllByProps: (props: Record) => Array | [], -} + UNSAFE_queryByType:

( + type: React.ComponentType

+ ) => ReactTestInstance | null; + UNSAFE_queryAllByType:

( + type: React.ComponentType

+ ) => Array | []; + UNSAFE_queryByProps: (props: Record) => ReactTestInstance | null; + UNSAFE_queryAllByProps: ( + props: Record + ) => Array | []; -export interface WaitForOptions { - timeout: number; - interval: number; + // Removed + /** + * @deprecated This function has been removed. Please use other queries. + */ + queryByName: (name: React.ReactType | string) => ReactTestInstance | null; + /** + * @deprecated This function has been renamed to `UNSAFE_queryByType`. + */ + queryByType:

(type: React.ComponentType

) => ReactTestInstance | null; + /** + * @deprecated This function has been renamed to `UNSAFE_queryByProps`. + */ + queryByProps: (props: Record) => ReactTestInstance | null; + /** + * @deprecated This function has been removed. Please use other queries. + */ + queryAllByName: ( + name: React.ReactType | string + ) => Array | []; + /** + * @deprecated This function has been renamed to `UNSAFE_queryAllByType`. + */ + queryAllByType:

( + type: React.ComponentType

+ ) => Array | []; + /** + * @deprecated This function has been renamed to `UNSAFE_queryAllByProps`. + */ + queryAllByProps: ( + props: Record + ) => Array | []; } interface FindByAPI { @@ -109,18 +157,18 @@ interface FindByAPI { // Not yet available in DefinitelyTyped export type A11yValue = { - min?: number, - max?: number, - now?: number, - text?: string, + min?: number; + max?: number; + now?: number; + text?: string; }; type A11yAPI = { // Label - getByA11yLabel: (matcher: string | RegExp) => GetReturn, - getAllByA11yLabel: (matcher: string | RegExp) => GetAllReturn, - queryByA11yLabel: (matcher: string | RegExp) => QueryReturn, - queryAllByA11yLabel: (matcher: string | RegExp) => QueryAllReturn, + getByA11yLabel: (matcher: string | RegExp) => GetReturn; + getAllByA11yLabel: (matcher: string | RegExp) => GetAllReturn; + queryByA11yLabel: (matcher: string | RegExp) => QueryReturn; + queryAllByA11yLabel: (matcher: string | RegExp) => QueryAllReturn; findByA11yLabel: ( matcher: string | RegExp, waitForOptions?: WaitForOptions @@ -131,10 +179,10 @@ type A11yAPI = { ) => FindAllReturn; // Hint - getByA11yHint: (matcher: string | RegExp) => GetReturn, - getAllByA11yHint: (matcher: string | RegExp) => GetAllReturn, - queryByA11yHint: (matcher: string | RegExp) => QueryReturn, - queryAllByA11yHint: (matcher: string | RegExp) => QueryAllReturn, + getByA11yHint: (matcher: string | RegExp) => GetReturn; + getAllByA11yHint: (matcher: string | RegExp) => GetAllReturn; + queryByA11yHint: (matcher: string | RegExp) => QueryReturn; + queryAllByA11yHint: (matcher: string | RegExp) => QueryAllReturn; findByA11yHint: ( matcher: string | RegExp, waitForOptions?: WaitForOptions @@ -145,10 +193,10 @@ type A11yAPI = { ) => FindAllReturn; // Role - getByA11yRole: (matcher: AccessibilityRole | RegExp) => GetReturn, - getAllByA11yRole: (matcher: AccessibilityRole | RegExp) => GetAllReturn, - queryByA11yRole: (matcher: AccessibilityRole | RegExp) => QueryReturn, - queryAllByA11yRole: (matcher: AccessibilityRole | RegExp) => QueryAllReturn, + getByA11yRole: (matcher: AccessibilityRole | RegExp) => GetReturn; + getAllByA11yRole: (matcher: AccessibilityRole | RegExp) => GetAllReturn; + queryByA11yRole: (matcher: AccessibilityRole | RegExp) => QueryReturn; + queryAllByA11yRole: (matcher: AccessibilityRole | RegExp) => QueryAllReturn; findByA11yRole: ( matcher: AccessibilityRole | RegExp, waitForOptions?: WaitForOptions @@ -159,16 +207,24 @@ type A11yAPI = { ) => FindAllReturn; // States - getByA11yStates: (matcher: AccessibilityStates | Array) => GetReturn, - getAllByA11yStates: (matcher: AccessibilityStates | Array) => GetAllReturn, - queryByA11yStates: (matcher: AccessibilityStates | Array) => QueryReturn, - queryAllByA11yStates: (matcher: AccessibilityStates | Array) => QueryAllReturn, + getByA11yStates: ( + matcher: AccessibilityStates | Array + ) => GetReturn; + getAllByA11yStates: ( + matcher: AccessibilityStates | Array + ) => GetAllReturn; + queryByA11yStates: ( + matcher: AccessibilityStates | Array + ) => QueryReturn; + queryAllByA11yStates: ( + matcher: AccessibilityStates | Array + ) => QueryAllReturn; // State - getByA11yState: (matcher: AccessibilityState) => GetReturn, - getAllByA11yState: (matcher: AccessibilityState) => GetAllReturn, - queryByA11yState: (matcher: AccessibilityState) => QueryReturn, - queryAllByA11yState: (matcher: AccessibilityState) => QueryAllReturn, + getByA11yState: (matcher: AccessibilityState) => GetReturn; + getAllByA11yState: (matcher: AccessibilityState) => GetAllReturn; + queryByA11yState: (matcher: AccessibilityState) => QueryReturn; + queryAllByA11yState: (matcher: AccessibilityState) => QueryAllReturn; findByA11yState: ( matcher: AccessibilityState, waitForOptions?: WaitForOptions @@ -179,10 +235,10 @@ type A11yAPI = { ) => FindAllReturn; // Value - getByA11yValue: (matcher: A11yValue) => GetReturn, - getAllByA11yValue: (matcher: A11yValue) => GetAllReturn, - queryByA11yValue: (matcher: A11yValue) => QueryReturn, - queryAllByA11yValue: (matcher: A11yValue) => QueryAllReturn, + getByA11yValue: (matcher: A11yValue) => GetReturn; + getAllByA11yValue: (matcher: A11yValue) => GetAllReturn; + queryByA11yValue: (matcher: A11yValue) => QueryReturn; + queryAllByA11yValue: (matcher: A11yValue) => QueryAllReturn; findByA11yValue: ( matcher: A11yValue, waitForOptions?: WaitForOptions @@ -238,12 +294,22 @@ export declare const render: ( component: React.ReactElement, options?: RenderOptions ) => RenderAPI; -export declare const shallow:

( - instance: ReactTestInstance | React.ReactElement

-) => { output: React.ReactElement

}; + export declare const flushMicrotasksQueue: () => Promise; export declare const cleanup: () => void; export declare const fireEvent: FireEventAPI; export declare const waitFor: WaitForFunction; export declare const act: (callback: () => void) => Thenable; export declare const within: (instance: ReactTestInstance) => Queries; + +/** + * @deprecated This function has been removed. Please use `waitFor` function. + */ +export declare const waitForElement: WaitForFunction; + +/** + * @deprecated This function has been removed. + */ +export declare const shallow:

( + instance: ReactTestInstance | React.ReactElement

+) => { output: React.ReactElement

}; diff --git a/website/docs/Migration20.md b/website/docs/Migration20.md index 088fa291d..cdf4ecd85 100644 --- a/website/docs/Migration20.md +++ b/website/docs/Migration20.md @@ -1,5 +1,5 @@ --- -id: migration20 +id: migration-v2 title: Migration to 2.0 --- @@ -41,7 +41,44 @@ Please note that in many cases `waitFor` call can be replaced by proper use of ` ## Removed global `debug` function -Global debug function has been removed in favor of `debug()` method returned from `render()` function. +Global `debug()` function has been removed in favor of `debug()` method returned from `render()` function. + +## Removed global `shallow` function + +Global `shallow()` functions which has been previously deprecated has been removed. + +Shallow rendering React component is usually not a good idea, so we decided to remove the API. However, if you find it useful or need to support legacy tests, feel free to implement it yourself. Here's a sample implementation: + +```js +import ShallowRenderer from 'react-test-renderer/shallow'; + +export function shallow(instance: ReactTestInstance | React.Element) { + const renderer = new ShallowRenderer(); + renderer.render(React.createElement(instance.type, instance.props)); + + return { output: renderer.getRenderOutput() }; +} +``` + +## Removed functions + +Following query functions have been removed after being deprecated for more than a year now: + +- `getByName` +- `getAllByName` +- `queryByName` +- `queryAllByName` + +The `*ByType` and `*ByProps` queries has been prefixed with `UNSAFE_`. You can safely rename them using global search/replace in your project: + +- `getByType` -> `UNSAFE_getByType` +- `getAllByType` -> `UNSAFE_getAllByType` +- `queryByType` -> `UNSAFE_queryByType` +- `queryAllByType` -> `UNSAFE_queryAllByType` +- `getByProps` -> `UNSAFE_getByProps` +- `getAllByProps` -> `UNSAFE_getAllByProps` +- `queryByProps` -> `UNSAFE_queryByProps` +- `queryAllByProps` -> `UNSAFE_queryAllByProps` ## Some `byTestId` queries behavior changes diff --git a/website/sidebars.js b/website/sidebars.js index d3c764e26..1d5e0411a 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -2,7 +2,7 @@ module.exports = { docs: { Introduction: ['getting-started'], 'API Reference': ['api', 'api-queries'], - Guides: ['migration20'], + Guides: ['migration-v2'], Examples: ['react-navigation', 'redux-integration'], }, }; From 25f2701fdf57e978a1a5390aba96e0193e875776 Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Tue, 19 May 2020 16:58:27 +0200 Subject: [PATCH 07/16] feat: update docs bases on cancelled `prepare-2` PR (#335) * Adapt docs changes from `prepare-2` branch * WIP * Removed unused `printUnsafeWarning` function # Conflicts: # src/helpers/errors.js * Code review changes --- src/helpers/errors.js | 14 -------------- website/docs/Queries.md | 23 ++++++++++++----------- 2 files changed, 12 insertions(+), 25 deletions(-) diff --git a/src/helpers/errors.js b/src/helpers/errors.js index 17a31de83..3a030ac3d 100644 --- a/src/helpers/errors.js +++ b/src/helpers/errors.js @@ -39,20 +39,6 @@ export function printDeprecationWarning(functionName: string) { warned[functionName] = true; } -export function printUnsafeWarning(functionName: string) { - if (warned[functionName]) { - return; - } - - console.warn(` - Deprecation Warning: - ${functionName} is not recommended for use and has been renamed to UNSAFE_${functionName}. - In react-native-testing-library 2.x only the UNSAFE_${functionName} name will work. - `); - - warned[functionName] = true; -} - export function throwRemovedFunctionError( functionName: string, docsRef: string diff --git a/website/docs/Queries.md b/website/docs/Queries.md index a4b8992a9..a0c0fd705 100644 --- a/website/docs/Queries.md +++ b/website/docs/Queries.md @@ -203,23 +203,24 @@ const element = getByA11yValue({ min: 40 }); The interface is the same as for other queries, but we won't provide full names so that they're harder to find by search engines. -### `UNSAFE_ByType`, `ByType` +### `UNSAFE_ByType` -> Note: added in v1.4 +> UNSAFE_getByType, UNSAFE_getAllByType, UNSAFE_queryByType, UNSAFE_queryAllByType -> This method has been **deprecated** and has been prepended with `UNSAFE_` prefix. In react-native-testing-library 2.x only the prefixed version will work. +Returns a `ReactTestInstance` with matching a React component type. -A method returning a `ReactTestInstance` with matching a React component type. Throws when no matches. - -### `UNSAFE_ByProps`, `ByProps` +:::caution +This method has been marked unsafe, since it requires knowledge about implementation details of the component. Use responsibly. +::: -> This method has been **deprecated** and has been prepended with `UNSAFE_` prefix. In react-native-testing-library 2.x only the prefixed version will work. +### `UNSAFE_ByProps` -A method returning a `ReactTestInstance` with matching props object +> UNSAFE_getByProps, UNSAFE_getAllByProps, UNSAFE_queryByProps, UNSAFE_queryAllByProps -### `ByName` +Returns a `ReactTestInstance` with matching props object. -> This method has been **deprecated** because using it results in fragile tests that may break between minor React Native versions. **DON'T USE IT**. It will be removed in next major release (v2.0). Use the other alternatives, such as [`getByText`](#bytext) instead. It's listed here only for back-compat purposes for early adopters of the library -> A method returning a `ReactTestInstance` with matching a React component type. Throws when no matches. +:::caution +This method has been marked unsafe, since it requires knowledge about implementation details of the component. Use responsibly. +::: From 3ef00642d241349c67fc442549e22d83154a3f39 Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Wed, 20 May 2020 10:37:18 +0200 Subject: [PATCH 08/16] chore: reorganize TS test file (#337) --- typings/__tests__/index.test.tsx | 283 +++++++++++++------------------ 1 file changed, 116 insertions(+), 167 deletions(-) diff --git a/typings/__tests__/index.test.tsx b/typings/__tests__/index.test.tsx index 9981ded7e..f6f7cb31b 100644 --- a/typings/__tests__/index.test.tsx +++ b/typings/__tests__/index.test.tsx @@ -4,7 +4,6 @@ import { ReactTestInstance } from 'react-test-renderer'; import { render, fireEvent, - shallow, flushMicrotasksQueue, waitFor, act, @@ -31,86 +30,89 @@ const TestComponent = () => ( const tree = render(); -// getByAPI tests -const getByType: ReactTestInstance = tree.UNSAFE_getByType(View); -const getByTypeWithRequiredProps: ReactTestInstance = tree.UNSAFE_getByType( - ElementWithRequiredProps -); -const getByTextString: ReactTestInstance = tree.getByText(''); -const getByTextRegExp: ReactTestInstance = tree.getByText(/View/g); -const getByPlaceholderString: ReactTestInstance = tree.getByPlaceholder( - 'my placeholder' -); -const getByPlaceholderRegExp: ReactTestInstance = tree.getByPlaceholder( - /placeholder/g -); -const getByDisplayValueString: ReactTestInstance = tree.getByDisplayValue( - 'my value' -); -const getByDisplayValueRegExp: ReactTestInstance = tree.getByDisplayValue( - /value/g -); -const getByProps: ReactTestInstance = tree.UNSAFE_getByProps({ value: 2 }); -const getByTestId: ReactTestInstance = tree.getByTestId('test-id'); -const getAllByTestId: ReactTestInstance[] = tree.getAllByTestId('test-id'); -const getAllByType: Array = tree.UNSAFE_getAllByType(View); -const getAllByTypeWithRequiredProps: Array = tree.UNSAFE_getAllByType( - ElementWithRequiredProps -); -const getAllByTextString: Array = tree.getAllByText( - '' -); -const getAllByTextRegExp: Array = tree.getAllByText(/Text/g); -const getAllByProps: Array = tree.UNSAFE_getAllByProps({ - value: 2, -}); +// getBy API +const getBy: ReactTestInstance[] = [ + tree.getByText(''), + tree.getByText(/View/g), + tree.getByPlaceholder('my placeholder'), + tree.getByPlaceholder(/placeholder/g), + tree.getByDisplayValue('my value'), + tree.getByDisplayValue(/value/g), + tree.getByTestId('test-id'), + tree.getByA11yLabel('label'), + tree.getByA11yHint('label'), + tree.getByA11yRole('button'), + tree.getByA11yStates('selected'), + tree.getByA11yStates(['selected']), + tree.getByA11yState({ busy: true }), + tree.getByA11yValue({ min: 10 }), + tree.UNSAFE_getByType(View), + tree.UNSAFE_getByType(ElementWithRequiredProps), + tree.UNSAFE_getByProps({ value: 2 }), +]; -// queuryByAPI tests -const queryByType: ReactTestInstance | null = tree.UNSAFE_queryByType(View); -const queryByTypeWithRequiredProps: ReactTestInstance | null = tree.UNSAFE_queryByType( - ElementWithRequiredProps -); -const queryByTextString: ReactTestInstance | null = tree.queryByText('View'); -const queryByTextRegExp: ReactTestInstance | null = tree.queryByText(/View/g); -const queryByPlaceholderString: ReactTestInstance | null = tree.queryByPlaceholder( - 'my placeholder' -); -const queryByPlaceholderRegExp: ReactTestInstance | null = tree.queryByPlaceholder( - /placeholder/g -); -const queryByDisplayValueString: ReactTestInstance | null = tree.queryByDisplayValue( - 'my value' -); -const queryByDisplayValueRegExp: ReactTestInstance | null = tree.queryByDisplayValue( - /value/g -); -const queryByProps: ReactTestInstance | null = tree.UNSAFE_queryByProps({ - value: 2, -}); -const queryByTestId: ReactTestInstance | null = tree.queryByTestId('test-id'); -const queryAllByTestId: ReactTestInstance[] | null = tree.queryAllByTestId( - 'test-id' -); -const queryAllByType: Array = tree.UNSAFE_queryAllByType( - View -); -const queryAllByTypeWithRequiredProps: Array = tree.UNSAFE_queryAllByType( - ElementWithRequiredProps -); -const queryAllByTextString: Array = tree.queryAllByText( - 'View' -); -const queryAllByTextRegExp: Array = tree.queryAllByText( - /View/g -); -const queryAllByDisplayValueString: Array = tree.queryAllByDisplayValue( - 'View' -); -const queryAllByDisplayValueRegExp: Array = tree.queryAllByDisplayValue( - /View/g -); +const getAllBy: ReactTestInstance[][] = [ + tree.getAllByText(''), + tree.getAllByText(/Text/g), + tree.getAllByPlaceholder('my placeholder'), + tree.getAllByPlaceholder(/placeholder/g), + tree.getAllByDisplayValue('my value'), + tree.getAllByDisplayValue(/value/g), + tree.getAllByTestId('test-id'), + tree.getAllByA11yLabel('label'), + tree.getAllByA11yHint('label'), + tree.getAllByA11yRole('button'), + tree.getAllByA11yStates('selected'), + tree.getAllByA11yStates(['selected']), + tree.getAllByA11yState({ busy: true }), + tree.getAllByA11yValue({ min: 10 }), + tree.UNSAFE_getAllByType(View), + tree.UNSAFE_getAllByType(ElementWithRequiredProps), + tree.UNSAFE_getAllByProps({ value: 2 }), +]; + +// queryBy API +const queryBy: Array = [ + tree.queryByText('View'), + tree.queryByText(/View/g), + tree.queryByPlaceholder('my placeholder'), + tree.queryByPlaceholder(/placeholder/g), + tree.queryByDisplayValue('my value'), + tree.queryByDisplayValue(/value/g), + tree.queryByTestId('test-id'), + tree.queryByA11yHint('label'), + tree.queryByA11yLabel('label'), + tree.queryByA11yRole('button'), + tree.queryByA11yStates('selected'), + tree.queryByA11yStates(['selected']), + tree.queryByA11yState({ busy: true }), + tree.queryByA11yValue({ min: 10 }), + tree.UNSAFE_queryByType(View), + tree.UNSAFE_queryByType(ElementWithRequiredProps), + tree.UNSAFE_queryByProps({ value: 2 }), +]; -// findBy API tests +const queryAllBy: ReactTestInstance[][] = [ + tree.queryAllByText('View'), + tree.queryAllByText(/View/g), + tree.queryAllByPlaceholder('my placeholder'), + tree.queryAllByPlaceholder(/placeholder/g), + tree.queryAllByDisplayValue('my value'), + tree.queryAllByDisplayValue(/value/g), + tree.queryAllByTestId('test-id'), + tree.queryAllByA11yLabel('label'), + tree.queryAllByA11yHint('label'), + tree.queryAllByA11yRole('button'), + tree.queryAllByA11yStates('selected'), + tree.queryAllByA11yStates(['selected']), + tree.queryAllByA11yState({ busy: true }), + tree.queryAllByA11yValue({ min: 10 }), + tree.UNSAFE_queryAllByType(View), + tree.UNSAFE_queryAllByType(ElementWithRequiredProps), + tree.UNSAFE_queryAllByProps({ value: 2 }), +]; + +// findBy API const findBy: Promise[] = [ tree.findByText('View'), tree.findByText('View', { timeout: 10, interval: 10 }), @@ -153,6 +155,8 @@ const findAllBy: Promise[] = [ tree.findAllByDisplayValue(/View/g, { timeout: 10, interval: 10 }), tree.findAllByTestId('test-id'), tree.findAllByTestId('test-id', { timeout: 10, interval: 10 }), + tree.findAllByA11yLabel('label'), + tree.findAllByA11yLabel('label', { timeout: 10, interval: 10 }), tree.findAllByA11yHint('label'), tree.findAllByA11yHint('label', { timeout: 10, interval: 10 }), tree.findAllByA11yState({ busy: true }), @@ -161,105 +165,50 @@ const findAllBy: Promise[] = [ tree.findAllByA11yValue({ min: 10 }, { timeout: 10, interval: 10 }), ]; -// Accessibility queries -const getByA11yLabel: ReactTestInstance = tree.getByA11yLabel('label'); -const getAllByA11yLabel: Array = tree.getAllByA11yLabel( - 'label' -); -const queryByA11yLabel: ReactTestInstance = tree.queryByA11yLabel('label'); -const queryAllByA11yLabel: Array = tree.queryAllByA11yLabel( - 'label' -); - -const getByA11yHint: ReactTestInstance = tree.getByA11yHint('label'); -const getAllByA11yHint: Array = tree.getAllByA11yHint( - 'label' -); -const queryByA11yHint: ReactTestInstance = tree.queryByA11yHint('label'); -const queryAllByA11yHint: Array = tree.queryAllByA11yHint( - 'label' -); - -const getByA11yRole: ReactTestInstance = tree.getByA11yRole('button'); -const getAllByA11yRole: Array = tree.getAllByA11yRole( - 'button' -); -const queryByA11yRole: ReactTestInstance = tree.queryByA11yRole('button'); -const queryAllByA11yRole: Array = tree.queryAllByA11yRole( - 'button' -); - -const getByA11yStates: ReactTestInstance = tree.getByA11yStates('selected'); -const getByA11yStatesArray: ReactTestInstance = tree.getByA11yStates([ - 'selected', -]); -const getAllByA11yStates: Array = tree.getAllByA11yStates( - 'selected' -); -const getAllByA11yStatesArray: Array = tree.getAllByA11yStates( - ['selected'] -); -const queryByA11yStates: ReactTestInstance = tree.queryByA11yStates('selected'); -const queryByA11yStatesArray: ReactTestInstance = tree.queryByA11yStates([ - 'selected', -]); -const queryAllByA11yStates: Array = tree.queryAllByA11yStates( - 'selected' -); -const queryAllByA11yStatesArray: Array = tree.queryAllByA11yStates( - ['selected'] -); - -const getByA11yState: ReactTestInstance = tree.getByA11yState({ busy: true }); -const getAllByA11yState: Array = tree.getAllByA11yState({ - busy: true, -}); -const queryByA11yState: ReactTestInstance = tree.queryByA11yState({ - busy: true, -}); -const queryAllByA11yState: Array = tree.queryAllByA11yState({ - busy: true, -}); - -const getByA11yValue: ReactTestInstance = tree.getByA11yValue({ min: 10 }); -const getAllByA11yValue: Array = tree.getAllByA11yValue({ - min: 10, -}); -const queryByA11yValue: ReactTestInstance = tree.queryByA11yValue({ min: 10 }); -const queryAllByA11yValue: Array = tree.queryAllByA11yValue({ - min: 10, -}); - +// debug API const debugFn = tree.debug(); const debugFnWithMessage = tree.debug('my message'); +// update API tree.update(); tree.rerender(); tree.unmount(); -// fireEvent API tests -fireEvent(getByA11yLabel, 'press'); -fireEvent(getByA11yLabel, 'press', 'data'); -fireEvent(getByA11yLabel, 'press', 'param1', 'param2'); -fireEvent.press(getByA11yLabel); -fireEvent.changeText(getByA11yLabel, 'string'); -fireEvent.scroll(getByA11yLabel, 'eventData'); +// fireEvent API +const element: ReactTestInstance = tree.getByText('text'); +fireEvent(element, 'press'); +fireEvent(element, 'press', 'data'); +fireEvent(element, 'press', 'param1', 'param2'); +fireEvent.press(element); +fireEvent.changeText(element, 'string'); +fireEvent.scroll(element, 'eventData'); -// shallow API -const shallowTree: { output: React.ReactElement } = shallow( - -); +// waitFor API +const waitGetBy: Promise[] = [ + waitFor(() => tree.getByA11yLabel('label')), + waitFor(() => tree.getByA11yLabel('label'), { + timeout: 10, + }), + waitFor(() => tree.getByA11yLabel('label'), { + timeout: 100, + interval: 10, + }), +]; -const waitForFlush: Promise = flushMicrotasksQueue(); +const waitGetAllBy: Promise[] = [ + waitFor(() => tree.getAllByA11yLabel('label')), + waitFor(() => tree.getAllByA11yLabel('label'), { + timeout: 10, + }), + waitFor(() => tree.getAllByA11yLabel('label'), { + timeout: 100, + interval: 10, + }), +]; -const waitBy: Promise = waitFor(() => - tree.getByA11yLabel('label') -); -const waitByAll: Promise = waitFor( - () => tree.getAllByA11yLabel('label'), - { timeout: 1000, interval: 50 } -); +const waitForFlush: Promise = flushMicrotasksQueue(); +// act API act(() => { render(); }); From 175b9b9fa2e1ac3e7b7a1cbb20bff4ef38f520c7 Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Wed, 20 May 2020 07:33:28 -0400 Subject: [PATCH 09/16] feat: call "cleanup" automatically (#238) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add "cleanup" function * docs: add "cleanup" section * feat: automatically call "cleanup" after each test ...if an "afterEach" global function is defined, and process.env.RTL_SKIP_AUTO_CLEANUP is falsy. Taken from: https://github.com/testing-library/react-testing-library/blob/14670debd45236d2c5d0a61a83dadc72af1bef7c/src/index.js * docs: mention automatic cleanup * add within * fix lint * Updated tests, added auto-cleanup test * Added ways to prevent auto cleanup * Small fix * Code review changes * Update website/docs/API.md Co-authored-by: Michał Pierzchała * More changes * Removed afterEach(cleanup) calls in examples * Code cleanup Co-authored-by: Michał Pierzchała Co-authored-by: Maciej Jastrzebski --- dont-cleanup-after-each.js | 1 + .../src/__tests__/AppNavigator.test.js | 4 +- examples/redux/components/AddTodo.test.js | 4 +- examples/redux/components/TodoList.test.js | 4 +- examples/redux/reducers/todoReducer.js | 1 - pure.js | 2 + src/__tests__/auto-cleanup-skip.js | 39 +++++++++++++++++++ src/__tests__/auto-cleanup.js | 33 ++++++++++++++++ src/__tests__/cleanup.test.js | 2 +- src/__tests__/waitFor.test.js | 4 ++ src/index.js | 30 +++++++------- src/pure.js | 18 +++++++++ website/docs/API.md | 12 +++++- website/docs/Migration20.md | 24 ++++++++++++ website/docs/ReactNavigation.md | 4 +- 15 files changed, 151 insertions(+), 31 deletions(-) create mode 100644 dont-cleanup-after-each.js create mode 100644 pure.js create mode 100644 src/__tests__/auto-cleanup-skip.js create mode 100644 src/__tests__/auto-cleanup.js create mode 100644 src/pure.js diff --git a/dont-cleanup-after-each.js b/dont-cleanup-after-each.js new file mode 100644 index 000000000..d9a79f173 --- /dev/null +++ b/dont-cleanup-after-each.js @@ -0,0 +1 @@ +process.env.RNTL_SKIP_AUTO_CLEANUP = true; diff --git a/examples/reactnavigation/src/__tests__/AppNavigator.test.js b/examples/reactnavigation/src/__tests__/AppNavigator.test.js index b2f4a85c1..f9731215b 100644 --- a/examples/reactnavigation/src/__tests__/AppNavigator.test.js +++ b/examples/reactnavigation/src/__tests__/AppNavigator.test.js @@ -1,6 +1,6 @@ import React from 'react'; import { NavigationContainer } from '@react-navigation/native'; -import { render, fireEvent, cleanup } from 'react-native-testing-library'; +import { render, fireEvent } from 'react-native-testing-library'; import AppNavigator from '../AppNavigator'; @@ -8,8 +8,6 @@ import AppNavigator from '../AppNavigator'; jest.mock('react-native/Libraries/Animated/src/NativeAnimatedHelper'); describe('Testing react navigation', () => { - afterEach(cleanup); - test('page contains the header and 10 items', () => { const component = ( diff --git a/examples/redux/components/AddTodo.test.js b/examples/redux/components/AddTodo.test.js index c90344cf2..edbbcc163 100644 --- a/examples/redux/components/AddTodo.test.js +++ b/examples/redux/components/AddTodo.test.js @@ -1,12 +1,10 @@ import React from 'react'; import { Provider } from 'react-redux'; -import { cleanup, fireEvent, render } from 'react-native-testing-library'; +import { fireEvent, render } from 'react-native-testing-library'; import configureStore from '../store'; import AddTodo from './AddTodo'; describe('Application test', () => { - afterEach(cleanup); - test('adds a new test when entry has been included', () => { const store = configureStore(); diff --git a/examples/redux/components/TodoList.test.js b/examples/redux/components/TodoList.test.js index 80b27f05f..595fcea27 100644 --- a/examples/redux/components/TodoList.test.js +++ b/examples/redux/components/TodoList.test.js @@ -1,12 +1,10 @@ import React from 'react'; import { Provider } from 'react-redux'; -import { cleanup, fireEvent, render } from 'react-native-testing-library'; +import { fireEvent, render } from 'react-native-testing-library'; import configureStore from '../store'; import TodoList from './TodoList'; describe('Application test', () => { - afterEach(cleanup); - test('it should execute with a store with 4 elements', () => { const initialState = { todos: [ diff --git a/examples/redux/reducers/todoReducer.js b/examples/redux/reducers/todoReducer.js index f55d4c3e6..f1e20731c 100644 --- a/examples/redux/reducers/todoReducer.js +++ b/examples/redux/reducers/todoReducer.js @@ -6,7 +6,6 @@ export default function todoReducer(state = [], action) { return state.concat(action.payload); case actions.REMOVE: - console.log(action); return state.filter((todo) => todo.id !== action.payload.id); case actions.MODIFY: diff --git a/pure.js b/pure.js new file mode 100644 index 000000000..bced0b756 --- /dev/null +++ b/pure.js @@ -0,0 +1,2 @@ +// makes it so people can import from 'react-native-testing-library/pure' +module.exports = require('./build/pure'); diff --git a/src/__tests__/auto-cleanup-skip.js b/src/__tests__/auto-cleanup-skip.js new file mode 100644 index 000000000..eece886ab --- /dev/null +++ b/src/__tests__/auto-cleanup-skip.js @@ -0,0 +1,39 @@ +import React from 'react'; +import { View } from 'react-native'; + +let render; +beforeAll(() => { + process.env.RNTL_SKIP_AUTO_CLEANUP = 'true'; + const rtl = require('../'); + render = rtl.render; +}); + +let isMounted = false; + +class Test extends React.Component<*> { + componentDidMount() { + isMounted = true; + } + + componentWillUnmount() { + isMounted = false; + if (this.props.onUnmount) { + this.props.onUnmount(); + } + } + render() { + return ; + } +} + +// This just verifies that by importing RNTL in pure mode in an environment which supports +// afterEach (like jest) we won't get automatic cleanup between tests. +test('component is mounted, but not umounted before test ends', () => { + const fn = jest.fn(); + render(); + expect(fn).not.toHaveBeenCalled(); +}); + +test('component is NOT automatically umounted after first test ends', () => { + expect(isMounted).toEqual(true); +}); diff --git a/src/__tests__/auto-cleanup.js b/src/__tests__/auto-cleanup.js new file mode 100644 index 000000000..5a26d7c8f --- /dev/null +++ b/src/__tests__/auto-cleanup.js @@ -0,0 +1,33 @@ +import React from 'react'; +import { View } from 'react-native'; +import { render } from '..'; + +let isMounted = false; + +class Test extends React.Component<*> { + componentDidMount() { + isMounted = true; + } + + componentWillUnmount() { + isMounted = false; + if (this.props.onUnmount) { + this.props.onUnmount(); + } + } + render() { + return ; + } +} + +// This just verifies that by importing RNTL in an environment which supports afterEach (like jest) +// we'll get automatic cleanup between tests. +test('component is mounted, but not umounted before test ends', () => { + const fn = jest.fn(); + render(); + expect(fn).not.toHaveBeenCalled(); +}); + +test('component is automatically umounted after first test ends', () => { + expect(isMounted).toEqual(false); +}); diff --git a/src/__tests__/cleanup.test.js b/src/__tests__/cleanup.test.js index 7ce5bfc46..1ccb752b6 100644 --- a/src/__tests__/cleanup.test.js +++ b/src/__tests__/cleanup.test.js @@ -2,7 +2,7 @@ /* eslint-disable react/no-multi-comp */ import React from 'react'; import { View } from 'react-native'; -import { cleanup, render } from '..'; +import { cleanup, render } from '../pure'; class Test extends React.Component<*> { componentWillUnmount() { diff --git a/src/__tests__/waitFor.test.js b/src/__tests__/waitFor.test.js index 891c8457c..e38f05c44 100644 --- a/src/__tests__/waitFor.test.js +++ b/src/__tests__/waitFor.test.js @@ -55,6 +55,10 @@ test('waits for element until timeout is met', async () => { await expect( waitFor(() => getByText('Fresh'), { timeout: 100 }) ).rejects.toThrow(); + + // Async action ends after 300ms and we only waited 100ms, so we need to wait + // for the remaining async actions to finish + await waitFor(() => getByText('Fresh')); }); test('waits for element with custom interval', async () => { diff --git a/src/index.js b/src/index.js index 534119714..fa21651f4 100644 --- a/src/index.js +++ b/src/index.js @@ -1,18 +1,18 @@ // @flow -import act from './act'; -import cleanup from './cleanup'; -import fireEvent from './fireEvent'; import flushMicrotasksQueue from './flushMicrotasksQueue'; -import render from './render'; -import shallow from './shallow'; -import waitFor, { waitForElement } from './waitFor'; -import within from './within'; +import { cleanup } from './pure'; -export { act }; -export { cleanup }; -export { fireEvent }; -export { flushMicrotasksQueue }; -export { render }; -export { shallow }; -export { waitFor, waitForElement }; -export { within }; +// If we're running in a test runner that supports afterEach +// then we'll automatically run cleanup afterEach test +// this ensures that tests run in isolation from each other +// if you don't like this then either import the `pure` module +// or set the RNTL_SKIP_AUTO_CLEANUP env variable to 'true'. +if (typeof afterEach === 'function' && !process.env.RNTL_SKIP_AUTO_CLEANUP) { + // eslint-disable-next-line no-undef + afterEach(async () => { + await flushMicrotasksQueue(); + cleanup(); + }); +} + +export * from './pure'; diff --git a/src/pure.js b/src/pure.js new file mode 100644 index 000000000..534119714 --- /dev/null +++ b/src/pure.js @@ -0,0 +1,18 @@ +// @flow +import act from './act'; +import cleanup from './cleanup'; +import fireEvent from './fireEvent'; +import flushMicrotasksQueue from './flushMicrotasksQueue'; +import render from './render'; +import shallow from './shallow'; +import waitFor, { waitForElement } from './waitFor'; +import within from './within'; + +export { act }; +export { cleanup }; +export { fireEvent }; +export { flushMicrotasksQueue }; +export { render }; +export { shallow }; +export { waitFor, waitForElement }; +export { within }; diff --git a/website/docs/API.md b/website/docs/API.md index 3e0f31d3f..629fd9226 100644 --- a/website/docs/API.md +++ b/website/docs/API.md @@ -74,7 +74,11 @@ Re-render the in-memory tree with a new root element. This simulates a React upd unmount(): void ``` -Unmount the in-memory tree, triggering the appropriate lifecycle events +Unmount the in-memory tree, triggering the appropriate lifecycle events. + +:::note +Usually you should not need to call `unmount` as it is done automatically if your test runner supports `afterEach` hook (like Jest, mocha, Jasmine). +::: ### `debug` @@ -123,10 +127,14 @@ const cleanup: () => void; Unmounts React trees that were mounted with `render`. +:::info +Please note that this is done automatically if the testing framework you're using supports the `afterEach` global (like mocha, Jest, and Jasmine). If not, you will need to do manual cleanups after each test. +::: + For example, if you're using the `jest` testing framework, then you would need to use the `afterEach` hook like so: ```jsx -import { cleanup, render } from 'react-native-testing-library'; +import { cleanup, render } from 'react-native-testing-library/pure'; import { View } from 'react-native'; afterEach(cleanup); diff --git a/website/docs/Migration20.md b/website/docs/Migration20.md index cdf4ecd85..935af648f 100644 --- a/website/docs/Migration20.md +++ b/website/docs/Migration20.md @@ -9,6 +9,30 @@ This guides describes major steps involved in migrating your testing code from u Node 8 reached its EOL more than 5 months ago, so it's about time to target the library to Node 10. If you used lower version, you'll have to upgrade to v10, but we suggest using the latest LTS version. +## Auto Cleanup + +`cleanup()` function is now called automatically after every test, if your testing framework supports `afterEach` hook (like Jest, mocha, and Jasmine). + +You should be able to safely remove all `afterEach(cleanup)` calls in your code. + +This change might break your code, if you tests are not isolated, i.e. you call `render` outside `test` block. Generally, you should [keep your tests isolated](https://kentcdodds.com/blog/test-isolation-with-react), but if you can't or don't want to do this right away you can prevent this behavior using any of the foloowing ways: + +1. by importing `'react-native-testing-library/pure'` instead of `'react-native-testing-library'` + +2. by importing `'react-native-testing-library/dont-cleanup-after-each'` before importing `'react-native-testing-library'`. You can do it in a global way by using Jest's `setupFiles` like this: + +```js +{ + setupFiles: ['react-native-testing-library/dont-cleanup-after-each']; +} +``` + +3. by setting `RTNL_SKIP_AUTO_CLEANUP` env variable to `true`. You can do this with `cross-evn` like this: + +```sh +cross-env RNTL_SKIP_AUTO_CLEANUP=true jest +``` + ## WaitFor API changes `waitForElement` function has been renamed to `waitFor` for consistency with React Testing Library. Additionally the signature has slightly changed from: diff --git a/website/docs/ReactNavigation.md b/website/docs/ReactNavigation.md index 0d4ced387..bdf574184 100644 --- a/website/docs/ReactNavigation.md +++ b/website/docs/ReactNavigation.md @@ -54,7 +54,7 @@ export default function HomeScreen({ navigation }) { new Array(20).fill(null).map((_, idx) => idx + 1) ); - const onOpacityPress = item => navigation.navigate('Details', item); + const onOpacityPress = (item) => navigation.navigate('Details', item); return ( @@ -162,8 +162,6 @@ import AppNavigator from '../AppNavigator'; jest.mock('react-native/Libraries/Animated/src/NativeAnimatedHelper'); describe('Testing react navigation', () => { - afterEach(cleanup); - test('page contains the header and 10 items', () => { const component = ( From 4d0e44895a7969186c0dbd058c2b705687cd10e8 Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Wed, 20 May 2020 13:43:33 +0200 Subject: [PATCH 10/16] Spelling mistake --- website/docs/Migration20.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/Migration20.md b/website/docs/Migration20.md index 935af648f..b97ff7bb5 100644 --- a/website/docs/Migration20.md +++ b/website/docs/Migration20.md @@ -15,7 +15,7 @@ Node 8 reached its EOL more than 5 months ago, so it's about time to target the You should be able to safely remove all `afterEach(cleanup)` calls in your code. -This change might break your code, if you tests are not isolated, i.e. you call `render` outside `test` block. Generally, you should [keep your tests isolated](https://kentcdodds.com/blog/test-isolation-with-react), but if you can't or don't want to do this right away you can prevent this behavior using any of the foloowing ways: +This change might break your code, if you tests are not isolated, i.e. you call `render` outside `test` block. Generally, you should [keep your tests isolated](https://kentcdodds.com/blog/test-isolation-with-react), but if you can't or don't want to do this right away you can prevent this behavior using any of the following ways: 1. by importing `'react-native-testing-library/pure'` instead of `'react-native-testing-library'` From b595c741d85cbbc69432940e8aa547b1db1d31c2 Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Wed, 20 May 2020 16:33:19 +0200 Subject: [PATCH 11/16] chore(breaking): deprecate flushMicrotasksQueue (#340) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Deprecated flushMicrotasksQueue * Update website/docs/Migration20.md Co-authored-by: Michał Pierzchała * Renamed flushMicrotasksQueueInternal to flushMicroTasks Co-authored-by: Michał Pierzchała --- README.md | 1 - src/flushMicroTasks.js | 15 +++++++++++++++ src/flushMicrotasksQueue.js | 7 ------- src/helpers/errors.js | 2 +- src/index.js | 4 ++-- src/pure.js | 2 +- typings/index.d.ts | 6 +++++- website/docs/API.md | 15 --------------- website/docs/Migration20.md | 12 ++++++++++++ 9 files changed, 36 insertions(+), 28 deletions(-) create mode 100644 src/flushMicroTasks.js delete mode 100644 src/flushMicrotasksQueue.js diff --git a/README.md b/README.md index 549ea139c..882c2a7a0 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,6 @@ The [public API](https://callstack.github.io/react-native-testing-library/docs/a - [`fireEvent`](https://callstack.github.io/react-native-testing-library/docs/api#fireevent) - invokes named event handler on the element. - [`waitFor`](https://callstack.github.io/react-native-testing-library/docs/api#waitfor) - waits for non-deterministic periods of time until your element appears or times out. - [`within`](https://callstack.github.io/react-native-testing-library/docs/api#within) - creates a queries object scoped for given element. -- [`flushMicrotasksQueue`](https://callstack.github.io/react-native-testing-library/docs/api#flushmicrotasksqueue) - waits for microtasks queue to flush. ## Migration Guides diff --git a/src/flushMicroTasks.js b/src/flushMicroTasks.js new file mode 100644 index 000000000..c4bb67903 --- /dev/null +++ b/src/flushMicroTasks.js @@ -0,0 +1,15 @@ +// @flow + +import { printDeprecationWarning } from './helpers/errors'; + +/** + * Wait for microtasks queue to flush + */ +export default function flushMicrotasksQueue(): Promise { + printDeprecationWarning('flushMicrotasksQueue'); + return flushMicroTasks(); +} + +export function flushMicroTasks(): Promise { + return new Promise((resolve) => setImmediate(resolve)); +} diff --git a/src/flushMicrotasksQueue.js b/src/flushMicrotasksQueue.js deleted file mode 100644 index caab8614b..000000000 --- a/src/flushMicrotasksQueue.js +++ /dev/null @@ -1,7 +0,0 @@ -// @flow -/** - * Wait for microtasks queue to flush - */ -export default function flushMicrotasksQueue(): Promise { - return new Promise((resolve) => setImmediate(resolve)); -} diff --git a/src/helpers/errors.js b/src/helpers/errors.js index 3a030ac3d..ce089b873 100644 --- a/src/helpers/errors.js +++ b/src/helpers/errors.js @@ -33,7 +33,7 @@ export function printDeprecationWarning(functionName: string) { console.warn(` Deprecation Warning: - ${functionName} is not recommended for use and will be deleted in react-native-testing-library 2.x. + Use of ${functionName} is not recommended and will be deleted in future versions of react-native-testing-library. `); warned[functionName] = true; diff --git a/src/index.js b/src/index.js index fa21651f4..d4cb964e8 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,6 @@ // @flow -import flushMicrotasksQueue from './flushMicrotasksQueue'; import { cleanup } from './pure'; +import { flushMicroTasks } from './flushMicroTasks'; // If we're running in a test runner that supports afterEach // then we'll automatically run cleanup afterEach test @@ -10,7 +10,7 @@ import { cleanup } from './pure'; if (typeof afterEach === 'function' && !process.env.RNTL_SKIP_AUTO_CLEANUP) { // eslint-disable-next-line no-undef afterEach(async () => { - await flushMicrotasksQueue(); + await flushMicroTasks(); cleanup(); }); } diff --git a/src/pure.js b/src/pure.js index 534119714..7765847a6 100644 --- a/src/pure.js +++ b/src/pure.js @@ -2,7 +2,7 @@ import act from './act'; import cleanup from './cleanup'; import fireEvent from './fireEvent'; -import flushMicrotasksQueue from './flushMicrotasksQueue'; +import flushMicrotasksQueue from './flushMicroTasks'; import render from './render'; import shallow from './shallow'; import waitFor, { waitForElement } from './waitFor'; diff --git a/typings/index.d.ts b/typings/index.d.ts index d38f9c0c6..b998fb93a 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -295,7 +295,6 @@ export declare const render: ( options?: RenderOptions ) => RenderAPI; -export declare const flushMicrotasksQueue: () => Promise; export declare const cleanup: () => void; export declare const fireEvent: FireEventAPI; export declare const waitFor: WaitForFunction; @@ -307,6 +306,11 @@ export declare const within: (instance: ReactTestInstance) => Queries; */ export declare const waitForElement: WaitForFunction; +/** + * @deprecated This function has been deprecated and will be removed in the next release. + */ +export declare const flushMicrotasksQueue: () => Promise; + /** * @deprecated This function has been removed. */ diff --git a/website/docs/API.md b/website/docs/API.md index 629fd9226..5d2adc734 100644 --- a/website/docs/API.md +++ b/website/docs/API.md @@ -377,21 +377,6 @@ Use cases for scoped queries include: - queries scoped to a single item inside a FlatList containing many items - queries scoped to a single screen in tests involving screen transitions (e.g. with react-navigation) -## `flushMicrotasksQueue` - -Waits for microtasks queue to flush. Useful if you want to wait for some promises with `async/await`. - -```jsx -import { flushMicrotasksQueue, render } from 'react-native-testing-library'; - -test('fetch data', async () => { - const { getByText } = render(); - getByText('fetch'); - await flushMicrotasksQueue(); - expect(getByText('fetch').props.title).toBe('loaded'); -}); -``` - ## `query` APIs Each of the get APIs listed in the render section above have a complimentary query API. The get APIs will throw errors if a proper node cannot be found. This is normally the desired effect. However, if you want to make an assertion that an element is not present in the hierarchy, then you can use the query API instead: diff --git a/website/docs/Migration20.md b/website/docs/Migration20.md index b97ff7bb5..d3c516e47 100644 --- a/website/docs/Migration20.md +++ b/website/docs/Migration20.md @@ -113,3 +113,15 @@ If you relied on setting `testID` for your custom components, you should probabl :::caution In general, you should avoid `byTestId` queries when possible and rather use queries that check things that can been seen by the user (e.g. `byText`, `byPlaceholder`, etc) or accessability queries (e.g. `byA11yHint`, `byA11yLabel`, etc). ::: + +## Deprecated `flushMicrotasksQueue` + +We have deprecated `flushMicrotasksQueue` and plan to remove it in the next major version, as currently there are better alternatives available for helping you write async tests: `findBy` async queries and `waitFor` helper. + +If you can't or don't want to migrate your tests, you can get rid of the deprecation warning by copy-pasting function's implementation into your project: + +```js +function flushMicrotasksQueue() { + return new Promise((resolve) => setImmediate(resolve)); +} +``` From c3074b67af7d9fd7991fe08f22228a36e1a5a826 Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Wed, 20 May 2020 16:39:50 +0200 Subject: [PATCH 12/16] chore: rename Migration20.md to MigrationV2.md (#341) --- website/docs/{Migration20.md => MigrationV2.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename website/docs/{Migration20.md => MigrationV2.md} (100%) diff --git a/website/docs/Migration20.md b/website/docs/MigrationV2.md similarity index 100% rename from website/docs/Migration20.md rename to website/docs/MigrationV2.md From 0253d4a8646bbcd6aa0d4f859484c53de0223372 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Wed, 20 May 2020 22:23:43 +0200 Subject: [PATCH 13/16] docs: cleanup migration doc (#342) --- website/docs/MigrationV2.md | 71 +++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/website/docs/MigrationV2.md b/website/docs/MigrationV2.md index d3c516e47..9c1c08f53 100644 --- a/website/docs/MigrationV2.md +++ b/website/docs/MigrationV2.md @@ -3,46 +3,45 @@ id: migration-v2 title: Migration to 2.0 --- -This guides describes major steps involved in migrating your testing code from using React Native Testing Library version `1.x` to version `2.0`. +This guide describes steps necessary to migrate from React Native Testing Library `v1.x` to `v2.0`. ## Dropping Node 8 -Node 8 reached its EOL more than 5 months ago, so it's about time to target the library to Node 10. If you used lower version, you'll have to upgrade to v10, but we suggest using the latest LTS version. +Node 8 reached its EOL more than 5 months ago, so it's about time to target the library to Node 10. If you used lower version, you'll have to upgrade to v10, but we recommend using the latest LTS version. ## Auto Cleanup -`cleanup()` function is now called automatically after every test, if your testing framework supports `afterEach` hook (like Jest, mocha, and Jasmine). +`cleanup()` function is now called automatically after every test if your testing framework supports `afterEach` hook (like Jest, Mocha, and Jasmine). -You should be able to safely remove all `afterEach(cleanup)` calls in your code. +You should be able to remove all `afterEach(cleanup)` calls in your code. -This change might break your code, if you tests are not isolated, i.e. you call `render` outside `test` block. Generally, you should [keep your tests isolated](https://kentcdodds.com/blog/test-isolation-with-react), but if you can't or don't want to do this right away you can prevent this behavior using any of the following ways: +This change might break your code, if you tests are not isolated, i.e. you call `render` outside `test` block. Generally, you should [keep your tests isolated](https://kentcdodds.com/blog/test-isolation-with-react). But if you can't or don't want to do this right away you can prevent this behavior using any of the following ways: -1. by importing `'react-native-testing-library/pure'` instead of `'react-native-testing-library'` +- by importing `'react-native-testing-library/pure'` instead of `'react-native-testing-library'` +- by importing `'react-native-testing-library/dont-cleanup-after-each'` before importing `'react-native-testing-library'`. You can do it in a global way by using Jest's `setupFiles` like this: -2. by importing `'react-native-testing-library/dont-cleanup-after-each'` before importing `'react-native-testing-library'`. You can do it in a global way by using Jest's `setupFiles` like this: - -```js -{ - setupFiles: ['react-native-testing-library/dont-cleanup-after-each']; -} -``` + ```json + { + "setupFiles": ["react-native-testing-library/dont-cleanup-after-each"]; + } + ``` -3. by setting `RTNL_SKIP_AUTO_CLEANUP` env variable to `true`. You can do this with `cross-evn` like this: +- by setting `RTNL_SKIP_AUTO_CLEANUP` env variable to `true`. You can do this with `cross-evn` like this: -```sh -cross-env RNTL_SKIP_AUTO_CLEANUP=true jest -``` + ```sh + cross-env RNTL_SKIP_AUTO_CLEANUP=true jest + ``` ## WaitFor API changes -`waitForElement` function has been renamed to `waitFor` for consistency with React Testing Library. Additionally the signature has slightly changed from: +We renamed `waitForElement` function to `waitFor` for consistency with React Testing Library. Additionally, the signature has slightly changed from: ```jsx export default function waitForElement( expectation: () => T, timeout?: number, interval?: number - : Promise { +): Promise {} ``` to: @@ -50,28 +49,26 @@ to: ```jsx export default function waitFor( expectation: () => T, - { + options: { timeout?: number, - interval?: number + interval?: number, } -): Promise { +): Promise {} ``` Both changes should improve code readibility. -:::note -Please note that in many cases `waitFor` call can be replaced by proper use of `findBy` asynchonous queries resulting in more streamlined test code. +:::tip +You can usually avoid `waitFor` by a proper use of `findBy` asynchronous queries. It will result in more streamlined testing experience. ::: ## Removed global `debug` function -Global `debug()` function has been removed in favor of `debug()` method returned from `render()` function. +The `debug()` method returned from `render()` function is all you need. We removed the global export to avoid confusion. ## Removed global `shallow` function -Global `shallow()` functions which has been previously deprecated has been removed. - -Shallow rendering React component is usually not a good idea, so we decided to remove the API. However, if you find it useful or need to support legacy tests, feel free to implement it yourself. Here's a sample implementation: +Shallow rendering React component is usually not a good idea, so we decided to remove the API. But, if you find it useful or need to support legacy tests, feel free to use this implementation: ```js import ShallowRenderer from 'react-test-renderer/shallow'; @@ -93,7 +90,7 @@ Following query functions have been removed after being deprecated for more than - `queryByName` - `queryAllByName` -The `*ByType` and `*ByProps` queries has been prefixed with `UNSAFE_`. You can safely rename them using global search/replace in your project: +The `*ByType` and `*ByProps` queries has been prefixed with `UNSAFE_`. You can rename them using global search/replace in your project: - `getByType` -> `UNSAFE_getByType` - `getAllByType` -> `UNSAFE_getAllByType` @@ -104,21 +101,25 @@ The `*ByType` and `*ByProps` queries has been prefixed with `UNSAFE_`. You can s - `queryByProps` -> `UNSAFE_queryByProps` - `queryAllByProps` -> `UNSAFE_queryAllByProps` -## Some `byTestId` queries behavior changes +## Some `ByTestId` queries behavior changes + +In version `1.x` the `getByTestId` and `queryByTestId` queries could return non-native tinstances. This was a serious bug. Other query functions like `getAllByTestId`, `queryAllByTestId`, `findByTestId` and `findAllByTestId` didn't have this issue. These correctly returned only native components instances (e.g. `View`, `Text`, etc) that got the `testID`. -In version `1.x` `getByTestId` and `queryByTestId` could return non-native elements in tests. This was in contrast with other query functions: `getAllByTestId`, `queryAllByTestId`, `findByTestId` and `findAllByTestId` which returned only elements that would be rendered to native components (e.g. `View`, `Text`, etc). +In v2 we fixed this inconsistency, which may result in failing tests, if you relied on this behavior. There are few ways to handle these failures: -If you relied on setting `testID` for your custom components, you should probably set them on the root element of the returned JSX. +- pass the `testID` prop down so it can reach a native component, like `View` or `Text` +- replace `testID` with proper `accessibilityHint` or `accessibilityLabel` if it benefits the user +- use safe queries like `*ByText` or `*ByA11yHint` :::caution -In general, you should avoid `byTestId` queries when possible and rather use queries that check things that can been seen by the user (e.g. `byText`, `byPlaceholder`, etc) or accessability queries (e.g. `byA11yHint`, `byA11yLabel`, etc). +In general, you should avoid `*byTestId` queries when possible. Use queries that check things that the user can interact with. Like `*ByText` or `*ByPlaceholder` or accessibility queries (e.g. `*ByA11yHint`, `*ByA11yLabel`). ::: ## Deprecated `flushMicrotasksQueue` -We have deprecated `flushMicrotasksQueue` and plan to remove it in the next major version, as currently there are better alternatives available for helping you write async tests: `findBy` async queries and `waitFor` helper. +We have deprecated `flushMicrotasksQueue` and plan to remove it in the next major. We have better alternatives available for helping you write async tests – `findBy` async queries and `waitFor` helper. -If you can't or don't want to migrate your tests, you can get rid of the deprecation warning by copy-pasting function's implementation into your project: +If you can't or don't want to migrate your tests, don't worry. You can use the same implementation we have today: ```js function flushMicrotasksQueue() { From 10bae7d89126d4a49d7e2e8e47d61e537cb7dd86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Thu, 21 May 2020 09:49:53 +0200 Subject: [PATCH 14/16] v2.0.0-rc.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7d50ae132..d1411f351 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-testing-library", - "version": "1.14.0", + "version": "2.0.0-rc.0", "description": "Simple React Native testing utilities helping you write better tests with less effort", "main": "build/index.js", "typings": "./typings/index.d.ts", From 4adb92b608f69c47b1de4ccf05c04baa6ebea9bd Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Thu, 21 May 2020 16:07:57 +0200 Subject: [PATCH 15/16] feat: wrap `waitFor` in (async) act to support async queries without explicit `act` call (#344) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Wrapped waitFor call in act to support async waitFor/findBy * Updated docs * Spelling mistake * Added short info in migration guide * Corrected spelling * Clarified comments * Added async act implementation conditional on React version * Code review changes * Update src/waitFor.js Co-authored-by: Michał Pierzchała * Update src/waitFor.js Co-authored-by: Michał Pierzchała * Fixed prettier * Update src/waitFor.js Co-authored-by: Michał Pierzchała Co-authored-by: Michał Pierzchała --- src/waitFor.js | 30 +++++++++++++++++++++++++++++- website/docs/API.md | 6 +++++- website/docs/GettingStarted.md | 4 ++++ website/docs/MigrationV2.md | 2 ++ website/docs/Queries.md | 4 ++++ 5 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/waitFor.js b/src/waitFor.js index 5275643e5..1820dbe88 100644 --- a/src/waitFor.js +++ b/src/waitFor.js @@ -1,16 +1,25 @@ // @flow +import * as React from 'react'; +import act from './act'; import { throwRemovedFunctionError } from './helpers/errors'; const DEFAULT_TIMEOUT = 4500; const DEFAULT_INTERVAL = 50; +function checkReactVersionAtLeast(major: number, minor: number): boolean { + if (React.version === undefined) return false; + const [actualMajor, actualMinor] = React.version.split('.').map(Number); + + return actualMajor > major || (actualMajor === major && actualMinor >= minor); +} + export type WaitForOptions = { timeout?: number, interval?: number, }; -export default function waitFor( +function waitForInternal( expectation: () => T, options?: WaitForOptions ): Promise { @@ -38,6 +47,25 @@ export default function waitFor( }); } +export default async function waitFor( + expectation: () => T, + options?: WaitForOptions +): Promise { + if (!checkReactVersionAtLeast(16, 9)) { + return waitForInternal(expectation, options); + } + + let result: T; + + //$FlowFixMe: `act` has incorrect flow typing + await act(async () => { + result = await waitForInternal(expectation, options); + }); + + //$FlowFixMe: either we have result or `waitFor` threw error + return result; +} + export function waitForElement( expectation: () => T, _timeout: number = 4500, diff --git a/website/docs/API.md b/website/docs/API.md index 5d2adc734..a5ad19ed1 100644 --- a/website/docs/API.md +++ b/website/docs/API.md @@ -336,6 +336,10 @@ function waitFor( Waits for non-deterministic periods of time until your element appears or times out. `waitFor` periodically calls `expectation` every `interval` milliseconds to determine whether the element appeared or not. +:::info +In order to properly use `waitFor` you need at least React >=16.9.0 (featuring async `act`) or React Native >=0.60 (which comes with React >=16.9.0). +::: + ```jsx import { render, waitFor } from 'react-testing-library'; @@ -403,4 +407,4 @@ expect(submitButtons).toHaveLength(3); // expect 3 elements ## `act` -Useful function to help testing components that use hooks API. By default any `render`, `update`, and `fireEvent` calls are wrapped by this function, so there is no need to wrap it manually. This method is re-exported from [`react-test-renderer`](https://github.com/facebook/react/blob/master/packages/react-test-renderer/src/ReactTestRenderer.js#L567]). +Useful function to help testing components that use hooks API. By default any `render`, `update`, `fireEvent`, and `waitFor` calls are wrapped by this function, so there is no need to wrap it manually. This method is re-exported from [`react-test-renderer`](https://github.com/facebook/react/blob/master/packages/react-test-renderer/src/ReactTestRenderer.js#L567]). diff --git a/website/docs/GettingStarted.md b/website/docs/GettingStarted.md index dc69fac3e..ebe68f750 100644 --- a/website/docs/GettingStarted.md +++ b/website/docs/GettingStarted.md @@ -66,6 +66,10 @@ This library has a peerDependencies listing for `react-test-renderer` and, of co As you may have noticed, it's not tied to React Native at all – you can safely use it in your React components if you feel like not interacting directly with DOM. +:::info +In order to properly use helpers for async tests (`findBy` queries and `waitFor`) you need at least React >=16.9.0 (featuring async `act`) or React Native >=0.60 (which comes with React >=16.9.0). +::: + ### Additional Jest matchers In order to use addtional React Native-specific jest matchers from [@testing-library/jest-native](https://github.com/testing-library/jest-native) package add it to your project: diff --git a/website/docs/MigrationV2.md b/website/docs/MigrationV2.md index 9c1c08f53..2a0a5a98c 100644 --- a/website/docs/MigrationV2.md +++ b/website/docs/MigrationV2.md @@ -58,6 +58,8 @@ export default function waitFor( Both changes should improve code readibility. +`waitFor` calls (and hence also `findBy` queries) are now wrapped in `act` by default, so that you should no longer need to use `act` directly in your tests. + :::tip You can usually avoid `waitFor` by a proper use of `findBy` asynchronous queries. It will result in more streamlined testing experience. ::: diff --git a/website/docs/Queries.md b/website/docs/Queries.md index a0c0fd705..5a288b119 100644 --- a/website/docs/Queries.md +++ b/website/docs/Queries.md @@ -32,6 +32,10 @@ title: Queries `findAllBy` queries return a promise which resolves to an array when any matching elements are found. The promise is rejected if no elements match after a default timeout of 4500ms. +:::info +In order to properly use `findBy` and `findAllBy` queries you need at least React >=16.9.0 (featuring async `act`) or React Native >=0.60 (which comes with React >=16.9.0). +::: + :::info `findBy` and `findAllBy` queries accept optional `waitForOptions` object argument which can contain `timeout` and `interval` properies which have the same meaning as respective options for [`waitFor`](https://callstack.github.io/react-native-testing-library/docs/api#waitfor) function. ::: From c2c39c46430070daa30e51b1f3b3dbfa12bf5aa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Thu, 21 May 2020 16:18:17 +0200 Subject: [PATCH 16/16] v2.0.0-rc.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d1411f351..a7f2844bb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-testing-library", - "version": "2.0.0-rc.0", + "version": "2.0.0-rc.1", "description": "Simple React Native testing utilities helping you write better tests with less effort", "main": "build/index.js", "typings": "./typings/index.d.ts",