-
-
Notifications
You must be signed in to change notification settings - Fork 251
/
type-in.ts
93 lines (81 loc) · 3.02 KB
/
type-in.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import { nextTickPromise } from '../-utils';
import settled from '../settled';
import getElement from './-get-element';
import isFormControl from './-is-form-control';
import { __focus__ } from './focus';
import { Promise } from 'rsvp';
import fireEvent from './fire-event';
import Target from './-target';
/**
* Mimics character by character entry into the target `input` or `textarea` element.
*
* Allows for simulation of slow entry by passing an optional millisecond delay
* between key events.
* The major difference between `typeIn` and `fillIn` is that `typeIn` triggers
* keyboard events as well as `input` and `change`.
* Typically this looks like `focus` -> `focusin` -> `keydown` -> `keypress` -> `keyup` -> `input` -> `change`
* per character of the passed text (this may vary on some browsers).
*
* @public
* @param {string|Element} target the element or selector to enter text into
* @param {string} text the test to fill the element with
* @param {Object} options {delay: x} (default 50) number of milliseconds to wait per keypress
* @return {Promise<void>} resolves when the application is settled
*/
export default function typeIn(target: Target, text, options = { delay: 50 }): Promise<void> {
return nextTickPromise().then(() => {
if (!target) {
throw new Error('Must pass an element or selector to `typeIn`.');
}
const element = getElement(target);
if (!element) {
throw new Error(`Element not found when calling \`typeIn('${target}')\``);
}
let isControl = isFormControl(element);
if (!isControl) {
throw new Error('`typeIn` is only usable on form controls.');
}
if (typeof text === 'undefined' || text === null) {
throw new Error('Must provide `text` when calling `typeIn`.');
}
__focus__(element);
return fillOut(element, text, options.delay)
.then(() => fireEvent(element, 'change'))
.then(settled);
});
}
// eslint-disable-next-line require-jsdoc
function fillOut(element, text, delay) {
const inputFunctions = text.split('').map(character => keyEntry(element, character));
return inputFunctions.reduce((currentPromise, func) => {
return currentPromise.then(() => delayedExecute(func, delay));
}, Promise.resolve());
}
// eslint-disable-next-line require-jsdoc
function keyEntry(element, character) {
const charCode = character.charCodeAt();
const eventOptions = {
bubbles: true,
cancellable: true,
charCode,
key: character,
};
const keyEvents = {
down: new KeyboardEvent('keydown', eventOptions),
press: new KeyboardEvent('keypress', eventOptions),
up: new KeyboardEvent('keyup', eventOptions),
};
return function() {
element.dispatchEvent(keyEvents.down);
element.dispatchEvent(keyEvents.press);
element.value = element.value + character;
fireEvent(element, 'input');
element.dispatchEvent(keyEvents.up);
};
}
// eslint-disable-next-line require-jsdoc
function delayedExecute(func, delay) {
return new Promise(resolve => {
setTimeout(resolve, delay);
}).then(func);
}