高温氦气物性补全;三通四通能量计算bug修正

This commit is contained in:
ljz committed 2026-09-15 17:57:20 +08:00
1 parent 6fc9afe41d
commit 2b07d996cf
133 files changed
+5230 -253310

No files matched your search

+1 -1
View File
@@ -2,7 +2,7 @@ import { expect, test, type Locator, type Page } from "@playwright/test";
import { buildAlignmentIndex, findAlignment, type AlignmentAnchor } from "../../src/alignmentGuides";
import { componentCatalog, expectAllNodesInsideCanvas, prepareApp, wideProject } from "./fixtures";
const KEY = "system-simulation-flow:project:demo-system";
const KEY = "system-simulation-flow:autosave";
const anchor = (nodeId: string, kind: AlignmentAnchor["kind"], x: number, y: number): AlignmentAnchor =>
({ id: `${nodeId}:${kind}`, nodeId, kind, x, y });
+10 -10
View File
@@ -1782,7 +1782,7 @@ test("旧版 LMECHN1 工程会把固定参考端口迁移到动态最大编号",
await page.getByRole("button", { name: "保存工程", exact: true }).click();
const downgraded = await page.evaluate(() => {
const key = Object.keys(localStorage).find((candidate) =>
candidate.startsWith("system-simulation-flow:project:"),
candidate === "system-simulation-flow:autosave",
);
const raw = key ? localStorage.getItem(key) : null;
if (!key || !raw) {
@@ -1848,7 +1848,7 @@ test("旧版 LMECHN1 工程会把固定参考端口迁移到动态最大编号",
.poll(() =>
page.evaluate(() => {
const key = Object.keys(localStorage).find((candidate) =>
candidate.startsWith("system-simulation-flow:project:"),
candidate === "system-simulation-flow:autosave",
);
const raw = key ? localStorage.getItem(key) : null;
const project = raw ? JSON.parse(raw) : null;
@@ -1982,7 +1982,7 @@ test("旧版 FORC 工程会迁移为默认正向且不再依赖图标旋转", as
const downgraded = await page.evaluate(() => {
const key = Object.keys(localStorage).find((candidate) =>
candidate.startsWith("system-simulation-flow:project:"),
candidate === "system-simulation-flow:autosave",
);
const raw = key ? localStorage.getItem(key) : null;
if (!key || !raw) {
@@ -2026,7 +2026,7 @@ test("旧版 FORC 工程会迁移为默认正向且不再依赖图标旋转", as
.poll(() =>
page.evaluate(() => {
const key = Object.keys(localStorage).find((candidate) =>
candidate.startsWith("system-simulation-flow:project:"),
candidate === "system-simulation-flow:autosave",
);
const raw = key ? localStorage.getItem(key) : null;
const project = raw ? JSON.parse(raw) : null;
@@ -2898,7 +2898,7 @@ test("恢复工程时采用当前目录端口并明确丢弃已删除端口的
await page.getByRole("button", { name: "保存工程", exact: true }).click();
const changed = await page.evaluate(() => {
const key = Object.keys(window.localStorage).find((candidate) =>
candidate.startsWith("system-simulation-flow:project:"),
candidate === "system-simulation-flow:autosave",
);
const raw = key ? window.localStorage.getItem(key) : null;
if (!key || !raw) {
@@ -3168,7 +3168,7 @@ test("AMESim canvas nodes use icon anchors and highlight only compatible free po
await page.getByRole("button", { name: "保存工程", exact: true }).click();
const routedEdge = await page.evaluate(() => {
const raw = window.localStorage.getItem(
"system-simulation-flow:project:demo-system",
"system-simulation-flow:autosave",
);
const project = raw ? JSON.parse(raw) : null;
return project?.edges?.[0] ?? null;
@@ -3659,7 +3659,7 @@ test("兼容接口接近时吸附为无连线接触连接,拖开后自动断
await page.getByRole("button", { name: "保存工程", exact: true }).click();
const savedEdge = await page.evaluate(() => {
const raw = window.localStorage.getItem(
"system-simulation-flow:project:demo-system",
"system-simulation-flow:autosave",
);
const project = raw ? JSON.parse(raw) : null;
return project?.edges?.[0] ?? null;
@@ -3823,7 +3823,7 @@ test("信号输出连接后隐藏且拒绝第二条连接", async ({ page }) =>
await page.getByRole("button", { name: "保存工程", exact: true }).click();
const addedLegacyFanOut = await page.evaluate(() => {
const key = "system-simulation-flow:project:demo-system";
const key = "system-simulation-flow:autosave";
const raw = window.localStorage.getItem(key);
if (!raw) {
return false;
@@ -3860,7 +3860,7 @@ test("信号输出连接后隐藏且拒绝第二条连接", async ({ page }) =>
await page.getByRole("button", { name: "保存工程", exact: true }).click();
const normalizedEdgeIds = await page.evaluate(() => {
const raw = window.localStorage.getItem(
"system-simulation-flow:project:demo-system",
"system-simulation-flow:autosave",
);
const project = raw ? JSON.parse(raw) : null;
return (project?.edges ?? []).map((edge: { id?: string }) => edge.id);
@@ -3949,7 +3949,7 @@ test("一次拖动会吸附同一落点内的全部兼容接口", async ({ page
await page.getByRole("button", { name: "保存工程", exact: true }).click();
const savedEndpointKeys = await page.evaluate(() => {
const raw = window.localStorage.getItem(
"system-simulation-flow:project:demo-system",
"system-simulation-flow:autosave",
);
const edges = raw ? (JSON.parse(raw)?.edges ?? []) : [];
return edges.flatMap(
+1 -1
View File
@@ -3,7 +3,7 @@ import { Position } from "@xyflow/react";
import { componentCatalog, expectAllNodesInsideCanvas, prepareApp, wideProject } from "./fixtures";
import { confirmOrthogonalCorner, extendOrthogonalRoute } from "../../src/edgeRouting";
const PROJECT_KEY = "system-simulation-flow:project:demo-system";
const PROJECT_KEY = "system-simulation-flow:autosave";
test("每次左键只确认一段并转向,空白预览不插入自动中点", () => {
const source = { x: 0, y: 0 };
+5 -5
View File
@@ -2,7 +2,7 @@ import { expect, test, type Locator, type Page } from "@playwright/test";
import { prepareApp, wideProject } from "./fixtures";
const PROJECT_KEY = "system-simulation-flow:project:demo-system";
const PROJECT_KEY = "system-simulation-flow:autosave";
test.beforeEach(async ({ page }) => {
await prepareApp(page);
@@ -316,7 +316,7 @@ test("选中正交连接线不显示拐点,仍可拖动线段、撤销并保
};
await page.addInitScript((storedProject) => {
window.localStorage.setItem(
"system-simulation-flow:project:demo-system",
"system-simulation-flow:autosave",
JSON.stringify(storedProject),
);
}, project);
@@ -447,7 +447,7 @@ test("选中正交连接线不显示拐点,仍可拖动线段、撤销并保
test("缩小画布后连接线端点仍与端口图标边缘对齐", async ({ page }) => {
await page.addInitScript((storedProject) => {
window.localStorage.setItem(
"system-simulation-flow:project:demo-system",
"system-simulation-flow:autosave",
JSON.stringify(storedProject),
);
}, wideProject);
@@ -552,7 +552,7 @@ for (const route of [
};
await page.addInitScript((storedProject) => {
window.localStorage.setItem(
"system-simulation-flow:project:demo-system",
"system-simulation-flow:autosave",
JSON.stringify(storedProject),
);
}, project);
@@ -688,7 +688,7 @@ test("同一组件不同端口发出的线路在远端交叉时仍显示跨线
};
await page.addInitScript((storedProject) => {
window.localStorage.setItem(
"system-simulation-flow:project:demo-system",
"system-simulation-flow:autosave",
JSON.stringify(storedProject),
);
}, project);
+4 -4
View File
@@ -323,7 +323,7 @@ test("加载本地工程后自动适配建模画布", async ({ page }) => {
await prepareApp(page);
await page.addInitScript((project) => {
window.localStorage.setItem(
"system-simulation-flow:project:demo-system",
"system-simulation-flow:autosave",
JSON.stringify(project),
);
}, wideProject);
@@ -341,7 +341,7 @@ test("加载本地工程后自动适配建模画布", async ({ page }) => {
);
await expect(consolePanel.locator(".simulation-console-dock-log")).toHaveCount(0);
await page.getByRole("button", { name: "展开仿真控制台" }).click();
await expect(consolePanel).toContainText("工程已加载");
await expect(consolePanel).toContainText("已加载最后一次浏览器存档");
});
test("导入工程 JSON 后自动适配建模画布", async ({ page }) => {
@@ -495,7 +495,7 @@ test("切换到结果页时自动适配只读系统图,旧网格线设置不
JSON.stringify(snapshot),
);
window.localStorage.setItem(
"system-simulation-flow:project:demo-system",
"system-simulation-flow:autosave",
JSON.stringify(project),
);
window.localStorage.setItem(
@@ -704,7 +704,7 @@ test("从结果页返回建模页后保留用户调整的建模视口", async ({
JSON.stringify(snapshot),
);
window.localStorage.setItem(
"system-simulation-flow:project:demo-system",
"system-simulation-flow:autosave",
JSON.stringify(project),
);
}, { project: wideProject, snapshot: resultSnapshot });
@@ -0,0 +1,58 @@
import { test, expect } from "@playwright/test";
import { expandSimulationConsole, prepareApp, wideProject } from "./fixtures";
const latestKey = "system-simulation-flow:autosave";
test.beforeEach(async ({ page }) => { await prepareApp(page); });
test("刷新恢复最新工程名称,初始空画布及忽略提示不会覆盖最新存档", async ({ page }) => {
await page.goto("/");
const project = { ...wideProject, name: "持久化 工程名称.v2" };
await page.evaluate(({ key, project }) => localStorage.setItem(key, JSON.stringify(project)), { key: latestKey, project });
await page.clock.install();
await page.reload();
await expect(page.getByRole("textbox", { name: "工程", exact: true })).toHaveValue(project.name);
await page.getByRole("button", { name: "忽略", exact: true }).click();
await page.clock.runFor(6000);
await expect(page.locator(".flow-canvas .react-flow__node")).toHaveCount(0);
expect(await page.evaluate(key => JSON.parse(localStorage.getItem(key)!), latestKey)).toEqual(project);
await page.getByRole("button", { name: "加载工程", exact: true }).click();
await expect(page.locator(".flow-canvas .react-flow__node")).toHaveCount(4);
await expect(page.getByRole("textbox", { name: "工程", exact: true })).toHaveValue(project.name);
});
test("自动保存和手动保存共用最新入口,哪个后完成就加载哪个", async ({ page }) => {
await page.goto("/");
await expect(page.getByRole("button", { name: /通用测试元件/ })).toBeVisible();
for (const [name, count, manual] of [["手动 A", 4, true], ["自动 B", 2, false], ["手动 C", 3, true]] as const) {
await page.locator('input[type="file"]').setInputFiles({ name: `${name}.json`, mimeType: "application/json",
buffer: Buffer.from(JSON.stringify({ ...wideProject, nodes: wideProject.nodes.slice(0, count), edges: [] })) });
if (manual) await page.getByRole("button", { name: "保存工程", exact: true }).click();
await expect.poll(() => page.evaluate(key => JSON.parse(localStorage.getItem(key) || "null")?.name, latestKey)).toBe(name);
await page.getByRole("button", { name: "加载工程", exact: true }).click();
await expect(page.locator(".flow-canvas .react-flow__node")).toHaveCount(count);
await expect(page.getByRole("textbox", { name: "工程", exact: true })).toHaveValue(name);
}
});
test("没有最新存档时不猜选历史命名工程,也不改动画布", async ({ page }) => {
await page.goto("/");
await page.evaluate(project => localStorage.setItem("system-simulation-flow:project:demo-system", JSON.stringify(project)), wideProject);
await page.getByRole("button", { name: "加载工程", exact: true }).click();
await expandSimulationConsole(page);
await expect(page.getByRole("complementary", { name: "仿真控制台", exact: true })).toContainText("尚无自动保存或保存到浏览器的工程");
await expect(page.locator(".flow-canvas .react-flow__node")).toHaveCount(0);
});
test("明确保存的空工程也是最新工程,不回退到旧的非空副本", async ({ page }) => {
await page.goto("/");
const empty = { ...wideProject, name: "已清空工程", nodes: [], edges: [] };
await page.evaluate(({ key, empty, old }) => {
localStorage.setItem(key, JSON.stringify(empty));
localStorage.setItem("system-simulation-flow:pending-recovery", JSON.stringify(old));
}, { key: latestKey, empty, old: wideProject });
await page.reload();
await expect(page.getByRole("textbox", { name: "工程", exact: true })).toHaveValue(empty.name);
await page.getByRole("button", { name: "加载工程", exact: true }).click();
await expect(page.locator(".flow-canvas .react-flow__node")).toHaveCount(0);
await expect(page.getByRole("textbox", { name: "工程", exact: true })).toHaveValue(empty.name);
});
+3 -3
View File
@@ -86,7 +86,7 @@ test("新版存档拒绝目录未定义的参数,保留当前有效工程及 X
await page.getByRole("button", { name: "保存工程", exact: true }).click();
await page.evaluate(() => {
const key = "system-simulation-flow:project:demo-system";
const key = "system-simulation-flow:autosave";
const raw = window.localStorage.getItem(key);
if (!raw) {
throw new Error("测试工程未保存");
@@ -119,7 +119,7 @@ test("XML 参数使用可往返的双精度文本", async ({ page }) => {
await page.getByRole("button", { name: "保存工程", exact: true }).click();
await page.evaluate(() => {
const key = "system-simulation-flow:project:demo-system";
const key = "system-simulation-flow:autosave";
const raw = window.localStorage.getItem(key);
if (!raw) {
throw new Error("测试工程未保存");
@@ -302,7 +302,7 @@ test("复制已连接组件时副本 ID 避让画布中的同名节点且不会
};
await page.addInitScript((storedProject) => {
window.localStorage.setItem(
"system-simulation-flow:project:demo-system",
"system-simulation-flow:autosave",
JSON.stringify(storedProject),
);
}, project);
+47 -1
View File
@@ -1,6 +1,13 @@
import { expect, test } from "@playwright/test";
import { expandSimulationConsole, prepareApp, resultSnapshot, wideProject } from "./fixtures";
test.beforeEach(async ({ page }) => {
await page.route("**/api/simulation/runtime-check", route => route.fulfill({ json: {
checkId: "test-runtime", status: "completed", stage: "complete",
message: "仿真环境自检通过", durationMs: 1, error: null, failure: null,
} }));
});
for (const diagnosticsMode of ["native", "legacy-native", "python"] as const) {
test(`completed ${diagnosticsMode} results can be published and restored`, async ({ page }) => {
await prepareApp(page);
@@ -21,7 +28,7 @@ for (const diagnosticsMode of ["native", "legacy-native", "python"] as const) {
sourceHandle: "port_b", targetHandle: "port_a", data: { isContactEdge: false },
}] })),
});
await expect(page.getByRole("textbox", { name: "工程", exact: true })).toHaveValue(wideProject.name);
await expect(page.getByRole("textbox", { name: "工程", exact: true })).toHaveValue("native-result");
await page.getByRole("button", { name: "运行仿真", exact: true }).click();
await expandSimulationConsole(page);
const consolePanel = page.getByRole("complementary", { name: "仿真控制台", exact: true });
@@ -45,3 +52,42 @@ for (const diagnosticsMode of ["native", "legacy-native", "python"] as const) {
await expect(page.getByText("3 个采样点", { exact: false }).first()).toBeVisible();
});
}
for (const live of [true, false]) {
test(`property temperature warning is shown once with live delivery ${live}`, async ({ page }) => {
await prepareApp(page);
const message = "氦气物性有效温度越界:状态方程及热力性质的真实气体修正项,t = 0.001 s,T = 1600 K > Tmax = 1500 K。仿真继续计算,超范围物性不保证有效性。";
const warning = { code: "helium-equation-of-state-high", message };
const events = [
...(live ? [0, 1].map(() => ({ event: "progress", progress: 1,
phase: "property-warning", message, simulatedTime: .001, totalTime: 1 })) : []),
{ event: "result", progress: 100, phase: "completed", result: {
...resultSnapshot.result, diagnostics: { backend: "native-c", stateCount: 1,
sampleCount: 3, propertyWarnings: [warning] },
} },
];
await page.route("**/api/system-xml/simulate-stream", route => route.fulfill({
contentType: "application/x-ndjson", body: events.map(e => JSON.stringify(e)).join("\n") + "\n",
}));
await page.goto("/");
await page.locator('input[type="file"]').setInputFiles({
name: "property-warning.json", mimeType: "application/json",
buffer: Buffer.from(JSON.stringify({ ...wideProject, edges: [...wideProject.edges, {
id: "close-test-loop", source: "generic_sensor_4", target: "generic_sensor_1",
sourceHandle: "port_b", targetHandle: "port_a", data: { isContactEdge: false },
}] })),
});
await expect(page.getByRole("textbox", { name: "工程", exact: true })).toHaveValue("property-warning");
await page.getByRole("button", { name: "运行仿真", exact: true }).click();
await expandSimulationConsole(page);
const panel = page.getByRole("complementary", { name: "仿真控制台", exact: true });
await expect(panel.getByText(message, { exact: true })).toHaveCount(1);
await expect(panel).toContainText("仿真完成,已生成新的结果,共 3 个采样点");
await expect(panel).not.toContainText("仿真失败");
await expect.poll(() => page.evaluate(async () => {
// @ts-expect-error Vite serves the persistence module in the test browser.
const { loadStoredResultSnapshot } = await import("/src/resultPersistence.ts");
return (await loadStoredResultSnapshot())?.result.diagnostics.propertyWarnings;
})).toEqual([warning]);
});
}
+3 -3
View File
@@ -824,7 +824,7 @@ test("MECMAS21 旧的 0/1 编码不转换并阻止执行", async ({
const changed = await page.evaluate(() => {
const key = Object.keys(window.localStorage).find((candidate) =>
candidate.startsWith("system-simulation-flow:project:"),
candidate === "system-simulation-flow:autosave",
);
const raw = key ? window.localStorage.getItem(key) : null;
if (!key || !raw) {
@@ -891,7 +891,7 @@ test("缺失模型版本加载时警告并补默认值,保存时升级当前
await page.getByRole("button", { name: "保存工程", exact: true }).click();
const changed = await page.evaluate(() => {
const key = Object.keys(window.localStorage).find((candidate) =>
candidate.startsWith("system-simulation-flow:project:"),
candidate === "system-simulation-flow:autosave",
);
const raw = key ? window.localStorage.getItem(key) : null;
if (!key || !raw) {
@@ -928,7 +928,7 @@ test("缺失模型版本加载时警告并补默认值,保存时升级当前
await page.getByRole("button", { name: "保存工程", exact: true }).click();
const persistedWithCurrentVersion = await page.evaluate(() => {
const key = Object.keys(window.localStorage).find((candidate) =>
candidate.startsWith("system-simulation-flow:project:"),
candidate === "system-simulation-flow:autosave",
);
const raw = key ? window.localStorage.getItem(key) : null;
if (!raw) {
@@ -15,7 +15,7 @@ for (const action of ["import", "load", "recover"] as const) {
await expect(page.locator(".flow-canvas")).not.toHaveClass(/is-fitting-project/);
}
await page.evaluate(({ project, action }) => {
const key = action === "recover" ? "system-simulation-flow:autosave" : "system-simulation-flow:project:target";
const key = "system-simulation-flow:autosave";
localStorage.setItem(key, JSON.stringify(project));
}, { project: wideProject, action });
if (action === "recover") {
@@ -243,7 +243,7 @@ test("不能编码新版的工程不覆盖已有浏览器存档,也不下载
expect(downloads).toEqual([]);
});
test("浏览器工程内容保留旧名称时,按存储名称连续加载仍能恢复工程", async ({
test("加载始终读取最后一次浏览器存档并采用存档名称,不按输入名称选历史工程", async ({
page,
}) => {
const storedName = "已重命名 工程.v3";
@@ -251,8 +251,9 @@ test("浏览器工程内容保留旧名称时,按存储名称连续加载仍
({ name, project }) => {
localStorage.setItem(
`system-simulation-flow:project:${encodeURIComponent(name)}`,
JSON.stringify(project),
JSON.stringify({ ...project, name, nodes: [] , edges: [] }),
);
localStorage.setItem("system-simulation-flow:autosave", JSON.stringify(project));
},
{ name: storedName, project: wideProject },
);
@@ -261,14 +262,14 @@ test("浏览器工程内容保留旧名称时,按存储名称连续加载仍
await expect(page.locator(".flow-canvas .react-flow__node")).toHaveCount(
wideProject.nodes.length,
);
await expect(projectNameField(page)).toHaveValue(storedName);
await expect(projectNameField(page)).toHaveValue(wideProject.name);
await deleteFirstNode(page);
await page.getByRole("button", { name: "加载工程", exact: true }).click();
await expect(page.locator(".flow-canvas .react-flow__node")).toHaveCount(
wideProject.nodes.length,
);
await expect(projectNameField(page)).toHaveValue(storedName);
await expect(projectNameField(page)).toHaveValue(wideProject.name);
});
for (const { internalName, expectedName } of [
+1 -1
View File
@@ -65,7 +65,7 @@ async function importMirrorProject(page: Page, project: ReturnType<typeof mirror
async function saveProject(page: Page) {
await page.getByRole("button", { name: "保存工程", exact: true }).click();
return page.evaluate(() => JSON.parse(window.localStorage.getItem(
"system-simulation-flow:project:vertical-mirror",
"system-simulation-flow:autosave",
)!)) as Promise<ReturnType<typeof mirrorProject>>;
}