Advance Blender 5.2 web parity through M12-03D
This commit is contained in:
139
web/tests/e2e/library-append-main.spec.ts
Normal file
139
web/tests/e2e/library-append-main.spec.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import crypto from "node:crypto";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { computeLibraryAppendClosureSha256, LIBRARY_MAIN_APPEND_SCHEMA } from "../../protocol/library-main-append";
|
||||
import { createLibraryOperationBinding, createLibrarySourceIdentity, LIBRARY_OPERATION_IDENTITY_SCHEMA } from "../../protocol/library-operation-identity";
|
||||
|
||||
const root = path.resolve(import.meta.dirname, "../../..");
|
||||
const source = fs.readFileSync(path.join(root, "tests/files/web/m12_library_append_v1/m12_append_source.blend"));
|
||||
const target = fs.readFileSync(path.join(root, "tests/files/web/empty.blend"));
|
||||
|
||||
test("M12-03D appends one fully-local dependency closure through WASM Main", async ({ page }) => {
|
||||
test.setTimeout(120_000);
|
||||
await page.goto("/");
|
||||
const closure = {
|
||||
object: "Object/M12 Append Object",
|
||||
mesh: "Mesh/M12 Append Mesh",
|
||||
material: "Material/M12 Append Material",
|
||||
image: "Image/M12 Append Image",
|
||||
} as const;
|
||||
const sourceSha256 = crypto.createHash("sha256").update(source).digest("hex");
|
||||
const sourceIdentity = await createLibrarySourceIdentity({
|
||||
sourceLocator: "project-assets/libraries/m12_append_source.blend",
|
||||
sourceSha256,
|
||||
});
|
||||
const dependencyClosureSha256 = await computeLibraryAppendClosureSha256(closure);
|
||||
const binding = await createLibraryOperationBinding({
|
||||
schemaVersion: LIBRARY_OPERATION_IDENTITY_SCHEMA,
|
||||
operation: "APPEND",
|
||||
source: sourceIdentity,
|
||||
sourceDataBlockId: closure.object,
|
||||
owner: { kind: "LOCAL_MAIN", projectId: "project:m12-03d", localDataBlockId: closure.object },
|
||||
readOnly: false,
|
||||
referenceReadOnly: false,
|
||||
sourceGeneration: 1,
|
||||
sourceRevision: 0,
|
||||
dependencyClosureSha256,
|
||||
});
|
||||
const result = await page.evaluate(async ({ sourceBytes, targetBytes, closure, binding }) => {
|
||||
const { WebEngineClient } = await import("/src/engine-client/WebEngineClient.ts");
|
||||
const sourceBuffer = Uint8Array.from(sourceBytes).buffer;
|
||||
const targetBuffer = Uint8Array.from(targetBytes).buffer;
|
||||
const digest = async (bytes: ArrayBuffer): Promise<string> => {
|
||||
const hash = await crypto.subtle.digest("SHA-256", bytes);
|
||||
return Array.from(new Uint8Array(hash), (value) => value.toString(16).padStart(2, "0")).join("");
|
||||
};
|
||||
const makeRequest = (baseRevision: number) => ({
|
||||
schemaVersion: 1 as const,
|
||||
baseRevision,
|
||||
binding,
|
||||
expectedClosure: closure,
|
||||
});
|
||||
const client = new WebEngineClient({ timeoutMs: 60_000 });
|
||||
const reopened = new WebEngineClient({ timeoutMs: 60_000 });
|
||||
const ids = {
|
||||
object: "object:M12 Append Object",
|
||||
mesh: "mesh:M12 Append Mesh",
|
||||
material: "material:M12 Append Material",
|
||||
image: "image:M12 Append Image",
|
||||
};
|
||||
const closureState = (snapshot: any) => ({
|
||||
object: snapshot.nodes.find((item: any) => item.id === ids.object),
|
||||
mesh: snapshot.meshes.find((item: any) => item.id === ids.mesh),
|
||||
material: snapshot.materials.find((item: any) => item.id === ids.material),
|
||||
image: snapshot.images.find((item: any) => item.id === ids.image),
|
||||
});
|
||||
try {
|
||||
const opened = await client.openBlend(targetBuffer.slice(0));
|
||||
const baseRevision = opened.snapshot.revision;
|
||||
const request = await makeRequest(baseRevision);
|
||||
const appended = await client.appendLibraryObject(sourceBuffer.slice(0), request);
|
||||
const appendedClosure = closureState(appended.snapshot);
|
||||
const stale = await client.appendLibraryObject(sourceBuffer.slice(0), makeRequest(baseRevision))
|
||||
.then(() => ({ code: "NO_ERROR" }))
|
||||
.catch((error: any) => ({ code: error.code, message: error.message }));
|
||||
const collisionRequest = await makeRequest(appended.snapshot.revision);
|
||||
const collision = await client.appendLibraryObject(sourceBuffer.slice(0), collisionRequest)
|
||||
.then(() => ({ code: "NO_ERROR" }))
|
||||
.catch((error: any) => ({ code: error.code, message: error.message }));
|
||||
const afterCollision = await client.snapshot();
|
||||
const undone = await client.applyCommand({ type: "undo" });
|
||||
const redone = await client.applyCommand({ type: "redo" });
|
||||
const saved = await client.saveBlend();
|
||||
const reopenedResult = await reopened.openBlend(saved);
|
||||
const reopenedClosure = closureState(reopenedResult.snapshot);
|
||||
return {
|
||||
sourceSha256: await digest(sourceBuffer),
|
||||
baseRevision,
|
||||
appendedRevision: appended.snapshot.revision,
|
||||
receipt: appended.receipt,
|
||||
delta: appended.delta,
|
||||
appendedClosure: {
|
||||
object: appendedClosure.object && { type: appendedClosure.object.type, dataId: appendedClosure.object.dataId },
|
||||
mesh: appendedClosure.mesh && { vertexCount: appendedClosure.mesh.vertexCount, materialSlotIds: appendedClosure.mesh.materialSlotIds },
|
||||
material: appendedClosure.material && {
|
||||
imageIds: appendedClosure.material.imageIds ?? appendedClosure.material.nodes?.flatMap((node: any) => node.imageId ? [node.imageId] : []),
|
||||
},
|
||||
image: appendedClosure.image && { libraryLinked: appendedClosure.image.libraryLinked, width: appendedClosure.image.width, height: appendedClosure.image.height },
|
||||
},
|
||||
stale,
|
||||
collision,
|
||||
collisionRevision: afterCollision.snapshot.revision,
|
||||
undoHasObject: Boolean(closureState(undone.snapshot).object),
|
||||
redoHasObject: Boolean(closureState(redone.snapshot).object),
|
||||
reopenedRevision: reopenedResult.snapshot.revision,
|
||||
reopenedHasObject: Boolean(reopenedClosure.object),
|
||||
reopenedHasLocalImage: reopenedClosure.image?.libraryLinked === false,
|
||||
};
|
||||
}
|
||||
catch (error: any) {
|
||||
return { error: { code: error?.code, message: error?.message, detail: error?.detail } };
|
||||
}
|
||||
finally {
|
||||
client.terminate();
|
||||
reopened.terminate();
|
||||
}
|
||||
}, { sourceBytes: Array.from(source), targetBytes: Array.from(target), closure, binding });
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.sourceSha256).toMatch(/^[a-f0-9]{64}$/);
|
||||
expect(result.appendedRevision).toBe(result.baseRevision + 1);
|
||||
expect(result.receipt).toMatchObject({ operation: "APPEND", transactionCount: 1, baseRevision: result.baseRevision, nextRevision: result.appendedRevision });
|
||||
expect(result.receipt.mapping).toHaveLength(4);
|
||||
expect(result.receipt.mapping.every((item: any) => item.owner === "LOCAL_MAIN" && item.readOnly === false && item.source === item.local)).toBe(true);
|
||||
expect(result.delta).toMatchObject({ schemaVersion: 1, baseRevision: result.baseRevision, nextRevision: result.appendedRevision });
|
||||
expect(result.appendedClosure).toEqual({
|
||||
object: { type: "MESH", dataId: "mesh:M12 Append Mesh" },
|
||||
mesh: { vertexCount: 4, materialSlotIds: ["material:M12 Append Material"] },
|
||||
material: { imageIds: ["image:M12 Append Image"] },
|
||||
image: { libraryLinked: false, width: 2, height: 2 },
|
||||
});
|
||||
expect(result.stale.code).toBe("REVISION_CONFLICT");
|
||||
expect(result.collision.code).toBe("ASSET_MANIFEST_INVALID");
|
||||
expect(result.collisionRevision).toBe(result.appendedRevision);
|
||||
expect(result.undoHasObject).toBe(false);
|
||||
expect(result.redoHasObject).toBe(true);
|
||||
expect(result.reopenedHasObject).toBe(true);
|
||||
expect(result.reopenedHasLocalImage).toBe(true);
|
||||
});
|
||||
Reference in New Issue
Block a user