main
js 577 lines 16 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
15 let act;
16
17 describe('SyntheticKeyboardEvent', () => {
18 let container;
19 let root;
20
21 beforeEach(() => {
22 React = require('react');
23 ReactDOMClient = require('react-dom/client');
24
25 act = require('internal-test-utils').act;
26
27 // The container has to be attached for events to fire.
28 container = document.createElement('div');
29 root = ReactDOMClient.createRoot(container);
30 document.body.appendChild(container);
31 });
32
33 afterEach(async () => {
34 await act(() => {
35 root.unmount();
36 });
37 document.body.removeChild(container);
38 container = null;
39 });
40
41 describe('KeyboardEvent interface', () => {
42 describe('charCode', () => {
43 describe('when event is `keypress`', () => {
44 describe('when charCode is present in nativeEvent', () => {
45 it('when charCode is 0 and keyCode is 13, returns 13', async () => {
46 let charCode = null;
47 await act(() => {
48 root.render(
49 <input
50 onKeyPress={e => {
51 charCode = e.charCode;
52 }}
53 />,
54 );
55 });
56 container.firstChild.dispatchEvent(
57 new KeyboardEvent('keypress', {
58 charCode: 0,
59 keyCode: 13,
60 bubbles: true,
61 cancelable: true,
62 }),
63 );
64 expect(charCode).toBe(13);
65 });
66
67 it('when charCode is 32 or bigger and keyCode is missing, returns charCode', async () => {
68 let charCode = null;
69 await act(() => {
70 root.render(
71 <input
72 onKeyPress={e => {
73 charCode = e.charCode;
74 }}
75 />,
76 );
77 });
78 container.firstChild.dispatchEvent(
79 new KeyboardEvent('keypress', {
80 charCode: 32,
81 bubbles: true,
82 cancelable: true,
83 }),
84 );
85 expect(charCode).toBe(32);
86 });
87
88 it('when charCode is 13 and keyCode is missing, returns charCode', async () => {
89 let charCode = null;
90 await act(() => {
91 root.render(
92 <input
93 onKeyPress={e => {
94 charCode = e.charCode;
95 }}
96 />,
97 );
98 });
99 container.firstChild.dispatchEvent(
100 new KeyboardEvent('keypress', {
101 charCode: 13,
102 bubbles: true,
103 cancelable: true,
104 }),
105 );
106 expect(charCode).toBe(13);
107 });
108
109 // Firefox creates a keypress event for function keys too. This removes
110 // the unwanted keypress events. Enter is however both printable and
111 // non-printable. One would expect Tab to be as well (but it isn't).
112 it('when charCode is smaller than 32 but is not 13, and keyCode is missing, ignores keypress', async () => {
113 let called = false;
114 await act(() => {
115 root.render(
116 <input
117 onKeyPress={() => {
118 called = true;
119 }}
120 />,
121 );
122 });
123 container.firstChild.dispatchEvent(
124 new KeyboardEvent('keypress', {
125 charCode: 31,
126 bubbles: true,
127 cancelable: true,
128 }),
129 );
130 expect(called).toBe(false);
131 });
132
133 it('when charCode is 10, returns 13', async () => {
134 let charCode = null;
135 await act(() => {
136 root.render(
137 <input
138 onKeyPress={e => {
139 charCode = e.charCode;
140 }}
141 />,
142 );
143 });
144 container.firstChild.dispatchEvent(
145 new KeyboardEvent('keypress', {
146 charCode: 10,
147 bubbles: true,
148 cancelable: true,
149 }),
150 );
151 expect(charCode).toBe(13);
152 });
153
154 it('when charCode is 10 and ctrl is pressed, returns 13', async () => {
155 let charCode = null;
156 await act(() => {
157 root.render(
158 <input
159 onKeyPress={e => {
160 charCode = e.charCode;
161 }}
162 />,
163 );
164 });
165 container.firstChild.dispatchEvent(
166 new KeyboardEvent('keypress', {
167 charCode: 10,
168 ctrlKey: true,
169 bubbles: true,
170 cancelable: true,
171 }),
172 );
173 expect(charCode).toBe(13);
174 });
175 });
176
177 // TODO: this seems IE8 specific.
178 // We can probably remove this normalization.
179 describe('when charCode is not present in nativeEvent', () => {
180 let charCodeDescriptor;
181
182 beforeEach(() => {
183 charCodeDescriptor = Object.getOwnPropertyDescriptor(
184 KeyboardEvent.prototype,
185 'charCode',
186 );
187 delete KeyboardEvent.prototype.charCode;
188 });
189
190 afterEach(() => {
191 // Don't forget to restore for other tests.
192 Object.defineProperty(
193 KeyboardEvent.prototype,
194 'charCode',
195 charCodeDescriptor,
196 );
197 charCodeDescriptor = null;
198 });
199
200 it('when keyCode is 32 or bigger, returns keyCode', async () => {
201 let charCode = null;
202 await act(() => {
203 root.render(
204 <input
205 onKeyPress={e => {
206 charCode = e.charCode;
207 }}
208 />,
209 );
210 });
211 container.firstChild.dispatchEvent(
212 new KeyboardEvent('keypress', {
213 keyCode: 32,
214 bubbles: true,
215 cancelable: true,
216 }),
217 );
218 expect(charCode).toBe(32);
219 });
220
221 it('when keyCode is 13, returns 13', async () => {
222 let charCode = null;
223 await act(() => {
224 root.render(
225 <input
226 onKeyPress={e => {
227 charCode = e.charCode;
228 }}
229 />,
230 );
231 });
232 container.firstChild.dispatchEvent(
233 new KeyboardEvent('keypress', {
234 keyCode: 13,
235 bubbles: true,
236 cancelable: true,
237 }),
238 );
239 expect(charCode).toBe(13);
240 });
241
242 it('when keyCode is smaller than 32 and is not 13, ignores keypress', async () => {
243 let called = false;
244 await act(() => {
245 root.render(
246 <input
247 onKeyPress={e => {
248 called = true;
249 }}
250 />,
251 );
252 });
253 container.firstChild.dispatchEvent(
254 new KeyboardEvent('keypress', {
255 keyCode: 31,
256 bubbles: true,
257 cancelable: true,
258 }),
259 );
260 expect(called).toBe(false);
261 });
262 });
263 });
264
265 describe('when event is not `keypress`', () => {
266 it('returns 0', async () => {
267 let charCodeDown = null;
268 let charCodeUp = null;
269 await act(() => {
270 root.render(
271 <input
272 onKeyDown={e => {
273 charCodeDown = e.charCode;
274 }}
275 onKeyUp={e => {
276 charCodeUp = e.charCode;
277 }}
278 />,
279 );
280 });
281 container.firstChild.dispatchEvent(
282 new KeyboardEvent('keydown', {
283 key: 'Del',
284 bubbles: true,
285 cancelable: true,
286 }),
287 );
288 container.firstChild.dispatchEvent(
289 new KeyboardEvent('keyup', {
290 key: 'Del',
291 bubbles: true,
292 cancelable: true,
293 }),
294 );
295 expect(charCodeDown).toBe(0);
296 expect(charCodeUp).toBe(0);
297 });
298 });
299
300 it('when charCode is smaller than 32 but is not 13, and keyCode is missing, charCode is 0', async () => {
301 let charCode = null;
302 await act(() => {
303 root.render(
304 <input
305 onKeyDown={e => {
306 charCode = e.charCode;
307 }}
308 />,
309 );
310 });
311 container.firstChild.dispatchEvent(
312 new KeyboardEvent('keydown', {
313 charCode: 31,
314 bubbles: true,
315 cancelable: true,
316 }),
317 );
318 expect(charCode).toBe(0);
319 });
320 });
321
322 describe('keyCode', () => {
323 describe('when event is `keydown` or `keyup`', () => {
324 it('returns a passed keyCode', async () => {
325 let keyCodeDown = null;
326 let keyCodeUp = null;
327 await act(() => {
328 root.render(
329 <input
330 onKeyDown={e => {
331 keyCodeDown = e.keyCode;
332 }}
333 onKeyUp={e => {
334 keyCodeUp = e.keyCode;
335 }}
336 />,
337 );
338 });
339 container.firstChild.dispatchEvent(
340 new KeyboardEvent('keydown', {
341 keyCode: 40,
342 bubbles: true,
343 cancelable: true,
344 }),
345 );
346 container.firstChild.dispatchEvent(
347 new KeyboardEvent('keyup', {
348 keyCode: 40,
349 bubbles: true,
350 cancelable: true,
351 }),
352 );
353 expect(keyCodeDown).toBe(40);
354 expect(keyCodeUp).toBe(40);
355 });
356 });
357
358 describe('when event is `keypress`', () => {
359 it('returns 0', async () => {
360 let keyCode = null;
361 await act(() => {
362 root.render(
363 <input
364 onKeyPress={e => {
365 keyCode = e.keyCode;
366 }}
367 />,
368 );
369 });
370 container.firstChild.dispatchEvent(
371 new KeyboardEvent('keypress', {
372 charCode: 65,
373 bubbles: true,
374 cancelable: true,
375 }),
376 );
377 expect(keyCode).toBe(0);
378 });
379 });
380 });
381
382 describe('which', () => {
383 describe('when event is `keypress`', () => {
384 it('is consistent with `charCode`', async () => {
385 let calls = 0;
386 await act(() => {
387 root.render(
388 <input
389 onKeyPress={e => {
390 expect(e.which).toBe(e.charCode);
391 calls++;
392 }}
393 />,
394 );
395 });
396 // Try different combinations from other tests.
397 container.firstChild.dispatchEvent(
398 new KeyboardEvent('keypress', {
399 charCode: 0,
400 keyCode: 13,
401 bubbles: true,
402 cancelable: true,
403 }),
404 );
405 container.firstChild.dispatchEvent(
406 new KeyboardEvent('keypress', {
407 charCode: 32,
408 bubbles: true,
409 cancelable: true,
410 }),
411 );
412 container.firstChild.dispatchEvent(
413 new KeyboardEvent('keypress', {
414 charCode: 13,
415 bubbles: true,
416 cancelable: true,
417 }),
418 );
419 expect(calls).toBe(3);
420 });
421 });
422
423 describe('when event is `keydown` or `keyup`', () => {
424 it('is consistent with `keyCode`', async () => {
425 let calls = 0;
426 await act(() => {
427 root.render(
428 <input
429 onKeyDown={e => {
430 expect(e.which).toBe(e.keyCode);
431 calls++;
432 }}
433 onKeyUp={e => {
434 expect(e.which).toBe(e.keyCode);
435 calls++;
436 }}
437 />,
438 );
439 });
440 container.firstChild.dispatchEvent(
441 new KeyboardEvent('keydown', {
442 key: 'Del',
443 bubbles: true,
444 cancelable: true,
445 }),
446 );
447 container.firstChild.dispatchEvent(
448 new KeyboardEvent('keydown', {
449 charCode: 31,
450 bubbles: true,
451 cancelable: true,
452 }),
453 );
454 container.firstChild.dispatchEvent(
455 new KeyboardEvent('keydown', {
456 keyCode: 40,
457 bubbles: true,
458 cancelable: true,
459 }),
460 );
461 container.firstChild.dispatchEvent(
462 new KeyboardEvent('keyup', {
463 key: 'Del',
464 bubbles: true,
465 cancelable: true,
466 }),
467 );
468 container.firstChild.dispatchEvent(
469 new KeyboardEvent('keyup', {
470 keyCode: 40,
471 bubbles: true,
472 cancelable: true,
473 }),
474 );
475 expect(calls).toBe(5);
476 });
477 });
478 });
479
480 describe('code', () => {
481 it('returns code on `keydown`, `keyup` and `keypress`', async () => {
482 let codeDown = null;
483 let codeUp = null;
484 let codePress = null;
485 await act(() => {
486 root.render(
487 <input
488 onKeyDown={e => {
489 codeDown = e.code;
490 }}
491 onKeyUp={e => {
492 codeUp = e.code;
493 }}
494 onKeyPress={e => {
495 codePress = e.code;
496 }}
497 />,
498 );
499 });
500 container.firstChild.dispatchEvent(
501 new KeyboardEvent('keydown', {
502 code: 'KeyQ',
503 bubbles: true,
504 cancelable: true,
505 }),
506 );
507 container.firstChild.dispatchEvent(
508 new KeyboardEvent('keyup', {
509 code: 'KeyQ',
510 bubbles: true,
511 cancelable: true,
512 }),
513 );
514 container.firstChild.dispatchEvent(
515 new KeyboardEvent('keypress', {
516 code: 'KeyQ',
517 charCode: 113,
518 bubbles: true,
519 cancelable: true,
520 }),
521 );
522 expect(codeDown).toBe('KeyQ');
523 expect(codeUp).toBe('KeyQ');
524 expect(codePress).toBe('KeyQ');
525 });
526 });
527 });
528
529 describe('EventInterface', () => {
530 it('is able to `preventDefault` and `stopPropagation`', async () => {
531 let expectedCount = 0;
532 const eventHandler = event => {
533 expect(event.isDefaultPrevented()).toBe(false);
534 event.preventDefault();
535 expect(event.isDefaultPrevented()).toBe(true);
536
537 expect(event.isPropagationStopped()).toBe(false);
538 event.stopPropagation();
539 expect(event.isPropagationStopped()).toBe(true);
540 expectedCount++;
541 };
542 await act(() => {
543 root.render(
544 <div
545 onKeyDown={eventHandler}
546 onKeyUp={eventHandler}
547 onKeyPress={eventHandler}
548 />,
549 );
550 });
551
552 container.firstChild.dispatchEvent(
553 new KeyboardEvent('keydown', {
554 keyCode: 40,
555 bubbles: true,
556 cancelable: true,
557 }),
558 );
559 container.firstChild.dispatchEvent(
560 new KeyboardEvent('keyup', {
561 keyCode: 40,
562 bubbles: true,
563 cancelable: true,
564 }),
565 );
566 container.firstChild.dispatchEvent(
567 new KeyboardEvent('keypress', {
568 charCode: 40,
569 keyCode: 40,
570 bubbles: true,
571 cancelable: true,
572 }),
573 );
574 expect(expectedCount).toBe(3);
575 });
576 });
577 });