From 90cd69becc1f82034fe85b8618a4bbfa5b22587a Mon Sep 17 00:00:00 2001 From: mlasky Date: Thu, 6 Jun 2019 02:46:25 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20selectOptions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Simulates selecting options from a tag. --- README.md | 34 ++++++++ __tests__/selectoptions.js | 174 +++++++++++++++++++++++++++++++++++++ src/index.js | 38 ++++++++ 3 files changed, 246 insertions(+) create mode 100644 __tests__/selectoptions.js diff --git a/README.md b/README.md index a578d4b2..a3be2292 100644 --- a/README.md +++ b/README.md @@ -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 `` +element. + +```jsx +import React from "react"; +import { render } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; + +const { getByTestId } = render( + +); + +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 diff --git a/__tests__/selectoptions.js b/__tests__/selectoptions.js new file mode 100644 index 00000000..7fd649bd --- /dev/null +++ b/__tests__/selectoptions.js @@ -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( + + ); + + 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 ", () => { + 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( + + ); + + 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 + + + + + ); + + 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( +
+ +
+ ); + + userEvent.selectOptions(getByTestId("element"), ["1", "3"]); + + expect(getByTestId("val1").selected).toBe(true); + expect(getByTestId("val2").selected).toBe(false); + expect(getByTestId("val3").selected).toBe(true); + }); +}); diff --git a/src/index.js b/src/index.js index 9462cb01..697520fd 100644 --- a/src/index.js +++ b/src/index.js @@ -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; @@ -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,