90 lines
8.2 KiB
JavaScript
90 lines
8.2 KiB
JavaScript
const assert = require("assert");
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
|
|
const root = path.join(__dirname, "..");
|
|
const read = (file) => fs.readFileSync(path.join(root, file), "utf8");
|
|
|
|
const interactions = read("src/web/public/lumi-interactions.js");
|
|
const request = read("src/web/public/lumi-request.js");
|
|
const pages = read("src/web/public/lumi-page.js");
|
|
const dialogs = read("src/web/public/lumi-dialog.js");
|
|
const app = read("src/web/public/app.js");
|
|
const overlay = read("src/web/public/overlay-management.js");
|
|
const overlayCss = read("src/web/public/overlay-management.css");
|
|
const overlayPreviewWindow = read("src/web/public/overlay-preview-window.js");
|
|
const overlayRoute = read("src/services/overlay-routes.js");
|
|
const layout = read("src/web/views/partials/layout-top.ejs");
|
|
const tokens = read("src/web/public/lumi-tokens.css");
|
|
const legacy = read("src/web/public/styles.css");
|
|
const navigationTemplate = read("src/web/views/admin-navigation.ejs");
|
|
const navigationClient = read("src/web/public/navigation-builder.js");
|
|
const server = read("src/web/server.js");
|
|
const themeTemplate = read("src/web/views/admin-theme.ejs");
|
|
const themeClient = read("src/web/public/theme-editor.js");
|
|
const aiTemplate = read("plugins/lumi_ai/views/settings.ejs");
|
|
const aiClient = read("plugins/lumi_ai/public/settings.js");
|
|
const aiServer = read("plugins/lumi_ai/index.js");
|
|
const commandTemplate = read("src/web/views/admin-commands.ejs");
|
|
const commandClient = read("src/web/public/command-management.js");
|
|
const policyTemplate = read("src/web/views/admin-command-policies.ejs");
|
|
const policyClient = read("src/web/public/command-policies.js");
|
|
const updateTemplate = read("src/web/views/admin-updates.ejs");
|
|
const updateClient = read("src/web/public/update-management.js");
|
|
|
|
assert(interactions.includes("window.LumiRequest.form(form)"), "settings saves must use the shared request contract");
|
|
assert(!interactions.includes("Saved. Reloading"), "ordinary settings saves must not announce an implicit reload");
|
|
assert(!interactions.includes("executePageScripts"), "soft navigation must not re-execute arbitrary page scripts");
|
|
assert(interactions.includes('a[data-lumi-soft-nav][href]'), "soft navigation must be explicit while routes migrate");
|
|
assert(interactions.includes("window.LumiEvents") && interactions.includes('window.addEventListener("beforeunload", closeEvents)'), "shared live events must release their connection before full navigation");
|
|
assert(!app.includes('new EventSource("/admin/updates/events")') || app.includes("window.LumiEvents?.subscribe"), "updates must prefer the shared event stream");
|
|
assert(request.includes('"X-Lumi-Enhanced": "1"') && request.includes("reloadRequired"));
|
|
assert(request.includes('getAttribute?.("formaction")') && !request.includes("submitter?.formAction"), "enhanced requests must use authored form actions");
|
|
assert(pages.includes("controller.abort()") && pages.includes("teardown"));
|
|
assert(dialogs.includes("restoreFocus") && dialogs.includes('event.key !== "Tab"') && dialogs.includes("inert"));
|
|
assert(app.includes("page.inert = true") && app.includes("closeSidebar") && app.includes('event.key === "Escape"'));
|
|
assert(layout.includes('id="lumi-sidebar"') && layout.includes('aria-controls="lumi-sidebar"'));
|
|
assert(!overlay.includes("--overlay-settings-max-height"), "overlay height must not be calculated from viewport geometry");
|
|
assert(!overlay.includes("scrollIntoView"), "scene/source selection must not force viewport movement");
|
|
assert(!overlay.includes("window.location.reload"), "normal overlay actions must not reload the document");
|
|
assert(overlay.includes("window.clearInterval(obsStatusTimer)"), "overlay polling must be lifecycle-owned");
|
|
assert(!overlayCss.includes("--overlay-settings-max-height"));
|
|
assert(/@media \(min-width: 1180px\)[\s\S]*\.overlay-settings-column\s*\{[^}]*overflow-y:\s*auto/s.test(overlayCss), "desktop overlay settings need a bounded workspace scroller");
|
|
assert(!/\.overlay-preview-panel\s*\{[^}]*overflow:\s*auto/s.test(overlayCss), "the sticky preview cannot become a nested main scroller");
|
|
assert(overlayCss.includes(".overlay-preview-panel.is-open") && overlayCss.includes("position: fixed"), "thin overlay layouts need a viewport-contained edge drawer");
|
|
assert(overlay.includes("editorRoot.inert = !open") && overlay.includes("restoreFocus"), "the preview drawer must hide closed controls and restore focus");
|
|
assert(overlay.includes("BroadcastChannel") && overlay.includes("dataset.previewPopoutUrl"), "the editor must synchronize its desktop pop-out preview");
|
|
assert(overlayPreviewWindow.includes("Math.min(previewViewport.clientWidth / width, previewViewport.clientHeight / height)"), "the pop-out must preserve the canvas aspect ratio");
|
|
assert(overlayPreviewWindow.includes("editor: true") && overlayPreviewWindow.includes("/transform") && overlayPreviewWindow.includes('type: "transform"'), "the pop-out must support synchronized transform editing");
|
|
assert(overlayRoute.includes('app.get("/admin/overlays/:id/preview-window", requireOverlayAccess(OVERLAY_CAPABILITIES.MANAGE)'), "the pop-out must remain behind overlay management access");
|
|
assert(tokens.includes("@layer reset, tokens, base, layout, components, utilities, features, overrides"));
|
|
assert(!legacy.includes("fonts.googleapis.com"), "core CSS cannot depend on remote Google Fonts");
|
|
assert(!navigationTemplate.includes("<style>"), "the navigation builder must use the feature stylesheet instead of page-local CSS");
|
|
assert(!navigationTemplate.includes("<script>"), "the navigation builder must use lifecycle-owned client behavior");
|
|
assert(navigationTemplate.includes("data-lumi-enhanced-form") && navigationTemplate.includes('pageId: "navigation-builder"'));
|
|
assert(navigationClient.includes('LumiPage.register("navigation-builder"'));
|
|
assert(navigationClient.includes("data-item-previous-group") && navigationClient.includes("data-item-next-group"), "navigation items need non-drag movement controls");
|
|
assert(server.includes('message: "Navigation saved without leaving the editor."'));
|
|
assert(themeTemplate.includes("data-lumi-enhanced-form") && themeTemplate.includes('pageId: "theme-studio"'));
|
|
assert(themeClient.includes('LumiPage.register("theme-studio"') && themeClient.includes("applyCanonicalTheme"));
|
|
assert(server.includes("data: { theme, applied }"), "theme saves must return canonical values to the editor");
|
|
assert(aiTemplate.includes('pageId: "lumi-ai-settings"'));
|
|
assert(aiClient.includes('LumiPage.register("lumi-ai-settings"') && aiClient.includes("intervals.forEach"));
|
|
assert(aiClient.includes(" pollDownloads();\n return () =>"), "Lumi AI download polling must start once per mount");
|
|
assert(aiServer.includes('req.get("X-Lumi-Enhanced")') && aiServer.includes("reloadRequired: false"));
|
|
assert(!commandTemplate.includes("<script>"), "custom command behavior must not remain inline");
|
|
assert(commandTemplate.includes('pageId: "command-management"') && commandTemplate.includes("data-command-list-section"));
|
|
assert(commandClient.includes('LumiPage.register("command-management"') && commandClient.includes("refreshCommandList"));
|
|
assert(!policyTemplate.includes("<script>"), "command policy behavior must not remain inline");
|
|
assert(policyTemplate.includes('pageId: "command-policies"') && policyTemplate.includes("data-lumi-enhanced-form"));
|
|
assert(policyClient.includes('LumiPage.register("command-policies"') && policyClient.includes("refreshInheritedSummaries"));
|
|
assert(server.includes("sendCommandMutationSuccess") && server.includes('patch: { refresh: "command-list" }'));
|
|
assert(!updateTemplate.includes("<script>"), "update behavior must not remain inline");
|
|
assert(updateTemplate.includes('pageId: "update-management"') && updateTemplate.includes("data-update-recovery-region"));
|
|
assert(updateClient.includes('LumiPage.register("update-management"') && updateClient.includes("subscriptions.splice(0)"));
|
|
assert(updateClient.includes("LumiConfirm?.destructiveToken") && updateClient.includes("confirmation_token"));
|
|
assert(!updateClient.includes("window.location.reload"), "restart recovery must reconnect without a document reload");
|
|
assert(server.includes("Correct the snapshot cleanup limits and try again."), "snapshot limits need field-linked validation");
|
|
|
|
console.log("UX foundation verification passed: request, lifecycle, dialogs, drawer, cascade, navigation, commands, updates, and overlay regressions.");
|