main
js 210 lines 5.62 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 describe('ReactDOMComponentTree', () => {
13 let React;
14 let ReactDOMClient;
15 let act;
16 let container;
17 let assertConsoleErrorDev;
18
19 beforeEach(() => {
20 React = require('react');
21 ReactDOMClient = require('react-dom/client');
22 act = require('internal-test-utils').act;
23 assertConsoleErrorDev =
24 require('internal-test-utils').assertConsoleErrorDev;
25
26 container = document.createElement('div');
27 document.body.appendChild(container);
28 });
29
30 afterEach(() => {
31 document.body.removeChild(container);
32 container = null;
33 });
34
35 it('finds nodes for instances on events', async () => {
36 const mouseOverID = 'mouseOverID';
37 const clickID = 'clickID';
38 let currentTargetID = null;
39 // the current target of an event is set to result of getNodeFromInstance
40 // when an event is dispatched so we can test behavior by invoking
41 // events on elements in the tree and confirming the expected node is
42 // set as the current target
43 function Component() {
44 const handler = e => {
45 currentTargetID = e.currentTarget.id;
46 };
47
48 return (
49 <div id={mouseOverID} onMouseOver={handler}>
50 <div id={clickID} onClick={handler} />
51 </div>
52 );
53 }
54
55 function simulateMouseEvent(elem, type) {
56 const event = new MouseEvent(type, {
57 bubbles: true,
58 });
59 elem.dispatchEvent(event);
60 }
61
62 const root = ReactDOMClient.createRoot(container);
63 await act(() => {
64 root.render(<Component />);
65 });
66 expect(currentTargetID).toBe(null);
67 simulateMouseEvent(document.getElementById(mouseOverID), 'mouseover');
68 expect(currentTargetID).toBe(mouseOverID);
69 simulateMouseEvent(document.getElementById(clickID), 'click');
70 expect(currentTargetID).toBe(clickID);
71 });
72
73 it('finds closest instance for node when an event happens', async () => {
74 const nonReactElemID = 'aID';
75 const innerHTML = {__html: `<div id="${nonReactElemID}"></div>`};
76 const closestInstanceID = 'closestInstance';
77 let currentTargetID = null;
78
79 function ClosestInstance() {
80 const onClick = e => {
81 currentTargetID = e.currentTarget.id;
82 };
83
84 return (
85 <div
86 id={closestInstanceID}
87 onClick={onClick}
88 dangerouslySetInnerHTML={innerHTML}
89 />
90 );
91 }
92
93 function simulateClick(elem) {
94 const event = new MouseEvent('click', {
95 bubbles: true,
96 });
97 elem.dispatchEvent(event);
98 }
99
100 const root = ReactDOMClient.createRoot(container);
101 await act(() => {
102 root.render(
103 <section>
104 <ClosestInstance />
105 </section>,
106 );
107 });
108 expect(currentTargetID).toBe(null);
109 simulateClick(document.getElementById(nonReactElemID));
110 expect(currentTargetID).toBe(closestInstanceID);
111 });
112
113 it('updates event handlers from fiber props', async () => {
114 let action = '';
115 let flip;
116 const handlerA = () => (action = 'A');
117 const handlerB = () => (action = 'B');
118
119 function simulateMouseOver(target) {
120 const event = new MouseEvent('mouseover', {
121 bubbles: true,
122 });
123 target.dispatchEvent(event);
124 }
125
126 function HandlerFlipper() {
127 const [flipVal, setFlipVal] = React.useState(false);
128 flip = () => setFlipVal(true);
129
130 return <div id="update" onMouseOver={flipVal ? handlerB : handlerA} />;
131 }
132
133 const root = ReactDOMClient.createRoot(container);
134 await act(() => {
135 root.render(<HandlerFlipper key="1" />);
136 });
137 const node = container.firstChild;
138
139 await act(() => {
140 simulateMouseOver(node);
141 });
142 expect(action).toEqual('A');
143 action = '';
144
145 // Render with the other event handler.
146 await act(() => {
147 flip();
148 });
149 await act(() => {
150 simulateMouseOver(node);
151 });
152 expect(action).toEqual('B');
153 });
154
155 it('finds a controlled instance from node and gets its current fiber props', async () => {
156 let inputRef;
157 const inputID = 'inputID';
158 const startValue = undefined;
159 const finishValue = 'finish';
160
161 function Controlled() {
162 const [state, setState] = React.useState(startValue);
163 const ref = React.useRef();
164 inputRef = ref;
165 const onChange = e => setState(e.currentTarget.value);
166
167 return (
168 <input
169 id={inputID}
170 type="text"
171 ref={ref}
172 value={state}
173 onChange={onChange}
174 />
175 );
176 }
177
178 const setUntrackedInputValue = Object.getOwnPropertyDescriptor(
179 HTMLInputElement.prototype,
180 'value',
181 ).set;
182
183 function simulateInput(elem, value) {
184 const inputEvent = new Event('input', {
185 bubbles: true,
186 });
187 setUntrackedInputValue.call(elem, value);
188 elem.dispatchEvent(inputEvent);
189 }
190
191 const root = ReactDOMClient.createRoot(container);
192 await act(() => {
193 root.render(<Controlled />);
194 });
195
196 await act(() => {
197 simulateInput(inputRef.current, finishValue);
198 });
199 assertConsoleErrorDev([
200 'A component is changing an uncontrolled input to be controlled. ' +
201 'This is likely caused by the value changing from undefined to ' +
202 'a defined value, which should not happen. ' +
203 'Decide between using a controlled or uncontrolled input ' +
204 'element for the lifetime of the component. More info: ' +
205 'https://react.dev/link/controlled-components\n' +
206 ' in input (at **)\n' +
207 ' in Controlled (at **)',
208 ]);
209 });
210 });