feat: complete advanced Sudoku workbench

This commit is contained in:
2026-08-31 08:20:30 +02:00
parent 8ca9300ab3
commit 0a1bdc1a8c
99 changed files with 20793 additions and 923 deletions
+76 -4
View File
@@ -4,7 +4,10 @@ import { describe, expect, it, vi } from "vitest";
import { ImportExportDialog } from "../../src/components/ImportExportDialog";
import { createEmptyPuzzle } from "../../src/domain";
import type { PuzzleDefinition } from "../../src/domain/types";
import { fromDomainPuzzle } from "../../src/formats";
import {
fromDomainPuzzle,
type PreservedSudokuDocumentExtras,
} from "../../src/formats";
import type { PortableAidMemoire } from "../../src/state/aidMemoire";
import { createSession } from "../../src/state/session";
@@ -40,6 +43,7 @@ function renderDialog(
options: {
readonly puzzle?: PuzzleDefinition;
readonly aidMemoire?: PortableAidMemoire;
readonly preservedExtras?: PreservedSudokuDocumentExtras;
} = {},
) {
const puzzle = options.puzzle ?? createEmptyPuzzle(4);
@@ -51,6 +55,7 @@ function renderDialog(
puzzle={puzzle}
session={createSession(puzzle.givens)}
aidMemoire={options.aidMemoire}
preservedExtras={options.preservedExtras}
onClose={onClose}
onImport={onImport}
/>,
@@ -79,11 +84,14 @@ describe("ImportExportDialog interoperability", () => {
expect(onImport).toHaveBeenCalledWith(
expect.objectContaining({ size: 4, givens: expect.any(Array) }),
expect.any(Object),
expect.objectContaining({
source: { format: "sudokupad", id: "synthetic-local-test" },
}),
);
expect(onClose).toHaveBeenCalledOnce();
});
it("reports unsupported constructs and never fetches short IDs", async () => {
it("previews safe visuals and never fetches short IDs", async () => {
const user = userEvent.setup();
const fetchSpy = vi.fn();
vi.stubGlobal("fetch", fetchSpy);
@@ -91,12 +99,35 @@ describe("ImportExportDialog interoperability", () => {
const input = screen.getByPlaceholderText(/Paste 81 characters/u);
fireEvent.change(input, {
target: { value: localScl({ overlays: [{ text: "visual only" }] }) },
target: {
value: localScl({
overlays: [
{
center: [0.5, 0.5],
width: 1,
height: 1,
text: "visual only",
},
],
}),
},
});
await user.click(
screen.getByRole("button", { name: "Check compatibility" }),
);
expect(await screen.findByText(/visual overlays/u)).toBeInTheDocument();
expect(
await screen.findByRole("region", { name: "Import mapping preview" }),
).toBeInTheDocument();
expect(screen.getByText("Preserved visuals")).toBeInTheDocument();
expect(screen.getByText("Text overlay: 1")).toBeInTheDocument();
fireEvent.change(input, {
target: { value: localScl({ overlays: [{ text: "missing center" }] }) },
});
await user.click(
screen.getByRole("button", { name: "Check compatibility" }),
);
expect(await screen.findByText(/center.*point/u)).toBeInTheDocument();
fireEvent.change(input, {
target: { value: "https://sudokupad.app/serverOnly42" },
@@ -164,6 +195,47 @@ describe("ImportExportDialog interoperability", () => {
expect(onImport).toHaveBeenCalledWith(
expect.objectContaining({ size: 4 }),
expect.objectContaining({ aidMemoire: TEST_AID_MEMOIRE }),
{ source: { format: "sudoku-tools" } },
);
});
it("includes preserved source extras in SudokuPad exports", async () => {
const user = userEvent.setup();
let downloaded: Blob | undefined;
vi.mocked(URL.createObjectURL).mockImplementation((value) => {
downloaded = value as Blob;
return "blob:scl-export-test";
});
vi.spyOn(HTMLAnchorElement.prototype, "click").mockImplementation(() => {});
renderDialog({
preservedExtras: {
source: { format: "sudokupad", id: "kept-source" },
metadata: { edition: "nightly" },
visuals: [
{
type: "text",
layer: "overlay",
position: { kind: "cell", cell: 0 },
text: "kept visual",
style: { fill: "#123456" },
},
],
},
});
await user.click(
screen.getByRole("button", { name: "Download SudokuPad JSON" }),
);
const exported = JSON.parse((await downloaded?.text()) ?? "{}") as {
metadata?: Record<string, unknown>;
overlays?: unknown[];
};
expect(exported.metadata?.edition).toBe("nightly");
expect(exported.overlays).toEqual(
expect.arrayContaining([
expect.objectContaining({ text: "kept visual" }),
]),
);
});