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
+126
View File
@@ -248,3 +248,129 @@ test("keeps navigation, scratch work, branches and analysis tools local", async
expect(runtimeErrors).toEqual([]);
});
test("migrates the local database and recovers autosaved history", async ({
page,
}) => {
await page.goto("/deep/nested/sudoku/favicon.svg");
await page.evaluate(async () => {
await new Promise<void>((resolve, reject) => {
const request = indexedDB.deleteDatabase("sudoku-tools");
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
await new Promise<void>((resolve, reject) => {
const request = indexedDB.open("sudoku-tools", 1);
request.onupgradeneeded = () => {
const store = request.result.createObjectStore("projects", {
keyPath: "id",
});
store.createIndex("updatedAt", "updatedAt");
};
request.onsuccess = () => {
request.result.close();
resolve();
};
request.onerror = () => reject(request.error);
});
});
await page.goto("/deep/nested/sudoku/");
const grid = page.getByRole("grid", { name: "9 by 9 Sudoku grid" });
await grid.getByRole("gridcell", { name: "Row 1, column 3, empty" }).click();
await page
.getByRole("group", { name: "Digits" })
.getByRole("button", { name: "2", exact: true })
.click();
await expect
.poll(
async () =>
await page.evaluate(
async () =>
await new Promise<number | undefined>((resolve, reject) => {
const open = indexedDB.open("sudoku-tools");
open.onerror = () => reject(open.error);
open.onsuccess = () => {
const database = open.result;
const request = database
.transaction("autosaves", "readonly")
.objectStore("autosaves")
.get("current");
request.onerror = () => reject(request.error);
request.onsuccess = () => {
const value = request.result as
| { record?: { progress?: { values?: number[] } } }
| undefined;
database.close();
resolve(value?.record?.progress?.values?.[2]);
};
};
}),
),
{ timeout: 10_000 },
)
.toBe(2);
await page.reload();
await expect(
page.getByRole("heading", { name: "A first classic" }),
).toBeVisible();
await expect(page.getByText("Recover unsaved local work?")).toBeVisible();
await page.getByRole("button", { name: "Restore" }).click();
await expect(
page
.getByRole("grid", { name: "9 by 9 Sudoku grid" })
.getByRole("gridcell", { name: "Row 1, column 3, 2" }),
).toBeVisible();
await page.getByRole("button", { name: "History & branches" }).click();
await expect(
page
.getByRole("list", { name: "Solve history" })
.getByRole("button", { name: /Set r1c3 to 2/u }),
).toBeVisible();
});
test("installs a subpath-safe application shell that reopens offline", async ({
context,
page,
}) => {
await page.goto("/deep/nested/sudoku/");
await page.waitForFunction(async () => {
await navigator.serviceWorker.ready;
return navigator.serviceWorker.controller !== null;
});
const cachedUrls = await page.evaluate(async () => {
const names = await caches.keys();
return (
await Promise.all(
names
.filter((name) => name.startsWith("sudoku-tools-shell-"))
.map(async (name) =>
(await caches.open(name))
.keys()
.then((requests) => requests.map((request) => request.url)),
),
)
).flat();
});
expect(
cachedUrls.some((url) => /\/solver\.worker-[^/]+\.js$/u.test(url)),
).toBe(true);
await page.reload();
await expect(
page.getByRole("heading", { name: "A first classic" }),
).toBeVisible();
await context.setOffline(true);
try {
await page.reload();
await expect(
page.getByRole("heading", { name: "A first classic" }),
).toBeVisible();
await expect(
page.getByRole("grid", { name: "9 by 9 Sudoku grid" }),
).toBeVisible();
} finally {
await context.setOffline(false);
}
});