From ea08d2bb96bc4cc5e364e0746d6280980706669a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Sun, 7 Oct 2018 08:51:26 +0200 Subject: [PATCH 1/5] feat: add query API --- README.md | 24 +++++++++++ src/__tests__/render.test.js | 54 ++++++++++++++++-------- src/helpers/queryBy.js | 81 ++++++++++++++++++++++++++++++++++++ src/render.js | 18 ++++++++ 4 files changed, 160 insertions(+), 17 deletions(-) create mode 100644 src/helpers/queryBy.js diff --git a/README.md b/README.md index f82129b2b..1b9b7abf7 100644 --- a/README.md +++ b/README.md @@ -282,6 +282,30 @@ test('fetch data', async () => { }); ``` +## `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 DOM, then you can use the query API instead: + +```jsx +import { render } from 'react-native-testing-library'; + +const { queryByText } = render(
); +const submitButton = queryByText('submit'); +expect(submitButton).toBeNull(); // it doesn't exist +``` + +## `queryAll` API + +Each of the query APIs have a corresponding queryAll version that always returns an Array of matching nodes. getAll is the same but throws when the array has a length of 0. + +```jsx +import { render } from 'react-native-testing-library'; + +const { queryAllByText } = render(); +const submitButtons = queryAllByText('submit'); +expect(submitButtons).toHaveLength(3); // expect 3 elements +``` + [build-badge]: https://img.shields.io/circleci/project/github/callstack/react-native-testing-library/master.svg?style=flat-square diff --git a/src/__tests__/render.test.js b/src/__tests__/render.test.js index a952ebd10..3b6a28ddb 100644 --- a/src/__tests__/render.test.js +++ b/src/__tests__/render.test.js @@ -51,16 +51,19 @@ class Banana extends React.Component { } } -test('getByTestId', () => { - const { getByTestId } = render(); +test('getByTestId, queryByTestId', () => { + const { getByTestId, queryByTestId } = render(); const component = getByTestId('bananaFresh'); expect(component.props.children).toBe('not fresh'); expect(() => getByTestId('InExistent')).toThrow(); + + expect(getByTestId('bananaFresh')).toBe(component); + expect(queryByTestId('InExistent')).toBeNull(); }); -test('getByName', () => { - const { getByTestId, getByName } = render(); +test('getByName, queryByName', () => { + const { getByTestId, getByName, queryByName } = render(); const bananaFresh = getByTestId('bananaFresh'); const button = getByName('Button'); @@ -72,22 +75,27 @@ test('getByName', () => { sameButton.props.onPress(); expect(bananaFresh.props.children).toBe('not fresh'); - expect(() => getByName('InExistent')).toThrow(); + + expect(queryByName('Button')).toBe(button); + expect(queryByName('InExistent')).toBeNull(); }); -test('getAllByName', () => { - const { getAllByName } = render(); +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(); + + expect(queryAllByName('Text')[1]).toBe(status); + expect(queryAllByName('InExistent')).toBeNull(); }); -test('getByText', () => { - const { getByText } = render(); +test('getByText, queryByText', () => { + const { getByText, queryByText } = render(); const button = getByText(/change/i); expect(button.props.children).toBe('Change freshness!'); @@ -96,30 +104,42 @@ test('getByText', () => { expect(sameButton.props.children).toBe('not fresh'); expect(() => getByText('InExistent')).toThrow(); + + expect(queryByText(/change/i)).toBe(button); + expect(queryByText('InExistent')).toBeNull(); }); -test('getAllByText', () => { - const { getAllByText } = render(); - const button = getAllByText(/fresh/i); +test('getAllByText, queryAllByText', () => { + const { getAllByText, queryAllByText } = render(); + const buttons = getAllByText(/fresh/i); - expect(button).toHaveLength(3); + expect(buttons).toHaveLength(3); expect(() => getAllByText('InExistent')).toThrow(); + + expect(queryAllByText(/fresh/i)).toEqual(buttons); + expect(queryAllByText('InExistent')).toBeNull(); }); -test('getByProps', () => { - const { getByProps } = render(); +test('getByProps, queryByProps', () => { + const { getByProps, queryByProps } = render(); const primaryType = getByProps({ type: 'primary' }); expect(primaryType.props.children).toBe('Change freshness!'); expect(() => getByProps({ type: 'inexistent' })).toThrow(); + + expect(queryByProps({ type: 'primary' })).toBe(primaryType); + expect(queryByProps({ type: 'inexistent' })).toBeNull(); }); -test('getAllByProps', () => { - const { getAllByProps } = render(); +test('getAllByProp, queryAllByProps', () => { + const { getAllByProps, queryAllByProps } = render(); const primaryTypes = getAllByProps({ type: 'primary' }); expect(primaryTypes).toHaveLength(1); expect(() => getAllByProps({ type: 'inexistent' })).toThrow(); + + expect(queryAllByProps({ type: 'primary' })).toEqual(primaryTypes); + expect(queryAllByProps({ type: 'inexistent' })).toBeNull(); }); test('update', () => { diff --git a/src/helpers/queryBy.js b/src/helpers/queryBy.js new file mode 100644 index 000000000..1246583fd --- /dev/null +++ b/src/helpers/queryBy.js @@ -0,0 +1,81 @@ +// @flow +import * as React from 'react'; +import { + getByTestId, + getByName, + getByText, + getByProps, + getAllByName, + getAllByText, + getAllByProps, +} from './getBy'; + +export const queryByName = (instance: ReactTestInstance) => ( + name: string | React.Element<*> +) => { + try { + return getByName(instance)(name); + } catch (error) { + return null; + } +}; + +export const queryByText = (instance: ReactTestInstance) => ( + text: string | RegExp +) => { + try { + return getByText(instance)(text); + } catch (error) { + return null; + } +}; + +export const queryByProps = (instance: ReactTestInstance) => (props: { + [propName: string]: any, +}) => { + try { + return getByProps(instance)(props); + } catch (error) { + return null; + } +}; + +export const queryByTestId = (instance: ReactTestInstance) => ( + testID: string +) => { + try { + return getByTestId(instance)(testID); + } catch (error) { + return null; + } +}; + +export const queryAllByName = (instance: ReactTestInstance) => ( + name: string | React.Element<*> +) => { + try { + return getAllByName(instance)(name); + } catch (error) { + return null; + } +}; + +export const queryAllByText = (instance: ReactTestInstance) => ( + text: string | RegExp +) => { + try { + return getAllByText(instance)(text); + } catch (error) { + return null; + } +}; + +export const queryAllByProps = (instance: ReactTestInstance) => (props: { + [propName: string]: any, +}) => { + try { + return getAllByProps(instance)(props); + } catch (error) { + return null; + } +}; diff --git a/src/render.js b/src/render.js index f41583655..cbdbc2f28 100644 --- a/src/render.js +++ b/src/render.js @@ -10,6 +10,15 @@ import { getAllByText, getAllByProps, } from './helpers/getBy'; +import { + queryByTestId, + queryByName, + queryByText, + queryByProps, + queryAllByName, + queryAllByText, + queryAllByProps, +} from './helpers/queryBy'; /** * Renders test component deeply using react-test-renderer and exposes helpers @@ -30,6 +39,15 @@ export default function render( getAllByName: getAllByName(instance), getAllByText: getAllByText(instance), getAllByProps: getAllByProps(instance), + + queryByTestId: queryByTestId(instance), + queryByName: queryByName(instance), + queryByText: queryByText(instance), + queryByProps: queryByProps(instance), + queryAllByName: queryAllByName(instance), + queryAllByText: queryAllByText(instance), + queryAllByProps: queryAllByProps(instance), + update: renderer.update, unmount: renderer.unmount, }; From b7b16321565cfe29b7a2d32bbe31facf6183d8a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Sun, 7 Oct 2018 08:56:11 +0200 Subject: [PATCH 2/5] use plural for API --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1b9b7abf7..c038edc15 100644 --- a/README.md +++ b/README.md @@ -294,7 +294,7 @@ const submitButton = queryByText('submit'); expect(submitButton).toBeNull(); // it doesn't exist ``` -## `queryAll` API +## `queryAll` APIs Each of the query APIs have a corresponding queryAll version that always returns an Array of matching nodes. getAll is the same but throws when the array has a length of 0. From f2be9be4bc0c0ce0b7e145e3a9352f51c625ab2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Sun, 7 Oct 2018 08:59:40 +0200 Subject: [PATCH 3/5] return empty array from queryAll --- src/__tests__/render.test.js | 6 +++--- src/helpers/queryBy.js | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/__tests__/render.test.js b/src/__tests__/render.test.js index 3b6a28ddb..98a6c7cbc 100644 --- a/src/__tests__/render.test.js +++ b/src/__tests__/render.test.js @@ -91,7 +91,7 @@ test('getAllByName, queryAllByName', () => { expect(() => getAllByName('InExistent')).toThrow(); expect(queryAllByName('Text')[1]).toBe(status); - expect(queryAllByName('InExistent')).toBeNull(); + expect(queryAllByName('InExistent')).toHaveLength(0); }); test('getByText, queryByText', () => { @@ -117,7 +117,7 @@ test('getAllByText, queryAllByText', () => { expect(() => getAllByText('InExistent')).toThrow(); expect(queryAllByText(/fresh/i)).toEqual(buttons); - expect(queryAllByText('InExistent')).toBeNull(); + expect(queryAllByText('InExistent')).toHaveLength(0); }); test('getByProps, queryByProps', () => { @@ -139,7 +139,7 @@ test('getAllByProp, queryAllByProps', () => { expect(() => getAllByProps({ type: 'inexistent' })).toThrow(); expect(queryAllByProps({ type: 'primary' })).toEqual(primaryTypes); - expect(queryAllByProps({ type: 'inexistent' })).toBeNull(); + expect(queryAllByProps({ type: 'inexistent' })).toHaveLength(0); }); test('update', () => { diff --git a/src/helpers/queryBy.js b/src/helpers/queryBy.js index 1246583fd..7a6648997 100644 --- a/src/helpers/queryBy.js +++ b/src/helpers/queryBy.js @@ -56,7 +56,7 @@ export const queryAllByName = (instance: ReactTestInstance) => ( try { return getAllByName(instance)(name); } catch (error) { - return null; + return []; } }; @@ -66,7 +66,7 @@ export const queryAllByText = (instance: ReactTestInstance) => ( try { return getAllByText(instance)(text); } catch (error) { - return null; + return []; } }; @@ -76,6 +76,6 @@ export const queryAllByProps = (instance: ReactTestInstance) => (props: { try { return getAllByProps(instance)(props); } catch (error) { - return null; + return []; } }; From f23b1d66d1c99e4323a176f82652f3a865c56bef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Sun, 7 Oct 2018 10:42:42 +0200 Subject: [PATCH 4/5] fix wording in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c038edc15..a7e92f7fa 100644 --- a/README.md +++ b/README.md @@ -284,7 +284,7 @@ test('fetch data', async () => { ## `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 DOM, then you can use the query API instead: +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: ```jsx import { render } from 'react-native-testing-library'; From 2f12e2908a4a6dd2ed5dc5cfefd8c58dec7459da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Sun, 7 Oct 2018 10:52:06 +0200 Subject: [PATCH 5/5] move mass exports to get/query files --- src/helpers/{getBy.js => getByAPI.js} | 10 ++++++ src/helpers/{queryBy.js => queryByAPI.js} | 12 ++++++- src/render.js | 38 +++-------------------- 3 files changed, 25 insertions(+), 35 deletions(-) rename src/helpers/{getBy.js => getByAPI.js} (87%) rename src/helpers/{queryBy.js => queryByAPI.js} (79%) diff --git a/src/helpers/getBy.js b/src/helpers/getByAPI.js similarity index 87% rename from src/helpers/getBy.js rename to src/helpers/getByAPI.js index 783c9e2c6..312625117 100644 --- a/src/helpers/getBy.js +++ b/src/helpers/getByAPI.js @@ -75,3 +75,13 @@ export const getAllByProps = (instance: ReactTestInstance) => (props: { } return results; }; + +export const getByAPI = (instance: ReactTestInstance) => ({ + getByTestId: getByTestId(instance), + getByName: getByName(instance), + getByText: getByText(instance), + getByProps: getByProps(instance), + getAllByName: getAllByName(instance), + getAllByText: getAllByText(instance), + getAllByProps: getAllByProps(instance), +}); diff --git a/src/helpers/queryBy.js b/src/helpers/queryByAPI.js similarity index 79% rename from src/helpers/queryBy.js rename to src/helpers/queryByAPI.js index 7a6648997..0005f6dce 100644 --- a/src/helpers/queryBy.js +++ b/src/helpers/queryByAPI.js @@ -8,7 +8,7 @@ import { getAllByName, getAllByText, getAllByProps, -} from './getBy'; +} from './getByAPI'; export const queryByName = (instance: ReactTestInstance) => ( name: string | React.Element<*> @@ -79,3 +79,13 @@ export const queryAllByProps = (instance: ReactTestInstance) => (props: { return []; } }; + +export const queryByAPI = (instance: ReactTestInstance) => ({ + queryByTestId: queryByTestId(instance), + queryByName: queryByName(instance), + queryByText: queryByText(instance), + queryByProps: queryByProps(instance), + queryAllByName: queryAllByName(instance), + queryAllByText: queryAllByText(instance), + queryAllByProps: queryAllByProps(instance), +}); diff --git a/src/render.js b/src/render.js index cbdbc2f28..0a3184ede 100644 --- a/src/render.js +++ b/src/render.js @@ -1,24 +1,8 @@ // @flow import * as React from 'react'; import TestRenderer from 'react-test-renderer'; // eslint-disable-line import/no-extraneous-dependencies -import { - getByTestId, - getByName, - getByText, - getByProps, - getAllByName, - getAllByText, - getAllByProps, -} from './helpers/getBy'; -import { - queryByTestId, - queryByName, - queryByText, - queryByProps, - queryAllByName, - queryAllByText, - queryAllByProps, -} from './helpers/queryBy'; +import { getByAPI } from './helpers/getByAPI'; +import { queryByAPI } from './helpers/queryByAPI'; /** * Renders test component deeply using react-test-renderer and exposes helpers @@ -32,22 +16,8 @@ export default function render( const instance = renderer.root; return { - getByTestId: getByTestId(instance), - getByName: getByName(instance), - getByText: getByText(instance), - getByProps: getByProps(instance), - getAllByName: getAllByName(instance), - getAllByText: getAllByText(instance), - getAllByProps: getAllByProps(instance), - - queryByTestId: queryByTestId(instance), - queryByName: queryByName(instance), - queryByText: queryByText(instance), - queryByProps: queryByProps(instance), - queryAllByName: queryAllByName(instance), - queryAllByText: queryAllByText(instance), - queryAllByProps: queryAllByProps(instance), - + ...getByAPI(instance), + ...queryByAPI(instance), update: renderer.update, unmount: renderer.unmount, };