feat: add main features
Agents for developpement added + frontend add + backend added. Git viewer created + agent and template creator + layout and project creator
This commit is contained in:
117
frontend/src/shared/ui/ui.test.tsx
Normal file
117
frontend/src/shared/ui/ui.test.tsx
Normal file
@ -0,0 +1,117 @@
|
||||
/**
|
||||
* LD — design-system component tests. Pure UI, rendered with React Testing
|
||||
* Library; no backend, no gateways. We assert behaviour and accessibility
|
||||
* (roles, names, aria-*) rather than exact Tailwind classes, so the visual
|
||||
* design can evolve without breaking the suite.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { render, screen, fireEvent } from "@testing-library/react";
|
||||
|
||||
import { Button } from "./Button";
|
||||
import { Input } from "./Input";
|
||||
import { Field } from "./Field";
|
||||
import { Tabs } from "./Tabs";
|
||||
|
||||
describe("Button", () => {
|
||||
it("renders its label and fires onClick", () => {
|
||||
const onClick = vi.fn();
|
||||
render(<Button onClick={onClick}>Save</Button>);
|
||||
fireEvent.click(screen.getByRole("button", { name: "Save" }));
|
||||
expect(onClick).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("is disabled and shows a spinner while loading", () => {
|
||||
const onClick = vi.fn();
|
||||
render(
|
||||
<Button loading onClick={onClick}>
|
||||
Save
|
||||
</Button>,
|
||||
);
|
||||
const btn = screen.getByRole("button", { name: /Save/ }) as HTMLButtonElement;
|
||||
expect(btn.disabled).toBe(true);
|
||||
expect(screen.getByRole("status")).toBeTruthy(); // spinner
|
||||
fireEvent.click(btn);
|
||||
expect(onClick).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("defaults to type=button to avoid accidental form submits", () => {
|
||||
render(<Button>Go</Button>);
|
||||
expect(screen.getByRole("button", { name: "Go" }).getAttribute("type")).toBe(
|
||||
"button",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Input", () => {
|
||||
it("flags aria-invalid when invalid", () => {
|
||||
render(<Input aria-label="email" invalid />);
|
||||
expect(screen.getByLabelText("email").getAttribute("aria-invalid")).toBe("true");
|
||||
});
|
||||
|
||||
it("is not aria-invalid by default", () => {
|
||||
render(<Input aria-label="email" />);
|
||||
expect(screen.getByLabelText("email").getAttribute("aria-invalid")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Field", () => {
|
||||
it("associates the label with the control via id", () => {
|
||||
render(
|
||||
<Field label="Project name">
|
||||
{({ id, describedBy }) => (
|
||||
<input id={id} aria-describedby={describedBy} />
|
||||
)}
|
||||
</Field>,
|
||||
);
|
||||
// getByLabelText resolves the label→control association.
|
||||
expect(screen.getByLabelText("Project name")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("announces an error and wires aria-describedby", () => {
|
||||
render(
|
||||
<Field label="Root" error="must be absolute">
|
||||
{({ id, describedBy }) => (
|
||||
<input id={id} aria-describedby={describedBy} />
|
||||
)}
|
||||
</Field>,
|
||||
);
|
||||
const alert = screen.getByRole("alert");
|
||||
expect(alert.textContent).toBe("must be absolute");
|
||||
const input = screen.getByLabelText("Root");
|
||||
expect(input.getAttribute("aria-describedby")).toBe(alert.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Tabs", () => {
|
||||
const items = [
|
||||
{ id: "a", label: "alpha" },
|
||||
{ id: "b", label: "beta" },
|
||||
];
|
||||
|
||||
it("marks the active tab with aria-selected and reports selection", () => {
|
||||
const onSelect = vi.fn();
|
||||
render(<Tabs items={items} value="a" onSelect={onSelect} />);
|
||||
|
||||
const tabs = screen.getAllByRole("tab");
|
||||
expect(tabs).toHaveLength(2);
|
||||
const [alpha, beta] = tabs;
|
||||
expect(alpha.getAttribute("aria-selected")).toBe("true");
|
||||
expect(beta.getAttribute("aria-selected")).toBe("false");
|
||||
|
||||
fireEvent.click(beta);
|
||||
expect(onSelect).toHaveBeenCalledWith("b");
|
||||
});
|
||||
|
||||
it("renders a close control per tab only when onClose is given", () => {
|
||||
const onClose = vi.fn();
|
||||
const { rerender } = render(
|
||||
<Tabs items={items} value="a" onSelect={() => {}} />,
|
||||
);
|
||||
expect(screen.queryByRole("button", { name: "close alpha" })).toBeNull();
|
||||
|
||||
rerender(<Tabs items={items} value="a" onSelect={() => {}} onClose={onClose} />);
|
||||
fireEvent.click(screen.getByRole("button", { name: "close beta" }));
|
||||
expect(onClose).toHaveBeenCalledWith("b");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user