import { fireEvent, render, screen, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { beforeAll, describe, expect, it, vi } from "vitest"; import { Workbench } from "../../src/components/Workbench"; class WorkerStub { addEventListener() {} postMessage() {} terminate() {} } beforeAll(() => { vi.stubGlobal("Worker", WorkerStub); }); describe("Sudoku workbench", () => { it("opens with the local classic example rendered as an accessible grid", () => { render(); expect( screen.getByRole("heading", { name: "A first classic" }), ).toBeInTheDocument(); const grid = screen.getByRole("grid", { name: "9 by 9 Sudoku grid" }); expect(within(grid).getAllByRole("gridcell")).toHaveLength(81); expect( within(grid).getByRole("gridcell", { name: "Row 1, column 1, 4", }), ).toHaveClass("is-given"); expect( within(grid).getByRole("gridcell", { name: "Row 1, column 3, empty", }), ).toBeInTheDocument(); }); it("enters a digit in an empty cell and restores it with undo", async () => { const user = userEvent.setup(); render(); const emptyCell = screen.getByRole("gridcell", { name: "Row 1, column 3, empty", }); fireEvent.pointerDown(emptyCell, { buttons: 1 }); await user.click( within(screen.getByRole("group", { name: "Digits" })).getByRole( "button", { name: "2" }, ), ); expect( screen.getByRole("gridcell", { name: "Row 1, column 3, 2" }), ).toBeInTheDocument(); const undo = screen.getByRole("button", { name: "Undo" }); expect(undo).toBeEnabled(); await user.click(undo); expect( screen.getByRole("gridcell", { name: "Row 1, column 3, empty", }), ).toBeInTheDocument(); expect(undo).toBeDisabled(); }); it("switches between setting, solving, playing and helper workspaces", async () => { const user = userEvent.setup(); render(); await user.click(screen.getByRole("button", { name: "Set" })); expect( screen.getByRole("heading", { name: "Set a puzzle" }), ).toBeInTheDocument(); expect( screen.getByRole("heading", { name: "Enter givens" }), ).toBeInTheDocument(); await user.click(screen.getByRole("button", { name: "Solve" })); expect( screen.getByRole("heading", { name: "Solve and verify" }), ).toBeInTheDocument(); await user.click(screen.getByRole("button", { name: "Helpers" })); expect( screen.getByRole("heading", { name: "Sudoku helpers" }), ).toBeInTheDocument(); expect( screen.getByRole("tab", { name: "Killer combinations" }), ).toHaveAttribute("aria-selected", "true"); expect(document.querySelector(".helper-result")).toHaveTextContent( "combinations", ); await user.click(screen.getByRole("tab", { name: "45-rule residual" })); expect( screen.getByLabelText("Accounted values or cage sums"), ).toBeInTheDocument(); await user.click(screen.getByRole("button", { name: "Play" })); expect( screen.getByRole("heading", { name: "Enter your solve" }), ).toBeInTheDocument(); }); it("makes the import boundary and self-contained local sharing explicit", async () => { const user = userEvent.setup(); const fetchSpy = vi.fn(); vi.stubGlobal("fetch", fetchSpy); render(); await user.click(screen.getByRole("button", { name: "Import / export" })); expect( screen.getByRole("heading", { name: "Import and export" }), ).toBeInTheDocument(); expect( screen.getByText(/Server-only short IDs are never fetched\./u), ).toBeInTheDocument(); expect( screen.getByText(/Share links are self-contained\./u), ).toBeInTheDocument(); await user.click( screen.getByRole("button", { name: "Copy local share URL" }), ); expect( await screen.findByText("Local share URL copied."), ).toBeInTheDocument(); expect(fetchSpy).not.toHaveBeenCalled(); }); });