Skip to content

Commit

Permalink
Merge pull request #130 from testing-library/select-multiple
Browse files Browse the repository at this point in the history
Select multiple
  • Loading branch information
Gpx authored Jun 7, 2019
2 parents 0653587 + fcbe7a2 commit 92c46d4
Show file tree
Hide file tree
Showing 4 changed files with 261 additions and 3 deletions.
14 changes: 13 additions & 1 deletion .all-contributorsrc
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,18 @@
"contributions": [
"doc"
]
},
{
"login": "michaellasky",
"name": "Michael Lasky",
"avatar_url": "https://avatars2.githubusercontent.com/u/6646599?v=4",
"profile": "https://github.com/michaellasky",
"contributions": [
"code",
"doc",
"ideas"
]
}
]
],
"commitConvention": "none"
}
38 changes: 36 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
[![Build Status](https://travis-ci.org/testing-library/user-event.svg?branch=master)](https://travis-ci.org/testing-library/user-event)
[![Maintainability](https://api.codeclimate.com/v1/badges/75f1ff4397e994c6004e/maintainability)](https://codeclimate.com/github/testing-library/user-event/maintainability)
[![Test Coverage](https://api.codeclimate.com/v1/badges/75f1ff4397e994c6004e/test_coverage)](https://codeclimate.com/github/testing-library/user-event/test_coverage)
[![All Contributors](https://img.shields.io/badge/all_contributors-4-orange.svg?style=flat-square)](#contributors)
[![All Contributors](https://img.shields.io/badge/all_contributors-5-orange.svg?style=flat-square)](#contributors)

## The problem

Expand Down Expand Up @@ -133,14 +133,48 @@ 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
([emoji key](https://github.com/all-contributors/all-contributors#emoji-key)):

<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore -->
<table><tr><td align="center"><a href="https://twitter.com/Gpx"><img src="https://avatars0.githubusercontent.com/u/767959?v=4" width="100px;" alt="Giorgio Polvara"/><br /><sub><b>Giorgio Polvara</b></sub></a><br /><a href="https://github.com/testing-library/user-event/issues?q=author%3AGpx" title="Bug reports">🐛</a> <a href="https://github.com/testing-library/user-event/commits?author=Gpx" title="Code">💻</a> <a href="https://github.com/testing-library/user-event/commits?author=Gpx" title="Documentation">📖</a> <a href="#ideas-Gpx" title="Ideas, Planning, & Feedback">🤔</a> <a href="#infra-Gpx" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#review-Gpx" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/testing-library/user-event/commits?author=Gpx" title="Tests">⚠️</a></td><td align="center"><a href="https://github.com/weyert"><img src="https://avatars3.githubusercontent.com/u/7049?v=4" width="100px;" alt="Weyert de Boer"/><br /><sub><b>Weyert de Boer</b></sub></a><br /><a href="https://github.com/testing-library/user-event/commits?author=weyert" title="Code">💻</a> <a href="https://github.com/testing-library/user-event/commits?author=weyert" title="Tests">⚠️</a></td><td align="center"><a href="https://github.com/twhitbeck"><img src="https://avatars2.githubusercontent.com/u/762471?v=4" width="100px;" alt="Tim Whitbeck"/><br /><sub><b>Tim Whitbeck</b></sub></a><br /><a href="https://github.com/testing-library/user-event/issues?q=author%3Atwhitbeck" title="Bug reports">🐛</a> <a href="https://github.com/testing-library/user-event/commits?author=twhitbeck" title="Code">💻</a></td><td align="center"><a href="https://michaeldeboey.be"><img src="https://avatars3.githubusercontent.com/u/6643991?v=4" width="100px;" alt="Michaël De Boey"/><br /><sub><b>Michaël De Boey</b></sub></a><br /><a href="https://github.com/testing-library/user-event/commits?author=MichaelDeBoey" title="Documentation">📖</a></td></tr></table>
<table><tr><td align="center"><a href="https://twitter.com/Gpx"><img src="https://avatars0.githubusercontent.com/u/767959?v=4" width="100px;" alt="Giorgio Polvara"/><br /><sub><b>Giorgio Polvara</b></sub></a><br /><a href="https://github.com/testing-library/user-event/issues?q=author%3AGpx" title="Bug reports">🐛</a> <a href="https://github.com/testing-library/user-event/commits?author=Gpx" title="Code">💻</a> <a href="https://github.com/testing-library/user-event/commits?author=Gpx" title="Documentation">📖</a> <a href="#ideas-Gpx" title="Ideas, Planning, & Feedback">🤔</a> <a href="#infra-Gpx" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#review-Gpx" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/testing-library/user-event/commits?author=Gpx" title="Tests">⚠️</a></td><td align="center"><a href="https://github.com/weyert"><img src="https://avatars3.githubusercontent.com/u/7049?v=4" width="100px;" alt="Weyert de Boer"/><br /><sub><b>Weyert de Boer</b></sub></a><br /><a href="https://github.com/testing-library/user-event/commits?author=weyert" title="Code">💻</a> <a href="https://github.com/testing-library/user-event/commits?author=weyert" title="Tests">⚠️</a></td><td align="center"><a href="https://github.com/twhitbeck"><img src="https://avatars2.githubusercontent.com/u/762471?v=4" width="100px;" alt="Tim Whitbeck"/><br /><sub><b>Tim Whitbeck</b></sub></a><br /><a href="https://github.com/testing-library/user-event/issues?q=author%3Atwhitbeck" title="Bug reports">🐛</a> <a href="https://github.com/testing-library/user-event/commits?author=twhitbeck" title="Code">💻</a></td><td align="center"><a href="https://michaeldeboey.be"><img src="https://avatars3.githubusercontent.com/u/6643991?v=4" width="100px;" alt="Michaël De Boey"/><br /><sub><b>Michaël De Boey</b></sub></a><br /><a href="https://github.com/testing-library/user-event/commits?author=MichaelDeBoey" title="Documentation">📖</a></td><td align="center"><a href="https://github.com/michaellasky"><img src="https://avatars2.githubusercontent.com/u/6646599?v=4" width="100px;" alt="Michael Lasky"/><br /><sub><b>Michael Lasky</b></sub></a><br /><a href="https://github.com/testing-library/user-event/commits?author=michaellasky" title="Code">💻</a> <a href="https://github.com/testing-library/user-event/commits?author=michaellasky" title="Documentation">📖</a> <a href="#ideas-michaellasky" title="Ideas, Planning, & Feedback">🤔</a></td></tr></table>

<!-- ALL-CONTRIBUTORS-LIST:END -->

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 92c46d4

Please sign in to comment.