import { expect, test } from "@playwright/test"; import crypto from "node:crypto"; import fs from "node:fs"; import path from "node:path"; const fixture = path.resolve(import.meta.dirname, "../../../tests/files/web/nonmesh_scene.blend"); const golden = JSON.parse(fs.readFileSync(path.resolve(import.meta.dirname, "../../../tests/golden/M9-05/curve-toggle-cyclic.json"), "utf8")); test("M9-05 exposes TOGGLE_CYCLIC only after Main, undo, save and Blender golden verification", async ({ page }) => { test.setTimeout(120_000); const fixtureBytes = fs.readFileSync(fixture); expect(crypto.createHash("sha256").update(fixtureBytes).digest("hex")).toBe(golden.fixtureSha256); await page.goto("/"); await expect(page.getByTestId("engine-status")).toContainText("ready, open a .blend file", { timeout: 20_000 }); await page.getByTestId("blend-file-input").setInputFiles(fixture); const curveRow = page.locator('[data-data-id="curve:WebCurveData"]'); await expect(curveRow).toBeVisible(); await curveRow.click(); const app = page.locator(".blender-app"); const editor = page.getByTestId("curve-topology-editor"); const cyclic = page.getByRole("checkbox", { name: "Curve cyclic U" }); await expect(editor).toHaveAttribute("data-ready-operators", "TOGGLE_CYCLIC"); await expect(editor).toHaveAttribute("data-spline-types", golden.splineTypes.join(",")); await expect(editor).toHaveAttribute("data-point-count", String(golden.splinePointCounts.reduce((sum: number, count: number) => sum + count, 0))); await expect(editor).toHaveAttribute("data-cyclic-u", golden.beforeCyclicU.join(",")); await expect(cyclic).not.toBeChecked(); const selectedRevision = Number(await app.getAttribute("data-current-main-revision")); await cyclic.click(); await expect(editor).toHaveAttribute("data-cyclic-u", golden.afterCyclicU.join(",")); await expect(cyclic).toBeChecked(); await expect(app).toHaveAttribute("data-dirty", "true"); const toggledRevision = Number(await app.getAttribute("data-current-main-revision")); expect(toggledRevision).toBe(selectedRevision + 1); await page.getByRole("button", { name: "撤销" }).click(); await expect(editor).toHaveAttribute("data-cyclic-u", golden.beforeCyclicU.join(",")); const undoRevision = Number(await app.getAttribute("data-current-main-revision")); expect(undoRevision).toBe(toggledRevision + 1); await page.getByRole("button", { name: "重做" }).click(); await expect(editor).toHaveAttribute("data-cyclic-u", golden.afterCyclicU.join(",")); const redoRevision = Number(await app.getAttribute("data-current-main-revision")); expect(redoRevision).toBe(undoRevision + 1); const download = page.waitForEvent("download"); await page.getByRole("button", { name: "保存项目" }).click(); await download; await expect(app).toHaveAttribute("data-dirty", "false"); await page.getByRole("button", { name: "关闭项目" }).click(); await expect(editor).toHaveCount(0); await page.getByRole("button", { name: "恢复项目" }).click(); await expect(curveRow).toBeVisible(); await curveRow.click(); await expect(editor).toHaveAttribute("data-cyclic-u", golden.reopenedCyclicU.join(",")); await expect(cyclic).toBeChecked(); });