Files
SystemSimulationApp/frontend/tests/e2e/project-input-contract.spec.ts
T

60 lines
3.4 KiB
TypeScript

import { expect, test } from "@playwright/test";
import cases from "../../../tests/fixtures/parameter-expressions.json" with { type: "json" };
import { evaluateParameterExpression } from "../../src/parameterExpression";
import { componentVersionNotices, decodeProjectParameters, encodeProjectParameters } from "../../src/projectCompatibility";
const definitions = [{ type: "chamber", modelType: "chamber", modelVersion: "2", parameters: {
p0: { unit: "Pa", quantity: "pressure" }, T0: { unit: "K", quantity: "temperature" },
} }];
function project(value: number | string, version: 1 | 2 = 2) {
return { projectSchemaVersion: version, nodes: [{ id: "c", data: { label: "c", componentType: "chamber",
modelType: "chamber", modelVersion: "1", parameters: { p0: value, T0: 20 }, parameterUnits: { p0: "bar", T0: "degC" } } }] };
}
test("shared expression grammar and resource limits", () => {
for (const [source, expected] of cases.valid) {
const actual = evaluateParameterExpression(String(source));
expect(actual.ok, String(source)).toBe(true);
if (actual.ok) expect(actual.value).toBeCloseTo(Number(expected), 12);
}
for (const source of [...cases.invalid, "(".repeat(34) + "1" + ")".repeat(34), "1+".repeat(256) + "1", "1".repeat(513), `min(${Array(17).fill("1").join(",")})`]) {
expect(evaluateParameterExpression(source).ok, source).toBe(false);
}
});
test("v2 selected-unit numbers and expression values agree; legacy numbers retain SI", () => {
for (const value of [2.5, "2.5", "=2.5", "2+0.5"]) {
const input = project(value);
const original = structuredClone(input);
const internal = decodeProjectParameters(input, definitions);
const parameter = internal.nodes[0].data.parameters.p0;
const actual = typeof parameter === "number" ? parameter :
(() => { const e = evaluateParameterExpression(parameter); return e.ok ? e.value * 1e5 : NaN; })();
expect(actual).toBe(250000);
expect(internal.nodes[0].data.parameters.T0).toBe(293.15);
const exported = encodeProjectParameters(internal, definitions);
expect(exported.projectSchemaVersion).toBe(2);
expect(decodeProjectParameters(exported, definitions)).toEqual(internal);
expect(input).toEqual(original);
}
expect(decodeProjectParameters(project(250000, 1), definitions).nodes[0].data.parameters.p0).toBe(250000);
expect(() => encodeProjectParameters(project(2.5), definitions)).toThrow(/重复换算/);
});
test("version index finds stale and missing versions without mutating or per-node fetch", () => {
const input = project(2.5);
const original = structuredClone(input);
expect(componentVersionNotices(input.nodes, definitions)).toEqual([
{ componentId: "c", label: "c", storedVersion: "1", currentVersion: "2" },
]);
expect(input).toEqual(original);
input.nodes[0].data.modelVersion = "2";
expect(componentVersionNotices(input.nodes, definitions)).toEqual([]);
});
test("export preserves exact SI doubles when display-unit round trip loses a bit", () => {
const input = project(15299999.999999998, 1);
input.nodes[0].data.parameterUnits.p0 = "bar";
input.nodes[0].data.parameters.T0 = 293.15;
const encoded = encodeProjectParameters(input, definitions);
expect(encoded.nodes[0].data.parameterUnits.p0).toBe("Pa");
expect(encoded.nodes[0].data.parameters.p0).toBe(15299999.999999998);
expect(decodeProjectParameters(encoded, definitions).nodes[0].data.parameters).toEqual(input.nodes[0].data.parameters);
});