import { fireEvent, render, screen } from "@testing-library/react";
import { MemoryRouter } from "react-router";
import { afterEach, describe, expect, it, vi } from "vitest";
import { Button, Chip, IconButton, IconRefresh, Notice, Segmented, ThemeSwitch } from "./index.ts";
function stubMatchMedia(dark: boolean) {
vi.stubGlobal("matchMedia", (q: string) => ({ matches: dark && q.includes("dark"), media: q, addEventListener() {}, removeEventListener() {} }));
}
describe("Button", () => {
it("loading: aria-busy, недоступна, иконка не показана", () => {
render(}>Обновить);
const b = screen.getByRole("button", { name: /Обновить/ });
expect(b.getAttribute("aria-busy")).toBe("true");
expect((b as HTMLButtonElement).disabled).toBe(true);
expect(b.querySelector("svg")).toBeNull();
expect(b.querySelector(".ui-spinner")).not.toBeNull();
});
it("по умолчанию type=button, aria-pressed пробрасывается", () => {
render();
const b = screen.getByRole("button", { name: "Фильтр" });
expect(b.getAttribute("type")).toBe("button");
expect(b.getAttribute("aria-pressed")).toBe("true");
expect(b.className).toContain("ui-btn--secondary");
});
});
describe("IconButton", () => {
it("доступное имя из label", () => {
render(} />);
expect(screen.getByRole("button", { name: "Закрыть" }).getAttribute("title")).toBe("Закрыть");
});
});
describe("Segmented", () => {
const options = [{ value: "a", label: "А" }, { value: "b", label: "Б" }];
it("кнопками: radiogroup, onChange, aria-checked", () => {
const onChange = vi.fn();
render();
expect(screen.getByRole("radiogroup", { name: "Режим" })).toBeTruthy();
expect(screen.getByRole("radio", { name: "А" }).getAttribute("aria-checked")).toBe("true");
expect(screen.getByRole("radio", { name: "Б" }).getAttribute("aria-checked")).toBe("false");
fireEvent.click(screen.getByRole("radio", { name: "Б" }));
expect(onChange).toHaveBeenCalledWith("b");
});
it("ссылками: nav, aria-current у активной", () => {
render(
,
);
expect(screen.getByRole("navigation", { name: "Разделы" })).toBeTruthy();
expect(screen.getByRole("link", { name: "Б" }).getAttribute("aria-current")).toBe("page");
expect(screen.getByRole("link", { name: "А" }).hasAttribute("aria-current")).toBe(false);
});
});
describe("ThemeSwitch", () => {
afterEach(() => document.documentElement.removeAttribute("data-theme"));
it("системная тёмная -> checked; щелчки переключают light и dark", () => {
stubMatchMedia(true);
render();
const sw = screen.getByRole("switch", { name: "Тёмная тема" });
expect(sw.getAttribute("aria-checked")).toBe("true");
fireEvent.click(sw);
expect(localStorage.getItem("graph-theme")).toBe("light");
expect(document.documentElement.getAttribute("data-theme")).toBe("light");
expect(sw.getAttribute("aria-checked")).toBe("false");
fireEvent.click(sw);
expect(localStorage.getItem("graph-theme")).toBe("dark");
expect(document.documentElement.getAttribute("data-theme")).toBe("dark");
});
});
describe("Chip и Notice", () => {
it("Chip: кнопка снятия вызывает onRemove", () => {
const onRemove = vi.fn();
render(Спринт);
fireEvent.click(screen.getByRole("button", { name: "Снять фильтр Спринт" }));
expect(onRemove).toHaveBeenCalledOnce();
});
it("Notice: error — alert, info — status", () => {
render(<>текстинфо>);
expect(screen.getByRole("alert").textContent).toContain("Сбой");
expect(screen.getByRole("status").textContent).toContain("инфо");
});
});