Skip to content

Commit

Permalink
feat: 🎸 add selectOptions
Browse files Browse the repository at this point in the history
Simulates selecting options from a <select> or <select multiple> tag.
  • Loading branch information
michaellasky authored and Gpx committed Jun 7, 2019
1 parent 0653587 commit 90cd69b
Show file tree
Hide file tree
Showing 3 changed files with 246 additions and 0 deletions.
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,40 @@ one character at the time. `false` is the default value.
are typed. By default it's 0. You can use this option if your component has a
different behavior for fast or slow users.

### `selectOptions(element, values)`

Selects the specified option(s) of a `<select>` or a `<select multiple>`
element.

```jsx
import React from "react";
import { render } from "@testing-library/react";
import userEvent from "@testing-library/user-event";

const { getByTestId } = render(
<select multiple data-testid="select-multiple">
<option data-testid="val1" value="1">
1
</option>
<option data-testid="val2" value="2">
2
</option>
<option data-testid="val3" value="3">
3
</option>
</select>
);

userEvent.selectOptions(getByTestId("select-multiple"), ["1", "3"]);

expect(getByTestId("val1").selected).toBe(true);
expect(getByTestId("val2").selected).toBe(false);
expect(getByTestId("val3").selected).toBe(true);
```

The `values` parameter can be either an array of values or a singular scalar
value.

## Contributors

Thanks goes to these wonderful people
Expand Down
174 changes: 174 additions & 0 deletions __tests__/selectoptions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import React from "react";
import { render, cleanup, fireEvent } from "@testing-library/react";
import "jest-dom/extend-expect";
import userEvent from "../src";

afterEach(cleanup);

describe("userEvent.selectOptions", () => {
it.each(["select", "select multiple"])(
"should fire the correct events for <%s>",
type => {
const events = [];
const eventsHandler = jest.fn(evt => events.push(evt.type));
const multiple = type === "select multiple";
const eventHandlers = {
onMouseOver: eventsHandler,
onMouseMove: eventsHandler,
onMouseDown: eventsHandler,
onFocus: eventsHandler,
onMouseUp: eventsHandler,
onClick: eventsHandler
};

const { getByTestId } = render(
<select {...{ ...eventHandlers, multiple }} data-testid="element">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
);

userEvent.selectOptions(getByTestId("element"), "1");

expect(events).toEqual([
"mouseover",
"mousemove",
"mousedown",
"focus",
"mouseup",
"click",
"mouseover", // The events repeat because we click on the child OPTION too
"mousemove", // But these specifically are the events bubbling up to the <select>
"mousedown",
"focus",
"mouseup",
"click"
]);
}
);

it("should fire the correct events on selected OPTION child with <select>", () => {
function handleEvent(evt) {
const optValue = parseInt(evt.target.value);
events[optValue] = [...(events[optValue] || []), evt.type];
}

const events = [];
const eventsHandler = jest.fn(handleEvent);
const eventHandlers = {
onMouseOver: eventsHandler,
onMouseMove: eventsHandler,
onMouseDown: eventsHandler,
onFocus: eventsHandler,
onMouseUp: eventsHandler,
onClick: eventsHandler
};

const { getByTestId } = render(
<select data-testid="element">
<option {...eventHandlers} value="1">
1
</option>
<option {...eventHandlers} value="2">
2
</option>
<option {...eventHandlers} value="3">
3
</option>
</select>
);

userEvent.selectOptions(getByTestId("element"), ["2"]);

expect(events[1]).toBe(undefined);
expect(events[3]).toBe(undefined);
expect(events[2]).toEqual([
"mouseover",
"mousemove",
"mousedown",
"focus",
"mouseup",
"click"
]);
});

it("should fire the correct events on selected OPTION children with <select multiple>", () => {
function handleEvent(evt) {
const optValue = parseInt(evt.target.value);
events[optValue] = [...(events[optValue] || []), evt.type];
}

const events = [];
const eventsHandler = jest.fn(handleEvent);
const eventHandlers = {
onMouseOver: eventsHandler,
onMouseMove: eventsHandler,
onMouseDown: eventsHandler,
onFocus: eventsHandler,
onMouseUp: eventsHandler,
onClick: eventsHandler
};

const { getByTestId } = render(
<select multiple data-testid="element">
<option {...eventHandlers} value="1">
1
</option>
<option {...eventHandlers} value="2">
2
</option>
<option {...eventHandlers} value="3">
3
</option>
</select>
);

userEvent.selectOptions(getByTestId("element"), ["1", "3"]);

expect(events[2]).toBe(undefined);
expect(events[1]).toEqual([
"mouseover",
"mousemove",
"mousedown",
"focus",
"mouseup",
"click"
]);

expect(events[3]).toEqual([
"mouseover",
"mousemove",
"mousedown",
"focus",
"mouseup",
"click"
]);
});

it("sets the selected prop on the selected OPTION", () => {
const onSubmit = jest.fn();

const { getByTestId } = render(
<form onSubmit={onSubmit}>
<select multiple data-testid="element">
<option data-testid="val1" value="1">
1
</option>
<option data-testid="val2" value="2">
2
</option>
<option data-testid="val3" value="3">
3
</option>
</select>
</form>
);

userEvent.selectOptions(getByTestId("element"), ["1", "3"]);

expect(getByTestId("val1").selected).toBe(true);
expect(getByTestId("val2").selected).toBe(false);
expect(getByTestId("val3").selected).toBe(true);
});
});
38 changes: 38 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,17 @@ function dblClickCheckbox(checkbox) {
fireEvent.change(checkbox);
}

function selectOption(option) {
fireEvent.mouseOver(option);
fireEvent.mouseMove(option);
fireEvent.mouseDown(option);
fireEvent.focus(option);
fireEvent.mouseUp(option);
fireEvent.click(option);

option.selected = true;
}

const userEvent = {
click(element) {
const focusedElement = document.activeElement;
Expand Down Expand Up @@ -130,6 +141,33 @@ const userEvent = {
wasAnotherElementFocused && focusedElement.blur();
},

selectOptions(element, values) {
const focusedElement = document.activeElement;
const wasAnotherElementFocused =
focusedElement !== document.body && focusedElement !== element;
if (wasAnotherElementFocused) {
fireEvent.mouseMove(focusedElement);
fireEvent.mouseLeave(focusedElement);
}

clickElement(element);

const valArray = Array.isArray(values) ? values : [values];
const selectedOptions = Array.from(element.children).filter(
opt => opt.tagName === "OPTION" && valArray.includes(opt.value)
);

if (selectedOptions.length > 0) {
if (element.multiple) {
selectedOptions.forEach(option => selectOption(option));
} else {
selectOption(selectedOptions[0]);
}
}

wasAnotherElementFocused && focusedElement.blur();
},

async type(element, text, userOpts = {}) {
const defaultOpts = {
allAtOnce: false,
Expand Down

0 comments on commit 90cd69b

Please sign in to comment.