104 lines
10 KiB
JavaScript
104 lines
10 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import { resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const webuiRoot = resolve(fileURLToPath(new URL("..", import.meta.url)));
|
|
const read = (path) => readFileSync(resolve(webuiRoot, path), "utf8");
|
|
|
|
const settings = read("src/features/settings/SettingsPage.tsx");
|
|
const retention = read("src/features/privacy/RetentionPolicyManagement.tsx");
|
|
const confirmDialog = read("src/components/ConfirmDialog.tsx");
|
|
const credentials = read("src/components/CredentialEnvelopeManager.tsx");
|
|
const iconRail = read("src/layout/IconRail.tsx");
|
|
const moduleLoadBoundary = read("src/components/ModuleLoadBoundary.tsx");
|
|
const titlebar = read("src/layout/Titlebar.tsx");
|
|
const temporalDataMenu = read("src/layout/TemporalDataMenu.tsx");
|
|
const helpMenu = read("src/layout/HelpMenu.tsx");
|
|
const helpContext = read("src/utils/helpContext.ts");
|
|
const pageLayout = read("src/components/PageLayout.tsx");
|
|
const workspaceLayout = read("src/components/WorkspaceLayout.tsx");
|
|
const actionToolbar = read("src/components/ActionToolbar.tsx");
|
|
const pageActionBar = read("src/components/PageActionBar.tsx");
|
|
const contentGrid = read("src/components/ContentGrid.tsx");
|
|
const formSection = read("src/components/FormSection.tsx");
|
|
const dialogAnatomy = read("src/components/DialogAnatomy.tsx");
|
|
const adminPageLayout = read("src/components/admin/AdminPageLayout.tsx");
|
|
const layoutStyles = read("src/styles/layout.css");
|
|
const authGateStyles = read("src/styles/auth-gate.css");
|
|
|
|
assert.match(settings, /contextId: "core\.settings"/, "settings expose stable contextual documentation");
|
|
assert.match(settings, /archetype=\{editorSection \? "editor" : "workspace"\}/, "settings declare editor intent only for draft-owning sections");
|
|
assert.match(settings, /<PageActionBar[\s\S]*variant="editor"[\s\S]*dirty=\{editorDirty\}[\s\S]*discardAction=[\s\S]*saveAction=/, "settings use central dirty persistence actions");
|
|
|
|
assert.match(retention, /<ActionBlockerHint/, "retention renders the shared actionable blocker");
|
|
assert.match(retention, /contextId: "privacy\.retention"/, "retention exposes stable admin documentation");
|
|
assert.match(retention, /policy\.retention\.field\.store-raw-campaign-json/, "retention storage controls expose exact help contexts");
|
|
assert.match(retention, /policy\.retention\.field\.audit-detail-level/, "retention audit controls expose exact help contexts");
|
|
assert.match(retention, /policy\.retention\.action\.save/, "retention persistence exposes exact help contexts");
|
|
assert.match(retention, /helpModuleId="policy"/, "embedded retention controls retain Policy as their documentation owner");
|
|
assert.match(retention, /locked by platform configuration/, "retention explains platform locks");
|
|
assert.doesNotMatch(retention, /<textarea/, "retention does not use raw text or JSON as its primary editor");
|
|
assert.match(confirmDialog, /PlatformInterfaceIdentityProps/, "confirm dialogs accept stable interface help identities");
|
|
assert.match(confirmDialog, /helpContextId=\{helpContextId\}/, "confirm dialogs propagate their explicit help context");
|
|
|
|
assert.match(credentials, /<ActionBlockerHint/, "credentials render the shared actionable blocker");
|
|
assert.match(credentials, /contextId: "access\.credentials"/, "credentials expose stable admin documentation");
|
|
assert.match(credentials, /access\.credentials\.field\.secret/, "credential secrets expose exact handling guidance");
|
|
assert.match(credentials, /access\.credentials\.field\.inherit-to-lower-scopes/, "credential inheritance exposes exact scope guidance");
|
|
assert.match(credentials, /access\.credentials\.confirm-delete/, "credential deletion exposes exact consequence guidance");
|
|
assert.match(credentials, /helpModuleId="access"/, "embedded credential controls retain Access as their documentation owner");
|
|
assert.match(credentials, /disabledReason: writeDisabledReason/, "credential row actions retain actionable disabled reasons");
|
|
assert.doesNotMatch(credentials, /<textarea/, "credentials use typed controls rather than a primary JSON editor");
|
|
|
|
assert.match(pageLayout, /`page-layout-\$\{mode\}`/, "shared standalone and embedded pages use one central frame");
|
|
assert.match(pageLayout, /export type PageArchetype = "overview" \| "collection" \| "detail" \| "editor" \| "workspace"/, "page intent uses the five central archetypes");
|
|
assert.match(pageLayout, /data-page-archetype=\{archetype\}/, "shared pages expose their semantic archetype");
|
|
assert.match(pageLayout, /<PageHeader/, "shared pages use one central responsive heading");
|
|
assert.match(pageLayout, /data-help-scope="page"/, "shared pages preserve contextual-help identity");
|
|
assert.match(adminPageLayout, /<PageLayout/, "administration composes the central page layout instead of redefining it");
|
|
assert.match(workspaceLayout, /workspace-layout-\$\{variant\}/, "shared workspaces expose central navigation and split-pane variants");
|
|
assert.match(workspaceLayout, /data-help-scope="workspace"/, "shared workspaces preserve contextual-help identity");
|
|
assert.match(actionToolbar, /action-toolbar-wrap-\$\{wrap\}/, "shared toolbars own responsive wrapping");
|
|
assert.match(actionToolbar, /data-help-scope="toolbar"/, "shared toolbars preserve contextual-help identity");
|
|
assert.match(pageActionBar, /variant: "collection"/, "collection pages have a semantic action-bar contract");
|
|
assert.match(pageActionBar, /variant: "detail"/, "detail pages have a semantic action-bar contract");
|
|
assert.match(pageActionBar, /variant: "editor"/, "editors have a semantic action-bar contract");
|
|
assert.match(pageActionBar, /variant: "overview"/, "overview pages have a semantic action-bar contract");
|
|
assert.match(pageActionBar, /variant: "workspace"/, "task workspaces have a semantic action-bar contract");
|
|
assert.match(pageActionBar, /refreshable: true;\s*reloadAction: ReactNode;/, "refreshable pages require a Reload action at type level");
|
|
assert.match(pageActionBar, /dirty: boolean;/, "editors require an explicit dirty state");
|
|
assert.match(pageActionBar, /data-page-dirty-state=/, "editors announce clean, dirty, and saving states");
|
|
assert.match(pageActionBar, /<ActionSlot name="reload">/, "page action bars keep reload in a named stable slot");
|
|
assert.match(pageActionBar, /data-page-action-separation="destructive"/, "destructive page actions expose a separate semantic group");
|
|
assert.match(pageActionBar, /<ActionSlot name="discard">[\s\S]*<ActionSlot name="save">/, "editor save follows discard in the trailing group");
|
|
assert.match(contentGrid, /content-grid-collapse-\$\{collapseAt\}/, "shared grids make their collapse point explicit");
|
|
assert.match(formSection, /form-section-header/, "shared form sections own heading and action placement");
|
|
assert.match(dialogAnatomy, /dialog-actions-\$\{align\}/, "shared dialog anatomy owns footer action placement");
|
|
|
|
assert.match(iconRail, /<div className="icon-rail-scroll">\s*<nav className="icon-nav">/, "the module navigation has a dedicated scroll viewport");
|
|
assert.match(layoutStyles, /\.icon-rail-scroll \{[^}]*min-height: 0;[^}]*flex: 1 1 auto;[^}]*overflow-y: auto;/, "only the middle rail region scrolls");
|
|
assert.match(layoutStyles, /\.icon-rail-header \{[^}]*flex: 0 0 auto;/, "the rail logo remains fixed");
|
|
assert.match(layoutStyles, /\.icon-rail-bottom \{[^}]*flex: 0 0 auto;/, "the rail utility controls remain fixed");
|
|
|
|
assert.doesNotMatch(titlebar, /titlebar-status-pattern|TriangleAlert|WifiOff/, "shell state uses the readable text warnings rather than icon or background decoration");
|
|
assert.doesNotMatch(titlebar, /titlebar-global-search|has-global-search/, "global search no longer reserves a centered titlebar grid cell");
|
|
assert.match(titlebar, /<GlobalSearch[\s\S]*<LanguageMenu \/>[\s\S]*<ViewSelector[\s\S]*<TemporalDataMenu \/>/, "search is the first command before language, View, and temporal selectors");
|
|
assert.match(titlebar, /className="account-pill"[\s\S]*aria-label=\{displayUserName\}[\s\S]*aria-haspopup="menu"/, "the compact account menu retains an accessible name and menu state");
|
|
assert.match(temporalDataMenu, /Calendar, CalendarOff/, "the temporal selector distinguishes bounded and all-validity modes");
|
|
assert.match(temporalDataMenu, /useUnsavedChanges[\s\S]*requestNavigation/, "changing temporal context cannot silently discard a dirty page");
|
|
assert.match(temporalDataMenu, /recordedAt/, "valid time and recorded time remain independently selectable");
|
|
assert.match(layoutStyles, /\.maintenance-topbar-link,[\s\S]*\.backend-offline-topbar-alert \{[^}]*top: 50%;[^}]*transform: translate\(-50%, -50%\);/, "maintenance and offline warnings use their established centered titlebar placement");
|
|
assert.match(authGateStyles, /\.titlebar-link,[\s\S]*\.account-pill \{[^}]*margin: 0;/, "text and account titlebar controls use the same action spacing as icon controls");
|
|
assert.match(helpMenu, /helpContextForTarget\(routeHelpContext, event\.target, modules\)/, "F1 resolves the currently focused interface item");
|
|
assert.match(helpContext, /function moduleRouteContext/, "context help covers contributed module routes");
|
|
assert.match(helpContext, /function sectionContext/, "context help covers contributed administration and settings sections");
|
|
assert.match(helpContext, /data-help-context-id/, "context help honors explicit control metadata");
|
|
assert.match(helpContext, /explicit\.dataset\.helpModuleId \|\| base\.moduleId/, "context help honors an explicit documentation owner");
|
|
assert.match(layoutStyles, /@media \(max-width: 600px\)[\s\S]*\.app-main \{[\s\S]*grid-template-rows: 104px 51px minmax\(0, 1fr\);/, "the narrow shell reserves two non-overlapping titlebar rows");
|
|
assert.match(layoutStyles, /@media \(max-width: 600px\)[\s\S]*\.titlebar-context-selectors \{[\s\S]*overflow-x: auto;/, "narrow context selectors remain reachable without covering titlebar actions");
|
|
assert.match(layoutStyles, /@media \(max-width: 600px\)[\s\S]*\.account-pill span \{[\s\S]*display: none;/, "narrow account controls retain the icon while removing collision-prone text");
|
|
assert.match(moduleLoadBoundary, /<DismissibleAlert tone="danger" compact/, "module failures use the compact shared alert");
|
|
|
|
console.log("Core interface-pattern contracts passed.");
|