feat: expand sudoku analysis and interoperability
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
import { render, screen, waitFor, within } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { CandidateLab } from "../../src/components/CandidateLab";
|
||||
import { createEmptyPuzzle, normalizePuzzle } from "../../src/domain";
|
||||
import type { CandidateOverlay } from "../../src/helpers";
|
||||
|
||||
function mask(...values: number[]): number {
|
||||
return values.reduce((result, value) => result | (1 << (value - 1)), 0);
|
||||
}
|
||||
|
||||
const puzzle = normalizePuzzle(createEmptyPuzzle(4));
|
||||
|
||||
describe("candidate lab", () => {
|
||||
it("inspects selected cells, filters digits and focuses a link overlay", async () => {
|
||||
const user = userEvent.setup();
|
||||
const overlays: (CandidateOverlay | undefined)[] = [];
|
||||
const candidateMasks = new Array<number>(16).fill(0);
|
||||
candidateMasks[0] = mask(1, 2);
|
||||
candidateMasks[1] = mask(1, 2);
|
||||
|
||||
render(
|
||||
<CandidateLab
|
||||
puzzle={puzzle}
|
||||
values={puzzle.givens}
|
||||
candidateMasks={candidateMasks}
|
||||
selectedCells={[0]}
|
||||
onOverlayChange={(overlay) => overlays.push(overlay)}
|
||||
/>,
|
||||
);
|
||||
|
||||
const selected = screen.getByRole("heading", {
|
||||
name: "Selected cells",
|
||||
}).parentElement!;
|
||||
expect(within(selected).getByText("r1c1")).toBeInTheDocument();
|
||||
expect(within(selected).getByText("1 2")).toBeInTheDocument();
|
||||
|
||||
await user.click(
|
||||
screen.getByRole("button", { name: "Filter candidate 1" }),
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(overlays.at(-1)?.activeValues).toEqual([1]);
|
||||
expect(overlays.at(-1)?.candidateCells).toEqual([0, 1]);
|
||||
});
|
||||
|
||||
const link = screen.getByRole("button", { name: /r1c1\(1\).*r1c2\(1\)/u });
|
||||
await user.click(link);
|
||||
await waitFor(() => expect(overlays.at(-1)?.links).toHaveLength(1));
|
||||
expect(link).toHaveAttribute("aria-pressed", "true");
|
||||
await user.click(screen.getByRole("button", { name: "Show all" }));
|
||||
expect(link).toHaveAttribute("aria-pressed", "false");
|
||||
});
|
||||
|
||||
it("reveals weak links on demand within a chosen house", async () => {
|
||||
const user = userEvent.setup();
|
||||
const candidateMasks = new Array<number>(16).fill(0);
|
||||
candidateMasks[0] = mask(3);
|
||||
candidateMasks[1] = mask(3);
|
||||
candidateMasks[2] = mask(3);
|
||||
|
||||
render(
|
||||
<CandidateLab
|
||||
puzzle={puzzle}
|
||||
values={puzzle.givens}
|
||||
candidateMasks={candidateMasks}
|
||||
selectedCells={[0]}
|
||||
/>,
|
||||
);
|
||||
|
||||
await user.selectOptions(
|
||||
screen.getByLabelText("Candidate house scope"),
|
||||
"unit:0",
|
||||
);
|
||||
await user.click(
|
||||
screen.getByRole("button", { name: "Filter candidate 3" }),
|
||||
);
|
||||
expect(screen.getByText(/No links match this scope/u)).toBeInTheDocument();
|
||||
await user.click(screen.getByRole("checkbox", { name: "Weak links" }));
|
||||
|
||||
expect(
|
||||
screen.getAllByRole("button", { name: /weak · Row 1/u }),
|
||||
).toHaveLength(3);
|
||||
});
|
||||
|
||||
it("clears the board overlay when it unmounts", async () => {
|
||||
const onOverlayChange = vi.fn();
|
||||
const { unmount } = render(
|
||||
<CandidateLab
|
||||
puzzle={puzzle}
|
||||
values={puzzle.givens}
|
||||
candidateMasks={new Array<number>(16).fill(0)}
|
||||
selectedCells={[0]}
|
||||
onOverlayChange={onOverlayChange}
|
||||
/>,
|
||||
);
|
||||
await waitFor(() => expect(onOverlayChange).toHaveBeenCalled());
|
||||
unmount();
|
||||
expect(onOverlayChange).toHaveBeenLastCalledWith(undefined);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user