Skip to content

Commit 2bf04b4

Browse files
authored
Merge pull request #29 from Gpx/timeout
Timeout
2 parents 7947860 + b0f2c3c commit 2bf04b4

File tree

5 files changed

+111
-17
lines changed

5 files changed

+111
-17
lines changed

.babelrc

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
22
"presets": ["env", "react"]
3-
}
3+
}

.gitattributes

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
text=auto

README.md

+8-3
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,7 @@ test("double click", () => {
9797

9898
### `type(element, text, [options])`
9999

100-
Writes `text` inside an `<input>` or a `<textarea>`. `options` accepts one
101-
argument `allAtOnce` which is `false` by default. If it's set to `true` it will
102-
write `text` at once rather than one character at the time.
100+
Writes `text` inside an `<input>` or a `<textarea>`.
103101

104102
```jsx
105103
import React from "react";
@@ -113,3 +111,10 @@ const { getByText } = test("click", () => {
113111
userEvent.type(getByTestId("email"), "Hello, World!");
114112
expect(getByTestId("email")).toHaveAttribute("value", "Hello, World!");
115113
```
114+
115+
If `options.allAtOnce` is `true` type will write `text` at once rather than one
116+
character at the time. `false` is the default value`.
117+
118+
`options.delay` is the number of milliseconds that pass between to characters
119+
are typed. By default it's 0. You can use this option if your component has a
120+
different behavior for fast or slow users.

__tests__/type.js

+50-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from "react";
2-
import { render, cleanup } from "react-testing-library";
2+
import { cleanup, render, wait } from "react-testing-library";
33
import "jest-dom/extend-expect";
44
import userEvent from "../src";
55

@@ -9,15 +9,59 @@ describe("userEvent.type", () => {
99
it.each(["input", "textarea"])("should type text in <%s>", type => {
1010
const onChange = jest.fn();
1111
const { getByTestId } = render(
12-
React.createElement(type, { "data-testid": "input", onChange: onChange })
12+
React.createElement(type, {
13+
"data-testid": "input",
14+
onChange: onChange
15+
})
1316
);
1417
const text = "Hello, world!";
1518
userEvent.type(getByTestId("input"), text);
16-
1719
expect(onChange).toHaveBeenCalledTimes(text.length);
1820
expect(getByTestId("input")).toHaveProperty("value", text);
1921
});
2022

23+
it("should not type when event.preventDefault() is called", () => {
24+
const onChange = jest.fn();
25+
const onKeydown = jest
26+
.fn()
27+
.mockImplementation(event => event.preventDefault());
28+
const { getByTestId } = render(
29+
<input data-testid="input" onKeyDown={onKeydown} onChange={onChange} />
30+
);
31+
const text = "Hello, world!";
32+
userEvent.type(getByTestId("input"), text);
33+
expect(onKeydown).toHaveBeenCalledTimes(text.length);
34+
expect(onChange).toHaveBeenCalledTimes(0);
35+
expect(getByTestId("input")).not.toHaveProperty("value", text);
36+
});
37+
38+
it("should delayed the typing when opts.dealy is not 0", async () => {
39+
jest.useFakeTimers();
40+
const onChange = jest.fn();
41+
const { getByTestId } = render(
42+
React.createElement("input", {
43+
"data-testid": "input",
44+
onChange: onChange
45+
})
46+
);
47+
const text = "Hello, world!";
48+
const delay = 10;
49+
userEvent.type(getByTestId("input"), text, {
50+
delay
51+
});
52+
expect(onChange).not.toHaveBeenCalled();
53+
expect(getByTestId("input")).not.toHaveProperty("value", text);
54+
55+
for (let i = 0; i < text.length; i++) {
56+
jest.advanceTimersByTime(delay);
57+
await wait(() => expect(onChange).toHaveBeenCalledTimes(i + 1));
58+
expect(getByTestId("input")).toHaveProperty(
59+
"value",
60+
text.slice(0, i + 1)
61+
);
62+
}
63+
});
64+
2165
it.each(["input", "textarea"])(
2266
"should type text in <%s> all at once",
2367
type => {
@@ -29,7 +73,9 @@ describe("userEvent.type", () => {
2973
})
3074
);
3175
const text = "Hello, world!";
32-
userEvent.type(getByTestId("input"), text, { allAtOnce: true });
76+
userEvent.type(getByTestId("input"), text, {
77+
allAtOnce: true
78+
});
3379

3480
expect(onChange).toHaveBeenCalledTimes(1);
3581
expect(getByTestId("input")).toHaveProperty("value", text);

src/index.js

+51-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { fireEvent } from "dom-testing-library";
22

3+
function wait(time) {
4+
return new Promise(function(resolve) {
5+
setTimeout(() => resolve(), time);
6+
});
7+
}
8+
39
function findTagInParents(element, tagName) {
410
if (element.parentNode == null) return undefined;
511
if (element.parentNode.tagName === tagName) return element.parentNode;
@@ -122,19 +128,55 @@ const userEvent = {
122128
wasAnotherElementFocused && focusedElement.blur();
123129
},
124130

125-
type(element, text, userOpts = {}) {
126-
const defaultOpts = { allAtOnce: false };
131+
async type(element, text, userOpts = {}) {
132+
const defaultOpts = {
133+
allAtOnce: false,
134+
delay: 0
135+
};
127136
const opts = Object.assign(defaultOpts, userOpts);
128-
129-
this.click(element);
130137
if (opts.allAtOnce) {
131138
fireEvent.change(element, { target: { value: text } });
132139
} else {
133-
text
134-
.split("")
135-
.forEach((_, i) =>
136-
fireEvent.change(element, { target: { value: text.slice(0, i + 1) } })
137-
);
140+
const typedCharacters = text.split("");
141+
142+
let actuallyTyped = "";
143+
for (let index = 0; index < text.length; index++) {
144+
const char = text[index];
145+
const key = char; // TODO: check if this also valid for characters with diacritic markers e.g. úé etc
146+
const keyCode = char.charCodeAt(0);
147+
148+
if (opts.delay > 0) await wait(opts.delay);
149+
150+
const downEvent = fireEvent.keyDown(element, {
151+
key: key,
152+
keyCode: keyCode,
153+
which: keyCode
154+
});
155+
if (downEvent) {
156+
const pressEvent = fireEvent.keyPress(element, {
157+
key: key,
158+
keyCode,
159+
charCode: keyCode,
160+
keyCode: keyCode
161+
});
162+
if (pressEvent) {
163+
actuallyTyped += key;
164+
fireEvent.change(element, {
165+
target: {
166+
value: actuallyTyped
167+
},
168+
bubbles: true,
169+
cancelable: true
170+
});
171+
}
172+
}
173+
174+
fireEvent.keyUp(element, {
175+
key: key,
176+
keyCode: keyCode,
177+
which: keyCode
178+
});
179+
}
138180
}
139181
}
140182
};

0 commit comments

Comments
 (0)