src/lib/ui/__tests__/searchable-select-preact.test.tsx

Total Symbols
1
Lines of Code
98
Avg Complexity
2.0
Symbol Types
1

Symbols by Kind

function 1

All Symbols

Name Kind Visibility Status Lines Signature
mount function - 18-36 mount( props: Partial<(typeof SearchableSelectPreact)["prototype"]> & { entries?: SelectEntry[]; } = {}, )

Full Source

// @vitest-environment happy-dom
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { render, h } from "preact";
import { SearchableSelectPreact } from "../searchable-select-preact";
import type { SelectEntry } from "../searchable-select";

const GROUPED: SelectEntry[] = [
  {
    groupLabel: "G1",
    options: [
      { value: "a", label: "Alpha" },
      { value: "b", label: "Beta" },
    ],
  },
  { value: "c", label: "Charlie" },
];

function mount(
  props: Partial<(typeof SearchableSelectPreact)["prototype"]> & {
    entries?: SelectEntry[];
  } = {},
) {
  const container = document.createElement("div");
  document.body.appendChild(container);
  render(
    // @ts-ignore Preact JSX
    <SearchableSelectPreact
      entries={props.entries ?? GROUPED}
      value={props["value"] as string | undefined}
      onChange={props["onChange"] as any}
      placeholder={props["placeholder"]}
    />,
    container,
  );
  return container;
}

describe("SearchableSelectPreact (Preact) — basic behaviour", () => {
  let container: HTMLElement;

  afterEach(() => {
    document.body.innerHTML = "";
    vi.useRealTimers();
  });

  it("shows current label when closed and value provided", () => {
    container = mount({ value: "b" });
    const input = container.querySelector<HTMLInputElement>(".fa-ss__input")!;
    expect(input.value).toBe("Beta");
  });

  it("opens dropdown on focus and shows options", () => {
    container = mount();
    const input = container.querySelector<HTMLInputElement>(".fa-ss__input")!;
    input.dispatchEvent(new Event("focus"));
    const dropdown = container.querySelector<HTMLElement>(".fa-ss__dropdown")!;
    expect(dropdown).toBeTruthy();
    expect(dropdown.querySelectorAll(".fa-ss__opt").length).toBeGreaterThan(0);
  });

  it("filters results on input and shows empty message when none match", () => {
    container = mount();
    const input = container.querySelector<HTMLInputElement>(".fa-ss__input")!;
    input.dispatchEvent(new Event("focus"));
    input.value = "zzz";
    input.dispatchEvent(new Event("input"));
    expect(container.querySelector(".fa-ss__empty")).toBeTruthy();
  });

  it("calls onChange when option clicked", () => {
    const cb = vi.fn();
    container = mount({ onChange: cb });
    const input = container.querySelector<HTMLInputElement>(".fa-ss__input")!;
    input.dispatchEvent(new Event("focus"));
    const opt = Array.from(
      container.querySelectorAll<HTMLElement>(".fa-ss__opt"),
    ).find((o) => o.dataset.value === "a")!;
    opt.dispatchEvent(new MouseEvent("mousedown", { bubbles: true }));
    expect(cb).toHaveBeenCalledOnce();
    expect(cb).toHaveBeenCalledWith("a", "Alpha");
  });

  it("handles keyboard navigation and Enter to select", () => {
    const cb = vi.fn();
    container = mount({ onChange: cb });
    const input = container.querySelector<HTMLInputElement>(".fa-ss__input")!;
    // ArrowDown should open and highlight first
    input.dispatchEvent(
      new KeyboardEvent("keydown", { key: "ArrowDown", bubbles: true }),
    );
    // Enter should select highlighted
    input.dispatchEvent(
      new KeyboardEvent("keydown", { key: "Enter", bubbles: true }),
    );
    expect(cb).toHaveBeenCalled();
  });
});