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

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

Symbols by Kind

function 1

All Symbols

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

Full Source

// @vitest-environment happy-dom
import { describe, it, expect, 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(
    h(SearchableSelectPreact, {
      entries: props.entries ?? GROUPED,
      value: props["value"] as string | undefined,
      onChange: props["onChange"] as any,
      placeholder: props["placeholder"],
    } as any),
    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", async () => {
    container = mount();
    const input = container.querySelector<HTMLInputElement>(".fa-ss__input")!;
    input.focus();
    await Promise.resolve();
    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", async () => {
    container = mount();
    const input = container.querySelector<HTMLInputElement>(".fa-ss__input")!;
    input.focus();
    await Promise.resolve();
    input.value = "zzz";
    input.dispatchEvent(new InputEvent("input", { bubbles: true }));
    await Promise.resolve();
    expect(container.querySelector(".fa-ss__empty")).toBeTruthy();
  });

  it("calls onChange when option clicked", async () => {
    const cb = vi.fn();
    container = mount({ onChange: cb });
    const input = container.querySelector<HTMLInputElement>(".fa-ss__input")!;
    input.focus();
    await Promise.resolve();
    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", async () => {
    const cb = vi.fn();
    container = mount({ onChange: cb });
    const input = container.querySelector<HTMLInputElement>(".fa-ss__input")!;

    // First focus to open dropdown
    input.focus();
    await Promise.resolve();
    await Promise.resolve();

    // ArrowDown should highlight first item
    input.dispatchEvent(
      new KeyboardEvent("keydown", { key: "ArrowDown", bubbles: true }),
    );
    await Promise.resolve();
    await Promise.resolve();

    // Enter should select highlighted
    input.dispatchEvent(
      new KeyboardEvent("keydown", { key: "Enter", bubbles: true }),
    );
    await Promise.resolve();

    expect(cb).toHaveBeenCalled();
  });
});