Files
workinf_Blender_Wasm/web/tests/e2e/paint-stroke-session.spec.ts
mes123456 0fe8d2bb56
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled
Advance M8-M11 parity workflows
2026-08-17 04:37:07 -04:00

77 lines
4.7 KiB
TypeScript

import { expect, test } from "@playwright/test";
import fs from "node:fs";
import path from "node:path";
const attributeBlend = path.resolve(import.meta.dirname, "../../../tests/files/web/attribute_scene.blend");
test("M9-10 commits pointer chunks as one Main revision and one undo step", async ({ page }) => {
test.setTimeout(120_000);
await page.goto("/");
const blendBytes = fs.readFileSync(attributeBlend);
const result = await page.evaluate(async ({ blendBytes }) => {
const { WebEngineClient } = await import("/src/engine-client/WebEngineClient.ts");
const engine = new WebEngineClient({ timeoutMs: 60_000 });
await engine.init();
const opened = await engine.openBlend(blendBytes.buffer.slice(blendBytes.byteOffset, blendBytes.byteOffset + blendBytes.byteLength));
const mesh = opened.snapshot.meshes.find((candidate) => candidate.id === "mesh:AttributeMesh");
if (!mesh || mesh.vertexCount < 4) throw new Error("paint fixture is missing its vertex domain");
const pointerSessionId = "paint-pointer:chromium-main-1";
const session = {
schemaVersion: 1 as const,
pointerSessionId,
baseRevision: opened.snapshot.revision,
target: { mode: "VERTEX_COLOR" as const, meshId: mesh.id, attributeName: "M9StrokeColor", domain: "POINT" as const },
};
const started = await engine.beginPaintStroke(session);
const first = await engine.appendPaintStrokeChunk({ schemaVersion: 1, pointerSessionId, baseRevision: session.baseRevision, chunkIndex: 0, indices: [0, 1], values: [1, 0, 0, 1, 0, 1, 0, 1] });
const during = await engine.snapshot();
const second = await engine.appendPaintStrokeChunk({ schemaVersion: 1, pointerSessionId, baseRevision: session.baseRevision, chunkIndex: 1, indices: [1, 2, 3], values: [0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1] });
const committed = await engine.commitPaintStroke({ schemaVersion: 1, pointerSessionId, baseRevision: session.baseRevision, expectedChunkCount: 2 });
const committedMesh = committed.snapshot.meshes.find((candidate) => candidate.id === mesh.id);
const undone = await engine.applyCommand({ type: "undo" });
const undoneMesh = undone.snapshot.meshes.find((candidate) => candidate.id === mesh.id);
let secondUndoCode = "";
try { await engine.applyCommand({ type: "undo" }); }
catch (error) { secondUndoCode = (error as { code?: string }).code ?? String(error); }
const redone = await engine.applyCommand({ type: "redo" });
const redoneMesh = redone.snapshot.meshes.find((candidate) => candidate.id === mesh.id);
const cancelId = "paint-pointer:chromium-cancel-2";
const cancelBase = redone.snapshot.revision;
await engine.beginPaintStroke({ ...session, pointerSessionId: cancelId, baseRevision: cancelBase });
await engine.appendPaintStrokeChunk({ schemaVersion: 1, pointerSessionId: cancelId, baseRevision: cancelBase, chunkIndex: 0, indices: [0], values: [0, 0, 0, 1] });
const cancelled = await engine.cancelPaintStroke({ schemaVersion: 1, pointerSessionId: cancelId, baseRevision: cancelBase });
const afterCancel = await engine.snapshot();
engine.terminate();
const hasAttribute = (candidate: typeof mesh | undefined) => candidate?.attributes?.some((attribute) => attribute.name === "M9StrokeColor" && attribute.domain === "POINT") ?? false;
return {
started,
first,
second,
duringRevision: during.snapshot.revision,
committedRevision: committed.snapshot.revision,
committedReceipt: committed.paintStrokeSession,
committedAttribute: hasAttribute(committedMesh),
undoneAttribute: hasAttribute(undoneMesh),
redoneAttribute: hasAttribute(redoneMesh),
secondUndoCode,
cancelled,
cancelBase,
afterCancelRevision: afterCancel.snapshot.revision,
};
}, { blendBytes: new Uint8Array(blendBytes) });
expect(result.started).toMatchObject({ state: "OPEN", chunkCount: 0 });
expect(result.first).toMatchObject({ state: "OPEN", chunkCount: 1, receivedEntryCount: 2, uniqueEntryCount: 2 });
expect(result.second).toMatchObject({ state: "OPEN", chunkCount: 2, receivedEntryCount: 5, uniqueEntryCount: 4 });
expect(result.duringRevision).toBe(result.started.baseRevision);
expect(result.committedRevision).toBe(result.started.baseRevision + 1);
expect(result.committedReceipt).toMatchObject({ state: "COMMITTED", chunkCount: 2, receivedEntryCount: 5, uniqueEntryCount: 4, committedRevision: result.committedRevision });
expect(result.committedAttribute).toBe(true);
expect(result.undoneAttribute).toBe(false);
expect(result.redoneAttribute).toBe(true);
expect(result.secondUndoCode).toBe("INVALID_ARGUMENT");
expect(result.cancelled.state).toBe("CANCELLED");
expect(result.afterCancelRevision).toBe(result.cancelBase);
});