Files
siftlode/frontend/src/components/ui/SegmentedToggle.dom.test.tsx
T
peter 5bfcf5d83a refactor(e10): move the platform components into components/ui/ and enforce the boundary
The app HAS a platform layer -- AnchoredPopover (22 importers, with
arrows/typeahead/Escape/anchor-flip), QueryState (21), Modal (17),
PageShell, DataTable, Tabs, Tooltip and ~26 more -- but it sat flat among
~160 feature components with nothing marking it as one. Several of those
files describe themselves as 'the one place' or 'canonical' and were
still bypassed by hand-rolled markup, because a layer nobody can see is a
layer nobody uses; three separate inventory passes of this codebase
misread it the same way.

Moves the 33 domain-free modules into components/ui/, rewrites the
imports in 89 files, and makes the boundary a lint rule rather than a
convention: inside ui/ a one-level-up import (a feature component) and
any lib/api slice are errors. The rule was verified to fire on both and
to leave generic lib/ helpers alone. README.md states which side of the
line a component belongs on and why.

No behaviour change: 3x tsc, eslint, knip, prettier and 687 vitest tests
all green; no backend file touched.
2026-09-05 16:58:29 +02:00

73 lines
2.7 KiB
TypeScript

// @vitest-environment jsdom
//
// Rendering integration of the shared SegmentedToggle. Labels are plain props (no i18n); the active
// paint is CSS (`.pill[aria-pressed]`), so we assert the aria-pressed contract, not styling.
import { describe, it, expect, vi, afterEach } from "vitest";
import { render, screen, fireEvent, cleanup } from "@testing-library/react";
import SegmentedToggle from "./SegmentedToggle";
const OPTS = [
{ value: "or", label: "Any" },
{ value: "and", label: "All" },
] as const;
afterEach(cleanup);
describe("SegmentedToggle", () => {
it("renders one button per option with the given labels", () => {
render(<SegmentedToggle options={OPTS} value="or" onChange={vi.fn()} ariaLabel="Match" />);
expect(screen.getAllByRole("button").map((b) => b.textContent)).toEqual(["Any", "All"]);
});
it("marks exactly the active option aria-pressed", () => {
render(<SegmentedToggle options={OPTS} value="and" onChange={vi.fn()} ariaLabel="Match" />);
expect(screen.getByRole("button", { name: "Any" }).getAttribute("aria-pressed")).toBe("false");
expect(screen.getByRole("button", { name: "All" }).getAttribute("aria-pressed")).toBe("true");
});
it("clicking an option fires onChange with its value", () => {
const onChange = vi.fn();
render(<SegmentedToggle options={OPTS} value="or" onChange={onChange} ariaLabel="Match" />);
fireEvent.click(screen.getByRole("button", { name: "All" }));
expect(onChange).toHaveBeenCalledWith("and");
});
it("exposes the group aria-label", () => {
render(<SegmentedToggle options={OPTS} value="or" onChange={vi.fn()} ariaLabel="Match mode" />);
expect(screen.getByRole("group", { name: "Match mode" })).toBeTruthy();
});
it("renders the caption when label is set", () => {
render(
<SegmentedToggle
options={OPTS}
value="or"
onChange={vi.fn()}
ariaLabel="Match"
label="Match"
/>,
);
// caption text plus the two option labels
expect(screen.getByText("Match")).toBeTruthy();
expect(screen.getAllByRole("button")).toHaveLength(2);
});
it("renders per-option icons and tooltips (scope-bar shape)", () => {
render(
<SegmentedToggle
ariaLabel="Scope"
fullWidth
size="md"
options={[
{ value: "my", label: "Mine", icon: <svg data-testid="ic-my" />, title: "Your subs" },
{ value: "all", label: "Library", icon: <svg data-testid="ic-all" /> },
]}
value="my"
onChange={vi.fn()}
/>,
);
expect(screen.getByTestId("ic-my")).toBeTruthy();
expect(screen.getByRole("button", { name: "Mine" }).getAttribute("title")).toBe("Your subs");
});
});