Improve Android app UI and controller flows

This commit is contained in:
2026-10-09 16:43:58 +08:00
parent c0e26b97fe
commit 608229a497
90 changed files with 8103 additions and 1133 deletions
@@ -0,0 +1,155 @@
// Run against an isolated mock data root and a fresh Playwright browser context.
// The caller supplies its existing page; this script installs no browser tools.
import assert from "node:assert/strict";
export async function checkLayersBrowser(page, baseUrl) {
const errors = [], dialogs = [], policy = { value: "dismiss" };
const onError = error => errors.push(error.message);
const onDialog = async dialog => {
dialogs.push(dialog.message());
try {
if (policy.value === "dismiss") await dialog.dismiss();
else await dialog.accept(policy.value === "accept" ? undefined : policy.value);
} catch (error) { errors.push(error.message); }
};
const click = id => page.locator(`#${id}`).click();
const draft = async () => JSON.parse(await page.evaluate(() => localStorage.getItem("matrixController:scene")));
const ready = () => page.locator('#board-status[data-state="ready"]').waitFor();
page.on("pageerror", onError);
page.on("dialog", onDialog);
try {
await page.goto(`${baseUrl}/#canvas`);
await ready();
const backgroundBox = await page.locator("#layer-background-enabled").boundingBox();
assert.equal(backgroundBox.width, 20);
assert.equal(backgroundBox.height, 20);
const paintBox = await page.locator("#paint-color").boundingBox();
const colorBox = await page.locator("#background-color").boundingBox();
if (page.viewportSize().width >= 900) assert.ok(Math.abs(paintBox.y - colorBox.y) < 1);
assert.equal(await page.locator("#layer-list button").count(), 1);
assert.equal(await page.locator("#delete-layer").getAttribute("aria-disabled"), "true");
await click("new-text-layer");
await click("create-text");
await click("create-text");
await ready();
assert.equal(await page.locator("#text-element-list button").count(), 2);
await click("new-text-layer");
assert.equal(await page.locator("#text-element-list button").count(), 0);
await click("delete-layer");
assert.equal(await page.locator("#text-element-list button").count(), 2);
policy.value = "浏览器图层验收";
await click("rename-layer");
assert.match(await page.locator("#layer-list button").first().innerText(), /浏览器图层验收/);
await click("lower-layer");
await click("raise-layer");
await click("copy-layer");
assert.match(await page.locator("#layer-copy-dialog").evaluate(el => getComputedStyle(el).backgroundColor), /^rgb\(/);
await page.locator('#layer-copy-submit:not([aria-disabled])').waitFor();
await click("layer-copy-submit");
assert.equal(await page.locator("#layer-list button").count(), 3);
await click("delete-layer");
await page.locator("#text-element-list button").first().click();
await ready();
const before = await draft();
await page.locator("#text-x").fill("127");
assert.deepEqual(await draft(), before);
policy.value = "dismiss";
await page.locator("#text-x").press("Enter");
assert.equal(await page.locator("#text-x").inputValue(), "32");
policy.value = "accept";
await page.locator("#text-x").fill("127");
await page.locator("#text-x").press("Enter");
await ready();
const clip = (await draft()).layers[1].elements[0].clip;
assert.ok(clip);
await page.locator("#text-x").fill("32");
await page.locator("#text-x").press("Enter");
await ready();
assert.deepEqual((await draft()).layers[1].elements[0].clip, clip);
await page.locator("#text-y").fill("128");
await page.locator("#text-y").press("Enter");
assert.equal(await page.locator("#text-element-list button").count(), 1);
await click("start-canvas-edit");
await click("canvas-assistive-toggle");
await click("undo-canvas-edit");
assert.equal(await page.locator("#text-element-list button").count(), 2);
await click("exit-canvas-edit");
await click("new-brush-layer");
await page.locator("#layer-background-enabled").check();
await click("clear-canvas");
assert.equal(await page.locator("#layer-background-enabled").isChecked(), true);
await click("start-canvas-edit");
await click("canvas-assistive-toggle");
assert.equal(await page.locator("#toggle-expanded-board").getAttribute("aria-pressed"), "false");
await click("toggle-expanded-board");
await click("canvas-assistive-toggle");
const rect = await page.locator("#scene-overlay").boundingBox();
await page.mouse.click(rect.x + rect.width * 190 / 192, rect.y + rect.height * 80 / 192);
await click("canvas-assistive-toggle");
await click("move-current-layer");
const step = page.locator('.offset-steps[data-axis="x"] [data-offset="10"]');
policy.value = "dismiss";
const uncommitted = await draft();
await step.click();
assert.deepEqual(await draft(), uncommitted);
policy.value = "accept";
await step.click();
assert.equal(await page.locator("#layer-offset-x").inputValue(), "10");
await click("undo-canvas-edit");
assert.deepEqual(await draft(), uncommitted);
await click("move-canvas-view");
await click("canvas-assistive-toggle");
assert.equal(await page.locator("#layer-move-controls").isVisible(), true);
await click("canvas-assistive-toggle");
const viewBefore = await draft();
await page.mouse.move(rect.x + rect.width / 2, rect.y + rect.height / 2);
await page.mouse.down();
await page.mouse.move(rect.x + rect.width / 2 + 20, rect.y + rect.height / 2 + 12);
await page.mouse.up();
await page.mouse.wheel(0, -80);
assert.deepEqual(await draft(), viewBefore);
await click("canvas-assistive-toggle");
const viewStyle = await page.locator("#board-stage").getAttribute("style");
const undoBefore = await page.locator("#undo-canvas-edit").getAttribute("aria-disabled");
await click("exit-layer-move");
assert.equal(await page.locator("body").getAttribute("data-canvas-editor-mode"), "draw");
assert.equal(await page.locator("body").evaluate(el => el.classList.contains("canvas-edit-active")), true);
assert.equal(await page.locator("#layer-move-controls").getAttribute("hidden"), "");
assert.equal(await page.locator("#board-stage").getAttribute("style"), viewStyle);
assert.deepEqual(await draft(), viewBefore);
assert.equal(await page.locator("#undo-canvas-edit").getAttribute("aria-disabled"), undoBefore);
await click("canvas-assistive-toggle");
await click("exit-canvas-edit");
const saved = await draft();
await page.reload();
await ready();
assert.deepEqual(await draft(), saved);
await click("start-canvas-edit");
await click("canvas-assistive-toggle");
assert.equal(await page.locator("#toggle-expanded-board").getAttribute("aria-pressed"), "true");
await click("toggle-expanded-board");
assert.deepEqual(await draft(), saved);
await click("exit-canvas-edit");
await page.locator("#layer-list button").filter({ hasText: "浏览器图层验收" }).click();
await ready();
await click("start-canvas-edit");
await click("canvas-assistive-toggle");
await click("move-current-layer");
assert.equal(await page.locator("#continue-canvas-edit").getAttribute("aria-disabled"), null);
const textBeforeMove = await draft();
await page.locator('.offset-steps[data-axis="y"] [data-offset="1"]').click();
const textMoved = await draft();
await click("exit-layer-move");
assert.deepEqual(await draft(), textMoved);
assert.match(await page.locator("#canvas-edit-mode-hint").innerText(), /文字编辑模式/);
await click("canvas-assistive-toggle");
await click("undo-canvas-edit");
assert.deepEqual(await draft(), textBeforeMove);
await click("exit-canvas-edit");
assert.deepEqual(errors, []);
return { passed: true, nativeConfirmations: dialogs.length, layers: saved.layers.length, errors };
} finally {
page.off("pageerror", onError);
page.off("dialog", onDialog);
}
}