feat: consolidate shared UI and harden browser authority for release
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
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.`);
|
||||
Reference in New Issue
Block a user