import { render, screen } from "@testing-library/react"; import { describe, expect, it, vi } from "vitest"; import { normalizePuzzle } from "../../src/domain"; import { SudokuBoard } from "../../src/components/SudokuBoard"; describe("Sudoku board constraint visuals", () => { it("distinguishes XV sums from directional inequalities", () => { const puzzle = normalizePuzzle({ version: 1, size: 4, givens: new Array(16).fill(0), constraints: [ { type: "xv", a: 0, b: 1, total: 5 }, { type: "xv", a: 4, b: 5, total: 10 }, { type: "inequality", lesser: 8, greater: 9 }, ], }); const { container } = render( , ); expect(container.querySelector(".constraint-xv--5 text")).toHaveTextContent( "5", ); expect( container.querySelector(".constraint-xv--10 text"), ).toHaveTextContent("10"); expect( container.querySelector(".constraint-inequality path"), ).toHaveAttribute("d", "M0.12 -0.17L-0.12 0L0.12 0.17"); expect( container.querySelector(".constraint-inequality-tip"), ).toBeInTheDocument(); }); it("renders false local clues and combines dual-purpose outside labels", () => { const puzzle = normalizePuzzle({ version: 1, size: 4, givens: new Array(16).fill(0), constraints: [ { type: "maximum", cell: 5, negated: true }, { type: "thermo", cells: [8, 9], negated: true }, { type: "quadruple", cells: [0, 1, 4, 5], digits: [1, 3], negated: true, }, { type: "x-sum", side: "top", index: 0, sum: 42, negated: true, }, { type: "skyscraper", side: "top", index: 0, count: 42, negated: true, }, ], }); const { container } = render( , ); expect(container.querySelector(".sudoku-board-frame")).toHaveClass( "has-outside-clues", ); expect(container.querySelectorAll(".constraint-outside")).toHaveLength(1); expect(container.querySelector(".outside-clue-kinds")).toHaveTextContent( "Σ · ▥", ); expect(container.querySelector(".outside-clue-value")).toHaveTextContent( "≠42", ); expect(container.querySelectorAll(".is-negated")).toHaveLength(4); expect( container.querySelector(".constraint-thermo.is-negated polyline"), ).toBeInTheDocument(); expect( container.querySelector(".constraint-thermo .constraint-false-mark"), ).toHaveTextContent("≠"); const board = screen.getByRole("grid", { name: "4 by 4 Sudoku grid" }); expect(board).toHaveAccessibleDescription( expect.stringContaining("False clue: X-sum 42 from the top, column 1."), ); expect(board).toHaveAccessibleDescription( expect.stringContaining("False clue: maximum at row 2, column 2."), ); }); it("renders candidate filters and strong/weak graph overlays", () => { const puzzle = normalizePuzzle({ version: 1, size: 4, givens: new Array(16).fill(0), }); const { container } = render( , ); expect( container.querySelectorAll(".is-candidate-highlighted"), ).toHaveLength(2); expect( container.querySelectorAll(".candidate-overlay-link--strong"), ).toHaveLength(1); expect( container.querySelectorAll(".candidate-overlay-link--weak"), ).toHaveLength(1); expect(container.querySelectorAll(".candidate-overlay-node")).toHaveLength( 4, ); }); it("exposes real row semantics and detailed per-cell state", () => { const puzzle = normalizePuzzle({ version: 1, size: 4, givens: [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], constraints: [{ type: "killer-cage", cells: [0, 1], sum: 3 }], }); render( (16).fill(0)} candidates={[0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]} colors={[0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]} selected={new Set([1])} conflicts={new Set([1])} activeCell={1} showCandidates onCellPointerDown={vi.fn()} onCellPointerEnter={vi.fn()} onKeyDown={vi.fn()} />, ); const board = screen.getByRole("grid", { name: "4 by 4 Sudoku grid" }); expect(board).toHaveAttribute("aria-rowcount", "4"); expect(board).toHaveAttribute("aria-colcount", "4"); expect(board).toHaveAccessibleDescription( expect.stringContaining("Home and End move to the first and last cell"), ); expect(screen.getAllByRole("row")).toHaveLength(4); const given = screen.getByRole("gridcell", { name: "Row 1, column 1, 1", }); expect(given).toHaveAttribute("aria-readonly", "true"); expect(given).toHaveAccessibleDescription( expect.stringMatching(/region 1.*given digit.*killer cage 3/i), ); const annotated = screen.getByRole("gridcell", { name: "Row 1, column 2, empty", }); expect(annotated).toHaveAccessibleDescription( expect.stringMatching( /region 1.*corner notes 1.*automatic candidates 1, 2.*colour 2.*conflict/i, ), ); expect(annotated).toHaveAttribute("aria-colindex", "2"); expect(annotated).toHaveAttribute("aria-invalid", "true"); expect(annotated).toHaveAttribute("aria-selected", "true"); }); });