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("инфо"); }); });