import assert from "node:assert/strict"; import { existsSync, readFileSync } from "node:fs"; import { resolve } from "node:path"; import ts from "typescript"; // Collection-wide commands belong to the full workspace, never its left pane. // Keep editor/detail commands scoped to their own pane. The real-page browser // scenarios separately verify the shared action engine's physical placement. const pages = [ ["workflow", "workflow/WorkflowPage.tsx"], ["dataflow", "dataflow/DataflowPage.tsx"], ["templates", "templates/TemplatesPage.tsx"], ["datasources", "datasources/DatasourcesPage.tsx"], ["dist-lists", "distributionLists/DistributionListsPage.tsx"], ["tasks", "tasks/TasksPage.tsx"], ["voting", "voting/VotingPage.tsx"], ["scheduling", "scheduling/SchedulingPage.tsx"], ["committee", "committee/CommitteePage.tsx"] ]; function attribute(node, name) { const item = node.attributes.properties.find(prop => ts.isJsxAttribute(prop) && prop.name.getText() === name); return item?.initializer && ts.isStringLiteral(item.initializer) ? item.initializer.text : item; } let checked = 0; for (const [module, page] of pages) { const path = resolve(import.meta.dirname, `../../../govoplan-${module}/webui/src/features/${page}`); // Core-only checkouts must not acquire dependencies on optional modules. if (!existsSync(path)) continue; checked += 1; const source = ts.createSourceFile(path, readFileSync(path, "utf8"), ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX); const bars = []; function visit(node) { if (ts.isJsxSelfClosingElement(node) && node.tagName.getText() === "WorkspaceActionBar" && attribute(node, "variant") === "collection") bars.push(node); ts.forEachChild(node, visit); } visit(source); assert.equal(bars.length, 1, `${module}: one collection-wide action bar`); const bar = bars[0]; assert.equal(attribute(bar, "scope"), "workspace", `${module}: collection commands use workspace scope`); assert.ok(attribute(bar, "refreshable") && attribute(bar, "reloadAction") && attribute(bar, "createAction"), `${module}: Reload and creation stay in the same semantic bar`); let parent = bar.parent; while (parent && !ts.isSourceFile(parent)) { assert.ok(!(ts.isJsxAttribute(parent) && parent.name.getText() === "primary"), `${module}: collection commands must not be nested in the left pane`); parent = parent.parent; } let enclosingFrame = bar.parent; while (enclosingFrame && !(ts.isJsxElement(enclosingFrame) && enclosingFrame.openingElement.tagName.getText() === "WorkspaceFrame")) enclosingFrame = enclosingFrame.parent; assert.ok(enclosingFrame, `${module}: the persistent workspace owns its commands`); if (module !== "committee" && module !== "scheduling") { const layout = enclosingFrame.children.find(node => ts.isJsxElement(node) && node.openingElement.tagName.getText() === "WorkspaceLayout"); assert.ok(layout && bar.pos < layout.pos, `${module}: commands precede the split workspace`); } } // Explorer modules use custom virtualized panes, but not a custom page toolbar. for (const [module, page, variant] of [["files", "files/FilesPage.tsx", "collection"], ["mail", "mail/MailboxPage.tsx", "workspace"]]) { const path = resolve(import.meta.dirname, `../../../govoplan-${module}/webui/src/features/${page}`); if (!existsSync(path)) continue; checked += 1; const source = ts.createSourceFile(path, readFileSync(path, "utf8"), ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX); const nodes = []; function visit(node) { nodes.push(node); ts.forEachChild(node, visit); } visit(source); const bars = nodes.filter(node => ts.isJsxSelfClosingElement(node) && node.tagName.getText() === "WorkspaceActionBar" && attribute(node, "scope") === "workspace"); assert.equal(bars.length, 1, `${module}: one persistent explorer-wide action bar`); const bar = bars[0]; assert.equal(attribute(bar, "variant"), variant, `${module}: explicit semantic action variant`); assert.ok(attribute(bar, "refreshable") && attribute(bar, "reloadAction"), `${module}: current-view Reload is semantic`); assert.equal(Boolean(attribute(bar, "createAction")), module === "files", `${module}: creation only where an owning workflow exists`); const owner = bar.parent; const placements = ts.isVariableDeclaration(owner) ? nodes.filter(node => ts.isJsxExpression(node) && node.expression && ts.isIdentifier(node.expression) && node.expression.text === owner.name.getText()) : [bar]; assert.equal(placements.length, 1, `${module}: workspace bar has one persistent placement`); const placement = placements[0]; assert.ok(ts.isJsxElement(placement.parent) && placement.parent.openingElement.tagName.getText() === "WorkspaceFrame", `${module}: commands are direct workspace children, outside panes and selection branches`); const shell = placement.parent.children.find(node => ts.isJsxElement(node) && node.openingElement.tagName.getText() === "div" && /file-manager-shell/.test(node.openingElement.getText())); assert.ok(shell && placement.pos < shell.pos, `${module}: toolbar precedes the explorer panes`); } console.log(`Workspace actions: ${checked} present module pages conform.`);