Continue WBV2 module parity through P11
This commit is contained in:
77
web/tests/e2e/wbv2-asset-link-override-production.spec.ts
Normal file
77
web/tests/e2e/wbv2-asset-link-override-production.spec.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import crypto from "node:crypto";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { computeLibraryAppendClosureSha256 } 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 evidenceRoot = path.join(root, "tests/golden/WBV2-P11-003");
|
||||
const linkedFixture = fs.readFileSync(path.join(root, "tests/files/web/generated/WBV2-P3-005-ASSET.LIBRARY_OPERATIONS.blend"));
|
||||
const emptyFixture = fs.readFileSync(path.join(root, "tests/files/web/empty.blend"));
|
||||
const sourceLibrary = fs.readFileSync(path.join(root, "tests/files/web/generated/WBV2P3005Library.blend"));
|
||||
const sha256 = (bytes: Uint8Array): string => crypto.createHash("sha256").update(bytes).digest("hex");
|
||||
|
||||
test("WBV2-P11-003 enforces linked ownership and local append through compiled WASM Main", async ({ page }) => {
|
||||
test.setTimeout(180_000);
|
||||
await page.goto("/");
|
||||
const closure = { object: "Object/WBV2P3005AppendObject", mesh: "Mesh/WBV2P3005AppendMesh", material: "Material/WBV2P3005AppendMaterial", image: "Image/WBV2P3005AppendImage" } as const;
|
||||
const sourceSha256 = sha256(sourceLibrary);
|
||||
const sourceIdentity = await createLibrarySourceIdentity({ sourceLocator: "project-assets/libraries/WBV2P3005Library.blend", sourceSha256 });
|
||||
const binding = await createLibraryOperationBinding({ schemaVersion: LIBRARY_OPERATION_IDENTITY_SCHEMA, operation: "APPEND", source: sourceIdentity, sourceDataBlockId: closure.object, owner: { kind: "LOCAL_MAIN", projectId: "project:wbv2-p11-003", localDataBlockId: closure.object }, readOnly: false, referenceReadOnly: false, sourceGeneration: 1, sourceRevision: 0, dependencyClosureSha256: await computeLibraryAppendClosureSha256(closure) });
|
||||
const result = await page.evaluate(async (input) => {
|
||||
const { WebEngineClient } = await import("/src/engine-client/WebEngineClient.ts");
|
||||
const buffer = (values: number[]): ArrayBuffer => Uint8Array.from(values).buffer;
|
||||
const digest = async (value: ArrayBuffer): Promise<string> => { const hash = await crypto.subtle.digest("SHA-256", value); return Array.from(new Uint8Array(hash), (byte) => byte.toString(16).padStart(2, "0")).join(""); };
|
||||
const shapeError = (error: any) => ({ code: error?.code, message: error?.message });
|
||||
const linkedClient = new WebEngineClient({ timeoutMs: 60_000 });
|
||||
const appendClient = new WebEngineClient({ timeoutMs: 60_000 });
|
||||
const reopenedClient = new WebEngineClient({ timeoutMs: 60_000 });
|
||||
try {
|
||||
const linked = await linkedClient.openBlend(buffer(input.linkedBytes));
|
||||
const linkedObject: any = linked.snapshot.nodes.find((candidate: any) => candidate.id.includes("WBV2P3005LinkedObject"));
|
||||
const linkedBefore = { id: linkedObject?.id ?? null, visible: linkedObject?.visible ?? null, objectPresent: Boolean(linkedObject), libraries: linked.snapshot.libraries ?? [], libraryStatus: linked.snapshot.libraryStatus };
|
||||
const linkedMutation = linkedObject
|
||||
? await linkedClient.applyCommand({ type: "setObjectVisibility", objectId: linkedObject.id, visible: !linkedObject.visible, baseRevision: linked.snapshot.revision } as any).then(() => ({ code: "NO_ERROR" })).catch(shapeError)
|
||||
: { code: "LINKED_LIBRARY_REQUIRED", message: "linked object withheld while external library is unavailable" };
|
||||
const linkedAfterSnapshot = await linkedClient.snapshot();
|
||||
const linkedAfter = linkedObject ? linkedAfterSnapshot.snapshot.nodes.find((candidate: any) => candidate.id === linkedObject.id) : undefined;
|
||||
|
||||
const opened = await appendClient.openBlend(buffer(input.emptyBytes));
|
||||
const baseRevision = opened.snapshot.revision;
|
||||
const appended = await appendClient.appendLibraryObject(buffer(input.sourceBytes), { schemaVersion: 1, baseRevision, binding: input.binding, expectedClosure: input.closure });
|
||||
const localClosure = (snapshot: any) => ({ object: snapshot.nodes.find((candidate: any) => candidate.id === "object:WBV2P3005AppendObject"), mesh: snapshot.meshes.find((candidate: any) => candidate.id === "mesh:WBV2P3005AppendMesh"), material: snapshot.materials.find((candidate: any) => candidate.id === "material:WBV2P3005AppendMaterial"), image: snapshot.images.find((candidate: any) => candidate.id === "image:WBV2P3005AppendImage") });
|
||||
const local = localClosure(appended.snapshot);
|
||||
const saved = await appendClient.saveBlend();
|
||||
const savedSha256 = await digest(saved.slice(0));
|
||||
const reopened = await reopenedClient.openBlend(saved.slice(0));
|
||||
const reopenedLocal = localClosure(reopened.snapshot);
|
||||
const stale = await appendClient.appendLibraryObject(buffer(input.sourceBytes), { schemaVersion: 1, baseRevision, binding: input.binding, expectedClosure: input.closure }).then(() => ({ code: "NO_ERROR" })).catch(shapeError);
|
||||
const afterStale = (await appendClient.snapshot()).snapshot.revision;
|
||||
return { linked: { before: linkedBefore, mutation: linkedMutation, after: { id: linkedAfter?.id ?? null, visible: linkedAfter?.visible ?? null, objectPresent: Boolean(linkedAfter) }, revisionBefore: linked.snapshot.revision, revisionAfter: linkedAfterSnapshot.snapshot.revision }, append: { baseRevision, appendedRevision: appended.snapshot.revision, receipt: appended.receipt, local: { object: local.object && { id: local.object.id, type: local.object.type, dataId: local.object.dataId }, mesh: local.mesh && { id: local.mesh.id, materialSlotIds: local.mesh.materialSlotIds, vertexCount: local.mesh.vertexCount }, material: local.material && { id: local.material.id, imageIds: local.material.imageIds ?? local.material.nodes?.flatMap((node: any) => node.imageId ? [node.imageId] : []) }, image: local.image && { id: local.image.id, libraryLinked: local.image.libraryLinked, packed: local.image.packed, assetStatus: local.image.assetStatus } }, reopened: { object: Boolean(reopenedLocal.object), mesh: Boolean(reopenedLocal.mesh), material: Boolean(reopenedLocal.material), image: reopenedLocal.image && { libraryLinked: reopenedLocal.image.libraryLinked, packed: reopenedLocal.image.packed } }, saveReopenExact: Boolean(reopenedLocal.object && reopenedLocal.mesh && reopenedLocal.material && reopenedLocal.image?.libraryLinked === false && reopenedLocal.image?.packed === true), savedSha256, sourceSha256: await digest(buffer(input.sourceBytes)), stale, revisionAfterStale: afterStale } };
|
||||
}
|
||||
finally { linkedClient.terminate(); appendClient.terminate(); reopenedClient.terminate(); }
|
||||
}, { linkedBytes: Array.from(linkedFixture), emptyBytes: Array.from(emptyFixture), sourceBytes: Array.from(sourceLibrary), closure, binding });
|
||||
fs.mkdirSync(evidenceRoot, { recursive: true });
|
||||
const output = process.env.WBV2_ASSET_LINK_OVERRIDE_REPORT ?? "asset-link-override-production.json";
|
||||
fs.writeFileSync(path.join(evidenceRoot, output), `${JSON.stringify({ schemaVersion: 1, task: "WBV2-P11-003", operation: "ASSET_LINK_OVERRIDE_RELOAD_FAMILY_CHROMIUM", browser: "chromium", queueMutation: false, ...result }, null, 2)}\n`);
|
||||
expect(result.linked.before.libraryStatus).toBe("AVAILABLE");
|
||||
expect(result.linked.before.libraries.length).toBeGreaterThan(0);
|
||||
expect(result.linked.before.libraries.every((library: any) => library.status === "EXTERNAL_REQUIRED" && library.readOnly === true)).toBe(true);
|
||||
expect(result.linked.mutation.code).not.toBe("NO_ERROR");
|
||||
expect(result.linked.before.objectPresent).toBe(false);
|
||||
expect(result.linked.after.objectPresent).toBe(false);
|
||||
expect(result.linked.after.visible).toBe(result.linked.before.visible);
|
||||
expect(result.linked.revisionAfter).toBe(result.linked.revisionBefore);
|
||||
expect(result.append.appendedRevision).toBe(result.append.baseRevision + 1);
|
||||
expect(result.append.receipt.operation).toBe("APPEND");
|
||||
expect(result.append.receipt.mapping.every((entry: any) => entry.owner === "LOCAL_MAIN" && entry.readOnly === false && entry.source === entry.local)).toBe(true);
|
||||
expect(result.append.local.object.type).toBe("MESH");
|
||||
expect(result.append.local.mesh.vertexCount).toBe(4);
|
||||
expect(result.append.local.image.libraryLinked).toBe(false);
|
||||
expect(result.append.local.image.packed).toBe(true);
|
||||
expect(result.append.saveReopenExact).toBe(true);
|
||||
expect(result.append.stale.code).not.toBe("NO_ERROR");
|
||||
expect(result.append.stale.message).toContain("REVISION_CONFLICT");
|
||||
expect(result.append.revisionAfterStale).toBe(result.append.appendedRevision);
|
||||
});
|
||||
87
web/tests/e2e/wbv2-mesh-topology-production.spec.ts
Normal file
87
web/tests/e2e/wbv2-mesh-topology-production.spec.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import crypto from "node:crypto";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
const root = path.resolve(import.meta.dirname, "../../..");
|
||||
const evidenceRoot = path.join(root, "tests/golden/WBV2-P11-001");
|
||||
const fixturePath = path.join(root, "tests/files/web/generated/WBV2-P2-003-MESH.EDIT_CORE.blend");
|
||||
const fixture = fs.readFileSync(fixturePath);
|
||||
const sourceSha256 = crypto.createHash("sha256").update(fixture).digest("hex");
|
||||
|
||||
test("WBV2-P11-001 executes the six Mesh topology operations through compiled WASM Main", async ({ page }) => {
|
||||
test.setTimeout(240_000);
|
||||
await page.goto("/");
|
||||
const result = await page.evaluate(async (input) => {
|
||||
const { WebEngineClient } = await import("/src/engine-client/WebEngineClient.ts");
|
||||
const operations = [
|
||||
{ label: "MERGE", meshId: "mesh:WBV2P2003MergeMesh", selectionMode: "VERT", elementIndices: [0, 1] },
|
||||
{ label: "DISSOLVE", meshId: "mesh:WBV2P2003DissolveMesh", selectionMode: "EDGE", elementIndices: [0] },
|
||||
{ label: "EXTRUDE", meshId: "mesh:WBV2P2003ExtrudeMesh", selectionMode: "FACE", elementIndices: [0], offset: [0, 0, 1] },
|
||||
{ label: "INSET", meshId: "mesh:WBV2P2003InsetMesh", selectionMode: "FACE", elementIndices: [0], amount: 0.2 },
|
||||
{ label: "BEVEL", meshId: "mesh:WBV2P2003BevelMesh", selectionMode: "EDGE", elementIndices: [0], amount: 0.1, segments: 2 },
|
||||
{ label: "LOOP_CUT", meshId: "mesh:WBV2P2003LoopCutMesh", selectionMode: "EDGE", elementIndices: [0], segments: 1 },
|
||||
];
|
||||
const summary = (snapshot: any, meshId: string) => {
|
||||
const mesh = snapshot.meshes.find((candidate: any) => candidate.id === meshId);
|
||||
if (!mesh) throw new Error(`mesh missing: ${meshId}`);
|
||||
return { id: mesh.id, vertexCount: mesh.vertexCount, edgeCount: mesh.edgeCount, faceCount: mesh.faceCount, triangleCount: mesh.triangleCount, topology: mesh.topology };
|
||||
};
|
||||
const digest = async (value: ArrayBuffer): Promise<string> => { const hash = await crypto.subtle.digest("SHA-256", value); return Array.from(new Uint8Array(hash), (byte) => byte.toString(16).padStart(2, "0")).join(""); };
|
||||
const errors = async (client: any, commands: any[], revision: number) => {
|
||||
const values = [];
|
||||
for (const command of commands) {
|
||||
const error = await client.applyCommand({ baseRevision: revision, ...command } as any).then(() => null).catch((caught) => ({ code: caught?.code, message: caught?.message }));
|
||||
values.push(error);
|
||||
}
|
||||
return values;
|
||||
};
|
||||
const positive = [];
|
||||
for (const operation of operations) {
|
||||
const client = new WebEngineClient({ timeoutMs: 60_000 });
|
||||
const reopenedClient = new WebEngineClient({ timeoutMs: 60_000 });
|
||||
try {
|
||||
const opened = await client.openBlend(Uint8Array.from(input.bytes).buffer);
|
||||
const before = summary(opened.snapshot, operation.meshId);
|
||||
const baseRevision = opened.snapshot.revision;
|
||||
const edited = await client.applyCommand({ type: "meshEdit", meshId: operation.meshId, operation: operation.label, selectionMode: operation.selectionMode, elementIndices: operation.elementIndices, ...(operation.offset ? { offset: operation.offset } : {}), ...(operation.amount === undefined ? {} : { amount: operation.amount }), ...(operation.segments === undefined ? {} : { segments: operation.segments }), baseRevision } as any);
|
||||
const after = summary(edited.snapshot, operation.meshId);
|
||||
const saved = await client.saveBlend();
|
||||
const savedSha256 = await digest(saved.slice(0));
|
||||
const reopened = await reopenedClient.openBlend(saved.slice(0));
|
||||
const reopenedSummary = summary(reopened.snapshot, operation.meshId);
|
||||
positive.push({ operation: operation.label, meshId: operation.meshId, baseRevision, afterRevision: edited.snapshot.revision, before, after, reopened: reopenedSummary, savedSha256, exactSaveReopen: JSON.stringify(after) === JSON.stringify(reopenedSummary) });
|
||||
}
|
||||
finally { client.terminate(); reopenedClient.terminate(); }
|
||||
}
|
||||
|
||||
const negativeClient = new WebEngineClient({ timeoutMs: 60_000 });
|
||||
try {
|
||||
const opened = await negativeClient.openBlend(Uint8Array.from(input.bytes).buffer);
|
||||
const revision = opened.snapshot.revision;
|
||||
const before = summary(opened.snapshot, "mesh:WBV2P2003MergeMesh");
|
||||
const [invalidSelection, incompatible, stale] = await errors(negativeClient, [
|
||||
{ type: "meshEdit", meshId: "mesh:WBV2P2003MergeMesh", operation: "MERGE", selectionMode: "VERT", elementIndices: [999] },
|
||||
{ type: "meshEdit", meshId: "mesh:WBV2P2003MergeMesh", operation: "EXTRUDE", selectionMode: "EDGE", elementIndices: [0], offset: [0, 0, 1] },
|
||||
{ type: "meshEdit", meshId: "mesh:WBV2P2003MergeMesh", operation: "MERGE", selectionMode: "VERT", elementIndices: [0, 1], baseRevision: revision - 1 },
|
||||
], revision);
|
||||
const after = summary((await negativeClient.snapshot()).snapshot, "mesh:WBV2P2003MergeMesh");
|
||||
return { sourceSha256: input.sourceSha256, operations: positive, negative: { invalidSelection, incompatible, stale, before, after, revisionBefore: revision, revisionAfter: (await negativeClient.snapshot()).snapshot.revision } };
|
||||
}
|
||||
finally { negativeClient.terminate(); }
|
||||
}, { bytes: Array.from(fixture), sourceSha256 });
|
||||
|
||||
fs.mkdirSync(evidenceRoot, { recursive: true });
|
||||
const output = process.env.WBV2_MESH_TOPOLOGY_REPORT ?? "mesh-topology-production.json";
|
||||
fs.writeFileSync(path.join(evidenceRoot, output), `${JSON.stringify({ schemaVersion: 1, task: "WBV2-P11-001", operation: "MESH_TOPOLOGY_OPERATION_FAMILY_CHROMIUM", browser: "chromium", queueMutation: false, ...result }, null, 2)}\n`);
|
||||
expect(result.sourceSha256).toBe(sourceSha256);
|
||||
expect(result.operations).toHaveLength(6);
|
||||
expect(result.operations.every((entry) => entry.afterRevision === entry.baseRevision + 1 && entry.exactSaveReopen && entry.after.vertexCount > 0 && entry.after.edgeCount > 0 && entry.after.faceCount > 0)).toBe(true);
|
||||
expect(result.operations.map((entry) => entry.operation)).toEqual(["MERGE", "DISSOLVE", "EXTRUDE", "INSET", "BEVEL", "LOOP_CUT"]);
|
||||
expect(result.negative.invalidSelection?.code).toBe("INVALID_ARGUMENT");
|
||||
expect(result.negative.incompatible?.code).toBe("INVALID_ARGUMENT");
|
||||
expect(result.negative.stale?.code).toBe("INVALID_ARGUMENT");
|
||||
expect(result.negative.stale?.message).toContain("REVISION_CONFLICT");
|
||||
expect(result.negative.revisionAfter).toBe(result.negative.revisionBefore);
|
||||
expect(result.negative.after).toEqual(result.negative.before);
|
||||
});
|
||||
65
web/tests/e2e/wbv2-modifier-parameter-production.spec.ts
Normal file
65
web/tests/e2e/wbv2-modifier-parameter-production.spec.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import crypto from "node:crypto";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
const root = path.resolve(import.meta.dirname, "../../..");
|
||||
const evidenceRoot = path.join(root, "tests/golden/WBV2-P11-002");
|
||||
const fixturePath = path.join(root, "tests/files/web/generated/WBV2-P4-001-MODIFIER.DECLARED_SLICES.blend");
|
||||
const fixture = fs.readFileSync(fixturePath);
|
||||
const sourceSha256 = crypto.createHash("sha256").update(fixture).digest("hex");
|
||||
|
||||
test("WBV2-P11-002 applies a bounded Modifier parameter through compiled WASM Main", async ({ page }) => {
|
||||
test.setTimeout(180_000);
|
||||
await page.goto("/");
|
||||
const result = await page.evaluate(async (input) => {
|
||||
const { WebEngineClient } = await import("/src/engine-client/WebEngineClient.ts");
|
||||
const summary = (snapshot: any, meshId: string) => {
|
||||
const mesh = snapshot.meshes.find((candidate: any) => candidate.id === meshId);
|
||||
if (!mesh) throw new Error(`mesh missing: ${meshId}`);
|
||||
return { id: mesh.id, vertexCount: mesh.vertexCount, edgeCount: mesh.edgeCount, faceCount: mesh.faceCount, triangleCount: mesh.triangleCount, modifierStack: mesh.modifierStack?.map((modifier: any) => ({ uuid: modifier.uuid, type: modifier.type, enabled: modifier.enabled, evaluationStatus: modifier.evaluationStatus })) ?? [] };
|
||||
};
|
||||
const errorShape = (error: any) => ({ code: error?.code, message: error?.message });
|
||||
const client = new WebEngineClient({ timeoutMs: 60_000 });
|
||||
const reopenedClient = new WebEngineClient({ timeoutMs: 60_000 });
|
||||
try {
|
||||
const opened = await client.openBlend(Uint8Array.from(input.bytes).buffer);
|
||||
const mesh: any = opened.snapshot.meshes.find((candidate: any) => candidate.id === "mesh:WBV2P4001ModifierMesh");
|
||||
const decimate: any = mesh?.modifierStack?.find((candidate: any) => candidate.type === "DECIMATE");
|
||||
if (!mesh || !decimate) throw new Error("modifier fixture missing DECIMATE stack entry");
|
||||
const before = summary(opened.snapshot, mesh.id);
|
||||
let disabled = { snapshot: opened } as any;
|
||||
for (const activeModifier of mesh.modifierStack?.filter((candidate: any) => candidate.enabled) ?? []) {
|
||||
disabled = await client.applyCommand({ type: "setModifierEnabled", meshId: mesh.id, modifierUuid: activeModifier.uuid, enabled: false, baseRevision: disabled.snapshot.revision } as any);
|
||||
}
|
||||
const profile = { schemaVersion: 1 as const, sourceMeshRevision: disabled.snapshot.revision, mode: "COLLAPSE" as const, ratio: 0.75, iterations: 1, attributePolicy: "PRESERVE" as const, triangulate: false, useSymmetry: false };
|
||||
const applied = await client.applyCommand({ type: "decimateMesh", meshId: mesh.id, profile } as any);
|
||||
const after = summary(applied.snapshot, mesh.id);
|
||||
const stale = await client.applyCommand({ type: "decimateMesh", meshId: mesh.id, profile } as any).then(() => ({ code: "NO_ERROR" })).catch(errorShape);
|
||||
const afterStale = summary((await client.snapshot()).snapshot, mesh.id);
|
||||
const malformed = await client.applyCommand({ type: "decimateMesh", meshId: mesh.id, profile: { ...profile, sourceMeshRevision: applied.snapshot.revision, ratio: 2 } } as any).then(() => ({ code: "NO_ERROR" })).catch(errorShape);
|
||||
const afterMalformed = summary((await client.snapshot()).snapshot, mesh.id);
|
||||
const saved = await client.saveBlend();
|
||||
const savedSha256 = await (async () => { const hash = await crypto.subtle.digest("SHA-256", saved.slice(0)); return Array.from(new Uint8Array(hash), (byte) => byte.toString(16).padStart(2, "0")).join(""); })();
|
||||
const reopened = await reopenedClient.openBlend(saved.slice(0));
|
||||
const reopenedSummary = summary(reopened.snapshot, mesh.id);
|
||||
return { sourceSha256: input.sourceSha256, meshId: mesh.id, modifierUuid: decimate.uuid, before, disabledRevision: disabled.snapshot.revision, appliedRevision: applied.snapshot.revision, simplify: applied.simplify, after, afterStale, afterMalformed, stale, malformed, savedSha256, reopened: reopenedSummary, exactSaveReopen: JSON.stringify(after) === JSON.stringify(reopenedSummary) && JSON.stringify(after) === JSON.stringify(afterStale) && JSON.stringify(after) === JSON.stringify(afterMalformed) };
|
||||
}
|
||||
finally { client.terminate(); reopenedClient.terminate(); }
|
||||
}, { bytes: Array.from(fixture), sourceSha256 });
|
||||
fs.mkdirSync(evidenceRoot, { recursive: true });
|
||||
const output = process.env.WBV2_MODIFIER_PARAMETER_REPORT ?? "modifier-parameter-production.json";
|
||||
fs.writeFileSync(path.join(evidenceRoot, output), `${JSON.stringify({ schemaVersion: 1, task: "WBV2-P11-002", operation: "MODIFIER_PARAMETER_APPLY_FAMILY_CHROMIUM", browser: "chromium", queueMutation: false, ...result }, null, 2)}\n`);
|
||||
expect(result.sourceSha256).toBe(sourceSha256);
|
||||
expect(result.disabledRevision).toBe(4);
|
||||
expect(result.appliedRevision).toBe(5);
|
||||
expect(result.simplify?.status).toBe("applied");
|
||||
expect(result.after.vertexCount).toBeLessThan(result.before.vertexCount);
|
||||
expect(result.after.triangleCount).toBeLessThan(result.before.triangleCount);
|
||||
expect(result.exactSaveReopen).toBe(true);
|
||||
expect(result.stale.code).not.toBe("NO_ERROR");
|
||||
expect(result.malformed.code).not.toBe("NO_ERROR");
|
||||
expect(result.afterStale).toEqual(result.after);
|
||||
expect(result.afterMalformed).toEqual(result.after);
|
||||
expect(result.savedSha256).not.toBe(sourceSha256);
|
||||
});
|
||||
Reference in New Issue
Block a user