From 36e425e7335df8bba4323de83962093abd1af461 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Fri, 27 Jan 2023 14:30:06 +0000 Subject: [PATCH 01/11] refactor: fix `container` API --- .../render.breaking.test.tsx.snap | 39 +++ src/__tests__/render.breaking.test.tsx | 253 ++++++++++++++++++ src/__tests__/render.test.tsx | 18 +- src/render.tsx | 15 +- website/docs/API.md | 27 +- 5 files changed, 346 insertions(+), 6 deletions(-) create mode 100644 src/__tests__/__snapshots__/render.breaking.test.tsx.snap create mode 100644 src/__tests__/render.breaking.test.tsx diff --git a/src/__tests__/__snapshots__/render.breaking.test.tsx.snap b/src/__tests__/__snapshots__/render.breaking.test.tsx.snap new file mode 100644 index 000000000..7b42db7e6 --- /dev/null +++ b/src/__tests__/__snapshots__/render.breaking.test.tsx.snap @@ -0,0 +1,39 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`toJSON 1`] = ` + + + press me + + +`; diff --git a/src/__tests__/render.breaking.test.tsx b/src/__tests__/render.breaking.test.tsx new file mode 100644 index 000000000..3c4cf0a8f --- /dev/null +++ b/src/__tests__/render.breaking.test.tsx @@ -0,0 +1,253 @@ +/* eslint-disable no-console */ +import * as React from 'react'; +import { View, Text, TextInput, Pressable, SafeAreaView } from 'react-native'; +import { render, screen, fireEvent, RenderAPI } from '..'; +import { configureInternal } from '../config'; + +type ConsoleLogMock = jest.Mock; + +beforeEach(() => { + configureInternal({ useBreakingChanges: true }); +}); + +const PLACEHOLDER_FRESHNESS = 'Add custom freshness'; +const PLACEHOLDER_CHEF = 'Who inspected freshness?'; +const INPUT_FRESHNESS = 'Custom Freshie'; +const INPUT_CHEF = 'I inspected freshie'; +const DEFAULT_INPUT_CHEF = 'What did you inspect?'; +const DEFAULT_INPUT_CUSTOMER = 'What banana?'; + +class MyButton extends React.Component { + render() { + return ( + + {this.props.children} + + ); + } +} + +class Banana extends React.Component { + state = { + fresh: false, + }; + + componentDidUpdate() { + if (this.props.onUpdate) { + this.props.onUpdate(); + } + } + + componentWillUnmount() { + if (this.props.onUnmount) { + this.props.onUnmount(); + } + } + + changeFresh = () => { + this.setState((state) => ({ + fresh: !state.fresh, + })); + }; + + render() { + const test = 0; + return ( + + Is the banana fresh? + + {this.state.fresh ? 'fresh' : 'not fresh'} + + + + + + + Change freshness! + + First Text + Second Text + {test} + + ); + } +} + +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(() => UNSAFE_getAllByType(InExistent)).toThrow('No instances found'); + + expect(UNSAFE_queryAllByType(Text)[1]).toBe(status); + expect(UNSAFE_queryAllByType(InExistent)).toHaveLength(0); +}); + +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(() => UNSAFE_getByProps({ type: 'inexistent' })).toThrow( + 'No instances found' + ); + + expect(UNSAFE_queryByProps({ type: 'primary' })).toBe(primaryType); + expect(UNSAFE_queryByProps({ type: 'inexistent' })).toBeNull(); +}); + +test('UNSAFE_getAllByProp, UNSAFE_queryAllByProps', () => { + const { UNSAFE_getAllByProps, UNSAFE_queryAllByProps } = render(); + const primaryTypes = UNSAFE_getAllByProps({ type: 'primary' }); + + expect(primaryTypes).toHaveLength(1); + expect(() => UNSAFE_getAllByProps({ type: 'inexistent' })).toThrow( + 'No instances found' + ); + + expect(UNSAFE_queryAllByProps({ type: 'primary' })).toEqual(primaryTypes); + expect(UNSAFE_queryAllByProps({ type: 'inexistent' })).toHaveLength(0); +}); + +test('update', () => { + const fn = jest.fn(); + const { getByText, update, rerender } = render(); + + fireEvent.press(getByText('Change freshness!')); + + update(); + rerender(); + + expect(fn).toHaveBeenCalledTimes(3); +}); + +test('unmount', () => { + const fn = jest.fn(); + const { unmount } = render(); + unmount(); + expect(fn).toHaveBeenCalled(); +}); + +test('unmount should handle cleanup functions', () => { + const cleanup = jest.fn(); + const Component = () => { + React.useEffect(() => cleanup); + return null; + }; + + const { unmount } = render(); + + unmount(); + + expect(cleanup).toHaveBeenCalledTimes(1); +}); + +test('toJSON', () => { + const { toJSON } = render(press me); + + expect(toJSON()).toMatchSnapshot(); +}); + +test('renders options.wrapper around node', () => { + type WrapperComponentProps = { children: React.ReactNode }; + const WrapperComponent = ({ children }: WrapperComponentProps) => ( + {children} + ); + + const { toJSON, getByTestId } = render(, { + wrapper: WrapperComponent, + }); + + expect(getByTestId('wrapper')).toBeTruthy(); + expect(toJSON()).toMatchInlineSnapshot(` + + + + `); +}); + +test('renders options.wrapper around updated node', () => { + type WrapperComponentProps = { children: React.ReactNode }; + const WrapperComponent = ({ children }: WrapperComponentProps) => ( + {children} + ); + + const { toJSON, getByTestId, rerender } = render(, { + wrapper: WrapperComponent, + }); + + rerender( + + ); + + expect(getByTestId('wrapper')).toBeTruthy(); + expect(toJSON()).toMatchInlineSnapshot(` + + + + `); +}); + +test('returns host root', () => { + const { root } = render(); + + expect(root).toBeDefined(); + expect(root.type).toBe('View'); + expect(root.props.testID).toBe('inner'); +}); + +test('returns UNSAFE_root', () => { + const { UNSAFE_root } = render(); + + expect(UNSAFE_root).toBeDefined(); + expect(UNSAFE_root.type).toBe(View); + expect(UNSAFE_root.props.testID).toBe('inner'); +}); + +test('container displays deprecation', () => { + jest.spyOn(console, 'warn').mockImplementation(() => {}); + const mockCalls = (console.warn as ConsoleLogMock).mock.calls; + const view = render(); + + expect(() => view.container).toThrowErrorMatchingInlineSnapshot( + `"'container' property has been renamed to 'UNSAFE_root'"` + ); + expect(mockCalls[0][0]).toMatchInlineSnapshot( + `"'container' property has been renamed to 'UNSAFE_root'"` + ); + + expect(() => screen.container).toThrowErrorMatchingInlineSnapshot( + `"'container' property has been renamed to 'UNSAFE_root'"` + ); + expect(mockCalls[1][0]).toMatchInlineSnapshot( + `"'container' property has been renamed to 'UNSAFE_root'"` + ); +}); + +test('RenderAPI type', () => { + render() as RenderAPI; + expect(true).toBeTruthy(); +}); diff --git a/src/__tests__/render.test.tsx b/src/__tests__/render.test.tsx index 53a0185c8..ed88dad3b 100644 --- a/src/__tests__/render.test.tsx +++ b/src/__tests__/render.test.tsx @@ -204,6 +204,22 @@ test('renders options.wrapper around updated node', () => { `); }); +test('returns host root', () => { + const { root } = render(); + + expect(root).toBeDefined(); + expect(root.type).toBe('View'); + expect(root.props.testID).toBe('inner'); +}); + +test('returns UNSAFE_root', () => { + const { UNSAFE_root } = render(); + + expect(UNSAFE_root).toBeDefined(); + expect(UNSAFE_root.type).toBe(View); + expect(UNSAFE_root.props.testID).toBe('inner'); +}); + test('returns container', () => { const { container } = render(); @@ -214,7 +230,7 @@ test('returns container', () => { expect(container.props.testID).toBe('inner'); }); -test('returns wrapped component as container', () => { +test('returns wrapper component as container', () => { type WrapperComponentProps = { children: React.ReactNode }; const WrapperComponent = ({ children }: WrapperComponentProps) => ( {children} diff --git a/src/render.tsx b/src/render.tsx index b60d15139..e52749500 100644 --- a/src/render.tsx +++ b/src/render.tsx @@ -10,6 +10,7 @@ import { getQueriesForElement } from './within'; import { setRenderResult, screen } from './screen'; import { validateStringsRenderedWithinText } from './helpers/stringValidation'; import { getConfig } from './config'; +import { getHostChildren } from './helpers/component-tree'; import { configureHostComponentNamesIfNeeded } from './helpers/host-component-names'; export type RenderOptions = { @@ -103,10 +104,22 @@ function buildRenderResult( ...getQueriesForElement(instance), update, unmount, - container: instance, rerender: update, // alias for `update` toJSON: renderer.toJSON, debug: debug(instance, renderer), + get root() { + return getHostChildren(instance)[0]; + }, + UNSAFE_root: instance, + get container() { + if (!getConfig().useBreakingChanges) { + return instance; + } + + // eslint-disable-next-line no-console + console.warn(`'container' property has been renamed to 'UNSAFE_root'`); + throw new Error("'container' property has been renamed to 'UNSAFE_root'"); + }, }; setRenderResult(result); diff --git a/website/docs/API.md b/website/docs/API.md index a859bd008..3f954dca0 100644 --- a/website/docs/API.md +++ b/website/docs/API.md @@ -19,7 +19,8 @@ title: API - [`mapProps` option](#mapprops-option) - [`debug.shallow`](#debugshallow) - [`toJSON`](#tojson) - - [`container`](#container) + - [`root`](#root) + - [`UNSAFE_root`](#unsafe_root) - [`screen`](#screen) - [`cleanup`](#cleanup) - [`fireEvent`](#fireevent) @@ -251,13 +252,31 @@ toJSON(): ReactTestRendererJSON | null Get the rendered component JSON representation, e.g. for snapshot testing. -### `container` +### `root` ```ts -container: ReactTestInstance; +root: ReactTestInstance; ``` -A reference to the rendered root element. +Returns the rendered root host element. + +This API is primarily useful in component tests, as it allows you to access root host view without using `ByTestId` queries or similar methods. + +### `UNSAFE_root` + +```ts +UNSAFE_root: ReactTestInstance; +``` + +Returns the rendered root element. + +::caution +This API typically will return a composite view which goes against recommended testing practices. This API is primarily available for legacy test suits that rely on such testing. +:: + +::note +Previously this API has been named `container` for compatibility with React Testing Library. However, despite the same name, the actual behavior has been signficantly different, hence the name change to `UNSAFE_root`. +:: ## `screen` From cba854e2db79459d9eb23b2807700dd6b2a1bcb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Fri, 27 Jan 2023 14:31:28 +0000 Subject: [PATCH 02/11] chore: fix ts --- src/screen.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/screen.ts b/src/screen.ts index 34a1504d5..9de2c5465 100644 --- a/src/screen.ts +++ b/src/screen.ts @@ -16,6 +16,12 @@ const defaultScreen: RenderResult = { get container(): ReactTestInstance { throw new Error(SCREEN_ERROR); }, + get root(): ReactTestInstance { + throw new Error(SCREEN_ERROR); + }, + get UNSAFE_root(): ReactTestInstance { + throw new Error(SCREEN_ERROR); + }, debug: notImplementedDebug, update: notImplemented, unmount: notImplemented, From 04c42b148a1214faa41811346d00f3f571645f86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Fri, 27 Jan 2023 14:31:50 +0000 Subject: [PATCH 03/11] chore: fix lint --- src/__tests__/render.breaking.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/__tests__/render.breaking.test.tsx b/src/__tests__/render.breaking.test.tsx index 3c4cf0a8f..40c390164 100644 --- a/src/__tests__/render.breaking.test.tsx +++ b/src/__tests__/render.breaking.test.tsx @@ -1,6 +1,6 @@ /* eslint-disable no-console */ import * as React from 'react'; -import { View, Text, TextInput, Pressable, SafeAreaView } from 'react-native'; +import { View, Text, TextInput, Pressable } from 'react-native'; import { render, screen, fireEvent, RenderAPI } from '..'; import { configureInternal } from '../config'; From 28add877121a7be517352020cebf79772334e08c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Fri, 27 Jan 2023 14:36:58 +0000 Subject: [PATCH 04/11] refactor: self code review --- src/__tests__/render.test.tsx | 2 +- website/docs/API.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/__tests__/render.test.tsx b/src/__tests__/render.test.tsx index ed88dad3b..7015dfccb 100644 --- a/src/__tests__/render.test.tsx +++ b/src/__tests__/render.test.tsx @@ -212,7 +212,7 @@ test('returns host root', () => { expect(root.props.testID).toBe('inner'); }); -test('returns UNSAFE_root', () => { +test('returns composite UNSAFE_root', () => { const { UNSAFE_root } = render(); expect(UNSAFE_root).toBeDefined(); diff --git a/website/docs/API.md b/website/docs/API.md index 3f954dca0..09c246061 100644 --- a/website/docs/API.md +++ b/website/docs/API.md @@ -258,7 +258,7 @@ Get the rendered component JSON representation, e.g. for snapshot testing. root: ReactTestInstance; ``` -Returns the rendered root host element. +Returns the rendered root [host element](testing-env#host-and-composite-components). This API is primarily useful in component tests, as it allows you to access root host view without using `ByTestId` queries or similar methods. @@ -268,14 +268,14 @@ This API is primarily useful in component tests, as it allows you to access root UNSAFE_root: ReactTestInstance; ``` -Returns the rendered root element. +Returns the rendered [composite root element](testing-env#host-and-composite-components). ::caution This API typically will return a composite view which goes against recommended testing practices. This API is primarily available for legacy test suits that rely on such testing. :: ::note -Previously this API has been named `container` for compatibility with React Testing Library. However, despite the same name, the actual behavior has been signficantly different, hence the name change to `UNSAFE_root`. +This API has been previously named `container` for compatibility with [React Testing Library](https://testing-library.com/docs/react-testing-library/api#container-1). However, despite the same name, the actual behavior has been signficantly different, hence the name change to `UNSAFE_root`. :: ## `screen` From 5bec505d8579d0a553368e2731abe04a750a4e3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Fri, 27 Jan 2023 14:42:13 +0000 Subject: [PATCH 05/11] chore: improve codecov --- src/__tests__/screen.test.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/__tests__/screen.test.tsx b/src/__tests__/screen.test.tsx index 10f1751d4..f13685a51 100644 --- a/src/__tests__/screen.test.tsx +++ b/src/__tests__/screen.test.tsx @@ -52,6 +52,10 @@ test('screen works with nested re-mounting rerender', () => { }); test('screen throws without render', () => { + expect(() => screen.root).toThrow('`render` method has not been called'); + expect(() => screen.UNSAFE_root).toThrow( + '`render` method has not been called' + ); expect(() => screen.container).toThrow('`render` method has not been called'); expect(() => screen.debug()).toThrow('`render` method has not been called'); expect(() => screen.debug.shallow()).toThrow( From 6b0ac909dbc10eaf6418ace820e082cb494aff00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Fri, 27 Jan 2023 14:49:50 +0000 Subject: [PATCH 06/11] chore: flow types --- src/__tests__/render.breaking.test.tsx | 2 +- typings/index.flow.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/__tests__/render.breaking.test.tsx b/src/__tests__/render.breaking.test.tsx index 40c390164..d1e002016 100644 --- a/src/__tests__/render.breaking.test.tsx +++ b/src/__tests__/render.breaking.test.tsx @@ -219,7 +219,7 @@ test('returns host root', () => { expect(root.props.testID).toBe('inner'); }); -test('returns UNSAFE_root', () => { +test('returns composite UNSAFE_root', () => { const { UNSAFE_root } = render(); expect(UNSAFE_root).toBeDefined(); diff --git a/typings/index.flow.js b/typings/index.flow.js index 112609d60..16736cf28 100644 --- a/typings/index.flow.js +++ b/typings/index.flow.js @@ -436,6 +436,8 @@ declare module '@testing-library/react-native' { unmount(nextElement?: React.Element): void; toJSON(): ReactTestRendererJSON[] | ReactTestRendererJSON | null; debug: Debug; + root: ReactTestInstance; + UNSAFE_root: ReactTestInstance; container: ReactTestInstance; } From 9f09e693a3cce3a50b639e659d5153a811618e3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Fri, 27 Jan 2023 15:17:49 +0000 Subject: [PATCH 07/11] chore: code review changes --- .../__snapshots__/render.breaking.test.tsx.snap | 2 +- src/__tests__/__snapshots__/render.test.tsx.snap | 2 +- src/__tests__/render.breaking.test.tsx | 16 +++------------- src/__tests__/render.test.tsx | 2 +- .../__tests__/displayValue.breaking.test.tsx | 2 ++ .../__tests__/placeholderText.breaking.test.tsx | 2 ++ src/render.tsx | 2 -- 7 files changed, 10 insertions(+), 18 deletions(-) diff --git a/src/__tests__/__snapshots__/render.breaking.test.tsx.snap b/src/__tests__/__snapshots__/render.breaking.test.tsx.snap index 7b42db7e6..018ae7d63 100644 --- a/src/__tests__/__snapshots__/render.breaking.test.tsx.snap +++ b/src/__tests__/__snapshots__/render.breaking.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`toJSON 1`] = ` +exports[`toJSON renders host output 1`] = ` ; - beforeEach(() => { configureInternal({ useBreakingChanges: true }); }); @@ -155,9 +155,8 @@ test('unmount should handle cleanup functions', () => { expect(cleanup).toHaveBeenCalledTimes(1); }); -test('toJSON', () => { +test('toJSON renders host output', () => { const { toJSON } = render(press me); - expect(toJSON()).toMatchSnapshot(); }); @@ -228,23 +227,14 @@ test('returns composite UNSAFE_root', () => { }); test('container displays deprecation', () => { - jest.spyOn(console, 'warn').mockImplementation(() => {}); - const mockCalls = (console.warn as ConsoleLogMock).mock.calls; const view = render(); expect(() => view.container).toThrowErrorMatchingInlineSnapshot( `"'container' property has been renamed to 'UNSAFE_root'"` ); - expect(mockCalls[0][0]).toMatchInlineSnapshot( - `"'container' property has been renamed to 'UNSAFE_root'"` - ); - expect(() => screen.container).toThrowErrorMatchingInlineSnapshot( `"'container' property has been renamed to 'UNSAFE_root'"` ); - expect(mockCalls[1][0]).toMatchInlineSnapshot( - `"'container' property has been renamed to 'UNSAFE_root'"` - ); }); test('RenderAPI type', () => { diff --git a/src/__tests__/render.test.tsx b/src/__tests__/render.test.tsx index 7015dfccb..7d77a34e4 100644 --- a/src/__tests__/render.test.tsx +++ b/src/__tests__/render.test.tsx @@ -148,7 +148,7 @@ test('unmount should handle cleanup functions', () => { expect(cleanup).toHaveBeenCalledTimes(1); }); -test('toJSON', () => { +test('toJSON renders host output', () => { const { toJSON } = render(press me); expect(toJSON()).toMatchSnapshot(); diff --git a/src/queries/__tests__/displayValue.breaking.test.tsx b/src/queries/__tests__/displayValue.breaking.test.tsx index 1bcb79b88..01949d375 100644 --- a/src/queries/__tests__/displayValue.breaking.test.tsx +++ b/src/queries/__tests__/displayValue.breaking.test.tsx @@ -1,3 +1,5 @@ +/** This is a copy of regular tests with `useBreakingChanges` flag turned on. */ + import * as React from 'react'; import { View, TextInput } from 'react-native'; diff --git a/src/queries/__tests__/placeholderText.breaking.test.tsx b/src/queries/__tests__/placeholderText.breaking.test.tsx index c3a35fb0d..42962b685 100644 --- a/src/queries/__tests__/placeholderText.breaking.test.tsx +++ b/src/queries/__tests__/placeholderText.breaking.test.tsx @@ -1,3 +1,5 @@ +/** This is a copy of regular test with `useBreakingChanges` flag turned on. */ + import * as React from 'react'; import { View, TextInput } from 'react-native'; import { render } from '../..'; diff --git a/src/render.tsx b/src/render.tsx index e52749500..ba9ae68c5 100644 --- a/src/render.tsx +++ b/src/render.tsx @@ -116,8 +116,6 @@ function buildRenderResult( return instance; } - // eslint-disable-next-line no-console - console.warn(`'container' property has been renamed to 'UNSAFE_root'`); throw new Error("'container' property has been renamed to 'UNSAFE_root'"); }, }; From b65ce8ac3afb82b300fdfa591c31cbbd0e04c206 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Fri, 27 Jan 2023 15:20:12 +0000 Subject: [PATCH 08/11] chore: fix typo --- src/queries/__tests__/placeholderText.breaking.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/queries/__tests__/placeholderText.breaking.test.tsx b/src/queries/__tests__/placeholderText.breaking.test.tsx index 42962b685..ab2c82264 100644 --- a/src/queries/__tests__/placeholderText.breaking.test.tsx +++ b/src/queries/__tests__/placeholderText.breaking.test.tsx @@ -1,4 +1,4 @@ -/** This is a copy of regular test with `useBreakingChanges` flag turned on. */ +/** This is a copy of regular tests with `useBreakingChanges` flag turned on. */ import * as React from 'react'; import { View, TextInput } from 'react-native'; From 39685fe9a2a0ce6cbac2522715b68cf5beee6d4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Fri, 27 Jan 2023 15:27:53 +0000 Subject: [PATCH 09/11] docs: fixes --- website/docs/API.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/website/docs/API.md b/website/docs/API.md index 09c246061..873e09266 100644 --- a/website/docs/API.md +++ b/website/docs/API.md @@ -260,7 +260,7 @@ root: ReactTestInstance; Returns the rendered root [host element](testing-env#host-and-composite-components). -This API is primarily useful in component tests, as it allows you to access root host view without using `ByTestId` queries or similar methods. +This API is primarily useful in component tests, as it allows you to access root host view without using `*ByTestId` queries or similar methods. ### `UNSAFE_root` @@ -270,13 +270,13 @@ UNSAFE_root: ReactTestInstance; Returns the rendered [composite root element](testing-env#host-and-composite-components). -::caution +:::caution This API typically will return a composite view which goes against recommended testing practices. This API is primarily available for legacy test suits that rely on such testing. -:: +::: -::note +:::note This API has been previously named `container` for compatibility with [React Testing Library](https://testing-library.com/docs/react-testing-library/api#container-1). However, despite the same name, the actual behavior has been signficantly different, hence the name change to `UNSAFE_root`. -:: +::: ## `screen` From 3cadbdc841f3d580e19f69d106500dd859904e0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Sat, 28 Jan 2023 21:45:56 +0000 Subject: [PATCH 10/11] refactor: console.warn for current users --- src/__tests__/render.breaking.test.tsx | 16 ++++++++++------ src/__tests__/render.test.tsx | 13 +++++++++++++ src/render.tsx | 10 +++++++++- website/docs/Queries.md | 12 ++++++------ 4 files changed, 38 insertions(+), 13 deletions(-) diff --git a/src/__tests__/render.breaking.test.tsx b/src/__tests__/render.breaking.test.tsx index da9903345..ac6492664 100644 --- a/src/__tests__/render.breaking.test.tsx +++ b/src/__tests__/render.breaking.test.tsx @@ -229,12 +229,16 @@ test('returns composite UNSAFE_root', () => { test('container displays deprecation', () => { const view = render(); - expect(() => view.container).toThrowErrorMatchingInlineSnapshot( - `"'container' property has been renamed to 'UNSAFE_root'"` - ); - expect(() => screen.container).toThrowErrorMatchingInlineSnapshot( - `"'container' property has been renamed to 'UNSAFE_root'"` - ); + expect(() => view.container).toThrowErrorMatchingInlineSnapshot(` + "'container' property has been renamed to 'UNSAFE_root'. + + Consider using 'root' property which returns root host element." + `); + expect(() => screen.container).toThrowErrorMatchingInlineSnapshot(` + "'container' property has been renamed to 'UNSAFE_root'. + + Consider using 'root' property which returns root host element." + `); }); test('RenderAPI type', () => { diff --git a/src/__tests__/render.test.tsx b/src/__tests__/render.test.tsx index 7d77a34e4..145fc6248 100644 --- a/src/__tests__/render.test.tsx +++ b/src/__tests__/render.test.tsx @@ -3,6 +3,12 @@ import * as React from 'react'; import { View, Text, TextInput, Pressable, SafeAreaView } from 'react-native'; import { render, fireEvent, RenderAPI } from '..'; +type ConsoleLogMock = jest.Mock; + +beforeEach(() => { + jest.spyOn(console, 'warn').mockImplementation(() => {}); +}); + const PLACEHOLDER_FRESHNESS = 'Add custom freshness'; const PLACEHOLDER_CHEF = 'Who inspected freshness?'; const INPUT_FRESHNESS = 'Custom Freshie'; @@ -223,6 +229,13 @@ test('returns composite UNSAFE_root', () => { test('returns container', () => { const { container } = render(); + const mockCalls = (console.warn as any as ConsoleLogMock).mock.calls; + expect(mockCalls[0][0]).toMatchInlineSnapshot(` + "'container' property is deprecated and has been renamed to 'UNSAFE_root'. + + Consider using 'root' property which returns root host element." + `); + expect(container).toBeDefined(); // `View` composite component is returned. This behavior will break if we // start returning only host components. diff --git a/src/render.tsx b/src/render.tsx index ba9ae68c5..9fac09ee3 100644 --- a/src/render.tsx +++ b/src/render.tsx @@ -113,10 +113,18 @@ function buildRenderResult( UNSAFE_root: instance, get container() { if (!getConfig().useBreakingChanges) { + // eslint-disable-next-line no-console + console.warn( + "'container' property is deprecated and has been renamed to 'UNSAFE_root'.\n\n" + + "Consider using 'root' property which returns root host element." + ); return instance; } - throw new Error("'container' property has been renamed to 'UNSAFE_root'"); + throw new Error( + "'container' property has been renamed to 'UNSAFE_root'.\n\n" + + "Consider using 'root' property which returns root host element." + ); }, }; diff --git a/website/docs/Queries.md b/website/docs/Queries.md index fa539ec48..e40adb46b 100644 --- a/website/docs/Queries.md +++ b/website/docs/Queries.md @@ -39,7 +39,7 @@ title: Queries ## Variants -> `getBy` queries are shown by default in the [query documentation](#queries) +> `getBy*` queries are shown by default in the [query documentation](#queries) > below. ### getBy @@ -60,22 +60,22 @@ title: Queries ### findBy -`findBy` queries return a promise which resolves when a matching element is found. The promise is rejected if no elements match or if more than one match is found after a default timeout of 1000 ms. If you need to find more than one element, then use `findAllBy`. +`findBy*` queries return a promise which resolves when a matching element is found. The promise is rejected if no elements match or if more than one match is found after a default timeout of 1000 ms. If you need to find more than one element, then use `findAllBy*`. ### findAllBy -`findAllBy` queries return a promise which resolves to an array of matching elements. The promise is rejected if no elements match after a default timeout of 1000 ms. +`findAllBy*` queries return a promise which resolves to an array of matching elements. The promise is rejected if no elements match after a default timeout of 1000 ms. :::info -`findBy` and `findAllBy` queries accept optional `waitForOptions` object argument which can contain `timeout`, `interval` and `onTimeout` properies which have the same meaning as respective options for [`waitFor`](api#waitfor) function. +`findBy*` and `findAllBy*` queries accept optional `waitForOptions` object argument which can contain `timeout`, `interval` and `onTimeout` properies which have the same meaning as respective options for [`waitFor`](api#waitfor) function. ::: :::info -In cases when your `findBy` and `findAllBy` queries throw when not able to find matching elements it is useful to pass `onTimeout: () => { screen.debug(); }` callback using `waitForOptions` parameter. +In cases when your `findBy*` and `findAllBy*` queries throw when not able to find matching elements it is useful to pass `onTimeout: () => { screen.debug(); }` callback using `waitForOptions` parameter. ::: :::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.61 (which comes with React >=16.9.0). +In order to properly use `findBy*` and `findAllBy*` queries you need at least React >=16.9.0 (featuring async `act`) or React Native >=0.61 (which comes with React >=16.9.0). ::: ## Queries From 859770643b2138fbedc8177ba83207020b444cd3 Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Sat, 4 Feb 2023 00:13:44 +0100 Subject: [PATCH 11/11] Update website/docs/API.md Co-authored-by: Pierre Zimmermann <64224599+pierrezimmermannbam@users.noreply.github.com> --- website/docs/API.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/API.md b/website/docs/API.md index 873e09266..3f395fa23 100644 --- a/website/docs/API.md +++ b/website/docs/API.md @@ -271,7 +271,7 @@ UNSAFE_root: ReactTestInstance; Returns the rendered [composite root element](testing-env#host-and-composite-components). :::caution -This API typically will return a composite view which goes against recommended testing practices. This API is primarily available for legacy test suits that rely on such testing. +This API typically will return a composite view which goes against recommended testing practices. This API is primarily available for legacy test suites that rely on such testing. ::: :::note