main
js 221 lines 6.22 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 ReactDOMClient;
14 let act;
15
16 describe('SelectEventPlugin', () => {
17 let container;
18
19 beforeEach(() => {
20 React = require('react');
21 ReactDOMClient = require('react-dom/client');
22 act = require('internal-test-utils').act;
23 container = document.createElement('div');
24 document.body.appendChild(container);
25 });
26
27 afterEach(() => {
28 document.body.removeChild(container);
29 container = null;
30 });
31
32 // See https://github.com/facebook/react/pull/3639 for details.
33 it('does not get confused when dependent events are registered independently', async () => {
34 const select = jest.fn();
35 const onSelect = event => {
36 expect(typeof event).toBe('object');
37 expect(event.type).toBe('select');
38 expect(event.target).toBe(node);
39 select(event.currentTarget);
40 };
41
42 const root = ReactDOMClient.createRoot(container);
43 const node = await (async function () {
44 await act(() => {
45 // Pass `onMouseDown` so React registers a top-level listener.
46 root.render(<input type="text" onMouseDown={function () {}} />);
47 });
48 return container.firstChild;
49 })();
50
51 // Trigger `mousedown` and `mouseup`. Note that
52 // React is not currently listening to `mouseup`.
53 node.dispatchEvent(
54 new MouseEvent('mousedown', {
55 bubbles: true,
56 cancelable: true,
57 }),
58 );
59 node.dispatchEvent(
60 new MouseEvent('mouseup', {
61 bubbles: true,
62 cancelable: true,
63 }),
64 );
65
66 await act(() => {
67 // Now subscribe to `onSelect`
68 root.render(<input type="text" onSelect={onSelect} />);
69 });
70 node.focus();
71
72 // This triggers a `select` event in our polyfill.
73 node.dispatchEvent(
74 new KeyboardEvent('keydown', {bubbles: true, cancelable: true}),
75 );
76
77 // Verify that it doesn't get "stuck" waiting for
78 // a `mouseup` event that it has "missed" because
79 // a top-level listener didn't exist yet.
80 expect(select).toHaveBeenCalledTimes(1);
81 });
82
83 it('should fire `onSelect` when a listener is present', async () => {
84 const select = jest.fn();
85 const onSelect = event => {
86 expect(typeof event).toBe('object');
87 expect(event.type).toBe('select');
88 expect(event.target).toBe(node);
89 select(event.currentTarget);
90 };
91
92 const node = await (async function () {
93 const root = ReactDOMClient.createRoot(container);
94 await act(() => {
95 root.render(<input type="text" onSelect={onSelect} />);
96 });
97 return container.firstChild;
98 })();
99
100 node.focus();
101
102 let nativeEvent = new MouseEvent('focus', {
103 bubbles: true,
104 cancelable: true,
105 });
106 node.dispatchEvent(nativeEvent);
107 expect(select).toHaveBeenCalledTimes(0);
108
109 nativeEvent = new MouseEvent('mousedown', {
110 bubbles: true,
111 cancelable: true,
112 });
113 node.dispatchEvent(nativeEvent);
114 expect(select).toHaveBeenCalledTimes(0);
115
116 nativeEvent = new MouseEvent('mouseup', {bubbles: true, cancelable: true});
117 node.dispatchEvent(nativeEvent);
118 expect(select).toHaveBeenCalledTimes(1);
119 });
120
121 it('should fire `onSelectCapture` when a listener is present', async () => {
122 const select = jest.fn();
123 const onSelectCapture = event => {
124 expect(typeof event).toBe('object');
125 expect(event.type).toBe('select');
126 expect(event.target).toBe(node);
127 select(event.currentTarget);
128 };
129
130 const node = await (async function () {
131 const root = ReactDOMClient.createRoot(container);
132 await act(() => {
133 root.render(<input type="text" onSelectCapture={onSelectCapture} />);
134 });
135 return container.firstChild;
136 })();
137
138 node.focus();
139
140 let nativeEvent = new MouseEvent('focus', {
141 bubbles: true,
142 cancelable: true,
143 });
144 node.dispatchEvent(nativeEvent);
145 expect(select).toHaveBeenCalledTimes(0);
146
147 nativeEvent = new MouseEvent('mousedown', {
148 bubbles: true,
149 cancelable: true,
150 });
151 node.dispatchEvent(nativeEvent);
152 expect(select).toHaveBeenCalledTimes(0);
153
154 nativeEvent = new MouseEvent('mouseup', {bubbles: true, cancelable: true});
155 node.dispatchEvent(nativeEvent);
156 expect(select).toHaveBeenCalledTimes(1);
157 });
158
159 // Regression test for https://github.com/facebook/react/issues/11379
160 it('should not wait for `mouseup` after receiving `dragend`', async () => {
161 const select = jest.fn();
162 const onSelect = event => {
163 expect(typeof event).toBe('object');
164 expect(event.type).toBe('select');
165 expect(event.target).toBe(node);
166 select(event.currentTarget);
167 };
168
169 const node = await (async function () {
170 const root = ReactDOMClient.createRoot(container);
171 await act(() => {
172 root.render(<input type="text" onSelect={onSelect} />);
173 });
174 return container.firstChild;
175 })();
176
177 node.focus();
178
179 let nativeEvent = new MouseEvent('focus', {
180 bubbles: true,
181 cancelable: true,
182 });
183 node.dispatchEvent(nativeEvent);
184 expect(select).toHaveBeenCalledTimes(0);
185
186 nativeEvent = new MouseEvent('mousedown', {
187 bubbles: true,
188 cancelable: true,
189 });
190 node.dispatchEvent(nativeEvent);
191 expect(select).toHaveBeenCalledTimes(0);
192
193 nativeEvent = new MouseEvent('dragend', {bubbles: true, cancelable: true});
194 node.dispatchEvent(nativeEvent);
195 expect(select).toHaveBeenCalledTimes(1);
196 });
197
198 it('should handle selectionchange events', async function () {
199 const onSelect = jest.fn();
200
201 const node = await (async function () {
202 const root = ReactDOMClient.createRoot(container);
203 await act(() => {
204 root.render(<input type="text" onSelect={onSelect} />);
205 });
206 return container.firstChild;
207 })();
208
209 node.focus();
210
211 // Make sure the event was not called before we emit the selection change event
212 expect(onSelect).toHaveBeenCalledTimes(0);
213
214 // This is dispatched e.g. when using CMD+a on macOS
215 document.dispatchEvent(
216 new Event('selectionchange', {bubbles: false, cancelable: false}),
217 );
218
219 expect(onSelect).toHaveBeenCalledTimes(1);
220 });
221 });