77 lines
4.7 KiB
TypeScript
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);
|
|
});
|