import assert from "node:assert/strict"; import fs from "node:fs"; import path from "node:path"; import test from "node:test"; import ts from "typescript"; const repoRoot = path.resolve(import.meta.dirname, "../../.."); const sourcePath = path.join(repoRoot, "web/protocol/ui-schema.ts"); const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), { compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 }, fileName: sourcePath, reportDiagnostics: true, }); assert.deepEqual(transpiled.diagnostics, []); const moduleUrl = "data:text/javascript;base64," + Buffer.from(transpiled.outputText).toString("base64"); const { BLENDER_WORKSPACE_IDS, createDefaultWebWorkspaceState, reduceUICommand } = await import(moduleUrl); test("Blender 5.2 workspaces retain their source editor area sets", () => { const state = createDefaultWebWorkspaceState(); const expected = { Layout: ["3D Viewport", "Outliner", "Properties", "Timeline"], Modeling: ["3D Viewport", "Outliner", "Properties"], Sculpting: ["3D Viewport", "Outliner", "Properties"], "UV Editing": ["3D Viewport", "Image Editor", "Outliner", "Properties"], "Texture Paint": ["3D Viewport", "Image Editor", "Outliner", "Properties"], Shading: ["3D Viewport", "File Browser", "Image Editor", "Outliner", "Properties", "Shader Editor"], Animation: ["3D Viewport", "3D Viewport", "Dope Sheet", "Outliner", "Properties"], Rendering: ["Properties", "Render Result", "Timeline"], Compositing: ["Compositor", "Image Editor", "Properties", "Timeline"], "Geometry Nodes": ["3D Viewport", "Geometry Nodes", "Outliner", "Properties", "Spreadsheet", "Timeline"], Scripting: ["3D Viewport", "Info", "Outliner", "Outliner", "Properties", "Python Console", "Text Editor"], }; assert.deepEqual(Object.keys(state.workspaces), BLENDER_WORKSPACE_IDS); for (const id of BLENDER_WORKSPACE_IDS) { assert.deepEqual(state.workspaces[id].areas.map((area) => area.editor).sort(), expected[id].toSorted(), id); assert.ok(state.workspaces[id].areas.every((area) => area.rect.width > 0 && area.rect.height > 0), id); } }); test("M7-13 menu and modal overlays are mutually exclusive and Escape-closeable", () => { let state = createDefaultWebWorkspaceState(); state = reduceUICommand(state, { type: "toggleMenu", menu: "文件" }); assert.equal(state.openMenu, "文件"); assert.equal(state.operatorSearchOpen, false); state = reduceUICommand(state, { type: "toggleMenu", menu: "编辑" }); assert.equal(state.openMenu, "编辑"); assert.equal(state.operatorSearchOpen, false); state = reduceUICommand(state, { type: "toggleOperatorSearch", open: true }); assert.equal(state.operatorSearchOpen, true); assert.equal(state.openMenu, null); state = reduceUICommand(state, { type: "toggleOperatorSearch", open: false }); assert.equal(state.operatorSearchOpen, false); state = reduceUICommand(state, { type: "toggleMenu", menu: "窗口" }); assert.equal(state.openMenu, "窗口"); state = reduceUICommand(state, { type: "toggleMenu", menu: "窗口" }); assert.equal(state.openMenu, null); });