Advance bounded editor and cache workflows

This commit is contained in:
mes123456
2026-08-12 19:31:41 -04:00
parent d54d5dd913
commit 3da1dfc804
19 changed files with 563 additions and 50 deletions

View File

@@ -183,6 +183,26 @@ test("serializes concurrent saves for one project without journal races", async
expect(result).toEqual({ revision: 9, bytes: [9, 9, 9, 9], recovered: false });
});
test("deduplicates identical project revisions across independent StorageWorkers", async ({ page }) => {
await page.goto("/");
const result = await page.evaluate(async () => {
const { StorageClient } = await import("/src/storage/StorageClient.ts");
const first = new StorageClient();
const second = new StorageClient();
const projectId = `save-cross-worker-e2e-${Date.now()}`;
const bytes = Uint8Array.from([0x42, 0x4c, 0x45, 0x4e, 0x44, 7]).buffer;
const saves = await Promise.all([
first.saveProject(projectId, 7, bytes.slice(0)),
second.saveProject(projectId, 7, bytes.slice(0)),
]);
const restored = await second.readProject(projectId);
first.terminate();
second.terminate();
return { revisions: saves.map((save) => save.revision), bytes: Array.from(new Uint8Array(restored.buffer)), recovered: restored.recovered };
});
expect(result).toEqual({ revisions: [7, 7], bytes: [0x42, 0x4c, 0x45, 0x4e, 0x44, 7], recovered: false });
});
test("content-addresses assets, deduplicates them, and rediscovers them after worker restart", async ({ page }) => {
await page.goto("/");
const result = await page.evaluate(async () => {
@@ -494,6 +514,8 @@ test("validates the N-016 Grease Pencil editor context transaction boundary", as
expect(result.stale).toContain("REVISION_CONFLICT");
expect(result.duplicate).toContain("GREASE_PENCIL_EDITOR_INVALID");
expect(result.negative).toContain("GREASE_PENCIL_EDITOR_INVALID");
expect(result.translation).toEqual([1, [0, 0, 0], { position: [1.5, 1, 5], radius: 0.2, opacity: 0.8, vertexColor: [1, 0, 0, 1] }]);
expect(result.missingPoint).toContain("GREASE_PENCIL_EDITOR_INVALID");
});
test("edits N-016 Grease Pencil layers and frames from the bounded editor panel", async ({ page }) => {
@@ -511,11 +533,22 @@ test("edits N-016 Grease Pencil layers and frames from the bounded editor panel"
await expect(editor).toContainText("2 layers / 2 frames / 1 strokes");
});
test("enforces the N-017 paint stroke, PBVH/UV hit and brush budgets", async ({ page }) => {
test("moves an N-016 Grease Pencil point through one revision-bound Main transaction", async ({ page }) => {
await page.goto("/");
const result = await page.evaluate(() => new Promise<Record<string, string>>((resolve, reject) => {
await page.setInputFiles("[data-testid=blend-file-input]", greasePencilBlend);
await page.getByText("GreasePencilObject", { exact: true }).click();
const editor = page.getByTestId("grease-pencil-editor");
await expect(editor.getByTestId("grease-pencil-point-position")).toContainText("-1.5, 0, 0");
await editor.getByLabel("Grease Pencil point X delta").fill("0.5");
await editor.getByRole("button", { name: "Move Point" }).click();
await expect(editor.getByTestId("grease-pencil-point-position")).toContainText("-1, 0, 0", { timeout: 20_000 });
});
test("enforces the N-017 paint stroke, bounded brush and UDIM patch budgets", async ({ page }) => {
await page.goto("/");
const result = await page.evaluate(() => new Promise<Record<string, unknown>>((resolve, reject) => {
const worker = new Worker("/src/workers/paint-schema-test.worker.ts", { type: "module" });
worker.onmessage = (event: MessageEvent<Record<string, string>>) => { worker.terminate(); resolve(event.data); };
worker.onmessage = (event: MessageEvent<Record<string, unknown>>) => { worker.terminate(); resolve(event.data); };
worker.onerror = (event) => { worker.terminate(); reject(new Error(event.message)); };
worker.postMessage({});
}));
@@ -523,6 +556,10 @@ test("enforces the N-017 paint stroke, PBVH/UV hit and brush budgets", async ({
expect(result.weight).toBe("Group");
expect(result.hit).toContain("PAINT_SCHEMA_INVALID");
expect(result.budget).toContain("PAINT_BUDGET_EXCEEDED");
expect(result.brush).toEqual([{ index: 1, weight: 0.4 }, { index: 2, weight: 0.8 }]);
expect((result.udim as number[]).slice(4, 8)).toEqual([10, 20, 30, 255]);
expect(result.udimRevision).toContain("REVISION_CONFLICT");
expect(result.udimStale).toContain("PAINT_TILE_HASH_MISMATCH");
});
test("derives an N-017 source-face, barycentric and UV paint hit from a real raycast", async ({ page }) => {
@@ -587,6 +624,8 @@ test("validates the N-018 physics capability and cache manifests without claimin
expect(result.missingFrame).toContain("PHYSICS_CACHE_FRAME_MISMATCH");
expect(result.solver).toBe("PHYSICS_SOLVER_UNAVAILABLE");
expect(result.manifest).toBe("READY");
expect(result.browserPlayback).toEqual([7, "object:Cloth", [1, 2, 3]]);
expect(result.browserRotation).toContain("PHYSICS_CACHE_FRAME_MISMATCH");
});
test("maps N-019 Scene exposure and light shadow metadata without using legacy World exposure", async ({ page }) => {
@@ -866,7 +905,8 @@ test("autosaves a dirty project without starting a download", async ({ page }) =
await page.setInputFiles("[data-testid=blend-file-input]", basicBlend);
await expect(page.getByText("BasicCube", { exact: true })).toBeVisible();
await page.getByRole("button", { name: "已保存" }).click();
await expect(page.getByRole("button", { name: "保存" })).toBeVisible({ timeout: 5_000 });
await expect(page.getByRole("button", { name: "保存" })).toBeVisible();
await expect(page.getByRole("button", { name: "已保存" })).toBeVisible({ timeout: 15_000 });
});
test("persists an operation log entry with an inverse payload", async ({ page }) => {