From 12fca4c24f218daea883080db476fb71ed160f36 Mon Sep 17 00:00:00 2001 From: Jack Pope Date: Tue, 28 May 2024 20:05:05 +0100 Subject: [PATCH 1/3] Add act documentation (#6901) * Add act documentation * Update structure based on feedback * Add usage examples and async act --------- Co-authored-by: Rick Hanlon --- src/content/reference/react/act.md | 177 ++++++++++++++++++++++++++++ src/content/reference/react/apis.md | 1 + src/sidebarReference.json | 10 +- 3 files changed, 185 insertions(+), 3 deletions(-) create mode 100644 src/content/reference/react/act.md diff --git a/src/content/reference/react/act.md b/src/content/reference/react/act.md new file mode 100644 index 0000000000..b1fbe09c50 --- /dev/null +++ b/src/content/reference/react/act.md @@ -0,0 +1,177 @@ +--- +title: act +--- + + + +`act` is a test helper to apply pending React updates before making assertions. + +```js +await act(async actFn) +``` + + + +To prepare a component for assertions, wrap the code rendering it and performing updates inside an `await act()` call. This makes your test run closer to how React works in the browser. + + +You might find using `act()` directly a bit too verbose. To avoid some of the boilerplate, you could use a library like [React Testing Library](https://testing-library.com/docs/react-testing-library/intro), whose helpers are wrapped with `act()`. + + + + + +--- + +## Reference {/*reference*/} + +### `await act(async actFn)` {/*await-act-async-actfn*/} + +When writing UI tests, tasks like rendering, user events, or data fetching can be considered as “units” of interaction with a user interface. React provides a helper called `act()` that makes sure all updates related to these “units” have been processed and applied to the DOM before you make any assertions. + +The name `act` comes from the [Arrange-Act-Assert](https://wiki.c2.com/?ArrangeActAssert) pattern. + +```js {2,4} +it ('renders with button disabled', async () => { + await act(async () => { + root.render() + }); + expect(container.querySelector('button')).toBeDisabled(); +}); +``` + + + +We recommend using `act` with `await` and an `async` function. Although the sync version works in many cases, it doesn't work in all cases and due to the way React schedules updates internally, it's difficult to predict when you can use the sync version. + +We will deprecate and remove the sync version in the future. + + + +#### Parameters {/*parameters*/} + +* `async actFn`: An async function wrapping renders or interactions for components being tested. Any updates triggered within the `actFn`, are added to an internal act queue, which are then flushed together to process and apply any changes to the DOM. Since it is async, React will also run any code that crosses an async boundary, and flush any updates scheduled. + +#### Returns {/*returns*/} + +`act` does not return anything. + +## Usage {/*usage*/} + +When testing a component, you can use `act` to make assertions about its output. + +For example, let’s say we have this `Counter` component, the usage examples below show how to test it: + +```js +function Counter() { + const [count, setCount] = useState(0); + const handleClick = () => { + setCount(prev => prev + 1); + } + + useEffect(() => { + document.title = `You clicked ${this.state.count} times`; + }, [count]); + + return ( +
+

You clicked {this.state.count} times

+ +
+ ) +} +``` + +### Rendering components in tests {/*rendering-components-in-tests*/} + +To test the render output of a component, wrap the render inside `act()`: + +```js {10,12} +import {act} from 'react'; +import ReactDOM from 'react-dom/client'; +import Counter from './Counter'; + +it('can render and update a counter', async () => { + container = document.createElement('div'); + document.body.appendChild(container); + + // ✅ Render the component inside act(). + await act(() => { + ReactDOM.createRoot(container).render(); + }); + + const button = container.querySelector('button'); + const label = container.querySelector('p'); + expect(label.textContent).toBe('You clicked 0 times'); + expect(document.title).toBe('You clicked 0 times'); +}); +``` + +Here, wwe create a container, append it to the document, and render the `Counter` component inside `act()`. This ensures that the component is rendered and its effects are applied before making assertions. + +Using `act` ensures that all updates have been applied before we make assertions. + +### Dispatching events in tests {/*dispatching-events-in-tests*/} + +To test events, wrap the event dispatch inside `act()`: + +```js {14,16} +import {act} from 'react'; +import ReactDOM from 'react-dom/client'; +import Counter from './Counter'; + +it.only('can render and update a counter', async () => { + const container = document.createElement('div'); + document.body.appendChild(container); + + await act( async () => { + ReactDOMClient.createRoot(container).render(); + }); + + // ✅ Dispatch the event inside act(). + await act(async () => { + button.dispatchEvent(new MouseEvent('click', { bubbles: true })); + }); + + const button = container.querySelector('button'); + const label = container.querySelector('p'); + expect(label.textContent).toBe('You clicked 1 times'); + expect(document.title).toBe('You clicked 1 times'); +}); +``` + +Here, we render the component with `act`, and then dispatch the event inside another `act()`. This ensures that all updates from the event are applied before making assertions. + + + +Don’t forget that dispatching DOM events only works when the DOM container is added to the document. You can use a library like [React Testing Library](https://testing-library.com/docs/react-testing-library/intro) to reduce the boilerplate code. + + + +## Troubleshooting {/*troubleshooting*/} + +### I'm getting an error: "The current testing environment is not configured to support act"(...)" {/*error-the-current-testing-environment-is-not-configured-to-support-act*/} + +Using `act` requires setting `global.IS_REACT_ACT_ENVIRONMENT=true` in your test environment. This is to ensure that `act` is only used in the correct environment. + +If you don't set the global, you will see an error like this: + + + +Warning: The current testing environment is not configured to support act(...) + + + +To fix, add this to your global setup file for React tests: + +```js +global.IS_REACT_ACT_ENVIRONMENT=true +``` + + + +In testing frameworks like [React Testing Library](https://testing-library.com/docs/react-testing-library/intro), `IS_REACT_ACT_ENVIRONMENT` is already set for you. + + \ No newline at end of file diff --git a/src/content/reference/react/apis.md b/src/content/reference/react/apis.md index 4dd1d49ed1..cbab4a0e6f 100644 --- a/src/content/reference/react/apis.md +++ b/src/content/reference/react/apis.md @@ -15,6 +15,7 @@ In addition to [Hooks](/reference/react) and [Components](/reference/react/compo * [`lazy`](/reference/react/lazy) lets you defer loading a component's code until it's rendered for the first time. * [`memo`](/reference/react/memo) lets your component skip re-renders with same props. Used with [`useMemo`](/reference/react/useMemo) and [`useCallback`.](/reference/react/useCallback) * [`startTransition`](/reference/react/startTransition) lets you mark a state update as non-urgent. Similar to [`useTransition`.](/reference/react/useTransition) +* [`act`](/reference/react/act) lets you wrap renders and interactions in tests to ensure updates have processed before making assertions. --- diff --git a/src/sidebarReference.json b/src/sidebarReference.json index 7076e76f50..50e0a3dffb 100644 --- a/src/sidebarReference.json +++ b/src/sidebarReference.json @@ -112,6 +112,10 @@ "title": "APIs", "path": "/reference/react/apis", "routes": [ + { + "title": "act", + "path": "/reference/react/act" + }, { "title": "cache", "path": "/reference/react/cache", @@ -342,8 +346,8 @@ "path": "/reference/rules", "routes": [ { - "title": "Components and Hooks must be pure", - "path": "/reference/rules/components-and-hooks-must-be-pure" + "title": "Components and Hooks must be pure", + "path": "/reference/rules/components-and-hooks-must-be-pure" }, { "title": "React calls Components and Hooks", @@ -429,4 +433,4 @@ ] } ] -} +} \ No newline at end of file From 701235ee3d65719b641c4b2202a3c1b447657e1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=85=E7=A7=8B?= <1044514593@qq.com> Date: Wed, 29 May 2024 10:36:06 +0800 Subject: [PATCH 2/3] fix conflicts --- src/content/reference/react/apis.md | 11 +---------- src/sidebarReference.json | 9 ++------- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/src/content/reference/react/apis.md b/src/content/reference/react/apis.md index 3af12627a2..ae001da86a 100644 --- a/src/content/reference/react/apis.md +++ b/src/content/reference/react/apis.md @@ -13,21 +13,12 @@ translators: --- -<<<<<<< HEAD * [`createContext`](/reference/react/createContext) API 可以创建一个 context,你可以将其提供给子组件,通常会与 [`useContext`](/reference/react/useContext) 一起配合使用。 * [`forwardRef`](/reference/react/forwardRef) 允许组件将 DOM 节点作为 ref 暴露给父组件。 * [`lazy`](/reference/react/lazy) 允许你延迟加载组件,直到该组件需要第一次被渲染。 * [`memo`](/reference/react/memo) 允许你在 props 没有变化的情况下跳过组件的重渲染。通常 [`useMemo`](/reference/react/useMemo) 与 [`useCallback`](/reference/react/useCallback) 会一起配合使用。 * [`startTransition`](/reference/react/startTransition) 允许你可以标记一个状态更新是不紧急的。类似于 [`useTransition`](/reference/react/useTransition)。 -======= -* [`createContext`](/reference/react/createContext) lets you define and provide context to the child components. Used with [`useContext`.](/reference/react/useContext) -* [`forwardRef`](/reference/react/forwardRef) lets your component expose a DOM node as a ref to the parent. Used with [`useRef`.](/reference/react/useRef) -* [`lazy`](/reference/react/lazy) lets you defer loading a component's code until it's rendered for the first time. -* [`memo`](/reference/react/memo) lets your component skip re-renders with same props. Used with [`useMemo`](/reference/react/useMemo) and [`useCallback`.](/reference/react/useCallback) -* [`startTransition`](/reference/react/startTransition) lets you mark a state update as non-urgent. Similar to [`useTransition`.](/reference/react/useTransition) -* [`act`](/reference/react/act) lets you wrap renders and interactions in tests to ensure updates have processed before making assertions. ->>>>>>> 12fca4c24f218daea883080db476fb71ed160f36 - +* [`act`](/reference/react/act) 允许你在测试中包装渲染和交互,以确保在断言之前已完成更新。 --- ## Resource APIs {/*resource-apis*/} diff --git a/src/sidebarReference.json b/src/sidebarReference.json index 34c821ff7d..2510c0bb3a 100644 --- a/src/sidebarReference.json +++ b/src/sidebarReference.json @@ -346,13 +346,8 @@ "path": "/reference/rules", "routes": [ { -<<<<<<< HEAD - "title": "组件和 Hook 必须是纯粹的", - "path": "/reference/rules/components-and-hooks-must-be-pure" -======= - "title": "Components and Hooks must be pure", + "title": "组件和 Hook 必须是纯粹的", "path": "/reference/rules/components-and-hooks-must-be-pure" ->>>>>>> 12fca4c24f218daea883080db476fb71ed160f36 }, { "title": "React 调用组件和 Hook", @@ -438,4 +433,4 @@ ] } ] -} \ No newline at end of file +} From 09276126559b5b9e05df16b98cd49ab540e9309c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=85=E7=A7=8B?= <1044514593@qq.com> Date: Wed, 29 May 2024 10:38:55 +0800 Subject: [PATCH 3/3] fix conflicts --- src/content/reference/react/apis.md | 1 + 1 file changed, 1 insertion(+) diff --git a/src/content/reference/react/apis.md b/src/content/reference/react/apis.md index ae001da86a..bba46bdae8 100644 --- a/src/content/reference/react/apis.md +++ b/src/content/reference/react/apis.md @@ -19,6 +19,7 @@ translators: * [`memo`](/reference/react/memo) 允许你在 props 没有变化的情况下跳过组件的重渲染。通常 [`useMemo`](/reference/react/useMemo) 与 [`useCallback`](/reference/react/useCallback) 会一起配合使用。 * [`startTransition`](/reference/react/startTransition) 允许你可以标记一个状态更新是不紧急的。类似于 [`useTransition`](/reference/react/useTransition)。 * [`act`](/reference/react/act) 允许你在测试中包装渲染和交互,以确保在断言之前已完成更新。 + --- ## Resource APIs {/*resource-apis*/}