688a18af22
Foundation for the mobile-responsive-parity change. Adds: - useIsMobile() hook: single source of truth for the md:768px cut (SSR-safe) - MobileCardRow<T>: stacked card list for wide tables below md, with getRowId stable keys, primary field as title, optional onRowClick + actions slot - SheetForm: full-height form host (h-[100dvh], flex column, sticky header + footer via flex not position:sticky) for mobile edit flows - HoverEditButton: mobile prop (default 'always') -- always visible below md, hover-revealed at md+; desktop aesthetic preserved - .mobile-touch-target CSS utility: 44x44 min hit area below md (WCAG 2.5.5) - App.tsx refactored to use useIsMobile(); shell behavior unchanged Tests cover primary/field rendering, onRowClick, actions slot, empty rows, no-primary, stable keys (no duplicate-key warning), and all SheetForm interactions. 86 tests pass; lint/build green. MobileCardRow key strategy: uses getRowId when provided (falls back to index); per design §trade-offs, fields are declared per-table to prioritize by mobile importance rather than auto-derived from column defs. Refs openspec/changes/mobile-responsive-parity/ (design §Shared primitives, spec R1/R5/R6, tasks slice 1).
41 lines
1.6 KiB
TypeScript
41 lines
1.6 KiB
TypeScript
import { describe, it, expect, vi } from "vitest";
|
|
import { render, screen } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { HoverEditButton } from "../HoverEditButton";
|
|
|
|
describe("HoverEditButton", () => {
|
|
it("fires onClick and exposes the default aria-label", async () => {
|
|
const onClick = vi.fn();
|
|
render(<HoverEditButton onClick={onClick} />);
|
|
const button = screen.getByRole("button", { name: "Edit" });
|
|
await userEvent.click(button);
|
|
expect(onClick).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("honors a custom label", () => {
|
|
render(<HoverEditButton onClick={() => {}} label="Rename machine" />);
|
|
expect(
|
|
screen.getByRole("button", { name: "Rename machine" }),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it('defaults to always-visible below md (mobile="always")', () => {
|
|
render(<HoverEditButton onClick={() => {}} />);
|
|
const button = screen.getByRole("button", { name: "Edit" });
|
|
const tokens = button.className.split(/\s+/);
|
|
// The default mobile mode layers hover-reveal only at md+ via
|
|
// md:opacity-0/md:group-hover:opacity-100, so the button is visible by
|
|
// default below md (no base opacity-0 token).
|
|
expect(tokens).toContain("md:opacity-0");
|
|
expect(tokens).toContain("md:group-hover:opacity-100");
|
|
expect(tokens).not.toContain("opacity-0");
|
|
});
|
|
|
|
it('preserves the legacy opacity-0 behavior when mobile="hover"', () => {
|
|
render(<HoverEditButton onClick={() => {}} mobile="hover" />);
|
|
const button = screen.getByRole("button", { name: "Edit" });
|
|
expect(button.className).toContain("opacity-0");
|
|
expect(button.className).toContain("group-hover:opacity-100");
|
|
});
|
|
});
|