feat: expand sudoku analysis and interoperability
This commit is contained in:
@@ -63,6 +63,34 @@ describe("Sudoku workbench", () => {
|
||||
expect(undo).toBeDisabled();
|
||||
});
|
||||
|
||||
it("supports non-wrapping WAI-ARIA grid navigation", () => {
|
||||
render(<Workbench />);
|
||||
|
||||
const grid = screen.getByRole("grid", { name: "9 by 9 Sudoku grid" });
|
||||
const cell = (row: number, column: number) =>
|
||||
within(grid).getByRole("gridcell", {
|
||||
name: new RegExp(`^Row ${String(row)}, column ${String(column)},`, "u"),
|
||||
});
|
||||
|
||||
cell(1, 1).focus();
|
||||
fireEvent.keyDown(cell(1, 1), { key: "ArrowLeft" });
|
||||
expect(cell(1, 1)).toHaveAttribute("tabindex", "0");
|
||||
|
||||
fireEvent.keyDown(cell(1, 1), { key: "End" });
|
||||
expect(cell(1, 9)).toHaveAttribute("tabindex", "0");
|
||||
fireEvent.keyDown(cell(1, 9), { key: "ArrowRight" });
|
||||
expect(cell(1, 9)).toHaveAttribute("tabindex", "0");
|
||||
|
||||
fireEvent.keyDown(cell(1, 9), { key: "ArrowDown" });
|
||||
expect(cell(2, 9)).toHaveAttribute("tabindex", "0");
|
||||
fireEvent.keyDown(cell(2, 9), { key: "Home" });
|
||||
expect(cell(2, 1)).toHaveAttribute("tabindex", "0");
|
||||
fireEvent.keyDown(cell(2, 1), { key: "PageDown" });
|
||||
expect(cell(9, 1)).toHaveAttribute("tabindex", "0");
|
||||
fireEvent.keyDown(cell(9, 1), { key: "Home", ctrlKey: true });
|
||||
expect(cell(1, 1)).toHaveAttribute("tabindex", "0");
|
||||
});
|
||||
|
||||
it("shows digit progress and toggles matching-digit highlights separately from selection", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<Workbench />);
|
||||
@@ -124,6 +152,30 @@ describe("Sudoku workbench", () => {
|
||||
screen.getByRole("button", { name: "Remove selected cage" }),
|
||||
);
|
||||
expect(screen.queryByText("4 cage · 2 cells")).not.toBeInTheDocument();
|
||||
|
||||
await user.click(screen.getByLabelText("Cage digits may repeat"));
|
||||
await user.click(
|
||||
screen.getByLabelText("Newly added clues must be false (Wrogn mode)"),
|
||||
);
|
||||
await user.clear(cageSum);
|
||||
await user.type(cageSum, "2");
|
||||
await user.click(
|
||||
screen.getByRole("button", { name: "Add / replace cage" }),
|
||||
);
|
||||
expect(
|
||||
screen.getByText("false · 2 cage · 2 cells · repeats allowed"),
|
||||
).toBeInTheDocument();
|
||||
|
||||
await user.click(screen.getByLabelText("Cage digits may repeat"));
|
||||
await user.clear(cageSum);
|
||||
await user.type(cageSum, "3");
|
||||
await user.click(
|
||||
screen.getByRole("button", { name: "Add / replace cage" }),
|
||||
);
|
||||
expect(
|
||||
screen.queryByText("false · 2 cage · 2 cells · repeats allowed"),
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.getByText("false · 3 cage · 2 cells")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("opens the expanded built-in variant examples", async () => {
|
||||
@@ -157,6 +209,66 @@ describe("Sudoku workbench", () => {
|
||||
expect(
|
||||
container.querySelector(".constraint-xv--10 text"),
|
||||
).toHaveTextContent("10");
|
||||
|
||||
await user.selectOptions(
|
||||
screen.getByLabelText("Open built-in puzzle"),
|
||||
"truth-and-lies",
|
||||
);
|
||||
expect(
|
||||
screen.getByRole("heading", { name: "Truth and lies" }),
|
||||
).toBeInTheDocument();
|
||||
expect(container.querySelectorAll(".constraint-outside")).toHaveLength(4);
|
||||
expect(container.querySelectorAll(".constraint-quadruple")).toHaveLength(2);
|
||||
expect(container.querySelectorAll(".constraint-maximum")).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("sets and batch-toggles false local and outside clues", async () => {
|
||||
const user = userEvent.setup();
|
||||
const { container } = render(<Workbench />);
|
||||
|
||||
await user.click(screen.getByRole("button", { name: "New blank" }));
|
||||
expect(
|
||||
screen.getByRole("button", { name: "Add quadruple" }),
|
||||
).toBeDisabled();
|
||||
await user.clear(screen.getByLabelText("Quadruple digits"));
|
||||
await user.type(screen.getByLabelText("Quadruple digits"), "1");
|
||||
expect(
|
||||
screen.getByRole("button", { name: "Add quadruple" }),
|
||||
).toBeDisabled();
|
||||
await user.click(
|
||||
screen.getByLabelText("Newly added clues must be false (Wrogn mode)"),
|
||||
);
|
||||
await user.click(screen.getByRole("button", { name: "Maximum cell" }));
|
||||
await user.click(
|
||||
screen.getByRole("button", { name: "Add / replace outside clue" }),
|
||||
);
|
||||
|
||||
expect(screen.getByText("false · maximum · r1c1")).toBeInTheDocument();
|
||||
expect(screen.getByText("false · Σ3 · top 1")).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole("button", {
|
||||
name: "Require true for false · maximum · r1c1",
|
||||
}),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole("button", {
|
||||
name: "Remove false · Σ3 · top 1",
|
||||
}),
|
||||
).toBeInTheDocument();
|
||||
expect(container.querySelectorAll(".is-negated")).toHaveLength(2);
|
||||
|
||||
await user.clear(screen.getByLabelText("Clue"));
|
||||
await user.type(screen.getByLabelText("Clue"), "6562");
|
||||
expect(
|
||||
screen.getByRole("button", { name: "Add / replace outside clue" }),
|
||||
).toBeDisabled();
|
||||
|
||||
await user.click(
|
||||
screen.getByRole("button", { name: "Make all clues true" }),
|
||||
);
|
||||
expect(screen.getByText("maximum · r1c1")).toBeInTheDocument();
|
||||
expect(screen.getByText("Σ3 · top 1")).toBeInTheDocument();
|
||||
expect(container.querySelectorAll(".is-negated")).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("switches between setting, solving, playing and helper workspaces", async () => {
|
||||
@@ -206,6 +318,38 @@ describe("Sudoku workbench", () => {
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("projects candidate filters and links onto the board only while inspecting", async () => {
|
||||
const user = userEvent.setup();
|
||||
const { container } = render(<Workbench />);
|
||||
|
||||
await user.click(screen.getByRole("button", { name: "Helpers" }));
|
||||
await user.click(screen.getByRole("tab", { name: "Candidate links" }));
|
||||
expect(
|
||||
screen.getByRole("heading", { name: "Links and houses" }),
|
||||
).toBeInTheDocument();
|
||||
|
||||
await user.click(screen.getByRole("checkbox", { name: "Weak links" }));
|
||||
expect(
|
||||
container.querySelector(".candidate-link-layer"),
|
||||
).toBeInTheDocument();
|
||||
|
||||
await user.click(
|
||||
screen.getByRole("button", { name: "Filter candidate 1" }),
|
||||
);
|
||||
expect(
|
||||
container.querySelectorAll(".sudoku-cell.is-candidate-highlighted")
|
||||
.length,
|
||||
).toBeGreaterThan(0);
|
||||
|
||||
await user.click(screen.getByRole("button", { name: "Play" }));
|
||||
expect(
|
||||
container.querySelector(".candidate-link-layer"),
|
||||
).not.toBeInTheDocument();
|
||||
expect(
|
||||
container.querySelectorAll(".sudoku-cell.is-candidate-highlighted"),
|
||||
).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("makes the import boundary and self-contained local sharing explicit", async () => {
|
||||
const user = userEvent.setup();
|
||||
const fetchSpy = vi.fn();
|
||||
|
||||
Reference in New Issue
Block a user