UNPKG

react-autosuggestions

Version:

An accessible React component to take the pain out of creating auto-suggestion components

881 lines (866 loc) 41.3 kB
import { AutoSuggest } from "../src/components/AutoSuggest.js"; import { render, screen, fireEvent, act, waitFor } from "./test-utils.js"; import "@testing-library/jest-dom/extend-expect"; import userEvent from "@testing-library/user-event"; import React from "react"; import { rest } from "msw"; import { setupServer } from "msw/node"; const countries = ["United Arab Emirates", "United Kingdom", "United States"]; const states = ["AK", "AL", "AR", "AZ", "CA", "CO", "CT", "DC", "DE", "FL", "GA"]; const server = setupServer( rest.get("https://ntsb-server.herokuapp.com/api/accidents/countryList/:country", (req, res, ctx) => { const { country } = req.params; return res(ctx.json(countries.filter((val) => val.toUpperCase().startsWith(country.toUpperCase())))); }), rest.get("https://ntsb-server.herokuapp.com/api/accidents/stateList/:state", (req, res, ctx) => { const { state } = req.params; return res(ctx.json(states.filter((val => val.toUpperCase().startsWith(state.toUpperCase()))))) }) ); beforeAll(() => server.listen()); afterEach(() => server.resetHandlers()); afterAll(() => server.close()); const Form = ({ caseInsensitive, disabled, handleChange, id = "", name = "", options = [], styles, type = "", url = "", value, }) => { const [make, setMake] = React.useState(); const [formData, setFormData] = React.useState(); const handleSubmit = (e) => { e.preventDefault(); setFormData(`Make: ${make}`); }; return ( <> <form onSubmit={handleSubmit}> <AutoSuggest caseInsensitive={caseInsensitive} disabled={disabled ? true : false} handleChange={handleChange ? handleChange : setMake} id={id} name={name} options={options} styles={styles} type={type} url={url} value={value ? value : make} /> <button>Submit</button> </form> <p data-testid="Value">{make}</p> {formData && <p data-testid="AfterFormSubmit">{formData}</p>} </> ); }; describe("AutoSuggest rendering", () => { describe("Defaults", () => { test("It should default the name of the input to be 'Search'", () => { render(<AutoSuggest />); expect(screen.getByLabelText(/Search/)).toBeInTheDocument(); }); }); test("AutoSuggest should render AutoSuggestServer if a url is provided", () => { const { getByDataType } = render( <Form name="Make" url="https://ntsb-server.herokuapp.com/api/accidents/makeList" /> ); expect(getByDataType("Server")).toBeInTheDocument(); }); test("AutoSuggest should render AutoSuggestClient if no url is passed", () => { const { getByDataType } = render(<Form name="Make" options={["Bentley", "BMW", "Buick", "Honda", "Accord"]} />); expect(getByDataType("Client")).toBeInTheDocument(); }); test("AutoSuggest should ignore arrow down input when the listbox is closed", () => { render( <Form name="Make" options={["Bentley", "BMW", "Buick", "Honda", "Accord"]} /> ); const input = screen.getByRole("textbox", { name: "Make"}); userEvent.type(input, "{arrowdown}"); expect(screen.queryByRole("listbox")).not.toBeInTheDocument(); }); test("AutoSuggest should ignore arrow up input when the listbox is closed", () => { render( <Form name="Make" options={["Bentley", "BMW", "Buick", "Honda", "Accord"]} /> ); const input = screen.getByRole("textbox", { name: "Make"}); userEvent.type(input, "{arrowup}"); expect(screen.queryByRole("listbox")).not.toBeInTheDocument(); }) }); test("AutoSuggest should use user defined id if provided", () => { render(<Form id="testId" name="Make" options={["Acura", "BMW", "Audi", "Bentley", "Buick", "Cadillac", "Chevrolet"]} />); const input = screen.getByLabelText("Make"); expect(input.id).toBe("testId-input") }) test("AutoSuggest should accept input", () => { render(<Form name="Make" options={["Acura", "BMW", "Audi", "Bentley", "Buick", "Cadillac", "Chevrolet"]} />); let input = screen.queryByRole("textbox"); fireEvent.change(input, { target: { value: "B" } }); expect(input.value).toBe("B"); }); describe("AutoSuggest should display matching options when text is entered", () => { test("AutoSuggest should handle an options array of strings", () => { render(<Form name="Make" options={["Acura", "BMW", "Audi", "Bentley", "Buick", "Cadillac", "Chevrolet"]} />); let input = screen.queryByRole("textbox"); fireEvent.change(input, { target: { value: "B" } }); expect(screen.queryByRole("listbox")).toBeInTheDocument(); expect(screen.getByRole("option", { name: "Bentley" })).toHaveTextContent("Bentley"); expect(screen.getByRole("option", { name: "Bentley" })).not.toHaveAttribute("abbr") expect(screen.getByRole("option", { name: "Bentley" })).not.toHaveAttribute("name") expect(screen.getByRole("option", { name: "BMW" })).toHaveTextContent("BMW"); expect(screen.getByRole("option", { name: "BMW" })).not.toHaveAttribute("abbr"); expect(screen.getByRole("option", { name: "BMW" })).not.toHaveAttribute("name"); expect(screen.getByRole("option", { name: "Buick" })).toHaveTextContent("Buick") expect(screen.getByRole("option", { name: "Buick" })).not.toHaveAttribute("abbr"); expect(screen.getByRole("option", { name: "Buick" })).not.toHaveAttribute("name"); }); test("AutoSuggest should handle an options array of objects with name and value", () => { render( <Form name="Make" options={[ { name: "Acura", value: "Acura" }, { name: "BMW", value: "BMW" }, { name: "Audi", value: "Audi" }, { name: "Bentley", value: "Bentley" }, { name: "Buick", value: "Buick" }, { name: "Cadillac", value: "Cadillac" }, { name: "Chevrolet", value: "Chevrolet" } ]} /> ); let input = screen.queryByRole("textbox"); fireEvent.change(input, { target: { value: "B" } }); expect(screen.queryByRole("listbox")).toBeInTheDocument(); expect(screen.getByRole("option", { name: "Bentley" })).toHaveTextContent("Bentley"); expect(screen.getByRole("option", { name: "Bentley" })).not.toHaveAttribute("abbr") expect(screen.getByRole("option", { name: "Bentley" })).toHaveAttribute("name", "Bentley") expect(screen.getByRole("option", { name: "BMW" })).toHaveTextContent("BMW"); expect(screen.getByRole("option", { name: "BMW" })).not.toHaveAttribute("abbr"); expect(screen.getByRole("option", { name: "BMW" })).toHaveAttribute("name", "BMW"); expect(screen.getByRole("option", { name: "Buick" })).toHaveTextContent("Buick") expect(screen.getByRole("option", { name: "Buick" })).not.toHaveAttribute("abbr"); expect(screen.getByRole("option", { name: "Buick" })).toHaveAttribute("name", "Buick"); }); test("Autosuggest should handle an options array of objects with abbr, name, and value", () => { render( <Form name="Make" options={[ { name: "Acura", value: "Acura", abbr: "Acura" }, { name: "BMW", value: "BMW", abbr: "BMW" }, { name: "Audi", value: "Audi", abbr: "Audi" }, { name: "Bentley", value: "Bentley", abbr: "Bentley" }, { name: "Buick", value: "Buick", abbr: "Buick" }, { name: "Cadillac", value: "Cadillac", abbr: "Cadillac" }, { name: "Chevrolet", value: "Chevrolet", abbr: "Chevrolet" } ]} /> ); let input = screen.queryByRole("textbox"); fireEvent.change(input, { target: { value: "B" } }); expect(screen.queryByRole("listbox")).toBeInTheDocument(); expect(screen.getByRole("option", { name: "Bentley" })).toHaveTextContent("Bentley"); expect(screen.getByRole("option", { name: "Bentley"})).toHaveAttribute("abbr", "Bentley"); expect(screen.getByRole("option", { name: "Bentley"})).toHaveAttribute("name", "Bentley"); expect(screen.getByRole("option", { name: "BMW" })).toHaveTextContent("BMW"); expect(screen.getByRole("option", { name: "BMW" })).toHaveAttribute("abbr", "BMW"); expect(screen.getByRole("option", { name: "BMW" })).toHaveAttribute("name", "BMW"); expect(screen.getByRole("option", { name: "Buick" })).toHaveTextContent("Buick"); expect(screen.getByRole("option", { name: "Buick" })).toHaveAttribute("abbr", "Buick"); expect(screen.getByRole("option", { name: "Buick" })).toHaveAttribute("name", "Buick"); }); test("Autosuggest should handle an empty options array", () => { render( <Form name="Empty" options={[]} /> ); const input = screen.queryByRole("textbox"); fireEvent.change(input, { target: { value: "B" } }); expect(screen.queryByRole("listbox")).toBeNull(); }); }); /* It was easier to do some of these tests in a higher order element due to the ref passing that takes place */ test("AutoSuggestContainer should set input focus styling", () => { render( <Form name="Make" options={["Buick", "Acura", "Honda"]} styles={{ searchField: { focus: { color: "#f29" } } }} /> ); const textbox = screen.getByRole("textbox", { name: "Make" }); expect(textbox.style.color).toBe("rgb(0, 0, 0)"); textbox.focus(); expect(document.activeElement).toBe(textbox); expect(textbox.style.color).toBe("rgb(255, 34, 153)"); }); test("AutoSuggestContainer should set input blur styling", () => { render( <Form name="Make" options={["Buick", "Acura", "Honda"]} styles={{ searchField: { focus: { color: "#f29" } } }} /> ); const textbox = screen.getByRole("textbox", { name: "Make" }); expect(textbox.style.color).toBe("rgb(0, 0, 0)"); textbox.focus(); expect(document.activeElement).toBe(textbox); expect(textbox.style.color).toBe("rgb(255, 34, 153)"); textbox.blur(); expect(document.activeElemnt).not.toBe(textbox); expect(textbox.style.color).toBe("rgb(0, 0, 0)"); }); describe("Enter key", () => { describe("If an option has been selected", () => { test("It should change the value of the input to the currently selected option", () => { render( <Form name="Make" options={["Acura", "BMW", "Audi", "Bentley", "Buick", "Cadillac", "Chevrolet"]} styles={{ searchField: { focus: { color: "#f29" } } }} /> ); let input = screen.queryByRole("textbox"); fireEvent.change(input, { target: { value: "B" } }); expect(screen.getByRole("option", { name: "Bentley" })); expect(screen.getByRole("option", { name: "BMW" })); expect(screen.getByRole("option", { name: "Buick" })); expect(screen.queryByRole("option", { name: "Acura" })).toBeNull(); expect(screen.queryByRole("option", { name: "Audi" })).toBeNull(); expect(screen.queryByRole("option", { name: "Cadillac" })).toBeNull(); expect(screen.queryByRole("option", { name: "Chevrolet" })).toBeNull(); userEvent.type(input, "{arrowup}"); userEvent.type(input, "{arrowup}"); userEvent.type(input, "{arrowup}"); expect(input).toHaveFocus(); const option = screen.getByRole("option", { name: "Bentley" }); expect(option).toHaveAttribute("aria-selected", "true"); userEvent.type(input, "{enter}"); expect(screen.getByRole("textbox", { name: "Make" })).toHaveValue("Bentley"); expect(screen.getByTestId("Value")).toHaveTextContent("Bentley"); }); }); describe("If an option has not been selected", () => { test("It should keep the text previously entered into the input field", () => { render( <Form name="Make" options={["Acura", "BMW", "Audi", "Bentley", "Buick", "Cadillac", "Chevrolet"]} styles={{ searchField: { focus: { color: "#f29" } } }} /> ); let input = screen.queryByRole("textbox"); fireEvent.change(input, { target: { value: "B" } }); expect(screen.getByRole("option", { name: "Bentley" })); expect(screen.getByRole("option", { name: "BMW" })); expect(screen.getByRole("option", { name: "Buick" })); const announcement = expect(screen.queryByRole("option", { name: "Acura" })).toBeNull(); expect(screen.queryByRole("option", { name: "Audi" })).toBeNull(); expect(screen.queryByRole("option", { name: "Cadillac" })).toBeNull(); expect(screen.queryByRole("option", { name: "Chevrolet" })).toBeNull(); expect(input).toHaveFocus(); userEvent.type(input, "{enter}"); expect(screen.getByRole("textbox", { name: "Make" })).toHaveValue("B"); expect(screen.getByTestId("Value")).toHaveTextContent("B"); }); }); }); describe("Down arrow", () => { test("Pressing the down arrow should set input focus", () => { render( <Form name="Make" options={["Acura", "BMW", "Audi", "Bentley", "Buick", "Cadillac", "Chevrolet"]} styles={{ searchField: { focus: { color: "#f29" } } }} /> ); const input = screen.getByRole("textbox", { name: "Make" }); fireEvent.change(input, { target: { value: "B" } }); userEvent.type(input, "{arrowdown}"); expect(input).toHaveFocus(); }); test("Pressing down arrow should set aria-activedescendant to the selected option", () => { render( <Form name="Make" options={["Acura", "BMW", "Audi", "Bentley", "Buick", "Cadillac", "Chevrolet"]} styles={{ searchField: { focus: { color: "#f29" } } }} /> ); const input = screen.getByRole("textbox", { name: "Make" }); fireEvent.change(input, { target: { value: "B" } }); userEvent.type(input, "{arrowdown}"); userEvent.type(input, "{arrowdown}"); userEvent.type(input, "{arrowdown}"); expect(input).toHaveAttribute("aria-activedescendant", "Make-suggestion-2"); }); test("Pressing down arrow should set aria-selected true for the selected option", () => { render( <Form name="Make" options={["Acura", "BMW", "Audi", "Bentley", "Buick", "Cadillac", "Chevrolet"]} styles={{ searchField: { focus: { color: "#f29" } } }} /> ); const input = screen.getByRole("textbox", { name: "Make" }); fireEvent.change(input, { target: { value: "B" } }); userEvent.type(input, "{arrowdown}"); userEvent.type(input, "{arrowdown}"); userEvent.type(input, "{arrowdown}"); expect(input).toHaveFocus(); const option = screen.getByRole("option", { name: "Buick" }); expect(option).toHaveAttribute("aria-selected", "true"); }); }); describe("Up arrow", () => { test("Pressing the up arrow should set input focus", () => { render( <Form name="Make" options={["Acura", "BMW", "Audi", "Bentley", "Buick", "Cadillac", "Chevrolet"]} styles={{ searchField: { focus: { color: "#f29" } } }} /> ); const input = screen.getByRole("textbox", { name: "Make" }); fireEvent.change(input, { target: { value: "B" } }); userEvent.type(input, "{arrowup}"); expect(input).toHaveFocus(); }); test("Pressing up arrow should set aria-activedescendant to the selected option", () => { render( <Form name="Make" options={["Acura", "BMW", "Audi", "Bentley", "Buick", "Cadillac", "Chevrolet"]} styles={{ searchField: { focus: { color: "#f29" } } }} /> ); const input = screen.getByRole("textbox", { name: "Make" }); fireEvent.change(input, { target: { value: "B" } }); userEvent.type(input, "{arrowup}"); userEvent.type(input, "{arrowup}"); userEvent.type(input, "{arrowup}"); expect(input).toHaveAttribute("aria-activedescendant", "Make-suggestion-0"); }); test("Pressing down arrow should set aria-selected true for the selected option", () => { render( <Form name="Make" options={["Acura", "BMW", "Audi", "Bentley", "Buick", "Cadillac", "Chevrolet"]} styles={{ searchField: { focus: { color: "#f29" } } }} /> ); const input = screen.getByRole("textbox", { name: "Make" }); fireEvent.change(input, { target: { value: "B" } }); userEvent.type(input, "{arrowup}"); userEvent.type(input, "{arrowup}"); userEvent.type(input, "{arrowup}"); expect(input).toHaveFocus(); const option = screen.getByRole("option", { name: "Bentley" }); expect(option).toHaveAttribute("aria-selected", "true"); }); }); describe("Escape key", () => { test("It should collapse the listbox", () => { const { queryById } = render( <Form name="Make" options={["Acura", "BMW", "Audi", "Bentley", "Buick", "Cadillac", "Chevrolet"]} styles={{ searchField: { focus: { color: "#f29" } } }} /> ); const input = screen.getByRole("textbox", { name: "Make" }); fireEvent.change(input, { target: { value: "B" } }); expect(screen.getByRole("listbox")).toBeInTheDocument(); const makeAnnouncement = queryById("Make-announcement"); expect(makeAnnouncement).toHaveTextContent("3 suggestions displayed. To navigate, use up and down arrow keys.") userEvent.type(input, "{esc}"); expect(screen.queryByRole("listbox")).not.toBeInTheDocument(); expect(makeAnnouncement).not.toHaveTextContent("3 suggestions displayed. To navigate, use up and down arrow keys.") }); test("It should clear the textbox", () => { render( <Form name="Make" options={["Acura", "BMW", "Audi", "Bentley", "Buick", "Cadillac", "Chevrolet"]} styles={{ searchField: { focus: { color: "#f29" } } }} /> ); const input = screen.getByRole("textbox", { name: "Make" }); fireEvent.change(input, { target: { value: "B" } }); expect(screen.getByRole("textbox", { name: "Make" })).toHaveValue("B"); userEvent.type(input, "{esc}"); expect(screen.getByRole("textbox", { name: "Make" })).toHaveValue(""); expect(screen.getByTestId("Value")).toHaveTextContent(""); }); test("It should focus on the input field", () => { render( <Form name="Make" options={["Acura", "BMW", "Audi", "Bentley", "Buick", "Cadillac", "Chevrolet"]} styles={{ searchField: { focus: { color: "#f29" } } }} /> ); const input = screen.getByRole("textbox", { name: "Make" }); fireEvent.change(input, { target: { value: "B" } }); expect(screen.getByRole("textbox", { name: "Make" })).toHaveValue("B"); userEvent.type(input, "{arrowdown}"); const option = screen.getByRole("option", { name: "Bentley" }); expect(input).toHaveAttribute("aria-activedescendant", option.id); userEvent.type(input, "{esc}"); expect(input).toHaveFocus(); }); test("It should clear the aria-activedescendant", () => { render( <Form name="Make" options={["Acura", "BMW", "Audi", "Bentley", "Buick", "Cadillac", "Chevrolet"]} styles={{ searchField: { focus: { color: "#f29" } } }} /> ); const input = screen.getByRole("textbox", { name: "Make" }); fireEvent.change(input, { target: { value: "B" } }); expect(screen.getByRole("textbox", { name: "Make" })).toHaveValue("B"); userEvent.type(input, "{arrowdown}"); const option = screen.getByRole("option", { name: "Bentley" }); expect(input).toHaveAttribute("aria-activedescendant", option.id); userEvent.type(input, "{esc}"); expect(input).not.toHaveAttribute("aria-activedescendant"); }); }); describe("Tab key", () => { describe("If an option has been selected", () => { test("It should change the value of the input to the currently selected option", () => { render( <Form name="Make" options={["Acura", "BMW", "Audi", "Bentley", "Buick", "Cadillac", "Chevrolet"]} styles={{ searchField: { focus: { color: "#f29" } } }} /> ); let input = screen.queryByRole("textbox"); fireEvent.change(input, { target: { value: "B" } }); expect(screen.getByRole("option", { name: "Bentley" })); expect(screen.getByRole("option", { name: "BMW" })); expect(screen.getByRole("option", { name: "Buick" })); expect(screen.queryByRole("option", { name: "Acura" })).toBeNull(); expect(screen.queryByRole("option", { name: "Audi" })).toBeNull(); expect(screen.queryByRole("option", { name: "Cadillac" })).toBeNull(); expect(screen.queryByRole("option", { name: "Chevrolet" })).toBeNull(); userEvent.type(input, "{arrowup}"); userEvent.type(input, "{arrowup}"); userEvent.type(input, "{arrowup}"); expect(input).toHaveFocus(); const option = screen.getByRole("option", { name: "Bentley" }); expect(option).toHaveAttribute("aria-selected", "true"); userEvent.tab(); expect(screen.getByRole("textbox", { name: "Make" })).toHaveValue("Bentley"); expect(screen.getByTestId("Value")).toHaveTextContent("Bentley"); expect(screen.queryByText(/Loading/)).not.toBeInTheDocument(); }); }); describe("If an option has not been selected", () => { test("It should keep the value previously entered into the input field", () => { render( <Form name="Make" options={["Acura", "BMW", "Audi", "Bentley", "Buick", "Cadillac", "Chevrolet"]} styles={{ searchField: { focus: { color: "#f29" } } }} /> ); let input = screen.queryByRole("textbox"); fireEvent.change(input, { target: { value: "B" } }); expect(screen.getByRole("option", { name: "Bentley" })); expect(screen.getByRole("option", { name: "BMW" })); expect(screen.getByRole("option", { name: "Buick" })); expect(screen.queryByRole("option", { name: "Acura" })).toBeNull(); expect(screen.queryByRole("option", { name: "Audi" })).toBeNull(); expect(screen.queryByRole("option", { name: "Cadillac" })).toBeNull(); expect(screen.queryByRole("option", { name: "Chevrolet" })).toBeNull(); expect(input).toHaveFocus(); userEvent.tab(); expect(screen.getByRole("textbox", { name: "Make" })).toHaveValue("B"); expect(screen.getByTestId("Value")).toHaveTextContent("B"); expect(screen.queryByRole("listbox")).not.toBeInTheDocument(); expect(screen.queryByText(/Loading/)).not.toBeInTheDocument(); }); }); }); describe("Mouse selection", () => { test("It should update the input field to the selected option", () => { render( <Form name="Make" options={["Acura", "BMW", "Audi", "Bentley", "Buick", "Cadillac", "Chevrolet"]} styles={{ searchField: { focus: { color: "#f29" } } }} /> ); const input = screen.getByRole("textbox", { name: "Make" }); fireEvent.change(input, { target: { value: "B" } }); expect(screen.getByRole("textbox", { name: "Make" })).toHaveValue("B"); const option = screen.getByRole("option", { name: "Bentley" }); userEvent.click(option); expect(screen.getByRole("textbox", { name: "Make" })).toHaveValue("Bentley"); expect(screen.getByTestId("Value")).toHaveTextContent("Bentley"); expect(screen.queryByText(/Loading/)).not.toBeInTheDocument(); }); test("It should remove any aria-activedescendant previously set", () => { render( <Form name="Make" options={["Acura", "BMW", "Audi", "Bentley", "Buick", "Cadillac", "Chevrolet"]} styles={{ searchField: { focus: { color: "#f29" } } }} /> ); const input = screen.getByRole("textbox", { name: "Make" }); fireEvent.change(input, { target: { value: "B" } }); expect(screen.getByRole("textbox", { name: "Make" })).toHaveValue("B"); userEvent.type(input, "{arrowdown}"); const option = screen.getByRole("option", { name: "Bentley" }); expect(input).toHaveAttribute("aria-activedescendant", option.id); const newOption = screen.getByRole("option", { name: "BMW" }); userEvent.click(newOption); expect(input).not.toHaveAttribute("aria-activedescendant"); expect(screen.queryByRole("listbox")).not.toBeInTheDocument(); }); }); describe("Clicking outside of the AutoSuggest component", () => { test("It should close the options menu", () => { render( <> <Form name="Make" options={["Acura", "BMW", "Audi", "Bentley", "Buick", "Cadillac", "Chevrolet"]} styles={{ searchField: { focus: { color: "#f29" } } }} /> <p>Different input"</p> </> ); const input = screen.getByRole("textbox", { name: "Make" }); fireEvent.change(input, { target: { value: "B" } }); expect(screen.getByRole("textbox", { name: "Make" })).toHaveValue("B"); userEvent.type(input, "{arrowdown}"); const option = screen.getByRole("option", { name: "Bentley" }); expect(input).toHaveAttribute("aria-activedescendant", option.id); expect(screen.getByRole("listbox")).toBeInTheDocument(); const outsideInputField = screen.getByText(/Different input/); fireEvent.click(outsideInputField); expect(screen.queryByRole("listbox")).not.toBeInTheDocument(); expect(screen.getByRole("textbox", { name: "Make" })).toHaveValue("B"); expect(screen.getByTestId("Value")).toHaveTextContent("B"); }); }); test("Selection workflow", () => { render( <> <Form name="Make" options={["Acura", "BMW", "Audi", "Bentley", "Buick", "Cadillac", "Chevrolet"]} styles={{ searchField: { focus: { color: "#f29" } } }} /> <p>Different input"</p> </> ); const input = screen.getByRole("textbox", { name: "Make" }); fireEvent.change(input, { target: { value: "B" } }); expect(screen.getByRole("textbox", { name: "Make" })).toHaveValue("B"); userEvent.type(input, "{arrowdown}"); const option = screen.getByRole("option", { name: "Bentley" }); expect(input).toHaveAttribute("aria-activedescendant", option.id); expect(screen.getByRole("listbox")).toBeInTheDocument(); userEvent.tab(); expect(screen.getByRole("textbox", { name: "Make" })).toHaveValue("Bentley"); expect(screen.getByTestId("Value")).toHaveTextContent("Bentley"); const button = screen.getByRole("button"); expect(screen.queryByTestId("AfterFormSubmit")).not.toBeInTheDocument(); userEvent.click(button); expect(screen.queryByTestId("AfterFormSubmit")).toBeInTheDocument(); expect(screen.queryByTestId("AfterFormSubmit")).toHaveTextContent("Make: Bentley"); }); test("Input should be disabled if disabled evaluates to true", () => { render( <> <Form name="Make" options={["Acura", "BMW", "Audi", "Bentley", "Buick", "Cadillac", "Chevrolet"]} styles={{ searchField: { focus: { color: "#f29" } } }} disabled /> <p>Different input"</p> </> ); const input = screen.getByRole("textbox", { name: "Make" }); expect(input).toHaveAttribute("disabled"); }); test("Input should not be disabled if disabled does not evaluate to true", () => { render( <> <Form name="Make" options={["Acura", "BMW", "Audi", "Bentley", "Buick", "Cadillac", "Chevrolet"]} styles={{ searchField: { focus: { color: "#f29" } } }} disabled={false} /> <p>Different input"</p> </> ); const input = screen.getByRole("textbox", { name: "Make" }); expect(input).not.toHaveAttribute("disabled"); }); test("Input value should update if changed -- server", async () => { const StateAndCountry = () => { const [country, setCountry] = React.useState(); const [state, setState] = React.useState(); const [disabled, setDisabled] = React.useState(); React.useEffect(() => { if (country && country !== "United States") { setDisabled(true); setState(""); } }, [country]); return ( <> <AutoSuggest url="https://ntsb-server.herokuapp.com/api/accidents/countryList" name="Country" handleChange={setCountry} value={country} /> <AutoSuggest url="https://ntsb-server.herokuapp.com/api/accidents/stateList" name="State" handleChange={setState} disabled={disabled} value={state} /> <p data-testid="Country-value">{country}</p> <p data-testid="State-value">{state}</p> </> ); }; const { queryById } = render(<StateAndCountry />); const state = screen.getByRole("textbox", { name: "State" }); await act(async () => { fireEvent.change(state, { target: { value: "AK" } }); await waitFor(() => expect(screen.queryByText(/Loading/)).toBeInTheDocument()); }); await act(async () => { await waitFor(() => { expect(screen.queryByText(/Loading/)).not.toBeInTheDocument(); }); }); const stateAnnouncement = queryById("State-announcement"); expect(stateAnnouncement).toHaveTextContent("1 suggestions displayed. To navigate, use up and down arrow keys."); const stateOption = screen.getByRole("option", { name: "AK" }); await act(async () => { fireEvent( stateOption, new MouseEvent("click", { bubbles: true, cancelable: true }) ); await waitFor(() => { expect(screen.queryByRole("listbox")).not.toBeInTheDocument(); }); }); expect(state).toHaveValue("AK"); expect(screen.getByTestId("State-value")).toHaveTextContent("AK"); const country = screen.getByRole("textbox", { name: "Country" }); await act(async () => { fireEvent.change(country, { target: { value: "United" } }); await waitFor(() => expect(screen.queryByText(/Loading/)).toBeInTheDocument()); }); await act(async () => { await waitFor(() => { expect(screen.queryByText(/Loading/)).not.toBeInTheDocument(); }); }); const countryAnnouncement = queryById("Country-announcement"); expect(countryAnnouncement).toHaveTextContent("3 suggestions displayed. To navigate, use up and down arrow keys."); const countryOption = screen.getByRole("option", { name: "United Kingdom" }); await act(async () => { fireEvent( countryOption, new MouseEvent("click", { bubbles: true, cancelable: true }) ); await waitFor(() => { expect(screen.queryByRole("listbox")).not.toBeInTheDocument(); }); }); expect(country).toHaveValue("United Kingdom"); expect(screen.getByTestId("Country-value")).toHaveTextContent("United Kingdom"); expect(screen.getByRole("textbox", { name: "State" })).not.toHaveValue(); expect(screen.getByTestId("State-value")).toHaveTextContent(""); expect(stateAnnouncement).not.toHaveTextContent( "1 suggestions displayed. To navigate, use up and down arrow keys." ); expect(countryAnnouncement).not.toHaveTextContent( "3 suggestions displayed. To navigate, use up and down arrow keys." ); }); test("Input value should update if changed -- client", async () => { const StateAndCountryClient = () => { const [country, setCountry] = React.useState(); const [state, setState] = React.useState(); const [disabled, setDisabled] = React.useState(); React.useEffect(() => { if (country && country !== "United States") { setDisabled(true); setState(""); } }, [country]); return ( <> <AutoSuggest name="Country" handleChange={setCountry} options={countries} value={country} /> <AutoSuggest name="State" handleChange={setState} options={states} disabled={disabled} value={state} /> <p data-testid="Country-value">{country}</p> <p data-testid="State-value">{state}</p> </> ); }; const { queryById } = render(<StateAndCountryClient />); const state = screen.getByRole("textbox", { name: "State" }); fireEvent.change(state, { target: { value: "AK" } }); const stateAnnouncement = queryById("State-announcement"); expect(stateAnnouncement).toHaveTextContent("1 suggestions displayed. To navigate, use up and down arrow keys."); const stateOption = screen.getByRole("option", { name: "AK" }); await act(async () => { fireEvent( stateOption, new MouseEvent("click", { bubbles: true, cancelable: true }) ); await waitFor(() => { expect(screen.queryByRole("listbox")).not.toBeInTheDocument(); }); }); expect(state).toHaveValue("AK"); expect(screen.getByTestId("State-value")).toHaveTextContent("AK"); const country = screen.getByRole("textbox", { name: "Country" }); fireEvent.change(country, { target: { value: "United" } }); const countryAnnouncement = queryById("Country-announcement"); expect(countryAnnouncement).toHaveTextContent("3 suggestions displayed. To navigate, use up and down arrow keys."); const countryOption = screen.getByRole("option", { name: "United Kingdom" }); await act(async () => { fireEvent( countryOption, new MouseEvent("click", { bubbles: true, cancelable: true }) ); await waitFor(() => { expect(screen.queryByRole("listbox")).not.toBeInTheDocument(); }); }); expect(country).toHaveValue("United Kingdom"); expect(screen.getByTestId("Country-value")).toHaveTextContent("United Kingdom"); expect(screen.getByRole("textbox", { name: "State" })).not.toHaveValue(); expect(screen.getByTestId("State-value")).toHaveTextContent(""); expect(stateAnnouncement).not.toHaveTextContent( "1 suggestions displayed. To navigate, use up and down arrow keys." ); expect(countryAnnouncement).not.toHaveTextContent( "3 suggestions displayed. To navigate, use up and down arrow keys." ); }); describe("Client version should perform case-insensitive matche if caseInsensitive is true", () => { test("when options are strings", () => { const options = ["abba", "ABB", "aBbott", "Abberette"]; render(<Form options={options} name="Name" caseInsensitive={true} />); const input = screen.getByRole("textbox", { name: "Name" }); fireEvent.change(input, { target: { value: "Abb" } }); expect(screen.getByRole("option", { name: "abba" })); expect(screen.getByRole("option", { name: "ABB" })); expect(screen.getByRole("option", { name: "aBbott" })); expect(screen.getByRole("option", { name: "Abberette" })); }); test("when options are objects", () => { const options = [ { name: "abba", value: "abba" }, { name: "ABB", value: "ABB" }, { name: "aBbott", value: "aBbott" }, { name: "Abberette", value: "Abberette" } ]; render(<Form options={options} name="Name" caseInsensitive={true} />); const input = screen.getByRole("textbox", { name: "Name" }); fireEvent.change(input, { target: { value: "Abb" } }); expect(screen.getByRole("option", { name: "abba" })); expect(screen.getByRole("option", { name: "ABB" })); expect(screen.getByRole("option", { name: "aBbott" })); expect(screen.getByRole("option", { name: "Abberette" })); }); }); describe("Client version should perform case-insensitive match by default", () => { test("when options are strings", () => { const options = ["abba", "ABB", "aBbott", "Abberette"]; render(<Form options={options} name="Name" />); const input = screen.getByRole("textbox", { name: "Name" }); fireEvent.change(input, { target: { value: "Abb" } }); expect(screen.getByRole("option", { name: "abba" })); expect(screen.getByRole("option", { name: "ABB" })); expect(screen.getByRole("option", { name: "aBbott" })); expect(screen.getByRole("option", { name: "Abberette" })); }); test("when options are objects", () => { const options = [ { name: "abba", value: "abba" }, { name: "ABB", value: "ABB" }, { name: "aBbott", value: "aBbott" }, { name: "Abberette", value: "Abberette" } ]; render(<Form options={options} name="Name" />); const input = screen.getByRole("textbox", { name: "Name" }); fireEvent.change(input, { target: { value: "Abb" } }); expect(screen.getByRole("option", { name: "abba" })); expect(screen.getByRole("option", { name: "ABB" })); expect(screen.getByRole("option", { name: "aBbott" })); expect(screen.getByRole("option", { name: "Abberette" })); }); }); describe("Client version should not perform case-insensitive matches if caseInsensitive is false", () => { test("when options are strings", () => { const options = ["abba", "ABB", "aBbott", "Abberette"]; render(<Form options={options} name="Name" caseInsensitive={false} />); const input = screen.getByRole("textbox", { name: "Name" }); fireEvent.change(input, { target: { value: "a" } }); expect(screen.getByRole("option", { name: "abba" })); expect(screen.getByRole("option", { name: "aBbott" })); expect(screen.queryByRole("option", { name: "ABB" })).toBeNull(); expect(screen.queryByRole("option", { name: "Abberette" })).toBeNull(); }); test("when options are objects", () => { const options = [ { name: "abba", value: "abba" }, { name: "ABB", value: "ABB" }, { name: "aBbott", value: "aBbott" }, { name: "Abberette", value: "Abberette" } ]; render(<Form options={options} name="Name" caseInsensitive={false} />); const input = screen.getByRole("textbox", { name: "Name" }); fireEvent.change(input, { target: { value: "a" } }); expect(screen.getByRole("option", { name: "abba" })); expect(screen.getByRole("option", { name: "aBbott" })); expect(screen.queryByRole("option", { name: "ABB" })).toBeNull(); expect(screen.queryByRole("option", { name: "Abberette" })).toBeNull(); }); });