import { expect, test, type Page } from "@playwright/test"; import { prepareApp, wideProject } from "./fixtures"; const projectNameField = (page: Page) => page.getByRole("textbox", { name: "工程", exact: true }); async function exportProject(page: Page) { const downloadPromise = page.waitForEvent("download"); await page.getByRole("button", { name: "导出工程 JSON", exact: true }).click(); const download = await downloadPromise; const stream = await download.createReadStream(); if (!stream) throw new Error("无法读取导出的工程 JSON"); const chunks: Buffer[] = []; for await (const chunk of stream) chunks.push(Buffer.from(chunk)); const buffer = Buffer.concat(chunks); return { filename: download.suggestedFilename(), buffer, project: JSON.parse(buffer.toString("utf8")) as typeof wideProject, }; } async function deleteFirstNode(page: Page) { await page .locator('.flow-canvas .react-flow__node[data-id="generic_sensor_1"]') .click(); await page.getByRole("button", { name: "删除 (Delete)", exact: true }).click(); await expect(page.locator(".flow-canvas .react-flow__node")).toHaveCount( wideProject.nodes.length - 1, ); } test.beforeEach(async ({ page }) => { await prepareApp(page); await page.goto("/"); await expect(page.getByRole("button", { name: /通用测试元件/ })).toBeVisible(); }); test("外部重命名的工程 JSON 导入后名称一致,保存和刷新后均可加载", async ({ page, }) => { await page.locator('input[type="file"]').setInputFiles({ name: `${wideProject.name}.json`, mimeType: "application/json", buffer: Buffer.from(JSON.stringify(wideProject)), }); await expect(page.locator(".flow-canvas .react-flow__node")).toHaveCount( wideProject.nodes.length, ); const original = await exportProject(page); expect(original.filename).toBe(`${wideProject.name}.json`); expect(original.project.name).toBe(wideProject.name); // Only the external filename changes; the exported JSON still has its old name. const renamedProject = "气动 回路.v2"; await page.locator('input[type="file"]').setInputFiles({ name: `${renamedProject}.JSON`, mimeType: "application/json", buffer: original.buffer, }); await expect(projectNameField(page)).toHaveValue(renamedProject); const renamed = await exportProject(page); expect(renamed.filename).toBe(`${renamedProject}.json`); expect(renamed.project.name).toBe(renamedProject); expect(renamed.project.nodes).toEqual(original.project.nodes); expect(renamed.project.simulation).toEqual(original.project.simulation); await page.getByRole("button", { name: "保存工程", exact: true }).click(); const saved = await page.evaluate((name) => { const raw = localStorage.getItem( `system-simulation-flow:project:${encodeURIComponent(name)}`, ); return raw ? JSON.parse(raw) : null; }, renamedProject); expect(saved).toMatchObject({ name: renamedProject }); expect(saved.nodes).toHaveLength(wideProject.nodes.length); 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(renamedProject); await page.reload(); await page.getByRole("button", { name: "忽略", exact: true }).click(); await expect(page.locator(".flow-canvas .react-flow__node")).toHaveCount(0); await projectNameField(page).fill(renamedProject); 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(renamedProject); }); test("浏览器工程内容保留旧名称时,按存储名称连续加载仍能恢复工程", async ({ page, }) => { const storedName = "已重命名 工程.v3"; await page.evaluate( ({ name, project }) => { localStorage.setItem( `system-simulation-flow:project:${encodeURIComponent(name)}`, JSON.stringify(project), ); }, { name: storedName, project: wideProject }, ); await projectNameField(page).fill(storedName); 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 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); }); for (const { internalName, expectedName } of [ { internalName: " 内部 工程.v1 ", expectedName: "内部 工程.v1" }, { internalName: " ", expectedName: "untitled" }, ]) { test(`外部文件名没有基名时,工程名回退为 ${expectedName}`, async ({ page }) => { await page.locator('input[type="file"]').setInputFiles({ name: " .JSON", mimeType: "application/json", buffer: Buffer.from(JSON.stringify({ ...wideProject, name: internalName })), }); await expect(projectNameField(page)).toHaveValue(expectedName); const exported = await exportProject(page); expect(exported.filename).toBe(`${expectedName}.json`); expect(exported.project.name).toBe(expectedName); }); }