88 lines
4.4 KiB
TypeScript
88 lines
4.4 KiB
TypeScript
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(<Button loading icon={<IconRefresh />}>Обновить</Button>);
|
||
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(<Button aria-pressed={true}>Фильтр</Button>);
|
||
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(<IconButton label="Закрыть" icon={<IconRefresh />} />);
|
||
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(<Segmented label="Режим" value="a" options={options} onChange={onChange} />);
|
||
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(
|
||
<MemoryRouter initialEntries={["/b"]}>
|
||
<Segmented label="Разделы" value="b" options={[{ value: "a", label: "А", href: "/a" }, { value: "b", label: "Б", href: "/b" }]} />
|
||
</MemoryRouter>,
|
||
);
|
||
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(<ThemeSwitch />);
|
||
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(<Chip onRemove={onRemove} removeLabel="Снять фильтр Спринт">Спринт</Chip>);
|
||
fireEvent.click(screen.getByRole("button", { name: "Снять фильтр Спринт" }));
|
||
expect(onRemove).toHaveBeenCalledOnce();
|
||
});
|
||
it("Notice: error — alert, info — status", () => {
|
||
render(<><Notice tone="error" title="Сбой">текст</Notice><Notice tone="info">инфо</Notice></>);
|
||
expect(screen.getByRole("alert").textContent).toContain("Сбой");
|
||
expect(screen.getByRole("status").textContent).toContain("инфо");
|
||
});
|
||
});
|