1 | "use strict";
|
2 |
|
3 | Object.defineProperty(exports, "__esModule", {
|
4 | value: true
|
5 | });
|
6 | exports.type = type;
|
7 |
|
8 | var _dom = require("@testing-library/dom");
|
9 |
|
10 | var _utils = require("./utils");
|
11 |
|
12 | var _click = require("./click");
|
13 |
|
14 |
|
15 | const modifierCallbackMap = { ...createModifierCallbackEntries({
|
16 | name: 'shift',
|
17 | key: 'Shift',
|
18 | keyCode: 16,
|
19 | modifierProperty: 'shiftKey'
|
20 | }),
|
21 | ...createModifierCallbackEntries({
|
22 | name: 'ctrl',
|
23 | key: 'Control',
|
24 | keyCode: 17,
|
25 | modifierProperty: 'ctrlKey'
|
26 | }),
|
27 | ...createModifierCallbackEntries({
|
28 | name: 'alt',
|
29 | key: 'Alt',
|
30 | keyCode: 18,
|
31 | modifierProperty: 'altKey'
|
32 | }),
|
33 | ...createModifierCallbackEntries({
|
34 | name: 'meta',
|
35 | key: 'Meta',
|
36 | keyCode: 93,
|
37 | modifierProperty: 'metaKey'
|
38 | })
|
39 | };
|
40 | const specialCharCallbackMap = {
|
41 | '{enter}': handleEnter,
|
42 | '{esc}': handleEsc,
|
43 | '{del}': handleDel,
|
44 | '{backspace}': handleBackspace,
|
45 | '{selectall}': handleSelectall,
|
46 | '{space}': handleSpace,
|
47 | ' ': handleSpace
|
48 | };
|
49 |
|
50 | function wait(time) {
|
51 | return new Promise(resolve => setTimeout(() => resolve(), time));
|
52 | }
|
53 |
|
54 |
|
55 |
|
56 | async function type(element, text, {
|
57 | delay = 0,
|
58 | ...options
|
59 | } = {}) {
|
60 |
|
61 |
|
62 |
|
63 | let result;
|
64 |
|
65 | if (delay > 0) {
|
66 | await (0, _dom.getConfig)().asyncWrapper(async () => {
|
67 | result = await typeImpl(element, text, {
|
68 | delay,
|
69 | ...options
|
70 | });
|
71 | });
|
72 | } else {
|
73 | result = typeImpl(element, text, {
|
74 | delay,
|
75 | ...options
|
76 | });
|
77 | }
|
78 |
|
79 | return result;
|
80 | }
|
81 |
|
82 | async function typeImpl(element, text, {
|
83 | delay,
|
84 | skipClick = false,
|
85 | skipAutoClose = false,
|
86 | initialSelectionStart,
|
87 | initialSelectionEnd
|
88 | }) {
|
89 | if (element.disabled) return;
|
90 | if (!skipClick) (0, _click.click)(element);
|
91 |
|
92 | if ((0, _utils.isContentEditable)(element) && document.getSelection().rangeCount === 0) {
|
93 | const range = document.createRange();
|
94 | range.setStart(element, 0);
|
95 | range.setEnd(element, 0);
|
96 | document.getSelection().addRange(range);
|
97 | }
|
98 |
|
99 |
|
100 | const currentElement = () => (0, _utils.getActiveElement)(element.ownerDocument);
|
101 |
|
102 |
|
103 |
|
104 |
|
105 |
|
106 |
|
107 |
|
108 |
|
109 |
|
110 | const value = (0, _utils.getValue)(currentElement());
|
111 | const {
|
112 | selectionStart,
|
113 | selectionEnd
|
114 | } = (0, _utils.getSelectionRange)(element);
|
115 |
|
116 | if (value != null && selectionStart === 0 && selectionEnd === 0) {
|
117 | (0, _utils.setSelectionRangeIfNecessary)(currentElement(), initialSelectionStart != null ? initialSelectionStart : value.length, initialSelectionEnd != null ? initialSelectionEnd : value.length);
|
118 | }
|
119 |
|
120 | const eventCallbacks = function () {
|
121 | const callbacks = [];
|
122 | let remainingString = text;
|
123 |
|
124 | while (remainingString) {
|
125 | const {
|
126 | callback,
|
127 | remainingString: newRemainingString
|
128 | } = getNextCallback(remainingString, skipAutoClose);
|
129 | callbacks.push(callback);
|
130 | remainingString = newRemainingString;
|
131 | }
|
132 |
|
133 | return callbacks;
|
134 | }();
|
135 |
|
136 | await async function (callbacks) {
|
137 | const eventOverrides = {};
|
138 | let prevWasMinus, prevWasPeriod, prevValue, typedValue;
|
139 |
|
140 | for (const callback of callbacks) {
|
141 | if (delay > 0) await wait(delay);
|
142 |
|
143 | if (!currentElement().disabled) {
|
144 | const returnValue = callback({
|
145 | currentElement,
|
146 | prevWasMinus,
|
147 | prevWasPeriod,
|
148 | prevValue,
|
149 | eventOverrides,
|
150 | typedValue
|
151 | });
|
152 | Object.assign(eventOverrides, returnValue == null ? void 0 : returnValue.eventOverrides);
|
153 | prevWasMinus = returnValue == null ? void 0 : returnValue.prevWasMinus;
|
154 | prevWasPeriod = returnValue == null ? void 0 : returnValue.prevWasPeriod;
|
155 | prevValue = returnValue == null ? void 0 : returnValue.prevValue;
|
156 | typedValue = returnValue == null ? void 0 : returnValue.typedValue;
|
157 | }
|
158 | }
|
159 | }(eventCallbacks);
|
160 | }
|
161 |
|
162 | function getNextCallback(remainingString, skipAutoClose) {
|
163 | const modifierCallback = getModifierCallback(remainingString, skipAutoClose);
|
164 |
|
165 | if (modifierCallback) {
|
166 | return modifierCallback;
|
167 | }
|
168 |
|
169 | const specialCharCallback = getSpecialCharCallback(remainingString);
|
170 |
|
171 | if (specialCharCallback) {
|
172 | return specialCharCallback;
|
173 | }
|
174 |
|
175 | return getTypeCallback(remainingString);
|
176 | }
|
177 |
|
178 | function getModifierCallback(remainingString, skipAutoClose) {
|
179 | const modifierKey = Object.keys(modifierCallbackMap).find(key => remainingString.startsWith(key));
|
180 |
|
181 | if (!modifierKey) {
|
182 | return null;
|
183 | }
|
184 |
|
185 | const callback = modifierCallbackMap[modifierKey];
|
186 |
|
187 |
|
188 |
|
189 | if (!skipAutoClose && callback.closeName && !remainingString.includes(callback.closeName)) {
|
190 | remainingString += callback.closeName;
|
191 | }
|
192 |
|
193 | remainingString = remainingString.slice(modifierKey.length);
|
194 | return {
|
195 | callback,
|
196 | remainingString
|
197 | };
|
198 | }
|
199 |
|
200 | function getSpecialCharCallback(remainingString) {
|
201 | const specialChar = Object.keys(specialCharCallbackMap).find(key => remainingString.startsWith(key));
|
202 |
|
203 | if (!specialChar) {
|
204 | return null;
|
205 | }
|
206 |
|
207 | return {
|
208 | callback: specialCharCallbackMap[specialChar],
|
209 | remainingString: remainingString.slice(specialChar.length)
|
210 | };
|
211 | }
|
212 |
|
213 | function getTypeCallback(remainingString) {
|
214 | const character = remainingString[0];
|
215 | return {
|
216 | callback: context => typeCharacter(character, context),
|
217 | remainingString: remainingString.slice(1)
|
218 | };
|
219 | }
|
220 |
|
221 | function setSelectionRange({
|
222 | currentElement,
|
223 | newValue,
|
224 | newSelectionStart
|
225 | }) {
|
226 |
|
227 |
|
228 |
|
229 |
|
230 |
|
231 |
|
232 | const value = (0, _utils.getValue)(currentElement());
|
233 |
|
234 | if (value === newValue) {
|
235 | (0, _utils.setSelectionRangeIfNecessary)(currentElement(), newSelectionStart, newSelectionStart);
|
236 | } else {
|
237 |
|
238 |
|
239 |
|
240 | (0, _utils.setSelectionRangeIfNecessary)(currentElement(), value.length, value.length);
|
241 | }
|
242 | }
|
243 |
|
244 | function fireInputEventIfNeeded({
|
245 | currentElement,
|
246 | newValue,
|
247 | newSelectionStart,
|
248 | eventOverrides
|
249 | }) {
|
250 | const prevValue = (0, _utils.getValue)(currentElement());
|
251 |
|
252 | if (!currentElement().readOnly && !(0, _utils.isClickable)(currentElement()) && newValue !== prevValue) {
|
253 | if ((0, _utils.isContentEditable)(currentElement())) {
|
254 | _dom.fireEvent.input(currentElement(), {
|
255 | target: {
|
256 | textContent: newValue
|
257 | },
|
258 | ...eventOverrides
|
259 | });
|
260 | } else {
|
261 | _dom.fireEvent.input(currentElement(), {
|
262 | target: {
|
263 | value: newValue
|
264 | },
|
265 | ...eventOverrides
|
266 | });
|
267 | }
|
268 |
|
269 | setSelectionRange({
|
270 | currentElement,
|
271 | newValue,
|
272 | newSelectionStart
|
273 | });
|
274 | }
|
275 |
|
276 | return {
|
277 | prevValue
|
278 | };
|
279 | }
|
280 |
|
281 | function typeCharacter(char, {
|
282 | currentElement,
|
283 | prevWasMinus = false,
|
284 | prevWasPeriod = false,
|
285 | prevValue = '',
|
286 | typedValue = '',
|
287 | eventOverrides
|
288 | }) {
|
289 | const key = char;
|
290 |
|
291 | const keyCode = char.charCodeAt(0);
|
292 | let nextPrevWasMinus, nextPrevWasPeriod;
|
293 | const textToBeTyped = typedValue + char;
|
294 |
|
295 | const keyDownDefaultNotPrevented = _dom.fireEvent.keyDown(currentElement(), {
|
296 | key,
|
297 | keyCode,
|
298 | which: keyCode,
|
299 | ...eventOverrides
|
300 | });
|
301 |
|
302 | if (keyDownDefaultNotPrevented) {
|
303 | const keyPressDefaultNotPrevented = _dom.fireEvent.keyPress(currentElement(), {
|
304 | key,
|
305 | keyCode,
|
306 | charCode: keyCode,
|
307 | ...eventOverrides
|
308 | });
|
309 |
|
310 | if ((0, _utils.getValue)(currentElement()) != null && keyPressDefaultNotPrevented) {
|
311 | let newEntry = char;
|
312 |
|
313 | if (prevWasMinus) {
|
314 | newEntry = `-${char}`;
|
315 | } else if (prevWasPeriod) {
|
316 | newEntry = `${prevValue}.${char}`;
|
317 | }
|
318 |
|
319 | if ((0, _utils.isValidDateValue)(currentElement(), textToBeTyped)) {
|
320 | newEntry = textToBeTyped;
|
321 | }
|
322 |
|
323 | const inputEvent = fireInputEventIfNeeded({ ...(0, _utils.calculateNewValue)(newEntry, currentElement()),
|
324 | eventOverrides: {
|
325 | data: key,
|
326 | inputType: 'insertText',
|
327 | ...eventOverrides
|
328 | },
|
329 | currentElement
|
330 | });
|
331 | prevValue = inputEvent.prevValue;
|
332 |
|
333 | if ((0, _utils.isValidDateValue)(currentElement(), textToBeTyped)) {
|
334 | _dom.fireEvent.change(currentElement(), {
|
335 | target: {
|
336 | value: textToBeTyped
|
337 | }
|
338 | });
|
339 | }
|
340 |
|
341 |
|
342 |
|
343 |
|
344 |
|
345 |
|
346 |
|
347 | if (currentElement().type === 'number') {
|
348 | const newValue = (0, _utils.getValue)(currentElement());
|
349 |
|
350 | if (newValue === prevValue && newEntry !== '-') {
|
351 | nextPrevWasMinus = prevWasMinus;
|
352 | } else {
|
353 | nextPrevWasMinus = newEntry === '-';
|
354 | }
|
355 |
|
356 | if (newValue === prevValue && newEntry !== '.') {
|
357 | nextPrevWasPeriod = prevWasPeriod;
|
358 | } else {
|
359 | nextPrevWasPeriod = newEntry === '.';
|
360 | }
|
361 | }
|
362 | }
|
363 | }
|
364 |
|
365 | _dom.fireEvent.keyUp(currentElement(), {
|
366 | key,
|
367 | keyCode,
|
368 | which: keyCode,
|
369 | ...eventOverrides
|
370 | });
|
371 |
|
372 | return {
|
373 | prevWasMinus: nextPrevWasMinus,
|
374 | prevWasPeriod: nextPrevWasPeriod,
|
375 | prevValue,
|
376 | typedValue: textToBeTyped
|
377 | };
|
378 | }
|
379 |
|
380 |
|
381 |
|
382 |
|
383 |
|
384 | function calculateNewBackspaceValue(element) {
|
385 | const {
|
386 | selectionStart,
|
387 | selectionEnd
|
388 | } = (0, _utils.getSelectionRange)(element);
|
389 | const value = (0, _utils.getValue)(element);
|
390 | let newValue, newSelectionStart;
|
391 |
|
392 | if (selectionStart === null) {
|
393 |
|
394 |
|
395 | newValue = value.slice(0, value.length - 1);
|
396 | newSelectionStart = selectionStart - 1;
|
397 | } else if (selectionStart === selectionEnd) {
|
398 | if (selectionStart === 0) {
|
399 |
|
400 | newValue = value;
|
401 | newSelectionStart = selectionStart;
|
402 | } else if (selectionStart === value.length) {
|
403 |
|
404 | newValue = value.slice(0, value.length - 1);
|
405 | newSelectionStart = selectionStart - 1;
|
406 | } else {
|
407 |
|
408 | newValue = value.slice(0, selectionStart - 1) + value.slice(selectionEnd);
|
409 | newSelectionStart = selectionStart - 1;
|
410 | }
|
411 | } else {
|
412 |
|
413 | const firstPart = value.slice(0, selectionStart);
|
414 | newValue = firstPart + value.slice(selectionEnd);
|
415 | newSelectionStart = firstPart.length;
|
416 | }
|
417 |
|
418 | return {
|
419 | newValue,
|
420 | newSelectionStart
|
421 | };
|
422 | }
|
423 |
|
424 | function calculateNewDeleteValue(element) {
|
425 | const {
|
426 | selectionStart,
|
427 | selectionEnd
|
428 | } = (0, _utils.getSelectionRange)(element);
|
429 | const value = (0, _utils.getValue)(element);
|
430 | let newValue;
|
431 |
|
432 | if (selectionStart === null) {
|
433 |
|
434 |
|
435 | newValue = value;
|
436 | } else if (selectionStart === selectionEnd) {
|
437 | if (selectionStart === 0) {
|
438 |
|
439 | newValue = value.slice(1);
|
440 | } else if (selectionStart === value.length) {
|
441 |
|
442 | newValue = value;
|
443 | } else {
|
444 |
|
445 | newValue = value.slice(0, selectionStart) + value.slice(selectionEnd + 1);
|
446 | }
|
447 | } else {
|
448 |
|
449 | const firstPart = value.slice(0, selectionStart);
|
450 | newValue = firstPart + value.slice(selectionEnd);
|
451 | }
|
452 |
|
453 | return {
|
454 | newValue,
|
455 | newSelectionStart: selectionStart
|
456 | };
|
457 | }
|
458 |
|
459 | function createModifierCallbackEntries({
|
460 | name,
|
461 | key,
|
462 | keyCode,
|
463 | modifierProperty
|
464 | }) {
|
465 | const closeName = `{/${name}}`;
|
466 |
|
467 | function open({
|
468 | currentElement,
|
469 | eventOverrides
|
470 | }) {
|
471 | const newEventOverrides = {
|
472 | [modifierProperty]: true
|
473 | };
|
474 |
|
475 | _dom.fireEvent.keyDown(currentElement(), {
|
476 | key,
|
477 | keyCode,
|
478 | which: keyCode,
|
479 | ...eventOverrides,
|
480 | ...newEventOverrides
|
481 | });
|
482 |
|
483 | return {
|
484 | eventOverrides: newEventOverrides
|
485 | };
|
486 | }
|
487 |
|
488 | open.closeName = closeName;
|
489 | return {
|
490 | [`{${name}}`]: open,
|
491 | [closeName]: function ({
|
492 | currentElement,
|
493 | eventOverrides
|
494 | }) {
|
495 | const newEventOverrides = {
|
496 | [modifierProperty]: false
|
497 | };
|
498 |
|
499 | _dom.fireEvent.keyUp(currentElement(), {
|
500 | key,
|
501 | keyCode,
|
502 | which: keyCode,
|
503 | ...eventOverrides,
|
504 | ...newEventOverrides
|
505 | });
|
506 |
|
507 | return {
|
508 | eventOverrides: newEventOverrides
|
509 | };
|
510 | }
|
511 | };
|
512 | }
|
513 |
|
514 | function handleEnter({
|
515 | currentElement,
|
516 | eventOverrides
|
517 | }) {
|
518 | const key = 'Enter';
|
519 | const keyCode = 13;
|
520 |
|
521 | const keyDownDefaultNotPrevented = _dom.fireEvent.keyDown(currentElement(), {
|
522 | key,
|
523 | keyCode,
|
524 | which: keyCode,
|
525 | ...eventOverrides
|
526 | });
|
527 |
|
528 | if (keyDownDefaultNotPrevented) {
|
529 | _dom.fireEvent.keyPress(currentElement(), {
|
530 | key,
|
531 | keyCode,
|
532 | charCode: keyCode,
|
533 | ...eventOverrides
|
534 | });
|
535 |
|
536 | if ((0, _utils.isClickable)(currentElement())) {
|
537 | _dom.fireEvent.click(currentElement(), { ...eventOverrides
|
538 | });
|
539 | }
|
540 | }
|
541 |
|
542 | if (currentElement().tagName === 'TEXTAREA') {
|
543 | const {
|
544 | newValue,
|
545 | newSelectionStart
|
546 | } = (0, _utils.calculateNewValue)('\n', currentElement());
|
547 |
|
548 | _dom.fireEvent.input(currentElement(), {
|
549 | target: {
|
550 | value: newValue
|
551 | },
|
552 | inputType: 'insertLineBreak',
|
553 | ...eventOverrides
|
554 | });
|
555 |
|
556 | setSelectionRange({
|
557 | currentElement,
|
558 | newValue,
|
559 | newSelectionStart
|
560 | });
|
561 | }
|
562 |
|
563 | if (currentElement().tagName === 'INPUT' && currentElement().form && (currentElement().form.querySelectorAll('input').length === 1 || currentElement().form.querySelector('input[type="submit"]') || currentElement().form.querySelector('button[type="submit"]'))) {
|
564 | _dom.fireEvent.submit(currentElement().form);
|
565 | }
|
566 |
|
567 | _dom.fireEvent.keyUp(currentElement(), {
|
568 | key,
|
569 | keyCode,
|
570 | which: keyCode,
|
571 | ...eventOverrides
|
572 | });
|
573 | }
|
574 |
|
575 | function handleEsc({
|
576 | currentElement,
|
577 | eventOverrides
|
578 | }) {
|
579 | const key = 'Escape';
|
580 | const keyCode = 27;
|
581 |
|
582 | _dom.fireEvent.keyDown(currentElement(), {
|
583 | key,
|
584 | keyCode,
|
585 | which: keyCode,
|
586 | ...eventOverrides
|
587 | });
|
588 |
|
589 |
|
590 | _dom.fireEvent.keyUp(currentElement(), {
|
591 | key,
|
592 | keyCode,
|
593 | which: keyCode,
|
594 | ...eventOverrides
|
595 | });
|
596 | }
|
597 |
|
598 | function handleDel({
|
599 | currentElement,
|
600 | eventOverrides
|
601 | }) {
|
602 | const key = 'Delete';
|
603 | const keyCode = 46;
|
604 |
|
605 | const keyPressDefaultNotPrevented = _dom.fireEvent.keyDown(currentElement(), {
|
606 | key,
|
607 | keyCode,
|
608 | which: keyCode,
|
609 | ...eventOverrides
|
610 | });
|
611 |
|
612 | if (keyPressDefaultNotPrevented) {
|
613 | fireInputEventIfNeeded({ ...calculateNewDeleteValue(currentElement()),
|
614 | eventOverrides: {
|
615 | inputType: 'deleteContentForward',
|
616 | ...eventOverrides
|
617 | },
|
618 | currentElement
|
619 | });
|
620 | }
|
621 |
|
622 | _dom.fireEvent.keyUp(currentElement(), {
|
623 | key,
|
624 | keyCode,
|
625 | which: keyCode,
|
626 | ...eventOverrides
|
627 | });
|
628 | }
|
629 |
|
630 | function handleBackspace({
|
631 | currentElement,
|
632 | eventOverrides
|
633 | }) {
|
634 | const key = 'Backspace';
|
635 | const keyCode = 8;
|
636 |
|
637 | const keyPressDefaultNotPrevented = _dom.fireEvent.keyDown(currentElement(), {
|
638 | key,
|
639 | keyCode,
|
640 | which: keyCode,
|
641 | ...eventOverrides
|
642 | });
|
643 |
|
644 | if (keyPressDefaultNotPrevented) {
|
645 | fireInputEventIfNeeded({ ...calculateNewBackspaceValue(currentElement()),
|
646 | eventOverrides: {
|
647 | inputType: 'deleteContentBackward',
|
648 | ...eventOverrides
|
649 | },
|
650 | currentElement
|
651 | });
|
652 | }
|
653 |
|
654 | _dom.fireEvent.keyUp(currentElement(), {
|
655 | key,
|
656 | keyCode,
|
657 | which: keyCode,
|
658 | ...eventOverrides
|
659 | });
|
660 | }
|
661 |
|
662 | function handleSelectall({
|
663 | currentElement
|
664 | }) {
|
665 | currentElement().setSelectionRange(0, (0, _utils.getValue)(currentElement()).length);
|
666 | }
|
667 |
|
668 | function handleSpace(context) {
|
669 | if ((0, _utils.isClickable)(context.currentElement())) {
|
670 | handleSpaceOnClickable(context);
|
671 | return;
|
672 | }
|
673 |
|
674 | typeCharacter(' ', context);
|
675 | }
|
676 |
|
677 | function handleSpaceOnClickable({
|
678 | currentElement,
|
679 | eventOverrides
|
680 | }) {
|
681 | const key = ' ';
|
682 | const keyCode = 32;
|
683 |
|
684 | const keyDownDefaultNotPrevented = _dom.fireEvent.keyDown(currentElement(), {
|
685 | key,
|
686 | keyCode,
|
687 | which: keyCode,
|
688 | ...eventOverrides
|
689 | });
|
690 |
|
691 | if (keyDownDefaultNotPrevented) {
|
692 | _dom.fireEvent.keyPress(currentElement(), {
|
693 | key,
|
694 | keyCode,
|
695 | charCode: keyCode,
|
696 | ...eventOverrides
|
697 | });
|
698 | }
|
699 |
|
700 | const keyUpDefaultNotPrevented = _dom.fireEvent.keyUp(currentElement(), {
|
701 | key,
|
702 | keyCode,
|
703 | which: keyCode,
|
704 | ...eventOverrides
|
705 | });
|
706 |
|
707 | if (keyDownDefaultNotPrevented && keyUpDefaultNotPrevented) {
|
708 | _dom.fireEvent.click(currentElement(), { ...eventOverrides
|
709 | });
|
710 | }
|
711 | } |
\ | No newline at end of file |