/* * Portions of this file are based on code from react-spectrum. * Apache License Version 2.0, Copyright 2020 Adobe. * * Credits to the React Spectrum team: * https://github.com/adobe/react-spectrum/blob/5c1920e50d4b2b80c826ca91aff55c97350bf9f9/packages/@react-spectrum/picker/test/Picker.test.js */ import { installPointerEvent } from "@kobalte/tests"; import { fireEvent, render, within } from "@solidjs/testing-library"; import { vi } from "vitest"; import * as Search from "."; import { DebouncerTimeout } from "./utils"; interface DataSourceItem { key: string; label: string; textValue: string; disabled: boolean; } const DATA_SOURCE: DataSourceItem[] = [ { key: "1", label: "One", textValue: "One", disabled: false }, { key: "2", label: "Two", textValue: "Two", disabled: false }, { key: "3", label: "Three", textValue: "Three", disabled: false }, ]; // Skipped: jsdom stub for pointerEvent issue with vitest describe("Search", () => { installPointerEvent(); // structuredClone polyfill, kind of ^^' global.structuredClone = (val: any) => JSON.parse(JSON.stringify(val)); const onValueChange = vi.fn(); beforeEach(() => { vi.useFakeTimers(); }); afterEach(() => { vi.clearAllMocks(); vi.clearAllTimers(); }); it("debounce", () => { const callbackSpy = vi.fn(); const debouncer = DebouncerTimeout(); debouncer.setDebounceMillisecond(1000); debouncer.debounce(callbackSpy); expect(callbackSpy).not.toHaveBeenCalled(); vi.advanceTimersByTime(500); debouncer.debounce(callbackSpy); expect(callbackSpy).not.toHaveBeenCalled(); vi.advanceTimersByTime(1000); debouncer.debounce(callbackSpy); expect(callbackSpy).toHaveBeenCalledOnce(); vi.advanceTimersByTime(500); expect(callbackSpy).toHaveBeenCalledOnce(); vi.advanceTimersByTime(1000); expect(callbackSpy).toHaveBeenCalledTimes(2); }); it("renders correctly", () => { const placeholder = "Test placeholder"; const { getByRole, getByText, getByPlaceholderText } = render(() => ( ( {props.item.rawValue.label} )} > Label )); const root = getByRole("group"); expect(root).toBeInTheDocument(); expect(root).toBeInstanceOf(HTMLDivElement); const input = getByRole("combobox"); const placeHolderElement = getByPlaceholderText(placeholder); expect(input).toBe(placeHolderElement); expect(input).toHaveAttribute("aria-autocomplete", "list"); expect(input).not.toHaveAttribute("aria-controls"); expect(input).not.toHaveAttribute("aria-activedescendant"); expect(input).not.toBeDisabled(); const label = getByText("Label"); expect(label).toBeVisible(); }); describe("help text", () => { it("supports description", () => { const { getByRole, getByText } = render(() => ( ( {props.item.rawValue.label} )} > Label Description )); const input = getByRole("combobox"); const description = getByText("Description"); expect(description).toHaveAttribute("id"); expect(input).toHaveAttribute("aria-describedby", description.id); }); }); describe("autofill", () => { it("should have a hidden select element for form autocomplete", async () => { const dataSource: DataSourceItem[] = [ { key: "DE", label: "Germany", textValue: "Germany", disabled: false }, { key: "FR", label: "France", textValue: "France", disabled: false }, { key: "IT", label: "Italy", textValue: "Italy", disabled: false }, ]; const { getByRole, getAllByRole } = render(() => ( ( {props.item.rawValue.label} )} > Label )); const input = getByRole("combobox"); expect(input).toHaveAttribute("placeholder", "Placeholder"); const hiddenSelectBase = getAllByRole("listbox", { hidden: true, })[0]; expect(hiddenSelectBase).toHaveAttribute("tabIndex", "-1"); expect(hiddenSelectBase).toHaveAttribute( "autocomplete", "address-level1", ); const options = within(hiddenSelectBase).getAllByRole("option", { hidden: true, }); expect(options.length).toBe(4); options.forEach( (option, index) => index > 0 && expect(option).toHaveTextContent(dataSource[index - 1].label), ); fireEvent.change(hiddenSelectBase, { target: { value: "FR" } }); await Promise.resolve(); expect(onValueChange).toHaveBeenCalledTimes(1); expect(onValueChange.mock.calls[0][0]).toStrictEqual(dataSource[1]); expect(input).toHaveValue("France"); }); it("should have a hidden input to marshall focus to the Search input", async () => { const { getByRole } = render(() => ( ( {props.item.rawValue.label} )} > Label )); const hiddenInput = getByRole("textbox", { hidden: true }); // get the hidden ones expect(hiddenInput).toHaveAttribute("tabIndex", "0"); expect(hiddenInput).toHaveAttribute("style", "font-size: 16px;"); expect(hiddenInput.parentElement).toHaveAttribute("aria-hidden", "true"); hiddenInput.focus(); await Promise.resolve(); const input = getByRole("combobox"); expect(document.activeElement).toBe(input); expect(hiddenInput).toHaveAttribute("tabIndex", "-1"); fireEvent.blur(input); await Promise.resolve(); expect(hiddenInput).toHaveAttribute("tabIndex", "0"); }); }); describe("disabled", () => { it("disables the hidden select when disabled is true", async () => { const { getByRole } = render(() => ( ( {props.item.rawValue.label} )} > Label )); const select = getByRole("textbox", { hidden: true }); expect(select).toBeDisabled(); }); }); describe("form", () => { it("Should submit empty option by default", async () => { let value: {}; const onSubmit = vi.fn((e) => { e.preventDefault(); const formData = new FormData(e.currentTarget); value = Object.fromEntries(formData).test; // same name as the select "name" prop }); const { getByTestId } = render(() => (
( {props.item.rawValue.label} )} > Label
)); fireEvent.submit(getByTestId("form")); await Promise.resolve(); expect(onSubmit).toHaveBeenCalledTimes(1); // @ts-ignore expect(value).toBe(""); }); it("Should submit default option", async () => { let value: {}; const onSubmit = vi.fn((e) => { e.preventDefault(); const formData = new FormData(e.currentTarget); value = Object.fromEntries(formData).test; // same name as the select "name" prop }); const { getByTestId } = render(() => (
( {props.item.rawValue.label} )} > Label
)); fireEvent.submit(getByTestId("form")); await Promise.resolve(); expect(onSubmit).toHaveBeenCalledTimes(1); // @ts-ignore expect(value).toEqual("1"); }); }); });