1337 lines
51 KiB
TypeScript
1337 lines
51 KiB
TypeScript
import { expect, test, type Locator, type Page } from "@playwright/test";
|
|
|
|
import { prepareApp } from "./fixtures";
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
await prepareApp(page);
|
|
});
|
|
|
|
function parameterRow(table: Locator, name: string) {
|
|
return parameterNameCell(table, name).locator("..");
|
|
}
|
|
|
|
function parameterNameCell(table: Locator, name: string) {
|
|
return table.getByRole("rowheader", { name, exact: true });
|
|
}
|
|
|
|
async function addPneumaticTestComponent(page: Page) {
|
|
const canvas = page.locator(".flow-canvas .react-flow__pane");
|
|
await page
|
|
.getByRole("button", { name: "AMESim 气动测试元件", exact: true })
|
|
.dragTo(canvas, { targetPosition: { x: 620, y: 330 } });
|
|
const node = page.locator(
|
|
'.flow-canvas .react-flow__node[data-id="amesim_pneumatic_test_1"]',
|
|
);
|
|
await expect(node).toBeVisible();
|
|
await node.click();
|
|
}
|
|
|
|
async function addChoiceVisibilityTestComponent(page: Page) {
|
|
const canvas = page.locator(".flow-canvas .react-flow__pane");
|
|
await page
|
|
.getByRole("button", { name: "离散参数测试元件", exact: true })
|
|
.dragTo(canvas, { targetPosition: { x: 620, y: 330 } });
|
|
const node = page.locator(
|
|
'.flow-canvas .react-flow__node[data-id="choice_visibility_test_1"]',
|
|
);
|
|
await expect(node).toBeVisible();
|
|
await node.click();
|
|
}
|
|
|
|
async function addMecmasEncodingTestComponent(page: Page) {
|
|
const canvas = page.locator(".flow-canvas .react-flow__pane");
|
|
await page
|
|
.getByRole("button", { name: "MECMAS21 编码测试元件", exact: true })
|
|
.dragTo(canvas, { targetPosition: { x: 620, y: 330 } });
|
|
const node = page.locator(
|
|
'.flow-canvas .react-flow__node[data-id="amesim_mecmas21_1"]',
|
|
);
|
|
await expect(node).toBeVisible();
|
|
await node.click();
|
|
}
|
|
|
|
async function addParameterMetadataTestComponent(
|
|
page: Page,
|
|
label: string,
|
|
type: string,
|
|
) {
|
|
const canvas = page.locator(".flow-canvas .react-flow__pane");
|
|
await page
|
|
.getByRole("button", { name: label, exact: true })
|
|
.dragTo(canvas, { targetPosition: { x: 620, y: 330 } });
|
|
const node = page.locator(
|
|
`.flow-canvas .react-flow__node[data-id="${type}_1"]`,
|
|
);
|
|
await expect(node).toBeVisible();
|
|
await node.click();
|
|
}
|
|
|
|
test("组件参数和仿真设置使用共享的三列表格", async ({ page }) => {
|
|
await page.goto("/");
|
|
await addPneumaticTestComponent(page);
|
|
|
|
const componentTable = page.getByRole("table", { name: "组件参数" });
|
|
const simulationTable = page.getByRole("table", { name: "仿真设置" });
|
|
await expect(componentTable).toBeVisible();
|
|
await expect(simulationTable).toBeVisible();
|
|
|
|
await expect(componentTable.getByRole("columnheader")).toHaveText([
|
|
"参数",
|
|
"值",
|
|
"单位",
|
|
]);
|
|
await expect(componentTable.getByRole("rowheader")).toHaveText([
|
|
"组件名称",
|
|
"节点 ID",
|
|
"介质物性索引",
|
|
"参考压力",
|
|
"参考温度",
|
|
]);
|
|
await expect(simulationTable.getByRole("rowheader")).toHaveText([
|
|
"起始时间",
|
|
"结束时间",
|
|
"采样步长",
|
|
"最大积分步长",
|
|
"求解器",
|
|
]);
|
|
|
|
await expect(parameterRow(simulationTable, "起始时间").locator("td").last()).toHaveText(
|
|
"s",
|
|
);
|
|
await expect(parameterRow(simulationTable, "求解器").locator("td").last()).toHaveText(
|
|
"—",
|
|
);
|
|
|
|
const componentNameWidth = await componentTable
|
|
.locator("thead .parameter-table-name-cell")
|
|
.evaluate((element) => element.getBoundingClientRect().width);
|
|
const simulationNameWidth = await simulationTable
|
|
.locator("thead .parameter-table-name-cell")
|
|
.evaluate((element) => element.getBoundingClientRect().width);
|
|
expect(Math.abs(componentNameWidth - simulationNameWidth)).toBeLessThanOrEqual(1);
|
|
|
|
const panelTitleHeights = await page
|
|
.locator(".properties .panel-title")
|
|
.evaluateAll((elements) =>
|
|
elements.map((element) => element.getBoundingClientRect().height),
|
|
);
|
|
expect(panelTitleHeights).toHaveLength(2);
|
|
panelTitleHeights.forEach((height) => expect(height).toBeLessThanOrEqual(32));
|
|
|
|
const compactTableMetrics = await page
|
|
.locator(".properties .parameter-table")
|
|
.evaluateAll((tables) =>
|
|
tables.map((table) => {
|
|
const content = table.closest<HTMLElement>(".properties-subpane-content");
|
|
const headerCell = table.querySelector<HTMLElement>("thead th");
|
|
const bodyRows = Array.from(
|
|
table.querySelectorAll<HTMLElement>("tbody .parameter-table-row"),
|
|
);
|
|
const control = table.querySelector<HTMLElement>("input, select");
|
|
if (!content || !headerCell || !control) {
|
|
throw new Error("参数表缺少布局测量所需的元素");
|
|
}
|
|
const tableBounds = table.getBoundingClientRect();
|
|
const contentBounds = content.getBoundingClientRect();
|
|
const tableStyle = getComputedStyle(table);
|
|
const contentLeft = contentBounds.left + content.clientLeft;
|
|
const contentRight = contentLeft + content.clientWidth;
|
|
const controlBounds = control.getBoundingClientRect();
|
|
const controlCell = control.closest<HTMLElement>("td");
|
|
if (!controlCell) {
|
|
throw new Error("参数控件不在数据单元格中");
|
|
}
|
|
const controlCellBounds = controlCell.getBoundingClientRect();
|
|
return {
|
|
leftInset: tableBounds.left - contentLeft,
|
|
rightInset: contentRight - tableBounds.right,
|
|
borderRadius: tableStyle.borderRadius,
|
|
borderTopWidth: tableStyle.borderTopWidth,
|
|
headerHeight: headerCell.getBoundingClientRect().height,
|
|
headerPosition: getComputedStyle(headerCell).position,
|
|
maximumRowHeight: Math.max(
|
|
...bodyRows.map((row) => row.getBoundingClientRect().height),
|
|
),
|
|
controlHeight: controlBounds.height,
|
|
controlContained:
|
|
controlBounds.left >= controlCellBounds.left - 0.5 &&
|
|
controlBounds.right <= controlCellBounds.right + 0.5 &&
|
|
controlBounds.top >= controlCellBounds.top - 0.5 &&
|
|
controlBounds.bottom <= controlCellBounds.bottom + 0.5,
|
|
};
|
|
}),
|
|
);
|
|
expect(compactTableMetrics).toHaveLength(2);
|
|
compactTableMetrics.forEach((metrics) => {
|
|
expect(Math.abs(metrics.leftInset)).toBeLessThanOrEqual(1);
|
|
expect(Math.abs(metrics.rightInset)).toBeLessThanOrEqual(1);
|
|
expect(metrics.borderRadius).toBe("0px");
|
|
expect(metrics.borderTopWidth).toBe("0px");
|
|
expect(metrics.headerHeight).toBeLessThanOrEqual(28);
|
|
expect(metrics.headerPosition).toBe("sticky");
|
|
expect(metrics.maximumRowHeight).toBeLessThanOrEqual(32);
|
|
expect(metrics.controlHeight).toBeGreaterThanOrEqual(22);
|
|
expect(metrics.controlHeight).toBeLessThanOrEqual(28);
|
|
expect(metrics.controlContained).toBe(true);
|
|
});
|
|
await expect(
|
|
componentTable.getByRole("rowheader", {
|
|
name: "组件名称",
|
|
exact: true,
|
|
}),
|
|
).toHaveCSS("text-align", "left");
|
|
await expect(page.getByLabel("参考压力", { exact: true })).toHaveCSS(
|
|
"text-align",
|
|
"right",
|
|
);
|
|
const valueColumnAlignment = await page
|
|
.locator(".properties .parameter-table tbody .parameter-table-value-cell")
|
|
.evaluateAll((cells) =>
|
|
cells.every((cell) => {
|
|
const controls = Array.from(
|
|
cell.querySelectorAll<HTMLElement>("input, select"),
|
|
);
|
|
return (
|
|
getComputedStyle(cell).textAlign === "right" &&
|
|
controls.every(
|
|
(control) => getComputedStyle(control).textAlign === "right",
|
|
)
|
|
);
|
|
}),
|
|
);
|
|
expect(valueColumnAlignment).toBe(true);
|
|
await expect(
|
|
componentTable.locator(".parameter-table-read-only-value").first(),
|
|
).toHaveCSS("justify-content", "flex-end");
|
|
await expect(page.getByLabel("介质物性索引", { exact: true })).toHaveCSS(
|
|
"text-align-last",
|
|
"right",
|
|
);
|
|
const solver = page.getByLabel("求解器", { exact: true });
|
|
await expect(solver).toHaveCSS("text-align", "right");
|
|
await expect(solver).toHaveCSS("text-align-last", "right");
|
|
await solver.selectOption("Radau");
|
|
await expect(solver).toHaveValue("Radau");
|
|
});
|
|
|
|
test("通用离散选项按多条件控制参数行且保留隐藏值和未知旧值", async ({
|
|
page,
|
|
}) => {
|
|
await page.goto("/");
|
|
await addChoiceVisibilityTestComponent(page);
|
|
|
|
const componentTable = page.getByRole("table", { name: "组件参数" });
|
|
const mode = page.getByLabel("工作模式", { exact: true });
|
|
const variant = page.getByLabel("高级变体", { exact: true });
|
|
await expect(mode).toHaveValue("1");
|
|
await expect(mode.locator("option:checked")).toHaveText("基础模式");
|
|
await expect(variant).toHaveValue("10");
|
|
await expect(parameterRow(componentTable, "基础参数")).toBeVisible();
|
|
await expect(parameterRow(componentTable, "高级参数")).toHaveCount(0);
|
|
await expect(parameterRow(componentTable, "组合参数")).toHaveCount(0);
|
|
|
|
await mode.selectOption("2");
|
|
await expect(parameterRow(componentTable, "基础参数")).toHaveCount(0);
|
|
await expect(parameterRow(componentTable, "高级参数")).toBeVisible();
|
|
await expect(parameterRow(componentTable, "组合参数")).toHaveCount(0);
|
|
const advancedValue = page.getByLabel("高级参数", { exact: true });
|
|
await advancedValue.fill("42");
|
|
await advancedValue.press("Enter");
|
|
|
|
await variant.selectOption("20");
|
|
await expect(parameterRow(componentTable, "组合参数")).toBeVisible();
|
|
await mode.selectOption("3");
|
|
await expect(parameterRow(componentTable, "高级参数")).toBeVisible();
|
|
await expect(parameterRow(componentTable, "组合参数")).toBeVisible();
|
|
await mode.selectOption("1");
|
|
await expect(parameterRow(componentTable, "高级参数")).toHaveCount(0);
|
|
await expect(parameterRow(componentTable, "组合参数")).toHaveCount(0);
|
|
await mode.selectOption("2");
|
|
await expect(page.getByLabel("高级参数", { exact: true })).toHaveValue("42");
|
|
|
|
await page.getByRole("button", { name: "保存工程", exact: true }).click();
|
|
const changedProjectCount = await page.evaluate(() => {
|
|
let changed = 0;
|
|
Object.keys(window.localStorage)
|
|
.filter((key) =>
|
|
[
|
|
"system-simulation-flow:autosave",
|
|
"system-simulation-flow:project:",
|
|
].some((prefix) => key.startsWith(prefix)),
|
|
)
|
|
.forEach((key) => {
|
|
const raw = window.localStorage.getItem(key);
|
|
if (!raw) {
|
|
return;
|
|
}
|
|
const project = JSON.parse(raw) as {
|
|
nodes?: Array<{
|
|
id?: string;
|
|
data?: { parameters?: Record<string, number | string> };
|
|
}>;
|
|
};
|
|
const node = project.nodes?.find(
|
|
(candidate) => candidate.id === "choice_visibility_test_1",
|
|
);
|
|
if (!node?.data?.parameters) {
|
|
return;
|
|
}
|
|
node.data.parameters.mode = 99;
|
|
window.localStorage.setItem(key, JSON.stringify(project));
|
|
changed += 1;
|
|
});
|
|
return changed;
|
|
});
|
|
expect(changedProjectCount).toBeGreaterThanOrEqual(2);
|
|
|
|
await page.getByRole("button", { name: "加载工程", exact: true }).click();
|
|
await page
|
|
.locator('.flow-canvas .react-flow__node[data-id="choice_visibility_test_1"]')
|
|
.click();
|
|
await expect(mode).toHaveValue("");
|
|
await expect(mode).toHaveAttribute("aria-invalid", "true");
|
|
await expect(mode.locator("option:checked")).toHaveText(
|
|
"当前选项无效,请重新选择",
|
|
);
|
|
await expect(parameterRow(componentTable, "基础参数")).toHaveCount(0);
|
|
await expect(parameterRow(componentTable, "高级参数")).toHaveCount(0);
|
|
await expect(parameterRow(componentTable, "组合参数")).toHaveCount(0);
|
|
|
|
await page.getByRole("button", { name: "保存工程", exact: true }).click();
|
|
const persistedModes = await page.evaluate(() =>
|
|
Object.keys(window.localStorage)
|
|
.filter((key) => key.startsWith("system-simulation-flow:project:"))
|
|
.flatMap((key) => {
|
|
const raw = window.localStorage.getItem(key);
|
|
if (!raw) {
|
|
return [];
|
|
}
|
|
const project = JSON.parse(raw) as {
|
|
nodes?: Array<{
|
|
id?: string;
|
|
data?: { parameters?: Record<string, number | string> };
|
|
}>;
|
|
};
|
|
const node = project.nodes?.find(
|
|
(candidate) => candidate.id === "choice_visibility_test_1",
|
|
);
|
|
return node?.data?.parameters ? [node.data.parameters.mode] : [];
|
|
}),
|
|
);
|
|
expect(persistedModes).toContain(99);
|
|
|
|
await mode.selectOption("2");
|
|
await expect(mode).toHaveValue("2");
|
|
await expect(mode).not.toHaveAttribute("aria-invalid", "true");
|
|
await expect(page.getByLabel("高级参数", { exact: true })).toHaveValue("42");
|
|
});
|
|
|
|
test("PNOR001 和 PNL0001 控制项前置并切换折叠组中的条件参数", async ({
|
|
page,
|
|
}) => {
|
|
await page.goto("/");
|
|
await addParameterMetadataTestComponent(
|
|
page,
|
|
"PNOR001 常系数气动孔口",
|
|
"amesim_pnor001",
|
|
);
|
|
|
|
const componentTable = page.getByRole("table", { name: "组件参数" });
|
|
const flowset = page.getByLabel("流量系数设置", { exact: true });
|
|
const flowGroup = componentTable.getByRole("button", { name: /流量系数/ });
|
|
await expect(flowset.locator("option")).toHaveText(["Cq", "Cv", "Kv"]);
|
|
await expect(flowset).toHaveValue("1");
|
|
await expect(flowGroup).toHaveAttribute("aria-expanded", "false");
|
|
await expect(page.getByLabel("流量系数 Cq", { exact: true })).toBeHidden();
|
|
await expect(page.getByLabel("孔口面积", { exact: true })).toBeHidden();
|
|
await expect(parameterRow(componentTable, "流量系数 Cv")).toHaveCount(0);
|
|
const flowsetBox = await parameterRow(
|
|
componentTable,
|
|
"流量系数设置",
|
|
).boundingBox();
|
|
const flowGroupBox = await flowGroup.boundingBox();
|
|
expect(flowsetBox).not.toBeNull();
|
|
expect(flowGroupBox).not.toBeNull();
|
|
expect(flowsetBox!.y).toBeLessThan(flowGroupBox!.y);
|
|
|
|
await flowGroup.click();
|
|
const area = page.getByLabel("孔口面积", { exact: true });
|
|
await expect(page.getByLabel("流量系数 Cq", { exact: true })).toBeVisible();
|
|
await expect(area).toBeVisible();
|
|
await area.fill("7.5e-6");
|
|
await area.press("Enter");
|
|
await expect(area).toHaveValue("7.5e-6");
|
|
|
|
await flowset.selectOption("2");
|
|
await expect(parameterRow(componentTable, "流量系数 Cq")).toHaveCount(0);
|
|
await expect(parameterRow(componentTable, "孔口面积")).toHaveCount(0);
|
|
await expect(page.getByLabel("流量系数 Cv", { exact: true })).toBeVisible();
|
|
await expect(parameterRow(componentTable, "流量系数 Kv")).toHaveCount(0);
|
|
await flowset.selectOption("1");
|
|
await expect(area).toHaveValue("7.5e-6");
|
|
|
|
await addParameterMetadataTestComponent(
|
|
page,
|
|
"PNL0001 C-R 动态管路",
|
|
"amesim_pnl0001",
|
|
);
|
|
const mode = page.getByLabel("热模型", { exact: true });
|
|
const thermalGroup = componentTable.getByRole("button", { name: /热力学/ });
|
|
await expect(mode.locator("option")).toHaveText(["多方过程", "带换热"]);
|
|
await expect(mode).toHaveValue("2");
|
|
const pnlParameterOrder = (
|
|
await componentTable.getByRole("rowheader").allTextContents()
|
|
).map((text) => text.trim());
|
|
expect(pnlParameterOrder.indexOf("热模型")).toBeLessThan(
|
|
pnlParameterOrder.indexOf("管径"),
|
|
);
|
|
await expect(thermalGroup).toHaveAttribute("aria-expanded", "false");
|
|
await thermalGroup.click();
|
|
await expect(parameterRow(componentTable, "多方指数")).toHaveCount(0);
|
|
await expect(page.getByLabel("换热系数", { exact: true })).toBeVisible();
|
|
await expect(page.getByLabel("外部温度", { exact: true })).toBeVisible();
|
|
|
|
await mode.selectOption("1");
|
|
await expect(page.getByLabel("多方指数", { exact: true })).toBeVisible();
|
|
await expect(parameterRow(componentTable, "换热系数")).toHaveCount(0);
|
|
await expect(parameterRow(componentTable, "外部温度")).toHaveCount(0);
|
|
await expect(thermalGroup).toHaveAttribute("aria-expanded", "true");
|
|
});
|
|
|
|
test("UD00 段数前置并逐段显示第 2 至第 4 段参数", async ({ page }) => {
|
|
await page.goto("/");
|
|
await addParameterMetadataTestComponent(
|
|
page,
|
|
"UD00 分段线性信号",
|
|
"amesim_ud00",
|
|
);
|
|
|
|
const componentTable = page.getByRole("table", { name: "组件参数" });
|
|
const stageCount = page.getByLabel("段数", { exact: true });
|
|
const cyclic = page.getByLabel("循环", { exact: true });
|
|
const stageGroup = componentTable.getByRole("button", { name: /分段参数/ });
|
|
await expect(stageCount.locator("option")).toHaveText([
|
|
"1",
|
|
"2",
|
|
"3",
|
|
"4",
|
|
"5",
|
|
"6",
|
|
"7",
|
|
"8",
|
|
]);
|
|
await expect(cyclic.locator("option")).toHaveText(["否", "是"]);
|
|
const parameterOrder = (
|
|
await componentTable.getByRole("rowheader").allTextContents()
|
|
).map((text) => text.trim());
|
|
expect(parameterOrder.indexOf("段数")).toBeLessThan(
|
|
parameterOrder.indexOf("启动时间"),
|
|
);
|
|
expect(parameterOrder.indexOf("循环")).toBeLessThan(
|
|
parameterOrder.indexOf("启动时间"),
|
|
);
|
|
await expect(stageGroup).toHaveAttribute("aria-expanded", "false");
|
|
await expect(page.getByLabel("第 1 段起点", { exact: true })).toBeHidden();
|
|
await stageGroup.click();
|
|
await expect(page.getByLabel("第 1 段起点", { exact: true })).toBeVisible();
|
|
|
|
const expectStageVisibility = async (stage: number, visible: boolean) => {
|
|
for (const suffix of ["起点", "终点", "时长"]) {
|
|
const row = parameterRow(componentTable, `第 ${stage} 段${suffix}`);
|
|
if (visible) {
|
|
await expect(row).toBeVisible();
|
|
} else {
|
|
await expect(row).toHaveCount(0);
|
|
}
|
|
}
|
|
};
|
|
|
|
await expectStageVisibility(2, false);
|
|
await expectStageVisibility(3, false);
|
|
await expectStageVisibility(4, false);
|
|
await stageCount.selectOption("2");
|
|
await expectStageVisibility(2, true);
|
|
await expectStageVisibility(3, false);
|
|
await expectStageVisibility(4, false);
|
|
await stageCount.selectOption("3");
|
|
await expectStageVisibility(2, true);
|
|
await expectStageVisibility(3, true);
|
|
await expectStageVisibility(4, false);
|
|
await stageCount.selectOption("4");
|
|
await expectStageVisibility(2, true);
|
|
await expectStageVisibility(3, true);
|
|
await expectStageVisibility(4, true);
|
|
await expect(stageGroup).toHaveAttribute("aria-expanded", "true");
|
|
});
|
|
|
|
test("LSTP00A 刚度模式前置并切换数值与几何刚度参数", async ({ page }) => {
|
|
await page.goto("/");
|
|
await addParameterMetadataTestComponent(
|
|
page,
|
|
"LSTP00A 弹性接触",
|
|
"amesim_lstp00a",
|
|
);
|
|
|
|
const componentTable = page.getByRole("table", { name: "组件参数" });
|
|
const stiffnessMode = page.getByLabel("刚度模式", { exact: true });
|
|
const contactOption = page.getByLabel("允许负接触力", { exact: true });
|
|
const stiffnessGroup = componentTable.getByRole("button", { name: /刚度/ });
|
|
const contactGroup = componentTable.getByRole("button", { name: /接触/ });
|
|
await expect(stiffnessMode.locator("option")).toHaveText([
|
|
"数值刚度",
|
|
"几何参数",
|
|
]);
|
|
await expect(contactOption.locator("option")).toHaveText([
|
|
"允许负接触力",
|
|
"不允许负接触力",
|
|
]);
|
|
await expect(stiffnessGroup).toHaveAttribute("aria-expanded", "false");
|
|
await expect(contactGroup).toHaveAttribute("aria-expanded", "false");
|
|
const stiffnessModeBox = await parameterRow(
|
|
componentTable,
|
|
"刚度模式",
|
|
).boundingBox();
|
|
const stiffnessGroupBox = await stiffnessGroup.boundingBox();
|
|
expect(stiffnessModeBox).not.toBeNull();
|
|
expect(stiffnessGroupBox).not.toBeNull();
|
|
expect(stiffnessModeBox!.y).toBeLessThan(stiffnessGroupBox!.y);
|
|
|
|
await stiffnessGroup.click();
|
|
await expect(page.getByLabel("接触刚度", { exact: true })).toBeVisible();
|
|
for (const label of ["有效圈数", "剪切模量", "弹簧直径", "线径"]) {
|
|
await expect(parameterRow(componentTable, label)).toHaveCount(0);
|
|
}
|
|
|
|
await stiffnessMode.selectOption("2");
|
|
await expect(parameterRow(componentTable, "接触刚度")).toHaveCount(0);
|
|
for (const label of ["有效圈数", "剪切模量", "弹簧直径", "线径"]) {
|
|
await expect(page.getByLabel(label, { exact: true })).toBeVisible();
|
|
}
|
|
await expect(stiffnessGroup).toHaveAttribute("aria-expanded", "true");
|
|
|
|
await stiffnessMode.selectOption("1");
|
|
await expect(page.getByLabel("接触刚度", { exact: true })).toBeVisible();
|
|
await expect(parameterRow(componentTable, "剪切模量")).toHaveCount(0);
|
|
});
|
|
|
|
test("MECMAS21 可配置参数前置并按条件显示可折叠参数组", async ({
|
|
page,
|
|
}) => {
|
|
await page.goto("/");
|
|
await addMecmasEncodingTestComponent(page);
|
|
|
|
const componentTable = page.getByRole("table", { name: "组件参数" });
|
|
const useFriction = page.getByLabel("启用摩擦", { exact: true });
|
|
const stoptype = page.getByLabel("限位类型", { exact: true });
|
|
const frictionGroup = componentTable.getByRole("button", { name: /摩擦/ });
|
|
const endstopsGroup = componentTable.getByRole("button", { name: /限位/ });
|
|
const parameterOrder = (
|
|
await componentTable.getByRole("rowheader").allTextContents()
|
|
).map((text) => text.trim());
|
|
|
|
expect(parameterOrder.indexOf("启用摩擦")).toBeGreaterThanOrEqual(0);
|
|
expect(parameterOrder.indexOf("限位类型")).toBeGreaterThanOrEqual(0);
|
|
expect(parameterOrder.indexOf("质量")).toBeGreaterThanOrEqual(0);
|
|
expect(parameterOrder.indexOf("启用摩擦")).toBeLessThan(
|
|
parameterOrder.indexOf("质量"),
|
|
);
|
|
expect(parameterOrder.indexOf("限位类型")).toBeLessThan(
|
|
parameterOrder.indexOf("质量"),
|
|
);
|
|
await expect(useFriction).toHaveValue("1");
|
|
await expect(stoptype).toHaveValue("4");
|
|
await expect(frictionGroup).toHaveCount(0);
|
|
await expect(endstopsGroup).toHaveCount(0);
|
|
|
|
await useFriction.selectOption("2");
|
|
await expect(frictionGroup).toBeVisible();
|
|
await expect(frictionGroup).toHaveAttribute("aria-expanded", "false");
|
|
await expect(frictionGroup.locator("xpath=ancestor::th")).toHaveAttribute(
|
|
"colspan",
|
|
"3",
|
|
);
|
|
await expect(page.getByLabel("摩擦类型", { exact: true })).toBeHidden();
|
|
const frictionGroupBox = await frictionGroup.boundingBox();
|
|
const unrelatedRowBox = await parameterRow(
|
|
componentTable,
|
|
"无关编码",
|
|
).boundingBox();
|
|
expect(frictionGroupBox).not.toBeNull();
|
|
expect(unrelatedRowBox).not.toBeNull();
|
|
expect(frictionGroupBox!.y).toBeGreaterThan(unrelatedRowBox!.y);
|
|
|
|
await frictionGroup.focus();
|
|
await expect(frictionGroup).toBeFocused();
|
|
await page.keyboard.press("Enter");
|
|
await expect(frictionGroup).toHaveAttribute("aria-expanded", "true");
|
|
const frictionType = page.getByLabel("摩擦类型", { exact: true });
|
|
const strib = page.getByLabel("Stribeck 选项", { exact: true });
|
|
const viscousFriction = page.getByLabel("黏性摩擦系数", { exact: true });
|
|
await expect(frictionType).toBeVisible();
|
|
await expect(viscousFriction).toBeVisible();
|
|
await expect(strib).toHaveCount(0);
|
|
|
|
await frictionType.selectOption("2");
|
|
await expect(strib).toBeVisible();
|
|
const expandedOrder = (
|
|
await componentTable.getByRole("rowheader").allTextContents()
|
|
).map((text) => text.trim());
|
|
expect(expandedOrder.indexOf("摩擦类型")).toBeLessThan(
|
|
expandedOrder.indexOf("Stribeck 选项"),
|
|
);
|
|
expect(expandedOrder.indexOf("Stribeck 选项")).toBeLessThan(
|
|
expandedOrder.indexOf("黏性摩擦系数"),
|
|
);
|
|
await frictionGroup.focus();
|
|
await expect(frictionGroup).toBeFocused();
|
|
await page.keyboard.press("Space");
|
|
await expect(frictionGroup).toHaveAttribute("aria-expanded", "false");
|
|
await expect(frictionType).toBeHidden();
|
|
await expect(strib).toBeHidden();
|
|
await expect(viscousFriction).toBeHidden();
|
|
|
|
await page.keyboard.press("Space");
|
|
await expect(frictionGroup).toHaveAttribute("aria-expanded", "true");
|
|
await expect(frictionType).toHaveValue("2");
|
|
|
|
await stoptype.selectOption("2");
|
|
await expect(endstopsGroup).toBeVisible();
|
|
await expect(endstopsGroup).toHaveAttribute("aria-expanded", "false");
|
|
await expect(endstopsGroup.locator("xpath=ancestor::th")).toHaveAttribute(
|
|
"colspan",
|
|
"3",
|
|
);
|
|
await endstopsGroup.focus();
|
|
await page.keyboard.press("Enter");
|
|
await expect(endstopsGroup).toHaveAttribute("aria-expanded", "true");
|
|
await expect(page.getByLabel("允许负接触力", { exact: true })).toBeVisible();
|
|
await expect(page.getByLabel("下位移限位", { exact: true })).toBeVisible();
|
|
const endstopOrder = (
|
|
await componentTable.getByRole("rowheader").allTextContents()
|
|
).map((text) => text.trim());
|
|
expect(endstopOrder.indexOf("允许负接触力")).toBeLessThan(
|
|
endstopOrder.indexOf("下位移限位"),
|
|
);
|
|
const endstopsGroupBox = await endstopsGroup.boundingBox();
|
|
expect(endstopsGroupBox).not.toBeNull();
|
|
expect(frictionGroupBox!.y).toBeLessThan(endstopsGroupBox!.y);
|
|
|
|
await stoptype.selectOption("3");
|
|
await expect(frictionGroup).toBeVisible();
|
|
await expect(endstopsGroup).toBeVisible();
|
|
await expect(frictionType).toHaveCount(0);
|
|
await expect(strib).toHaveCount(0);
|
|
await expect(viscousFriction).toBeVisible();
|
|
await expect(page.getByLabel("允许负接触力", { exact: true })).toHaveCount(0);
|
|
await expect(page.getByLabel("下位移限位", { exact: true })).toBeVisible();
|
|
|
|
await stoptype.selectOption("4");
|
|
await expect(endstopsGroup).toHaveCount(0);
|
|
await expect(frictionType).toBeVisible();
|
|
});
|
|
|
|
test("MECMAS21 参数组折叠和条件切换保留值并清理详情浮层", async ({
|
|
page,
|
|
}) => {
|
|
await page.goto("/");
|
|
await addMecmasEncodingTestComponent(page);
|
|
|
|
const componentTable = page.getByRole("table", { name: "组件参数" });
|
|
const useFriction = page.getByLabel("启用摩擦", { exact: true });
|
|
const frictionGroup = componentTable.getByRole("button", { name: /摩擦/ });
|
|
await useFriction.selectOption("2");
|
|
await frictionGroup.click();
|
|
|
|
const frictionType = page.getByLabel("摩擦类型", { exact: true });
|
|
await frictionType.selectOption("2");
|
|
const strib = page.getByLabel("Stribeck 选项", { exact: true });
|
|
const viscousFriction = page.getByLabel("黏性摩擦系数", { exact: true });
|
|
await strib.selectOption("2");
|
|
await viscousFriction.fill("12.5");
|
|
await viscousFriction.press("Enter");
|
|
await expect(viscousFriction).toHaveValue("12.5");
|
|
|
|
await frictionGroup.focus();
|
|
await parameterNameCell(componentTable, "黏性摩擦系数").hover();
|
|
await page.waitForTimeout(600);
|
|
const preview = page.locator("body > #parameter-help-preview");
|
|
await expect(preview.locator(".parameter-help-preview-title")).toHaveText(
|
|
"黏性摩擦系数",
|
|
);
|
|
await page.keyboard.press("Enter");
|
|
await expect(frictionGroup).toHaveAttribute("aria-expanded", "false");
|
|
await expect(preview).toHaveCount(0);
|
|
|
|
await frictionGroup.click();
|
|
await expect(frictionType).toHaveValue("2");
|
|
await expect(strib).toHaveValue("2");
|
|
await expect(viscousFriction).toHaveValue("12.5");
|
|
|
|
await useFriction.selectOption("1");
|
|
await expect(frictionGroup).toHaveCount(0);
|
|
await useFriction.selectOption("2");
|
|
await expect(frictionGroup).toBeVisible();
|
|
if ((await frictionGroup.getAttribute("aria-expanded")) !== "true") {
|
|
await frictionGroup.click();
|
|
}
|
|
await expect(frictionType).toHaveValue("2");
|
|
await expect(strib).toHaveValue("2");
|
|
await expect(viscousFriction).toHaveValue("12.5");
|
|
|
|
await viscousFriction.fill("-1");
|
|
await viscousFriction.press("Enter");
|
|
await expect(componentTable.getByText("不能小于 0", { exact: true })).toBeVisible();
|
|
await frictionGroup.click();
|
|
await expect(frictionGroup).toHaveAttribute("aria-expanded", "false");
|
|
await expect(frictionGroup).toContainText("1 个错误");
|
|
|
|
await useFriction.selectOption("1");
|
|
await expect(frictionGroup).toBeVisible();
|
|
await expect(frictionGroup).toContainText("1 个错误");
|
|
await expect(frictionGroup).toHaveAttribute("aria-describedby");
|
|
await frictionGroup.click();
|
|
await expect(componentTable).toContainText(
|
|
"1 个错误参数被当前配置选项隐藏,请调整上方配置后修改",
|
|
);
|
|
});
|
|
|
|
test("MECMAS21 旧参数编码只迁移一次且新工程和 XML 写入版本", async ({
|
|
page,
|
|
}) => {
|
|
await page.goto("/");
|
|
await addMecmasEncodingTestComponent(page);
|
|
|
|
const useFriction = page.getByLabel("启用摩擦", { exact: true });
|
|
const frictionGroup = page
|
|
.getByRole("table", { name: "组件参数" })
|
|
.getByRole("button", { name: /摩擦/ });
|
|
await expect(useFriction).toHaveValue("1");
|
|
await expect(frictionGroup).toHaveCount(0);
|
|
|
|
await page.getByRole("button", { name: "保存工程", exact: true }).click();
|
|
const savedEncodingVersions = await page.evaluate(() =>
|
|
Object.keys(window.localStorage)
|
|
.filter((key) => key.startsWith("system-simulation-flow:project:"))
|
|
.flatMap((key) => {
|
|
const raw = window.localStorage.getItem(key);
|
|
if (!raw) {
|
|
return [];
|
|
}
|
|
const project = JSON.parse(raw) as {
|
|
amesimParameterEncodingVersion?: number | null;
|
|
nodes?: Array<{ id?: string }>;
|
|
};
|
|
return project.nodes?.some(
|
|
(node) => node.id === "amesim_mecmas21_1",
|
|
)
|
|
? [project.amesimParameterEncodingVersion]
|
|
: [];
|
|
}),
|
|
);
|
|
expect(savedEncodingVersions).toContain(1);
|
|
|
|
await page.getByRole("button", { name: "生成系统 XML", exact: true }).click();
|
|
await expect(page.getByLabel("生成的系统 XML")).toContainText(
|
|
'amesimParameterEncodingVersion="1"',
|
|
);
|
|
|
|
const migratedProjectCount = await page.evaluate(() => {
|
|
let changed = 0;
|
|
Object.keys(window.localStorage)
|
|
.filter((key) =>
|
|
[
|
|
"system-simulation-flow:autosave",
|
|
"system-simulation-flow:project:",
|
|
].some((prefix) => key.startsWith(prefix)),
|
|
)
|
|
.forEach((key) => {
|
|
const raw = window.localStorage.getItem(key);
|
|
if (!raw) {
|
|
return;
|
|
}
|
|
const project = JSON.parse(raw) as {
|
|
mediumReferenceVersion?: number | null;
|
|
amesimParameterEncodingVersion?: number | null;
|
|
nodes?: Array<{
|
|
id?: string;
|
|
data?: {
|
|
modelType?: string;
|
|
parameters?: Record<string, number | string>;
|
|
};
|
|
}>;
|
|
};
|
|
const node = project.nodes?.find(
|
|
(candidate) => candidate.id === "amesim_mecmas21_1",
|
|
);
|
|
if (!node?.data?.parameters) {
|
|
return;
|
|
}
|
|
project.amesimParameterEncodingVersion = null;
|
|
project.mediumReferenceVersion = null;
|
|
delete node.data.modelType;
|
|
node.data.parameters.useFriction = 0;
|
|
delete node.data.parameters.strib;
|
|
node.data.parameters.unrelatedCode = 9;
|
|
window.localStorage.setItem(key, JSON.stringify(project));
|
|
changed += 1;
|
|
});
|
|
return changed;
|
|
});
|
|
expect(migratedProjectCount).toBeGreaterThanOrEqual(2);
|
|
|
|
await page.getByRole("button", { name: "加载工程", exact: true }).click();
|
|
await page
|
|
.locator('.flow-canvas .react-flow__node[data-id="amesim_mecmas21_1"]')
|
|
.click();
|
|
await expect(useFriction).toHaveValue("1");
|
|
await expect(useFriction.locator("option:checked")).toHaveText("禁用");
|
|
await useFriction.selectOption("2");
|
|
await expect(frictionGroup).toHaveAttribute("aria-expanded", "false");
|
|
await frictionGroup.click();
|
|
const frictionType = page.getByLabel("摩擦类型", { exact: true });
|
|
await frictionType.selectOption("2");
|
|
const strib = page.getByLabel("Stribeck 选项", { exact: true });
|
|
await expect(strib).toHaveValue("2");
|
|
await expect(strib.locator("option:checked")).toHaveText("启用");
|
|
await expect(page.getByLabel("无关编码", { exact: true })).toHaveValue("9");
|
|
await useFriction.selectOption("1");
|
|
await expect(frictionGroup).toHaveCount(0);
|
|
|
|
await page.getByRole("button", { name: "保存工程", exact: true }).click();
|
|
await page.getByRole("button", { name: "加载工程", exact: true }).click();
|
|
await page
|
|
.locator('.flow-canvas .react-flow__node[data-id="amesim_mecmas21_1"]')
|
|
.click();
|
|
await expect(useFriction).toHaveValue("1");
|
|
await expect(page.getByLabel("无关编码", { exact: true })).toHaveValue("9");
|
|
await useFriction.selectOption("2");
|
|
await expect(frictionGroup).toBeVisible();
|
|
if ((await frictionGroup.getAttribute("aria-expanded")) !== "true") {
|
|
await frictionGroup.click();
|
|
}
|
|
await expect(frictionType).toHaveValue("2");
|
|
await expect(strib).toHaveValue("2");
|
|
});
|
|
|
|
test("详情浮层只在参数列悬停且下拉选择期间不触发", async ({ page }) => {
|
|
await page.goto("/");
|
|
await addMecmasEncodingTestComponent(page);
|
|
|
|
const componentTable = page.getByRole("table", { name: "组件参数" });
|
|
const choice = page.getByLabel("启用摩擦", { exact: true });
|
|
const otherNameCell = parameterNameCell(componentTable, "无关编码");
|
|
const preview = page.locator("body > #parameter-help-preview");
|
|
|
|
await otherNameCell.hover();
|
|
await page.waitForTimeout(600);
|
|
await expect(preview).toBeVisible();
|
|
await expect(preview.locator(".parameter-help-preview-title")).toHaveText(
|
|
"无关编码",
|
|
);
|
|
|
|
await choice.hover();
|
|
await expect(preview).toHaveCount(0);
|
|
await choice.focus();
|
|
await expect(choice).toBeFocused();
|
|
await otherNameCell.hover();
|
|
await page.waitForTimeout(600);
|
|
await expect(preview).toHaveCount(0);
|
|
|
|
await choice.selectOption("2");
|
|
await choice.focus();
|
|
await otherNameCell.hover();
|
|
await page.waitForTimeout(600);
|
|
await expect(preview).toHaveCount(0);
|
|
|
|
await choice.evaluate((element) => element.blur());
|
|
await page.locator(".panel-title").filter({ hasText: "组件参数" }).hover();
|
|
await otherNameCell.hover();
|
|
await page.waitForTimeout(600);
|
|
await expect(preview.locator(".parameter-help-preview-title")).toHaveText(
|
|
"无关编码",
|
|
);
|
|
});
|
|
|
|
test("最窄参数面板滚动时表头固定且值列保持可用宽度", async ({ page }) => {
|
|
await page.goto("/");
|
|
await addPneumaticTestComponent(page);
|
|
|
|
const widthSeparator = page.getByRole("separator", {
|
|
name: "调整参数面板宽度",
|
|
exact: true,
|
|
});
|
|
await widthSeparator.focus();
|
|
await page.keyboard.press("ArrowRight");
|
|
await page.keyboard.press("ArrowRight");
|
|
await page.keyboard.press("ArrowRight");
|
|
await expect(widthSeparator).toHaveAttribute("aria-valuenow", "280");
|
|
|
|
const heightSeparator = page.getByRole("separator", {
|
|
name: "调整组件参数和仿真设置区域高度",
|
|
exact: true,
|
|
});
|
|
await heightSeparator.focus();
|
|
for (let step = 0; step < 11; step += 1) {
|
|
await page.keyboard.press("ArrowUp");
|
|
}
|
|
await expect(heightSeparator).toHaveAttribute("aria-valuenow", "120");
|
|
|
|
const componentContent = page
|
|
.getByRole("region", { name: "组件参数子栏", exact: true })
|
|
.locator(".properties-subpane-content");
|
|
await expect
|
|
.poll(() =>
|
|
componentContent.evaluate(
|
|
(element) => element.scrollHeight - element.clientHeight,
|
|
),
|
|
)
|
|
.toBeGreaterThan(0);
|
|
await componentContent.evaluate((element) => {
|
|
element.scrollTop = 48;
|
|
});
|
|
await expect
|
|
.poll(() => componentContent.evaluate((element) => element.scrollTop))
|
|
.toBeGreaterThan(0);
|
|
|
|
const narrowLayout = await componentContent.evaluate((content) => {
|
|
const header = content.querySelector<HTMLElement>(
|
|
"thead .parameter-table-name-cell",
|
|
);
|
|
const valueHeader = content.querySelector<HTMLElement>(
|
|
"thead .parameter-table-value-cell",
|
|
);
|
|
const wrap = content.querySelector<HTMLElement>(".parameter-table-wrap");
|
|
if (!header || !valueHeader || !wrap) {
|
|
throw new Error("无法测量最窄参数表布局");
|
|
}
|
|
return {
|
|
stickyTopDelta:
|
|
header.getBoundingClientRect().top - content.getBoundingClientRect().top,
|
|
valueWidth: valueHeader.getBoundingClientRect().width,
|
|
horizontalOverflow: wrap.scrollWidth - wrap.clientWidth,
|
|
};
|
|
});
|
|
expect(Math.abs(narrowLayout.stickyTopDelta)).toBeLessThanOrEqual(1);
|
|
expect(narrowLayout.valueWidth).toBeGreaterThanOrEqual(95);
|
|
expect(narrowLayout.horizontalOverflow).toBeLessThanOrEqual(1);
|
|
});
|
|
|
|
test("参数说明只在参数列悬停显示,单位切换不改变 XML 基础值", async ({
|
|
page,
|
|
}) => {
|
|
await page.goto("/");
|
|
await addPneumaticTestComponent(page);
|
|
|
|
const componentTable = page.getByRole("table", { name: "组件参数" });
|
|
const pressureRow = parameterRow(componentTable, "参考压力");
|
|
const pressureNameCell = parameterNameCell(componentTable, "参考压力");
|
|
const pressureValueCell = pressureRow.locator(".parameter-table-value-cell");
|
|
const pressureUnitCell = pressureRow.locator(".parameter-table-unit-cell");
|
|
const preview = page.locator("body > #parameter-help-preview");
|
|
|
|
await pressureValueCell.hover();
|
|
await page.waitForTimeout(600);
|
|
await expect(preview).toHaveCount(0);
|
|
await pressureUnitCell.hover();
|
|
await page.waitForTimeout(600);
|
|
await expect(preview).toHaveCount(0);
|
|
|
|
const pressureNameCellBox = await pressureNameCell.boundingBox();
|
|
expect(pressureNameCellBox).not.toBeNull();
|
|
const pointer = {
|
|
x: pressureNameCellBox!.x + pressureNameCellBox!.width / 2,
|
|
y: pressureNameCellBox!.y + pressureNameCellBox!.height / 2,
|
|
};
|
|
await page.mouse.move(pointer.x, pointer.y);
|
|
await page.waitForTimeout(600);
|
|
await expect(preview).toBeVisible();
|
|
await expect(preview.locator(".parameter-help-preview-title")).toHaveText("参考压力");
|
|
await expect(preview.locator(".parameter-help-preview-description")).toContainText(
|
|
"单位换算和 XML 基础单位输出",
|
|
);
|
|
await expect(preview.locator(".parameter-help-preview-meta")).toContainText(
|
|
"默认值:100000",
|
|
);
|
|
await expect(preview.locator(".parameter-help-preview-meta")).toContainText(
|
|
"当前单位:Pa",
|
|
);
|
|
const previewBox = await preview.boundingBox();
|
|
expect(previewBox).not.toBeNull();
|
|
const horizontalGap =
|
|
pointer.x < previewBox!.x
|
|
? previewBox!.x - pointer.x
|
|
: pointer.x > previewBox!.x + previewBox!.width
|
|
? pointer.x - (previewBox!.x + previewBox!.width)
|
|
: 0;
|
|
const verticalGap =
|
|
pointer.y < previewBox!.y
|
|
? previewBox!.y - pointer.y
|
|
: pointer.y > previewBox!.y + previewBox!.height
|
|
? pointer.y - (previewBox!.y + previewBox!.height)
|
|
: 0;
|
|
expect(Math.hypot(horizontalGap, verticalGap)).toBeLessThanOrEqual(24);
|
|
await page.mouse.move(pointer.x + 24, pointer.y);
|
|
await page.waitForTimeout(100);
|
|
const stationaryPreviewBox = await preview.boundingBox();
|
|
expect(stationaryPreviewBox).not.toBeNull();
|
|
expect(Math.abs(stationaryPreviewBox!.x - previewBox!.x)).toBeLessThanOrEqual(1);
|
|
expect(Math.abs(stationaryPreviewBox!.y - previewBox!.y)).toBeLessThanOrEqual(1);
|
|
|
|
await pressureValueCell.hover();
|
|
await expect(preview).toHaveCount(0);
|
|
await page.locator(".panel-title").filter({ hasText: "组件参数" }).hover();
|
|
await expect(preview).toHaveCount(0);
|
|
|
|
await page.getByLabel("参考压力单位").selectOption("kPa");
|
|
await expect(page.getByLabel("参考压力", { exact: true })).toHaveValue("100");
|
|
|
|
await pressureNameCell.hover();
|
|
await page.waitForTimeout(600);
|
|
await expect(preview.locator(".parameter-help-preview-meta")).toContainText(
|
|
"默认值:100",
|
|
);
|
|
await expect(preview.locator(".parameter-help-preview-meta")).toContainText(
|
|
"当前单位:kPa",
|
|
);
|
|
|
|
await page.getByRole("button", { name: "生成系统 XML", exact: true }).click();
|
|
const consolePanel = page.getByRole("complementary", {
|
|
name: "仿真控制台",
|
|
exact: true,
|
|
});
|
|
await expect(consolePanel.getByLabel("生成的系统 XML")).toContainText(
|
|
'<Parameter name="reference_pressure" value="100000"',
|
|
);
|
|
});
|
|
|
|
test("科学计数法在提交、单位换算和工程加载后保持", async ({ page }) => {
|
|
await page.goto("/");
|
|
await addPneumaticTestComponent(page);
|
|
|
|
const pressureInput = page.getByLabel("参考压力", { exact: true });
|
|
const pressureUnit = page.getByLabel("参考压力单位", { exact: true });
|
|
|
|
await pressureInput.fill("1e-5");
|
|
await pressureInput.press("Enter");
|
|
await pressureUnit.selectOption("kPa");
|
|
await expect(pressureInput).toHaveValue("1e-8");
|
|
await pressureUnit.selectOption("Pa");
|
|
await expect(pressureInput).toHaveValue("1e-5");
|
|
|
|
await pressureUnit.selectOption("kPa");
|
|
await pressureInput.fill("1.234567890123E+3");
|
|
await pressureInput.press("Enter");
|
|
await pressureUnit.selectOption("Pa");
|
|
await expect(pressureInput).toHaveValue("1.234567890123E+6");
|
|
await pressureUnit.selectOption("kPa");
|
|
await expect(pressureInput).toHaveValue("1.234567890123E+3");
|
|
|
|
await pressureInput.fill("1.20E+3");
|
|
await pressureInput.press("Enter");
|
|
await pressureUnit.selectOption("Pa");
|
|
await expect(pressureInput).toHaveValue("1.20E+6");
|
|
await pressureUnit.selectOption("kPa");
|
|
await expect(pressureInput).toHaveValue("1.20E+3");
|
|
|
|
const temperatureInput = page.getByLabel("参考温度", { exact: true });
|
|
const temperatureUnit = page.getByLabel("参考温度单位", { exact: true });
|
|
await temperatureUnit.selectOption("degC");
|
|
await temperatureInput.fill("1e-10");
|
|
await temperatureInput.press("Enter");
|
|
await temperatureUnit.selectOption("K");
|
|
await expect(temperatureInput).toHaveValue("2.731500000001e2");
|
|
await temperatureUnit.selectOption("degC");
|
|
await expect(temperatureInput).toHaveValue("1e-10");
|
|
|
|
await page.getByRole("button", { name: "保存工程", exact: true }).click();
|
|
await pressureInput.fill("2");
|
|
await pressureInput.press("Enter");
|
|
await page.getByRole("button", { name: "加载工程", exact: true }).click();
|
|
await page
|
|
.locator('.flow-canvas .react-flow__node[data-id="amesim_pneumatic_test_1"]')
|
|
.click();
|
|
await expect(pressureUnit).toHaveValue("kPa");
|
|
await expect(pressureInput).toHaveValue("1.20E+3");
|
|
await expect(temperatureUnit).toHaveValue("degC");
|
|
await expect(temperatureInput).toHaveValue("1e-10");
|
|
|
|
await page.getByRole("button", { name: "生成系统 XML", exact: true }).click();
|
|
await expect(page.getByLabel("生成的系统 XML")).toContainText(
|
|
'<Parameter name="reference_pressure" value="1200000"',
|
|
);
|
|
});
|
|
|
|
test("参数表列宽可调整并在刷新后保留", async ({ page }) => {
|
|
await page.goto("/");
|
|
|
|
const simulationTable = page.getByRole("table", { name: "仿真设置" });
|
|
const nameHeader = simulationTable.locator("thead .parameter-table-name-cell");
|
|
const separator = simulationTable.getByRole("separator", {
|
|
name: "调整参数名列宽",
|
|
exact: true,
|
|
});
|
|
const initialWidth = await nameHeader.evaluate(
|
|
(element) => element.getBoundingClientRect().width,
|
|
);
|
|
const initialValue = Number(await separator.getAttribute("aria-valuenow"));
|
|
const separatorBox = await separator.boundingBox();
|
|
expect(separatorBox).not.toBeNull();
|
|
await page.mouse.move(
|
|
separatorBox!.x + separatorBox!.width / 2,
|
|
separatorBox!.y + separatorBox!.height / 2,
|
|
);
|
|
await page.mouse.down();
|
|
await page.mouse.move(
|
|
separatorBox!.x + separatorBox!.width / 2 + 16,
|
|
separatorBox!.y + separatorBox!.height / 2,
|
|
{ steps: 3 },
|
|
);
|
|
await page.mouse.up();
|
|
|
|
await expect(separator).toHaveAttribute(
|
|
"aria-valuenow",
|
|
String(initialValue + 16),
|
|
);
|
|
const resizedWidth = await nameHeader.evaluate(
|
|
(element) => element.getBoundingClientRect().width,
|
|
);
|
|
expect(resizedWidth).toBeGreaterThan(initialWidth + 14);
|
|
|
|
await page.reload();
|
|
const reloadedTable = page.getByRole("table", { name: "仿真设置" });
|
|
await expect(
|
|
reloadedTable.getByRole("separator", {
|
|
name: "调整参数名列宽",
|
|
exact: true,
|
|
}),
|
|
).toHaveAttribute("aria-valuenow", String(initialValue + 16));
|
|
|
|
const wrapOverflow = await reloadedTable.evaluate((table) => {
|
|
const wrap = table.closest<HTMLElement>(".parameter-table-wrap");
|
|
return wrap ? wrap.scrollWidth - wrap.clientWidth : Number.POSITIVE_INFINITY;
|
|
});
|
|
expect(wrapOverflow).toBeLessThanOrEqual(1);
|
|
|
|
const reloadedSeparator = reloadedTable.getByRole("separator", {
|
|
name: "调整参数名列宽",
|
|
exact: true,
|
|
});
|
|
await reloadedSeparator.dblclick();
|
|
await expect(reloadedSeparator).toHaveAttribute("aria-valuenow", "112");
|
|
await reloadedSeparator.focus();
|
|
await page.keyboard.press("ArrowRight");
|
|
await expect(reloadedSeparator).toHaveAttribute("aria-valuenow", "120");
|
|
});
|
|
|
|
test("组件参数和仿真设置是可独立滚动并可调高度的子栏", async ({ page }) => {
|
|
await page.goto("/");
|
|
await addPneumaticTestComponent(page);
|
|
|
|
const componentPane = page.getByRole("region", {
|
|
name: "组件参数子栏",
|
|
exact: true,
|
|
});
|
|
const simulationPane = page.getByRole("region", {
|
|
name: "仿真设置子栏",
|
|
exact: true,
|
|
});
|
|
const separator = page.getByRole("separator", {
|
|
name: "调整组件参数和仿真设置区域高度",
|
|
exact: true,
|
|
});
|
|
await expect(componentPane).toBeVisible();
|
|
await expect(simulationPane).toBeVisible();
|
|
await expect(separator).toBeVisible();
|
|
|
|
const initialValue = Number(await separator.getAttribute("aria-valuenow"));
|
|
const initialMaximum = Number(await separator.getAttribute("aria-valuemax"));
|
|
expect(initialMaximum).toBeGreaterThanOrEqual(initialValue);
|
|
const initialComponentBox = await componentPane.boundingBox();
|
|
const initialSimulationBox = await simulationPane.boundingBox();
|
|
const separatorBox = await separator.boundingBox();
|
|
expect(initialComponentBox).not.toBeNull();
|
|
expect(initialSimulationBox).not.toBeNull();
|
|
expect(separatorBox).not.toBeNull();
|
|
expect(initialSimulationBox!.height).toBeGreaterThanOrEqual(120);
|
|
|
|
await page.mouse.move(
|
|
separatorBox!.x + separatorBox!.width / 2,
|
|
separatorBox!.y + separatorBox!.height / 2,
|
|
);
|
|
await page.mouse.down();
|
|
await page.mouse.move(
|
|
separatorBox!.x + separatorBox!.width / 2,
|
|
separatorBox!.y + separatorBox!.height / 2 + 40,
|
|
{ steps: 4 },
|
|
);
|
|
await page.mouse.up();
|
|
|
|
await expect(separator).toHaveAttribute(
|
|
"aria-valuenow",
|
|
String(initialValue + 40),
|
|
);
|
|
const resizedComponentBox = await componentPane.boundingBox();
|
|
const resizedSimulationBox = await simulationPane.boundingBox();
|
|
expect(resizedComponentBox!.height).toBeGreaterThan(
|
|
initialComponentBox!.height + 38,
|
|
);
|
|
expect(resizedSimulationBox!.height).toBeLessThan(
|
|
initialSimulationBox!.height - 38,
|
|
);
|
|
|
|
const overflowModes = await page
|
|
.locator(".properties-subpane-content")
|
|
.evaluateAll((elements) =>
|
|
elements.map((element) => getComputedStyle(element).overflowY),
|
|
);
|
|
expect(overflowModes).toEqual(["auto", "auto"]);
|
|
|
|
await page.reload();
|
|
const reloadedSeparator = page.getByRole("separator", {
|
|
name: "调整组件参数和仿真设置区域高度",
|
|
exact: true,
|
|
});
|
|
await expect(reloadedSeparator).toHaveAttribute(
|
|
"aria-valuenow",
|
|
String(initialValue + 40),
|
|
);
|
|
await reloadedSeparator.focus();
|
|
await page.keyboard.press("ArrowUp");
|
|
await expect(reloadedSeparator).toHaveAttribute(
|
|
"aria-valuenow",
|
|
String(initialValue + 20),
|
|
);
|
|
await reloadedSeparator.dblclick();
|
|
await expect(reloadedSeparator).toHaveAttribute("aria-valuenow", "340");
|
|
});
|
|
|
|
test("组件参数公式保留原式,并在说明中显示计算值且 XML 只写数值", async ({
|
|
page,
|
|
}) => {
|
|
await page.goto("/");
|
|
await addPneumaticTestComponent(page);
|
|
|
|
const expression = "3.14*10^2/4";
|
|
const componentTable = page.getByRole("table", { name: "组件参数" });
|
|
const pressureRow = parameterRow(componentTable, "参考压力");
|
|
const pressureInput = page.getByLabel("参考压力", { exact: true });
|
|
|
|
await page.getByLabel("参考压力单位").selectOption("kPa");
|
|
await pressureInput.fill(expression);
|
|
await pressureInput.press("Enter");
|
|
|
|
await expect(pressureInput).toHaveValue(expression);
|
|
await expect(pressureRow).not.toContainText("78.5");
|
|
|
|
await parameterNameCell(componentTable, "参考压力").hover();
|
|
await page.waitForTimeout(600);
|
|
const preview = page.locator("body > #parameter-help-preview");
|
|
await expect(preview).toBeVisible();
|
|
await expect(preview.getByTestId("parameter-calculated-value")).toContainText(
|
|
"78.5",
|
|
);
|
|
await expect(preview.getByTestId("parameter-calculated-value")).toContainText(
|
|
"kPa",
|
|
);
|
|
|
|
await page.getByRole("button", { name: "生成系统 XML", exact: true }).click();
|
|
const consolePanel = page.getByRole("complementary", {
|
|
name: "仿真控制台",
|
|
exact: true,
|
|
});
|
|
const xmlBlock = consolePanel.getByLabel("生成的系统 XML");
|
|
await expect(xmlBlock).toContainText(
|
|
'<Parameter name="reference_pressure" value="78500"',
|
|
);
|
|
await expect(xmlBlock).not.toContainText(expression);
|
|
|
|
await page.getByRole("button", { name: "保存工程", exact: true }).click();
|
|
await pressureInput.fill("120");
|
|
await pressureInput.press("Enter");
|
|
await page.getByRole("button", { name: "加载工程", exact: true }).click();
|
|
await page
|
|
.locator('.flow-canvas .react-flow__node[data-id="amesim_pneumatic_test_1"]')
|
|
.click();
|
|
await expect(page.getByLabel("参考压力", { exact: true })).toHaveValue(
|
|
expression,
|
|
);
|
|
});
|
|
|
|
test("非法组件参数公式显示错误并阻止 XML 生成", async ({ page }) => {
|
|
await page.goto("/");
|
|
await addPneumaticTestComponent(page);
|
|
|
|
const invalidExpression = "3.14*/4";
|
|
const componentTable = page.getByRole("table", { name: "组件参数" });
|
|
const pressureInput = page.getByLabel("参考压力", { exact: true });
|
|
|
|
await pressureInput.fill(invalidExpression);
|
|
await pressureInput.press("Enter");
|
|
|
|
await expect(pressureInput).toHaveValue(invalidExpression);
|
|
await expect(pressureInput).toHaveAttribute("aria-invalid", "true");
|
|
await expect(
|
|
componentTable.locator(".parameter-table-message-row .field-error"),
|
|
).toBeVisible();
|
|
|
|
await page.getByRole("button", { name: "生成系统 XML", exact: true }).click();
|
|
const consolePanel = page.getByRole("complementary", {
|
|
name: "仿真控制台",
|
|
exact: true,
|
|
});
|
|
await expect(consolePanel).toContainText("XML 生成失败");
|
|
await expect(consolePanel).toContainText("参考压力");
|
|
await expect(consolePanel.getByLabel("生成的系统 XML")).toHaveCount(0);
|
|
});
|
|
|
|
test("仿真数值设置同样保留公式,并在执行协议中使用计算值", async ({
|
|
page,
|
|
}) => {
|
|
await page.goto("/");
|
|
|
|
const expression = "1+2*3";
|
|
const simulationTable = page.getByRole("table", { name: "仿真设置" });
|
|
const stopRow = parameterRow(simulationTable, "结束时间");
|
|
const stopInput = page.getByLabel("结束时间", { exact: true });
|
|
await stopInput.fill(expression);
|
|
await stopInput.press("Enter");
|
|
|
|
await expect(stopInput).toHaveValue(expression);
|
|
await expect(stopRow).not.toContainText("7");
|
|
await parameterNameCell(simulationTable, "结束时间").hover();
|
|
await page.waitForTimeout(600);
|
|
await expect(
|
|
page.getByTestId("parameter-calculated-value"),
|
|
).toContainText("7 s");
|
|
|
|
await page.getByRole("button", { name: "生成系统 XML", exact: true }).click();
|
|
const xmlBlock = page.getByLabel("生成的系统 XML");
|
|
await expect(xmlBlock).toContainText('tStop="7"');
|
|
await expect(xmlBlock).not.toContainText(expression);
|
|
});
|
|
|
|
test("仿真数值设置中的科学计数法在保存加载后保持", async ({ page }) => {
|
|
await page.goto("/");
|
|
|
|
const stepInput = page.getByLabel("采样步长", { exact: true });
|
|
const maxStepInput = page.getByLabel("最大积分步长", { exact: true });
|
|
await stepInput.fill("1e-5");
|
|
await stepInput.press("Enter");
|
|
await maxStepInput.fill("1.20E-4");
|
|
await maxStepInput.press("Enter");
|
|
|
|
await page.getByRole("button", { name: "保存工程", exact: true }).click();
|
|
await stepInput.fill("0.1");
|
|
await stepInput.press("Enter");
|
|
await maxStepInput.fill("0.01");
|
|
await maxStepInput.press("Enter");
|
|
await page.getByRole("button", { name: "加载工程", exact: true }).click();
|
|
|
|
await expect(stepInput).toHaveValue("1e-5");
|
|
await expect(maxStepInput).toHaveValue("1.20E-4");
|
|
await page.getByRole("button", { name: "生成系统 XML", exact: true }).click();
|
|
const xmlBlock = page.getByLabel("生成的系统 XML");
|
|
await expect(xmlBlock).toContainText('step="0.00001"');
|
|
await expect(xmlBlock).toContainText('maxStep="0.00012"');
|
|
});
|