test.web/presentation/components/search.component.test.ts

test.web/presentation/components/search.component.test.ts is a file in Codebase Testing. 93 lines of code and 0 definitions.

import { ROUTE_CHANGED, ROUTE_REQUESTED } from "@banes-lab/web/core/ids/route.ids.ts";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { emitEvent, subscribeEvent } from "@banes-lab/web/core/buses/base.bus.ts";
import { SEARCH_DEBOUNCE_MS } from "@banes-lab/web/configuration/constants/search.constants.ts";
import { SEARCH_PAGE } from "@banes-lab/web/core/ids/page.ids.ts";
import type { SearchToggle } from "@banes-lab/web/types/search.types.ts";
import { attachSearchToggle } from "@banes-lab/web/presentation/components/search.component.ts";
import { createElement } from "@banes-lab/web/core/factories/element.factory.ts";

interface Mounted {
    readonly bar: HTMLElement;
    readonly page: HTMLElement;
    readonly toggle: SearchToggle;
}

const mount = function mount(): Mounted {
    const bar = createElement("nav", { className: "tab-bar" });
    const page = createElement("button", { className: "tab-button" });
    document.body.append(bar);
    return { bar, page, toggle: attachSearchToggle(bar, [page]) };
};

const type = function type(field: HTMLInputElement, value: string): void {
    field.value = value;
    field.dispatchEvent(new Event("input"));
};

beforeEach(() => {
    vi.useFakeTimers();
});

afterEach(() => {
    vi.useRealTimers();
    window.history.replaceState({}, "", "/");
    document.body.replaceChildren();
});

describe("attachSearchToggle", () => {
    it("shows the page buttons and a search button until the search opens", () => {
        const { bar, page, toggle } = mount();
        expect(bar.firstElementChild).toBe(page);
        expect(bar.querySelector("input")).toBeNull();
        bar.querySelector<HTMLButtonElement>("button:last-child")?.click();
        expect(bar.contains(page)).toBe(false);
        expect(bar.querySelector("input.filter-box")).not.toBeNull();
        toggle.dispose();
    });

    it("requests the seeded results route once the typing settles", () => {
        const { bar, toggle } = mount();
        const requested: string[] = [];
        const release = subscribeEvent(ROUTE_REQUESTED, (event) => {
            requested.push(event.path ?? "");
        });
        toggle.showField("");
        const field = bar.querySelector<HTMLInputElement>("input");
        if (field === null) {
            throw new Error("the search field is shown once open");
        }
        type(field, "g");
        type(field, "gate check");
        vi.advanceTimersByTime(SEARCH_DEBOUNCE_MS);
        expect(requested).toStrictEqual(["/search?q=gate+check"]);
        release();
        toggle.dispose();
    });

    it("ignores a query too short to search", () => {
        const { bar, toggle } = mount();
        const requested: string[] = [];
        const release = subscribeEvent(ROUTE_REQUESTED, (event) => {
            requested.push(event.path ?? "");
        });
        toggle.showField("");
        const field = bar.querySelector<HTMLInputElement>("input");
        if (field !== null) {
            type(field, "g");
        }
        vi.advanceTimersByTime(SEARCH_DEBOUNCE_MS);
        expect(requested).toStrictEqual([]);
        release();
        toggle.dispose();
    });

    it("closes on Escape and restores the page buttons", () => {
        const { bar, page, toggle } = mount();
        toggle.showField("gate");
        bar.querySelector("input")?.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape" }));
        expect(bar.firstElementChild).toBe(page);
        toggle.dispose();
    });

    it("opens with the seeded query when the route lands on the results page", () => {
        const { bar, toggle } = mount();
        window.history.replaceState({}, "", "/search?q=drift");
        emitEvent({ name: ROUTE_CHANGED, page: SEARCH_PAGE });
        expect(bar.querySelector<HTMLInputElement>("input")?.value).toBe("drift");
        toggle.showButtons();
        toggle.dispose();
        emitEvent({ name: ROUTE_CHANGED, page: SEARCH_PAGE });
        expect(bar.querySelector("input")).toBeNull();
    });
});