main
js 194 lines 5.03 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @emails react-core
8 */
9
10 'use strict';
11
12 let React;
13 let ReactDOM;
14 let ReactDOMClient;
15 let act;
16 let waitForAll;
17
18 describe('ReactDOMHooks', () => {
19 let container;
20
21 beforeEach(() => {
22 jest.resetModules();
23
24 React = require('react');
25 ReactDOM = require('react-dom');
26 ReactDOMClient = require('react-dom/client');
27 act = require('internal-test-utils').act;
28 waitForAll = require('internal-test-utils').waitForAll;
29
30 container = document.createElement('div');
31 document.body.appendChild(container);
32 });
33
34 afterEach(() => {
35 document.body.removeChild(container);
36 });
37
38 // @gate !disableLegacyMode
39 it('can ReactDOM.render() from useEffect', async () => {
40 const container2 = document.createElement('div');
41 const container3 = document.createElement('div');
42
43 function Example1({n}) {
44 React.useEffect(() => {
45 ReactDOM.render(<Example2 n={n} />, container2);
46 });
47 return 1 * n;
48 }
49
50 function Example2({n}) {
51 React.useEffect(() => {
52 ReactDOM.render(<Example3 n={n} />, container3);
53 });
54 return 2 * n;
55 }
56
57 function Example3({n}) {
58 return 3 * n;
59 }
60
61 ReactDOM.render(<Example1 n={1} />, container);
62 expect(container.textContent).toBe('1');
63 expect(container2.textContent).toBe('');
64 expect(container3.textContent).toBe('');
65 await waitForAll([]);
66 expect(container.textContent).toBe('1');
67 expect(container2.textContent).toBe('2');
68 expect(container3.textContent).toBe('3');
69
70 ReactDOM.render(<Example1 n={2} />, container);
71 expect(container.textContent).toBe('2');
72 expect(container2.textContent).toBe('2'); // Not flushed yet
73 expect(container3.textContent).toBe('3'); // Not flushed yet
74 await waitForAll([]);
75 expect(container.textContent).toBe('2');
76 expect(container2.textContent).toBe('4');
77 expect(container3.textContent).toBe('6');
78 });
79
80 it('can render() from useEffect', async () => {
81 const container2 = document.createElement('div');
82 const container3 = document.createElement('div');
83
84 const root1 = ReactDOMClient.createRoot(container);
85 const root2 = ReactDOMClient.createRoot(container2);
86 const root3 = ReactDOMClient.createRoot(container3);
87
88 function Example1({n}) {
89 React.useEffect(() => {
90 root2.render(<Example2 n={n} />);
91 });
92 return 1 * n;
93 }
94
95 function Example2({n}) {
96 React.useEffect(() => {
97 root3.render(<Example3 n={n} />);
98 });
99 return 2 * n;
100 }
101
102 function Example3({n}) {
103 return 3 * n;
104 }
105
106 await act(() => {
107 root1.render(<Example1 n={1} />);
108 });
109 await waitForAll([]);
110 expect(container.textContent).toBe('1');
111 expect(container2.textContent).toBe('2');
112 expect(container3.textContent).toBe('3');
113
114 await act(() => {
115 root1.render(<Example1 n={2} />);
116 });
117 await waitForAll([]);
118 expect(container.textContent).toBe('2');
119 expect(container2.textContent).toBe('4');
120 expect(container3.textContent).toBe('6');
121 });
122
123 // @gate !disableLegacyMode
124 it('should not bail out when an update is scheduled from within an event handler', () => {
125 const {createRef, useCallback, useState} = React;
126
127 const Example = ({inputRef, labelRef}) => {
128 const [text, setText] = useState('');
129 const handleInput = useCallback(event => {
130 setText(event.target.value);
131 });
132
133 return (
134 <>
135 <input ref={inputRef} onInput={handleInput} />
136 <label ref={labelRef}>{text}</label>
137 </>
138 );
139 };
140
141 const inputRef = createRef();
142 const labelRef = createRef();
143
144 ReactDOM.render(
145 <Example inputRef={inputRef} labelRef={labelRef} />,
146 container,
147 );
148
149 inputRef.current.value = 'abc';
150 inputRef.current.dispatchEvent(
151 new Event('input', {bubbles: true, cancelable: true}),
152 );
153
154 expect(labelRef.current.innerHTML).toBe('abc');
155 });
156
157 it('should not bail out when an update is scheduled from within an event handler in Concurrent Mode', async () => {
158 const {createRef, useCallback, useState} = React;
159
160 const Example = ({inputRef, labelRef}) => {
161 const [text, setText] = useState('');
162 const handleInput = useCallback(event => {
163 setText(event.target.value);
164 });
165
166 return (
167 <>
168 <input ref={inputRef} onInput={handleInput} />
169 <label ref={labelRef}>{text}</label>
170 </>
171 );
172 };
173
174 const inputRef = createRef();
175 const labelRef = createRef();
176
177 const root = ReactDOMClient.createRoot(container);
178 root.render(<Example inputRef={inputRef} labelRef={labelRef} />);
179
180 await waitForAll([]);
181
182 inputRef.current.value = 'abc';
183 await act(() => {
184 inputRef.current.dispatchEvent(
185 new Event('input', {
186 bubbles: true,
187 cancelable: true,
188 }),
189 );
190 });
191
192 expect(labelRef.current.innerHTML).toBe('abc');
193 });
194 });