Files
Hare c78f7f7d80 feat: replace revision counters with domain state contracts
Use content digests, key fingerprints, binding and trust identities, immutable mutation receipts, and observation tokens instead of state-update ordinals across API, storage, Runtime, and Web contracts.

Preserve historical replay and secret material through frozen migrations; preflight uncertain removals before cutover, resume provisioning receipts, validate encrypted migration candidates, and align fresh/upgraded schemas. Include reviewed regression fixes, generated artifacts, and validation evidence.
2026-10-10 15:55:20 +09:00

890 lines
33 KiB
TypeScript

/// <reference lib="dom" />
import { assert, assertEquals } from "@std/assert";
import { dirname, fromFileUrl, join } from "@std/path";
import { chromium, type Page, type Route } from "playwright";
import { Buffer } from "node:buffer";
import type { FixtureLog } from "../../../../tools/web-ux/drive-fixture/api.ts";
const root = join(dirname(fromFileUrl(import.meta.url)), "../../../..");
const ready = (page: Page) =>
page.locator('[data-drive-ready="true"]').waitFor();
const button = (page: Page, name: string) =>
page.getByRole("button", { name, exact: true });
const draft = (page: Page) =>
page.getByRole("textbox", { name: /^Draft$/ });
const preview = (page: Page) =>
page.getByRole("article", { name: "Current saved content" });
function gate() {
let release!: () => void;
const promise = new Promise<void>((resolve) => release = resolve);
return {
release,
get promise() {
return (async () => {
let timer: ReturnType<typeof setTimeout> | undefined;
try {
return await Promise.race([
promise,
new Promise<void>((_resolve, reject) => {
timer = setTimeout(
() =>
reject(
new Error(
"Expected API gate was not observed/released within 30 seconds",
),
),
30000,
);
}),
]);
} finally {
clearTimeout(timer);
}
})();
},
};
}
async function harness(
run: (
origin: string,
browser: Awaited<ReturnType<typeof chromium.launch>>,
) => Promise<void>,
) {
// Intentionally no build here. Parent must authorize and build production UI first.
const listener = Deno.listen({ hostname: "127.0.0.1", port: 0 });
const port = (listener.addr as Deno.NetAddr).port;
listener.close();
const origin = `http://127.0.0.1:${port}`;
const server = new Deno.Command(Deno.execPath(), {
args: [
"run",
"--allow-read",
"--allow-net",
"--allow-run",
"--allow-env",
join(root, "tools/web-ux/drive-fixture/server.ts"),
String(port),
join(root, "web/workspace/build"),
],
stdout: "null",
stderr: "inherit",
}).spawn();
try {
const deadline = Date.now() + 10000;
while (true) {
try {
const response = await fetch(`${origin}/health`);
await response.body?.cancel();
if (response.ok) break;
} catch { /* startup */ }
if (Date.now() > deadline) {
throw new Error("Drive fixture failed startup");
}
await new Promise((resolve) => setTimeout(resolve, 30));
}
const browser = await chromium.launch({ headless: true });
try {
await run(origin, browser);
} finally {
await browser.close();
}
} finally {
server.kill("SIGTERM");
await server.status;
}
}
async function editor(page: Page, text: string) {
await ready(page);
await button(page, "Edit text").click();
await draft(page).fill(text);
}
async function rootFolder(page: Page) {
await page.getByRole("navigation", { name: "Drive folders" }).getByRole(
"link",
{
name: "Drive",
exact: true,
},
).click();
await page.getByRole("heading", { name: "Drive", exact: true }).waitFor();
await ready(page);
}
async function otherFile(page: Page) {
await rootFolder(page);
await page.getByRole("link", { name: "資料", exact: true }).click();
await ready(page);
await page.getByRole("link", { name: "notes.txt", exact: true }).click();
await ready(page);
}
async function fault(origin: string, workspace: string, mode: string) {
const response = await fetch(`${origin}/__fixture/fault`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ workspace, mode }),
});
await response.body?.cancel();
}
async function logs(origin: string) {
return await (await fetch(`${origin}/__fixture/log`)).json() as FixtureLog[];
}
async function safeFulfill(
route: Route,
response: Awaited<ReturnType<Route["fetch"]>>,
) {
try {
await route.fulfill({ response });
} catch { /* selection intentionally canceled this old request */ }
}
Deno.test("production Drive two tabs retain losing CAS draft and never retry with a newer observation", () =>
harness(async (origin, browser) => {
const context = await browser.newContext();
const first = await context.newPage(), second = await context.newPage();
const errors: string[] = [];
first.on("pageerror", (e) => errors.push(String(e)));
second.on("pageerror", (e) => errors.push(String(e)));
try {
await first.goto(`${origin}/w/home-owner/drive/3`);
await second.goto(`${origin}/w/home-owner/drive/3`);
await editor(first, "First actor wins");
await editor(second, "Second actor unsaved draft");
await button(first, "Save").click();
await first.getByText("Published — DB commit confirmed", { exact: false })
.last().waitFor();
await button(second, "Save").click();
await second.getByText("Content changed — your draft is retained.", {
exact: true,
})
.waitFor();
assertEquals(
await draft(second).inputValue(),
"Second actor unsaved draft",
);
assertEquals(await button(second, "Save").isDisabled(), true);
await button(second, "Refresh").click();
await ready(second);
assertEquals(
await draft(second).inputValue(),
"Second actor unsaved draft",
);
assert(
(await second.getByText("Draft", { exact: false })
.count()) > 0,
);
assert(
(await preview(second).textContent())!.includes("First actor wins"),
);
await otherFile(second);
await editor(second, "Independent second-file draft");
await rootFolder(second);
await second.getByRole("link", { name: "README.md", exact: true })
.click();
await ready(second);
await button(second, "Edit text").click();
assertEquals(
await draft(second).inputValue(),
"Second actor unsaved draft",
);
const casWrites = (await logs(origin)).filter((x) =>
x.path === "/mutate"
);
assertEquals(casWrites.map((x) => [x.nodeId, x.expectedMutationId]), [[
"3",
"1",
], ["3", "1"]]);
assertEquals(errors, []);
} finally {
await context.close();
}
}));
Deno.test("production Drive delayed old read cannot replace another file or workspace content", () =>
harness(async (origin, browser) => {
const context = await browser.newContext();
const page = await context.newPage();
const received = gate(), release = gate();
let handled: Promise<void> = Promise.resolve();
await page.route("**/drive/read-text?**", (route) => {
if (new URL(route.request().url()).searchParams.get("id") !== "3") {
return route.continue();
}
handled = (async () => {
const response = await route.fetch();
received.release();
await release.promise;
await safeFulfill(route, response);
})();
return handled;
});
try {
await page.goto(`${origin}/w/home-owner/drive/3`);
await received.promise;
await otherFile(page);
await editor(page, "Only current file draft");
release.release();
await handled;
assertEquals(await page.locator("main h1").textContent(), "notes.txt");
assertEquals(await draft(page).inputValue(), "Only current file draft");
await button(page, "Save").click();
await page.getByText("Published — DB commit confirmed", { exact: false })
.last().waitFor();
const mutation = (await logs(origin)).filter((x) => x.path === "/mutate");
assertEquals(
mutation.map((x) => [x.workspace, x.nodeId, x.expectedMutationId]),
[[
"home-owner",
"4",
"1",
]],
);
// Full workspace navigation must likewise never resurrect the old workspace.
await page.goto(`${origin}/w/home-member/drive/4`);
await ready(page);
assert((await preview(page).textContent())!.includes("Second document"));
assertEquals(
await page.locator(".drive-details dd a").getAttribute("href"),
"/w/home-member/drive/4",
);
} finally {
release.release();
await handled;
await context.close();
}
}));
Deno.test("production Drive delayed old save and status preserve destination drafts and save identity", () =>
harness(async (origin, browser) => {
const context = await browser.newContext();
const page = await context.newPage();
const sent = gate(), release = gate();
let handled = Promise.resolve();
await page.route("**/drive/mutate", (route) => {
handled = (async () => {
const response = await route.fetch();
sent.release();
await release.promise;
await safeFulfill(route, response);
})();
return handled;
});
try {
await page.goto(`${origin}/w/home-owner/drive/3`);
await editor(page, "Old file transmitted draft");
await button(page, "Save").click();
await sent.promise;
await otherFile(page);
await editor(page, "Destination draft never rebased");
release.release();
await handled;
await page.unroute("**/drive/mutate");
assertEquals(
await draft(page).inputValue(),
"Destination draft never rebased",
);
await button(page, "Save").click();
await page.getByText("Published — DB commit confirmed", { exact: false })
.last().waitFor();
await rootFolder(page);
await page.getByRole("link", { name: "README.md", exact: true }).click();
await ready(page);
await page.getByRole("button", { name: "Check request result" }).click();
await page.getByText("Published — DB commit confirmed", { exact: false })
.last().waitFor();
assert(
(await preview(page).textContent())!.includes(
"Old file transmitted draft",
),
);
await editor(page, "Another old request draft");
await fault(origin, "home-owner", "unknown");
await button(page, "Save").click();
await button(page, "Check request result").waitFor();
const statusSent = gate(), statusRelease = gate();
let statusHandled = Promise.resolve();
await page.route("**/drive/requests/*", (route) => {
statusHandled = (async () => {
const response = await route.fetch();
statusSent.release();
await statusRelease.promise;
await safeFulfill(route, response);
})();
return statusHandled;
});
await button(page, "Check request result").click();
await statusSent.promise;
await otherFile(page);
await editor(page, "Destination draft during old status");
statusRelease.release();
await statusHandled;
assertEquals(
await draft(page).inputValue(),
"Destination draft during old status",
);
assert(
(await preview(page).textContent())!.includes(
"Destination draft never rebased",
),
);
await button(page, "Save").click();
await page.getByText("Published — DB commit confirmed", { exact: false })
.last().waitFor();
const pinnedWrites = (await logs(origin)).filter((x) =>
x.path === "/mutate"
);
assertEquals(pinnedWrites.map((x) => [x.nodeId, x.expectedMutationId]), [
["3", "1"],
["4", "1"],
["3", pinnedWrites[0].requestId],
["4", pinnedWrites[1].requestId],
]);
assert(pinnedWrites.every((x) => typeof x.requestId === "string" && x.requestId.length > 0));
assertEquals(new Set(pinnedWrites.map((x) => x.requestId)).size, 4);
} finally {
release.release();
await handled;
await context.close();
}
}));
Deno.test("production Drive rename move delete recreate preserve stable identity and missing old URL", () =>
harness(async (origin, browser) => {
const page = await browser.newPage();
page.on("dialog", (dialog) => dialog.accept());
try {
await page.goto(`${origin}/w/home-owner/drive/3`);
await ready(page);
await page.getByLabel("More actions", { exact: true }).click();
await button(page, "Rename / move").click();
await page.getByRole("textbox", { name: "Name", exact: true }).fill(
"renamed.md",
);
await page.getByRole("dialog").getByRole("button", {
name: "資料",
exact: true,
}).click();
await button(page, "Apply rename / move").click();
await page.getByRole("heading", { name: "renamed.md", exact: true })
.waitFor();
assertEquals(new URL(page.url()).pathname, "/w/home-owner/drive/3");
await page.getByRole("navigation", { name: "Drive folders" }).getByRole(
"link",
{
name: "資料",
},
).click();
await ready(page);
await page.getByRole("link", { name: "renamed.md", exact: true }).click();
await ready(page);
await page.getByLabel("More actions", { exact: true }).click();
await button(page, "Delete").click();
await page.getByRole("alert").filter({ hasText: /not[_ ]found/i })
.waitFor();
assertEquals(new URL(page.url()).pathname, "/w/home-owner/drive/3");
await rootFolder(page);
await page.getByRole("link", { name: "資料", exact: true }).click();
await ready(page);
await page.locator(".drive-menu > summary").filter({ hasText: /^New$/ })
.click();
await button(page, "New Markdown").click();
await page.getByRole("textbox", { name: "Name", exact: true }).fill(
"renamed.md",
);
await page.getByRole("textbox", { name: "Markdown", exact: true }).fill(
"Recreated identity",
);
await button(page, "Create").click();
await page.getByRole("link", { name: "renamed.md", exact: true })
.waitFor();
const href = await page.getByRole("link", {
name: "renamed.md",
exact: true,
}).getAttribute(
"href",
);
assert(href !== "/w/home-owner/drive/3");
await page.goto(`${origin}/w/home-owner/drive/3`);
await ready(page);
await page.getByRole("alert").filter({ hasText: /not[_ ]found/i })
.waitFor();
} finally {
await page.close();
}
}));
Deno.test("production Drive upload response loss and navigation cancellation query receipts without resend", () =>
harness(async (origin, browser) => {
const context = await browser.newContext();
const page = await context.newPage();
try {
await page.goto(`${origin}/w/home-owner/drive`);
await ready(page);
await page.route("**/drive/upload?**", async (route) => {
await route.fetch();
await route.abort("failed");
});
await button(page, "Upload").click();
await page.getByLabel("File (maximum 16 MiB)").setInputFiles({
name: "lost-response.txt",
mimeType: "text/plain",
buffer: Buffer.from("Published despite response loss"),
});
await button(page, "Upload file").click();
await button(page, "Check request result").waitFor();
assertEquals(
(await logs(origin)).filter((x) => x.path === "/upload").length,
1,
);
await button(page, "Check request result").click();
await page.getByText("Published — DB commit confirmed", { exact: false })
.last().waitFor();
await page.unroute("**/drive/upload?**");
await button(page, "Refresh").click();
await ready(page);
await page.getByRole("link", { name: "lost-response.txt" }).waitFor();
const transmitted = gate(), release = gate();
let handled = Promise.resolve();
await page.route("**/drive/upload?**", (route) => {
handled = (async () => {
const response = await route.fetch();
transmitted.release();
await release.promise;
await safeFulfill(route, response);
})();
return handled;
});
await button(page, "Upload").click();
await page.getByLabel("File (maximum 16 MiB)").setInputFiles({
name: "canceled-response.txt",
mimeType: "text/plain",
buffer: Buffer.from("Transmitted before navigation abort"),
});
await button(page, "Upload file").click();
await transmitted.promise;
await button(page, "Cancel transfer / check outcome").click();
await button(page, "Check request result").waitFor();
await button(page, "Cancel form").click();
await page.getByRole("link", { name: "資料", exact: true }).click();
await page.getByRole("heading", { name: "資料", exact: true }).waitFor();
await ready(page);
release.release();
await handled;
await rootFolder(page);
await button(page, "Check request result").waitFor();
assertEquals(
(await logs(origin)).filter((x) => x.path === "/upload").length,
2,
);
await button(page, "Check request result").click();
await page.getByText("Published — DB commit confirmed", { exact: false })
.last().waitFor();
assertEquals(
(await logs(origin)).filter((x) => x.path === "/upload").length,
2,
);
} catch (error) {
console.log("UPLOAD FAILURE UI", await page.locator("main").innerText());
console.log("UPLOAD FAILURE REQUESTS", await logs(origin));
await page.screenshot({
path: join(root, "web/workspace/.svelte-kit/t724/upload-failure.png"),
fullPage: true,
});
throw error;
} finally {
await context.close();
}
}));
Deno.test("production Drive workspace switch fences a delayed previous workspace read", () =>
harness(async (origin, browser) => {
const context = await browser.newContext();
const page = await context.newPage();
const sent = gate(), release = gate();
let handled = Promise.resolve();
await page.route("**/home-owner/drive/read-text?**", (route) => {
handled = (async () => {
const response = await route.fetch();
sent.release();
await release.promise;
await safeFulfill(route, response);
})();
return handled;
});
try {
await page.goto(`${origin}/w/home-owner/drive/3`);
await sent.promise;
await page.getByRole("button", {
name: "Workspace Home Review",
exact: true,
}).first()
.click();
await page.getByRole("menuitem", {
name: "Shared Workspace",
exact: true,
}).click();
await page.waitForURL("**/w/home-member");
await page.locator("aside").getByRole("link", {
name: "Drive",
exact: true,
}).click();
await ready(page);
await page.getByRole("link", { name: "資料", exact: true }).click();
await ready(page);
await page.getByRole("link", { name: "notes.txt", exact: true }).click();
await ready(page);
await editor(page, "Member-only current draft");
release.release();
await handled;
assertEquals(new URL(page.url()).pathname, "/w/home-member/drive/4");
assertEquals(await draft(page).inputValue(), "Member-only current draft");
assert((await preview(page).textContent())!.includes("Second document"));
assertEquals(
(await logs(origin)).filter((x) => x.path === "/mutate").length,
0,
);
} finally {
release.release();
await handled;
await context.close();
}
}));
Deno.test("production Drive denial empty pages truncation unsafe content and image failure remain distinct", () =>
harness(async (origin, browser) => {
const page = await browser.newPage();
const external: string[] = [];
page.on("request", (request) => {
if (new URL(request.url()).hostname === "example.invalid") {
external.push(request.url());
}
});
try {
await page.goto(`${origin}/w/home-member/drive/3`);
await editor(page, "Denied member draft");
await button(page, "Save").click();
await page.getByText(
"Read only / access denied by Backend. Editing and upload are unavailable.",
).waitFor();
assertEquals(await draft(page).inputValue(), "Denied member draft");
assertEquals(await button(page, "Save").count(), 0);
assertEquals(await draft(page).getAttribute("readonly"), "");
for (
const [workspace, text] of [["drive-denied", "denied"], [
"drive-offline",
"unavailable",
]]
) {
await page.goto(`${origin}/w/${workspace}/drive`);
await ready(page);
await page.getByRole("alert").filter({ hasText: new RegExp(text, "i") })
.waitFor();
assertEquals(
await page.getByText("This folder is empty.", { exact: true })
.count(),
0,
);
}
await page.goto(`${origin}/w/drive-empty/drive`);
await ready(page);
await page.getByText("This folder is empty.", { exact: true }).waitFor();
await page.goto(`${origin}/w/drive-paged/drive`);
await ready(page);
const firstCount = await page.locator(".drive-table tbody tr").count();
await button(page, "Load more").click();
await ready(page);
assert(
(await page.locator(".drive-table tbody tr").count()) > firstCount,
);
await page.goto(`${origin}/w/home-owner/drive/9`);
await ready(page);
await page.getByText("Preview truncated at 64 KiB.", { exact: false })
.waitFor();
assertEquals(await button(page, "Edit text").count(), 0);
await page.goto(`${origin}/w/home-owner/drive/6`);
await ready(page);
await page.getByText("Empty file.", { exact: true }).waitFor();
await page.goto(`${origin}/w/home-owner/drive/3`);
await ready(page);
assertEquals(
await page.locator('main a[href^="javascript:"]').count(),
0,
);
assertEquals(await page.locator("main img[onerror]").count(), 0);
assertEquals(external, []);
const local = page.getByRole("link", {
name: "Local folder",
exact: true,
});
assertEquals(await local.getAttribute("href"), "/w/home-owner/drive/2");
await local.click();
await page.waitForURL("**/w/home-owner/drive/2");
await page.getByRole("heading", { name: "資料", exact: true }).waitFor();
await ready(page);
assertEquals(new URL(page.url()).pathname, "/w/home-owner/drive/2");
await page.goto(`${origin}/w/home-owner/drive/8`);
await ready(page);
await page.getByText("This file type is download-only.", { exact: false })
.waitFor();
assertEquals(
await page.locator(
"main svg:not([aria-hidden]), main iframe, main object",
).count(),
0,
);
await page.goto(`${origin}/w/home-owner/drive/5`);
await ready(page);
await page.waitForFunction(() => {
const image = document.querySelector("main .drive-image");
return image instanceof HTMLImageElement && image.complete &&
image.naturalWidth === 1200 &&
image.naturalHeight === 800;
});
assertEquals(
await page.getByRole("img", { name: "landscape.png", exact: true })
.evaluate((image) =>
image instanceof HTMLImageElement
? [image.naturalWidth, image.naturalHeight]
: []
),
[1200, 800],
);
await page.goto(`${origin}/w/home-owner/drive/7`);
await ready(page);
await page.getByRole("alert").filter({
hasText: "Image could not be decoded",
}).waitFor();
await fault(origin, "home-owner", "image_error");
await page.goto(`${origin}/w/home-owner/drive/5`);
await ready(page);
await page.getByRole("alert").filter({ hasText: /unavailable/i })
.waitFor();
} finally {
await page.close();
}
}));
Deno.test("production Workers owner grants use the authoritative catalog while members have no grant controls", () =>
harness(async (origin, browser) => {
const page = await browser.newPage();
const errors: string[] = [];
page.on("pageerror", (error) => errors.push(String(error)));
try {
await page.goto(`${origin}/w/home-owner/workers`);
await page.getByRole("combobox", { name: "Worker", exact: true })
.selectOption({
label: "Documentation Worker · fixture-runtime / fixture-worker-a",
});
await button(page, "Add Drive grant").waitFor();
await button(page, "Add Drive grant").click();
await button(page, "Create Drive grant").click();
const grants = page.getByRole("region", { name: "Worker Drive grants" });
await grants.getByText("Read only", { exact: true }).waitFor();
await page.getByRole("button", { name: /^Revoke Drive grant/ }).click();
await grants.getByText("No active Drive grants for this Worker.", {
exact: true,
}).waitFor();
await button(page, "Add Drive grant").click();
await page.getByRole("combobox", { name: "Access", exact: true })
.selectOption("read_write");
await button(page, "Create Drive grant").click();
await grants.getByText("Read and write", { exact: true }).waitFor();
await page.getByRole("button", { name: /^Revoke Drive grant/ }).click();
await grants.getByText("No active Drive grants for this Worker.", {
exact: true,
}).waitFor();
const mutations = (await logs(origin)).filter((event) =>
event.path.startsWith("/grants") && event.method !== "GET"
);
assertEquals(
mutations.map((event) => [event.workspace, event.method, event.path]),
[
["home-owner", "POST", "/grants"],
["home-owner", "DELETE", "/grants/1"],
["home-owner", "POST", "/grants"],
["home-owner", "DELETE", "/grants/2"],
],
);
await page.goto(`${origin}/w/home-member/workers`);
await page.locator(".workers-table tbody tr").first().waitFor();
assertEquals(
await page.getByRole("region", { name: "Worker Drive grants" }).count(),
0,
);
assertEquals(await button(page, "Add Drive grant").count(), 0);
assertEquals(errors, []);
} finally {
await page.close();
}
}));
Deno.test("production Drive Markdown safe image placeholders preserve each image alt label", () =>
harness(async (origin, browser) => {
const page = await browser.newPage();
try {
await page.goto(`${origin}/w/home-owner/drive/3`);
await ready(page);
const labels = await page.locator(".drive-inline-image")
.allTextContents();
assert(
labels.some((label) => label.includes("Image: External")),
`External image alt label lost: ${JSON.stringify(labels)}`,
);
assert(
labels.some((label) => label.includes("Image: Image")),
`Local image alt label lost: ${JSON.stringify(labels)}`,
);
assertEquals(await preview(page).locator("img").count(), 0);
} finally {
await page.close();
}
}));
Deno.test("production Drive menus dialogs folder picker and search remain usable at every viewport", () =>
harness(async (origin, browser) => {
for (const colorScheme of ["light", "dark"] as const) {
for (const width of [1440, 768, 390, 320]) {
const context = await browser.newContext({
viewport: { width, height: 900 },
colorScheme,
reducedMotion: "reduce",
});
const page = await context.newPage();
const errors: string[] = [];
page.on("pageerror", (error) => errors.push(String(error)));
const capture = async (surface: string) => {
const geometry = await page.evaluate(() => {
const main = document.querySelector("main")!;
const dialog = document.querySelector("dialog[open]");
return {
viewport: innerWidth,
document: document.documentElement.scrollWidth,
main: main.clientWidth,
mainScroll: main.scrollWidth,
dialog: dialog?.clientWidth,
dialogScroll: dialog?.scrollWidth,
};
});
assert(geometry.document <= width, JSON.stringify(geometry));
assert(
geometry.mainScroll <= geometry.main + 1,
JSON.stringify(geometry),
);
if (geometry.dialog) {
assert(
geometry.dialogScroll! <= geometry.dialog + 1,
JSON.stringify(geometry),
);
}
await page.screenshot({
path: join(
root,
`web/workspace/.svelte-kit/drive-redesign/${surface}-${colorScheme}-${width}.png`,
),
});
};
try {
const reset = await fetch(`${origin}/__fixture/reset`, {
method: "POST",
});
await reset.body?.cancel();
await page.goto(`${origin}/w/home-owner/drive`);
await ready(page);
const newMenu = page.locator(".drive-menu > summary").filter({
hasText: /^New$/,
});
assertEquals(await button(page, "New folder").isVisible(), false);
await capture("files");
await newMenu.press("Enter");
await button(page, "New folder").waitFor();
const menuBox = await page.locator(
".drive-create-menu .drive-menu-panel",
).boundingBox();
assert(
menuBox && menuBox.x >= 0 && menuBox.x + menuBox.width <= width,
JSON.stringify(menuBox),
);
await capture("menu");
await button(page, "New folder").click();
const dialog = page.getByRole("dialog", {
name: "New folder",
exact: true,
});
await dialog.waitFor();
assert(
await dialog.getByLabel("Name", { exact: true }).evaluate((el) =>
el === document.activeElement
),
);
await capture("create");
for (let i = 0; i < 8; i++) {
await page.keyboard.press("Tab");
// Native dialogs may cycle through browser chrome (reported as body),
// but background page controls must stay inert.
assert(
await dialog.evaluate((el) =>
el.contains(document.activeElement) ||
document.activeElement === document.body
),
"Modal must not focus background controls",
);
}
await page.keyboard.press("Escape");
await dialog.waitFor({ state: "hidden" });
await newMenu.locator(":scope:focus").waitFor();
await newMenu.press("Enter");
await button(page, "New folder").focus();
await page.keyboard.press("Escape");
assertEquals(await button(page, "New folder").isVisible(), false);
await newMenu.locator(":scope:focus").waitFor();
await newMenu.press("Enter");
await button(page, "New folder").click();
const folderName = `Created ${colorScheme} ${width}`;
await dialog.getByLabel("Name", { exact: true }).fill(folderName);
await button(page, "Create").click();
await dialog.waitFor({ state: "hidden" });
await page.getByRole("link", { name: folderName, exact: true })
.waitFor();
await page.getByRole("searchbox", { name: "Search Drive" }).fill(
folderName,
);
await button(page, "Search").click();
await button(page, "Show folder").waitFor();
await ready(page);
assertEquals(await page.locator(".drive-table tbody tr").count(), 1);
await button(page, "Show folder").click();
await ready(page);
await page.getByRole("link", { name: "README.md", exact: true })
.click();
await page.waitForURL("**/w/home-owner/drive/3");
await ready(page);
await preview(page).waitFor();
await capture("preview");
const more = page.getByLabel("More actions", { exact: true });
await more.click();
await button(page, "Rename / move").click();
const move = page.getByRole("dialog", {
name: "Rename / move",
exact: true,
});
await move.getByRole("button", { name: "資料", exact: true }).click();
await page.locator(".drive-folder-location strong").filter({
hasText: "資料",
}).waitFor();
await capture("move");
await button(page, "Parent folder").click();
await move.getByRole("button", { name: "資料", exact: true })
.waitFor();
assertEquals(
await move.getByRole("button", { name: "README.md", exact: true })
.count(),
0,
);
await page.keyboard.press("Escape");
await move.waitFor({ state: "hidden" });
await more.locator(":scope:focus").waitFor();
assertEquals(errors, []);
} finally {
await context.close();
}
}
}
}));