Skip to content

Commit

Permalink
Merge pull request #29 from Gpx/timeout
Browse files Browse the repository at this point in the history
Timeout
  • Loading branch information
Gpx committed Oct 21, 2018
2 parents 7947860 + b0f2c3c commit 2bf04b4
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 17 deletions.
2 changes: 1 addition & 1 deletion .babelrc
@@ -1,3 +1,3 @@
{
"presets": ["env", "react"]
}
}
1 change: 1 addition & 0 deletions .gitattributes
@@ -0,0 +1 @@
text=auto
11 changes: 8 additions & 3 deletions README.md
Expand Up @@ -97,9 +97,7 @@ test("double click", () => {

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

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

```jsx
import React from "react";
Expand All @@ -113,3 +111,10 @@ const { getByText } = test("click", () => {
userEvent.type(getByTestId("email"), "Hello, World!");
expect(getByTestId("email")).toHaveAttribute("value", "Hello, World!");
```

If `options.allAtOnce` is `true` type will write `text` at once rather than one
character at the time. `false` is the default value`.

`options.delay` is the number of milliseconds that pass between to characters
are typed. By default it's 0. You can use this option if your component has a
different behavior for fast or slow users.
54 changes: 50 additions & 4 deletions __tests__/type.js
@@ -1,5 +1,5 @@
import React from "react";
import { render, cleanup } from "react-testing-library";
import { cleanup, render, wait } from "react-testing-library";
import "jest-dom/extend-expect";
import userEvent from "../src";

Expand All @@ -9,15 +9,59 @@ describe("userEvent.type", () => {
it.each(["input", "textarea"])("should type text in <%s>", type => {
const onChange = jest.fn();
const { getByTestId } = render(
React.createElement(type, { "data-testid": "input", onChange: onChange })
React.createElement(type, {
"data-testid": "input",
onChange: onChange
})
);
const text = "Hello, world!";
userEvent.type(getByTestId("input"), text);

expect(onChange).toHaveBeenCalledTimes(text.length);
expect(getByTestId("input")).toHaveProperty("value", text);
});

it("should not type when event.preventDefault() is called", () => {
const onChange = jest.fn();
const onKeydown = jest
.fn()
.mockImplementation(event => event.preventDefault());
const { getByTestId } = render(
<input data-testid="input" onKeyDown={onKeydown} onChange={onChange} />
);
const text = "Hello, world!";
userEvent.type(getByTestId("input"), text);
expect(onKeydown).toHaveBeenCalledTimes(text.length);
expect(onChange).toHaveBeenCalledTimes(0);
expect(getByTestId("input")).not.toHaveProperty("value", text);
});

it("should delayed the typing when opts.dealy is not 0", async () => {
jest.useFakeTimers();
const onChange = jest.fn();
const { getByTestId } = render(
React.createElement("input", {
"data-testid": "input",
onChange: onChange
})
);
const text = "Hello, world!";
const delay = 10;
userEvent.type(getByTestId("input"), text, {
delay
});
expect(onChange).not.toHaveBeenCalled();
expect(getByTestId("input")).not.toHaveProperty("value", text);

for (let i = 0; i < text.length; i++) {
jest.advanceTimersByTime(delay);
await wait(() => expect(onChange).toHaveBeenCalledTimes(i + 1));
expect(getByTestId("input")).toHaveProperty(
"value",
text.slice(0, i + 1)
);
}
});

it.each(["input", "textarea"])(
"should type text in <%s> all at once",
type => {
Expand All @@ -29,7 +73,9 @@ describe("userEvent.type", () => {
})
);
const text = "Hello, world!";
userEvent.type(getByTestId("input"), text, { allAtOnce: true });
userEvent.type(getByTestId("input"), text, {
allAtOnce: true
});

expect(onChange).toHaveBeenCalledTimes(1);
expect(getByTestId("input")).toHaveProperty("value", text);
Expand Down
60 changes: 51 additions & 9 deletions src/index.js
@@ -1,5 +1,11 @@
import { fireEvent } from "dom-testing-library";

function wait(time) {
return new Promise(function(resolve) {
setTimeout(() => resolve(), time);
});
}

function findTagInParents(element, tagName) {
if (element.parentNode == null) return undefined;
if (element.parentNode.tagName === tagName) return element.parentNode;
Expand Down Expand Up @@ -122,19 +128,55 @@ const userEvent = {
wasAnotherElementFocused && focusedElement.blur();
},

type(element, text, userOpts = {}) {
const defaultOpts = { allAtOnce: false };
async type(element, text, userOpts = {}) {
const defaultOpts = {
allAtOnce: false,
delay: 0
};
const opts = Object.assign(defaultOpts, userOpts);

this.click(element);
if (opts.allAtOnce) {
fireEvent.change(element, { target: { value: text } });
} else {
text
.split("")
.forEach((_, i) =>
fireEvent.change(element, { target: { value: text.slice(0, i + 1) } })
);
const typedCharacters = text.split("");

let actuallyTyped = "";
for (let index = 0; index < text.length; index++) {
const char = text[index];
const key = char; // TODO: check if this also valid for characters with diacritic markers e.g. úé etc
const keyCode = char.charCodeAt(0);

if (opts.delay > 0) await wait(opts.delay);

const downEvent = fireEvent.keyDown(element, {
key: key,
keyCode: keyCode,
which: keyCode
});
if (downEvent) {
const pressEvent = fireEvent.keyPress(element, {
key: key,
keyCode,
charCode: keyCode,
keyCode: keyCode
});
if (pressEvent) {
actuallyTyped += key;
fireEvent.change(element, {
target: {
value: actuallyTyped
},
bubbles: true,
cancelable: true
});
}
}

fireEvent.keyUp(element, {
key: key,
keyCode: keyCode,
which: keyCode
});
}
}
}
};
Expand Down

0 comments on commit 2bf04b4

Please sign in to comment.