Files
nexus/frontend/scripts/test-regressions.js
T
2026-06-16 16:15:47 +01:00

439 lines
12 KiB
JavaScript

#!/usr/bin/env node
const fs = require("fs");
const path = require("path");
const root = path.resolve(__dirname, "..");
const repo = path.resolve(root, "..");
function read(rel) {
return fs.readFileSync(path.join(repo, rel), "utf8");
}
function assertContains(file, needle, message) {
const text = read(file);
if (!text.includes(needle)) {
throw new Error(`${message}\nMissing ${JSON.stringify(needle)} in ${file}`);
}
}
function assertNotContains(file, needle, message) {
const text = read(file);
if (text.includes(needle)) {
throw new Error(`${message}\nUnexpected ${JSON.stringify(needle)} in ${file}`);
}
}
function assertTreeNotContains(relDir, needle, message) {
const dir = path.join(repo, relDir);
const offenders = [];
const walk = (current) => {
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
const full = path.join(current, entry.name);
if (entry.isDirectory()) {
walk(full);
continue;
}
if (!entry.isFile() || !entry.name.endsWith(".js")) continue;
const rel = path.relative(repo, full);
if (read(rel).includes(needle)) offenders.push(rel);
}
};
walk(dir);
if (offenders.length) {
throw new Error(`${message}\nUnexpected ${JSON.stringify(needle)} in ${offenders.join(", ")}`);
}
}
assertContains(
"frontend/src/js/terminal.js",
"escapeHtml(title)",
"file-drop prompt titles must escape filenames containing HTML characters",
);
assertContains(
"frontend/src/js/terminal.js",
"SFTPUploadStart",
"drag-drop upload must use chunked SFTP streaming",
);
assertContains(
"frontend/src/js/terminal.js",
"SFTPUploadChunk",
"drag-drop upload must send bounded chunks",
);
assertContains(
"frontend/src/js/terminal.js",
"scheduleFit",
"terminal resize handling must be throttled through scheduled fits",
);
assertContains(
"frontend/src/js/terminal.js",
"dimensionsChanged",
"terminal resize handling must skip unchanged container dimensions",
);
assertContains(
"frontend/src/js/terminal.js",
"cancelScheduledFit",
"terminal teardown must cancel pending fit work",
);
assertNotContains(
"frontend/src/js/terminal.js",
"new ResizeObserver(() => {\n try {\n fitAddon.fit();",
"ResizeObserver must not call fitAddon.fit() directly",
);
assertNotContains(
"frontend/src/js/terminal.js",
"Use SFTP Upload for files over 25 MB",
"drag-drop upload must not reject files only because they exceed the old inline limit",
);
assertContains(
"frontend/src/js/app.js",
"confirmDirectVNCProfile",
"direct VNC connections must show an explicit warning",
);
assertContains(
"frontend/src/js/profiles.js",
"skipDirectVNCWarning",
"save-and-connect should not show duplicate direct VNC prompts",
);
assertContains(
"frontend/src/js/recordings.js",
"import-status",
"profile import failures must be visible inside the import modal",
);
assertContains(
"frontend/src/js/recordings.js",
"_formatImportError",
"profile import failures must be formatted without using HTML injection",
);
assertContains(
"internal/api/api_test.go",
"TestCreateProfileRejectsUnsafeGitHubBaseURL",
"GitHub HTTP/HTTPS URL validation must stay covered",
);
assertContains(
"frontend/index.html",
"mode-leviathan",
"Leviathan mode toggle must be present beside Nexus Terminal",
);
assertContains(
"frontend/src/css/sidebar.css",
"user-select: none",
"sidebar title and mode switcher text must not be selectable",
);
assertContains(
"frontend/src/css/sidebar.css",
"#mode-leviathan",
"Leviathan mode label must have its own right-alignment rule",
);
assertContains(
"frontend/src/css/sidebar.css",
"margin-left: auto;",
"Leviathan mode label must sit on the right side of the sidebar header",
);
assertContains(
"frontend/src/css/tokens.css",
"--tab-bar-height: 44px;",
"tab bar row must match the sidebar header height",
);
assertContains(
"frontend/src/css/sidebar.css",
"height: var(--tab-bar-height);",
"sidebar header must use the shared tab bar height",
);
assertContains(
"frontend/src/css/layout.css",
"height: var(--tab-bar-height);",
"tab bar must use the shared sidebar header height",
);
assertContains(
"frontend/src/js/tabs.js",
"this._setupScrolling();",
"tab bar must enable horizontal scroll handling for overflowed tabs",
);
assertContains(
"frontend/src/js/tabs.js",
"this._setupTooltip();",
"tab bar must show rich hover hints for truncated tabs",
);
assertContains(
"frontend/src/js/tabs.js",
"removeTab(sessionId) {\n this._hideTabTooltip();",
"removing a tab must clear any visible tab tooltip",
);
assertContains(
"frontend/src/js/tabs.js",
"async closeTab(sessionId) {\n this._hideTabTooltip();",
"closing a tab must clear any visible or pending tab tooltip",
);
assertContains(
"frontend/src/js/tabs.js",
"!tab?.isConnected",
"tab tooltip timers must not show hints for removed tab elements",
);
assertContains(
"frontend/src/js/tabs.js",
"_clearTabTooltipTimer",
"tab tooltip cleanup must cancel pending hover timers",
);
assertContains(
"frontend/src/js/tabs.js",
"session.protocol !== \"leviathan\"",
"Leviathan tabs must close without connected-session confirmation",
);
assertContains(
"frontend/src/js/tabs.js",
"Connected since:",
"connection tab hover hints must include the connected-since timestamp",
);
assertContains(
"frontend/src/js/tabs.js",
"tab.appendChild(closeBtn);\n\n // Protocol badge",
"tab close button must render on the left side of each tab",
);
assertContains(
"frontend/src/css/layout.css",
"scrollbar-gutter: stable;",
"overflowing tab lists must expose a stable horizontal scrollbar",
);
assertContains(
"frontend/src/css/tabs.css",
".tab-tooltip",
"tab hover hints must have dedicated tooltip styling",
);
assertContains(
"frontend/src/js/leviathan.js",
"Hex Editor",
"Leviathan mode must put Hex Editor in the tool list",
);
assertContains(
"frontend/src/js/settings.js",
"Leviathan Binary Tools",
"in-app help must document the integrated Leviathan workbench",
);
assertContains(
"frontend/src/js/settings.js",
"VNC Connections",
"in-app help must document VNC connection profiles",
);
assertContains(
"docs/user-manual.md",
"## 11. Leviathan binary workbench",
"user manual must include a full Leviathan section",
);
assertContains(
"docs/user-manual.md",
"### VNC",
"user manual must include VNC connection guidance",
);
assertContains(
"docs/overview.md",
"### Leviathan binary workbench",
"product overview must include Leviathan as a first-class feature group",
);
assertContains(
"frontend/src/js/leviathan.js",
"LeviathanSetByte",
"Leviathan hex view must support byte editing",
);
assertContains(
"frontend/src/js/leviathan.js",
"_handleHexByteInput",
"Leviathan hex byte edits must update while typing",
);
assertContains(
"frontend/src/js/leviathan.js",
"_updateHexAsciiCell",
"Leviathan hex byte edits must update the ASCII preview immediately",
);
assertContains(
"frontend/src/js/leviathan.js",
"_focusHexByte(cell, 1)",
"Leviathan hex byte edits must advance to the next byte after entry",
);
assertContains(
"frontend/src/js/leviathan.js",
"spectrogram-encode",
"Leviathan must expose an image-to-audio spectrogram encoder",
);
assertContains(
"frontend/src/js/leviathan.js",
"spectrogram-view",
"Leviathan must expose an audio spectrogram visualiser",
);
assertContains(
"frontend/src/js/leviathan.js",
"preserveAspectRatio",
"Leviathan spectrogram encoding must expose aspect-ratio preservation",
);
assertContains(
"frontend/src/js/leviathan.js",
"aspectRatio",
"Leviathan spectrogram visualiser must expose display aspect-ratio control",
);
assertContains(
"frontend/src/js/leviathan.js",
"spectrogram-insert",
"Leviathan spectrogram encoder must support inserting images into carrier audio",
);
assertContains(
"frontend/src/js/app.js",
"setMode(\"leviathan\")",
"App mode switch must route to Leviathan",
);
assertContains(
"internal/api/leviathan.go",
"LeviathanRunTool",
"Nexus API must expose the imported Leviathan engine",
);
assertNotContains(
"internal/api/leviathan.go",
"Pattern: \"*\"",
"Leviathan open dialogs must not use wildcard filters that grey out files on macOS",
);
assertContains(
"internal/leviathan/spectrogram/spectrogram.go",
"github.com/hajimehoshi/go-mp3",
"Leviathan spectrogram visualiser must support MP3 decoding",
);
assertNotContains(
"wails.json",
"\"obfuscated\": true",
"Wails obfuscation must stay disabled because this app embeds frontend/src and uses normal Wails globals",
);
assertNotContains(
"wails.json",
"\"garbleargs\"",
"garble arguments must not be configured when Wails obfuscation is disabled",
);
assertContains(
"frontend/index.html",
"wails-service.js",
"frontend must load the Wails service bridge before app code",
);
assertNotContains(
"frontend/index.html",
'type="module" src="/src/js/wails-service.js"',
"Wails bridge must load as a synchronous classic script before app code",
);
assertContains(
"frontend/src/js/wails-service.js",
'prop === "then"',
"Wails service proxy must not masquerade as a Promise thenable",
);
assertContains(
"frontend/src/js/wails-service.js",
"window.go?.api?.Service",
"Wails service bridge must use the normal named Wails API",
);
assertContains(
"frontend/package.json",
'"@xterm/xterm":',
"frontend dependencies must use the maintained scoped xterm package",
);
assertContains(
"frontend/package.json",
'"@xterm/addon-fit":',
"frontend dependencies must use the maintained scoped xterm addons",
);
assertContains(
"frontend/scripts/copy-assets.js",
"@xterm/xterm/lib/xterm.js",
"xterm browser assets must be copied from the maintained scoped package",
);
assertContains(
"frontend/scripts/copy-assets.js",
"@xterm/addon-fit/lib/addon-fit.js",
"xterm addon browser assets must be copied from the maintained scoped packages",
);
assertNotContains(
"frontend/package.json",
'"xterm":',
"deprecated unscoped xterm dependency must not be restored",
);
assertNotContains(
"frontend/package.json",
'"xterm-addon-',
"deprecated unscoped xterm addon dependencies must not be restored",
);
assertNotContains(
"frontend/package-lock.json",
'"node_modules/xterm"',
"package lock must not contain the deprecated unscoped xterm package",
);
assertNotContains(
"frontend/package-lock.json",
'"node_modules/xterm-addon-',
"package lock must not contain deprecated unscoped xterm addon packages",
);
assertContains(
"frontend/scripts/build.js",
"sourceMappingURL",
"frontend production build must remove source-map references",
);
assertContains(
"frontend/scripts/build.js",
"bundle-novnc.js",
"frontend production build must regenerate the noVNC bundle",
);
assertContains(
"frontend/scripts/novnc-entry.js",
"initLogging(\"error\")",
"bundled noVNC logging must default to errors only",
);
assertNotContains(
"frontend/src/js/app.js",
"console.debug(\"[session:debug]\"",
"release UI must not print telnet negotiation debug events",
);
assertNotContains(
"frontend/src/js/app.js",
"console.log(\"Backend is ready\")",
"release UI must not print backend-ready lifecycle events",
);
assertContains(
"build.sh",
"NEXUS_COMPRESS",
"local builds must expose an explicit compression switch",
);
assertContains(
"scripts/build-common.sh",
"nexus_use_upx_for_target",
"build scripts must share one UPX target-selection helper",
);
assertContains(
"build.sh",
"Skipping UPX compression on macOS",
"local compressed builds must avoid unsupported macOS UPX by default",
);
assertContains(
"build.sh",
"-trimpath",
"local production builds should trim file paths from binaries",
);
assertContains(
"scripts/build-common.sh",
"-buildid=",
"production builds must strip the Go build ID",
);
assertContains(
"scripts/build-common.sh",
"-O2 -g0",
"production builds must remove cgo debug output by default",
);
assertContains(
"Makefile",
"build-compressed",
"Makefile must expose an explicit compressed build target",
);
assertContains(
"Makefile",
"NEXUS_FORCE_MACOS_UPX",
"Makefile compressed builds must make macOS UPX opt-in",
);
assertTreeNotContains(
"frontend/src/js",
"window.go.api.Service",
"app code must not call named Wails backend methods directly",
);
console.log("frontend regression checks passed");