Files
workinf_Blender_Wasm/web/tests/unit/curve-topology-editor.test.mjs
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

143 lines
6.3 KiB
JavaScript

import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import test from "node:test";
import ts from "typescript";
const repoRoot = path.resolve(import.meta.dirname, "../../..");
const sourcePath = path.join(repoRoot, "web/protocol/curve-topology-editor.ts");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "curve-topology-editor-unit-"));
const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), {
compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 },
fileName: sourcePath,
reportDiagnostics: true,
});
assert.deepEqual(transpiled.diagnostics, []);
const modulePath = path.join(temporary, "curve-topology-editor.mjs");
fs.writeFileSync(modulePath, transpiled.outputText);
const curve = await import(pathToFileURL(modulePath));
function claim(operator, selection) {
const base = {
schemaVersion: 1,
operator,
dataId: "curve:Curve",
baseRevision: 17,
inputSplineCount: 4,
inputPointCount: 12,
selectedSplineIndices: selection === "SPLINES" || selection === "POINTS_OR_SPLINES" ? [1] : [],
selectedPointIndices: selection === "POINTS" ? [2] : [],
addedSplineCount: 0,
addedPointCount: 0,
outputSplineCount: 4,
outputPointCount: 12,
payloadBytes: 256,
};
if (operator === "ADD_SPLINE") return { ...base, addedSplineCount: 1, addedPointCount: 2, outputSplineCount: 5, outputPointCount: 14 };
if (operator === "DUPLICATE") return { ...base, addedSplineCount: 1, addedPointCount: 2, outputSplineCount: 5, outputPointCount: 14 };
if (operator === "EXTRUDE") return { ...base, addedPointCount: 1, outputPointCount: 13 };
if (operator === "SPLIT") return { ...base, addedSplineCount: 1, outputSplineCount: 5 };
if (operator === "SUBDIVIDE") return { ...base, addedPointCount: 2, outputPointCount: 14, subdivideCuts: 2 };
return base;
}
test("M9-04 freezes the Blender-sourced Curve topology allowlist and budgets", () => {
const manifest = curve.createCurveTopologyEditorManifest();
assert.equal(manifest.schemaVersion, 1);
assert.equal(manifest.sourceAuthority, "blender-5.2.0/source/blender/editors/curve/curve_ops.cc");
assert.equal(manifest.atomicMainTransaction, true);
assert.deepEqual(manifest.budget, {
maxSplines: 65_536,
maxPoints: 1_000_000,
maxSelectedElements: 100_000,
maxAddedSplinesPerOperation: 4_096,
maxAddedPointsPerOperation: 100_000,
maxPayloadBytes: 67_108_864,
maxSubdivideCuts: 64,
maxDataIdBytes: 256,
maxOperationsPerMainTransaction: 1,
});
assert.deepEqual(manifest.operators.map((operator) => operator.id), [
"ADD_SPLINE", "DECIMATE", "DELETE", "DISSOLVE_VERTICES", "DUPLICATE", "EXTRUDE",
"MAKE_SEGMENT", "SEPARATE", "SET_HANDLE_TYPE", "SET_SPLINE_TYPE", "SPLIT", "SUBDIVIDE",
"SWITCH_DIRECTION", "TOGGLE_CYCLIC",
]);
assert.deepEqual(manifest.operators.filter((operator) => operator.gate.status === "READY").map((operator) => operator.id), ["TOGGLE_CYCLIC"]);
assert.ok(manifest.operators.filter((operator) => operator.id !== "TOGGLE_CYCLIC").every((operator) => operator.gate.status === "BLOCKED" && operator.gate.reasonCode === "CURVE_TOPOLOGY_OPERATOR_NOT_VERIFIED"));
});
test("M9-04 accepts one revision-bound budget claim for every allowlisted operator", () => {
for (const descriptor of curve.CURVE_TOPOLOGY_EDITOR_OPERATORS) {
const parsed = curve.parseCurveTopologyOperationClaim(claim(descriptor.id, descriptor.selection), 17);
assert.equal(parsed.operator, descriptor.id);
assert.equal(parsed.baseRevision, 17);
}
});
test("M9-04 rejects unknown, stale, ambiguous and over-budget Curve topology claims", () => {
const valid = claim("SUBDIVIDE", "POINTS_OR_SPLINES");
const cases = [
[{ ...valid, operator: "SPIN" }, "NON_MESH_TOPOLOGY_EDIT_UNSUPPORTED"],
[valid, "REVISION_CONFLICT", 18],
[{ ...valid, selectedSplineIndices: [1, 1] }, "NON_MESH_PROPERTY_INVALID"],
[{ ...valid, selectedPointIndices: [2] }, "NON_MESH_PROPERTY_INVALID"],
[{ ...valid, dataId: "data:Curve" }, "NON_MESH_PROPERTY_INVALID"],
[{ ...claim("DELETE", "POINTS_OR_SPLINES"), inputSplineCount: 0, selectedSplineIndices: [0] }, "NON_MESH_PROPERTY_INVALID"],
[{ ...valid, outputPointCount: 15 }, "NON_MESH_PROPERTY_INVALID"],
[{ ...valid, payloadBytes: curve.CURVE_TOPOLOGY_EDITOR_BUDGET.maxPayloadBytes + 1 }, "NON_MESH_DATA_BUDGET_EXCEEDED"],
[{ ...valid, subdivideCuts: 65 }, "NON_MESH_DATA_BUDGET_EXCEEDED"],
[{ ...claim("DELETE", "POINTS_OR_SPLINES"), subdivideCuts: 1 }, "NON_MESH_PROPERTY_INVALID"],
[{ ...valid, futureField: true }, "NON_MESH_PROPERTY_INVALID"],
];
for (const [value, code, revision = 17] of cases) {
assert.throws(() => curve.parseCurveTopologyOperationClaim(value, revision), (error) => error.code === code);
}
});
test("M9-05 builds one revision-bound TOGGLE_CYCLIC Main command", () => {
const operation = curve.buildCurveToggleCyclicOperation({
schemaVersion: 1,
dataId: "curve:WebCurveData",
baseRevision: 23,
splineIndex: 1,
splineCount: 2,
pointCount: 7,
cyclicU: [false, true],
}, 23);
assert.deepEqual(operation.command, {
type: "setCurveTopology",
dataId: "curve:WebCurveData",
baseRevision: 23,
cyclicU: [false, false],
});
assert.equal(operation.claim.operator, "TOGGLE_CYCLIC");
assert.deepEqual(operation.claim.selectedSplineIndices, [1]);
assert.equal(operation.previousCyclic, true);
assert.equal(operation.nextCyclic, false);
});
test("M9-05 keeps stale and malformed TOGGLE_CYCLIC requests out of Main", () => {
const valid = {
schemaVersion: 1,
dataId: "curve:WebCurveData",
baseRevision: 23,
splineIndex: 0,
splineCount: 2,
pointCount: 7,
cyclicU: [false, false],
};
assert.throws(() => curve.buildCurveToggleCyclicOperation(valid, 24), (error) => error.code === "REVISION_CONFLICT");
for (const value of [
{ ...valid, splineIndex: 2 },
{ ...valid, splineCount: 0 },
{ ...valid, cyclicU: [false] },
{ ...valid, dataId: "surface:WebSurfaceData" },
]) {
assert.throws(() => curve.buildCurveToggleCyclicOperation(value, 23), (error) => error.code === "NON_MESH_PROPERTY_INVALID");
}
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));