views.js sat at the project's 750-line limit, so the diff cap in the previous commit pushed it over and every further change would have meant shaving comments elsewhere. That is the file asking for decomposition, which is what the architecture audit says to do. Diff rendering is self-contained: the line cap, the line classifier and the change-map illustration depend on nothing in views.js beyond ui and escapeHtml. They now live in src/renderer/diff-view.js and are registered in index.html and in the three renderer file lists that scan the bridge surface, so anything added there is covered by the existing contract tests. views.js drops from 755 to 714 lines and no source file exceeds 750 again. The nested ternary that classified a diff line became a named function with guard clauses on the way. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
250 lines
9.7 KiB
JavaScript
250 lines
9.7 KiB
JavaScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { readFile } from "node:fs/promises";
|
|
|
|
const rendererFiles = ["app.js", "diff-view.js", "views.js", "dialogs.js", "operations.js", "actions/shell.js", "actions/inventory.js", "actions/deployment-profile.js", "actions/deployment-operation.js", "actions/setup-and-settings.js", "actions/recovery.js", "actions/command.js", "events.js"];
|
|
async function rendererSource() {
|
|
return (await Promise.all(rendererFiles.map((file) => readFile(new URL(`../src/renderer/${file}`, import.meta.url), "utf8")))).join("\n");
|
|
}
|
|
async function ipcSource() {
|
|
return (await Promise.all(["ipc.cjs", "ipc/repository-handlers.cjs", "ipc/deployment-handlers.cjs", "ipc/operations-handlers.cjs"].map((file) => readFile(new URL(`../src/main/${file}`, import.meta.url), "utf8")))).join("\n");
|
|
}
|
|
|
|
test("desktop shell serializes ForgeFlow to one configuration writer", async () => {
|
|
const main = await readFile(new URL("../main.cjs", import.meta.url), "utf8");
|
|
assert.match(main, /requestSingleInstanceLock\(\)/);
|
|
assert.match(main, /second-instance/);
|
|
assert.match(main, /showMainWindow\(\)/);
|
|
});
|
|
|
|
test("changed file list has an independently scrollable bounded layout", async () => {
|
|
const css = await readFile(
|
|
new URL("../src/renderer/styles.css", import.meta.url),
|
|
"utf8",
|
|
);
|
|
assert.match(
|
|
css,
|
|
/\.main-canvas\.repository-canvas\s*\{[^}]*overflow:\s*hidden/,
|
|
);
|
|
assert.match(
|
|
css,
|
|
/\.file-panel\s*\{[^}]*min-height:\s*0[^}]*overflow:\s*hidden/,
|
|
);
|
|
assert.match(
|
|
css,
|
|
/\.file-list\s*\{[^}]*flex:\s*1 1 auto[^}]*overflow-y:\s*auto/,
|
|
);
|
|
});
|
|
|
|
test("commit workflow explains every disabled prerequisite", async () => {
|
|
const renderer = await rendererSource();
|
|
assert.match(renderer, /Commit message <span class="required-mark">required/);
|
|
assert.match(renderer, /Enter a commit message to enable commit and push/);
|
|
assert.match(renderer, /ForgeFlow stages the selected files automatically/);
|
|
assert.match(renderer, /data-action="commit-push"/);
|
|
assert.match(renderer, /Commit staged hunks/);
|
|
});
|
|
|
|
test("ITWorx branding is integrated into titlebar and setup", async () => {
|
|
const renderer = await rendererSource();
|
|
assert.match(renderer, /itworx-mark\.png/);
|
|
assert.match(renderer, /itworx-wordmark-(?:light|dark)\.png/);
|
|
});
|
|
|
|
test("all modal content stays inside the viewport with a persistent action footer", async () => {
|
|
const css = await readFile(
|
|
new URL("../src/renderer/styles.css", import.meta.url),
|
|
"utf8",
|
|
);
|
|
assert.match(
|
|
css,
|
|
/\.modal\s*\{[^}]*max-height:\s*calc\(100dvh[^}]*display:\s*flex[^}]*flex-direction:\s*column/,
|
|
);
|
|
assert.match(
|
|
css,
|
|
/\.modal-body\s*\{[^}]*min-height:\s*0[^}]*overflow-y:\s*auto/,
|
|
);
|
|
assert.match(css, /\.modal-footer\s*\{[^}]*flex:\s*0 0 auto/);
|
|
});
|
|
|
|
test("settings provides one-click normalization for legacy Gitea origins", async () => {
|
|
const renderer = await rendererSource();
|
|
assert.match(renderer, /data-action="normalize-origins"/);
|
|
assert.match(renderer, /Normalize all origins/);
|
|
});
|
|
|
|
test("Git mutations are serialized per repository and expose repair actions", async () => {
|
|
const ipc = await ipcSource();
|
|
const renderer = await rendererSource();
|
|
assert.match(ipc, /repositoryMutations = new Map/);
|
|
assert.match(ipc, /withRepositoryMutation/);
|
|
assert.match(ipc, /GIT_LOCKS_RECENT/);
|
|
assert.match(ipc, /setTimeout\(resolve, 2_500\)/);
|
|
assert.match(renderer, /data-action="repair-git-locks"/);
|
|
assert.match(renderer, /Repository troubleshooting/);
|
|
assert.match(renderer, /data-action="repair-origin"/);
|
|
assert.match(renderer, /Open guided repository repair/);
|
|
});
|
|
|
|
test("SSH secrets are captured before the loading render clears password inputs", async () => {
|
|
const renderer = await rendererSource();
|
|
const passwordCapture = renderer.search(
|
|
/const password = document\.querySelector\(["']#server-password["']\)/,
|
|
);
|
|
const loading = renderer.search(
|
|
/setLoading\(true, ["']Saving encrypted SSH configuration/,
|
|
);
|
|
assert.ok(passwordCapture >= 0 && loading > passwordCapture);
|
|
});
|
|
|
|
test("SSH deployments are polled in the background and Portfolio casing is preserved", async () => {
|
|
const renderer = await rendererSource();
|
|
assert.match(renderer, /function startOperationPolling\(\)/);
|
|
assert.match(renderer, /startOperationPolling\(\);/);
|
|
assert.match(renderer, /Visible container name/);
|
|
assert.match(renderer, /Compose services to verify/);
|
|
});
|
|
|
|
test("deployment profiles expose built-in/uploaded DockerMan icons and automatic metadata repair", async () => {
|
|
const renderer = await rendererSource();
|
|
assert.match(renderer, /Built-in high-contrast ITWorx mark/);
|
|
assert.match(renderer, /profile-icon-mode/);
|
|
assert.match(renderer, /Repair DockerMan integration/);
|
|
assert.match(renderer, /reconcile-deployment/);
|
|
});
|
|
|
|
test("repository troubleshooting offers personalized synchronization repair actions", async () => {
|
|
const renderer = await rendererSource();
|
|
const ipc = await ipcSource();
|
|
assert.match(renderer, /repair-repository-sync/);
|
|
assert.match(renderer, /safety branch/);
|
|
assert.match(ipc, /repository:repair-sync/);
|
|
});
|
|
|
|
test("advanced Git, desktop, backup, policy and audit workflows are exposed in the renderer", async () => {
|
|
const renderer = await rendererSource();
|
|
const preload = await readFile(
|
|
new URL("../preload.cjs", import.meta.url),
|
|
"utf8",
|
|
);
|
|
for (const phrase of [
|
|
"Stage hunks",
|
|
"Conflict guide",
|
|
"Create pull request",
|
|
"Open pull requests",
|
|
"load-pull-requests",
|
|
"Check branch protection",
|
|
"Encrypted configuration backup",
|
|
"Deployment policy",
|
|
"Operational audit log",
|
|
])
|
|
assert.match(renderer, new RegExp(phrase, "i"));
|
|
for (const method of [
|
|
"stageHunks",
|
|
"resolveConflict",
|
|
"createPullRequest",
|
|
"branchProtection",
|
|
"openEditor",
|
|
"openTerminal",
|
|
"exportConfigurationBackup",
|
|
"listAuditEvents",
|
|
])
|
|
assert.match(preload, new RegExp(`${method}:`));
|
|
});
|
|
|
|
test("one-click troubleshooting excludes destructive or publishing Git actions", async () => {
|
|
const renderer = await rendererSource();
|
|
const ipc = await readFile(
|
|
new URL("../src/main/ipc.cjs", import.meta.url),
|
|
"utf8",
|
|
);
|
|
assert.match(ipc, /action:\s*["']abort-operation["'],\s*safe:\s*false/);
|
|
assert.match(ipc, /action:\s*["']push["'],\s*safe:\s*false/);
|
|
assert.match(ipc, /const stale = lock\.ageMs >= 10_000/);
|
|
assert.match(
|
|
ipc,
|
|
/\[\s*["']fast-forward["'],\s*["']fetch["']\s*\]\.includes\(issue\.action\)/,
|
|
);
|
|
assert.doesNotMatch(
|
|
ipc,
|
|
/\[\s*["']fast-forward["'],\s*["']push["'],\s*["']fetch["']\s*\]\.includes\(issue\.action\)/,
|
|
);
|
|
assert.match(
|
|
renderer,
|
|
/trouble\?\.issues\?\.some\(\(item\) => item\.repairable && item\.safe\)/,
|
|
);
|
|
});
|
|
|
|
test("premium repository workspace reserves separate rows for actions and release status", async () => {
|
|
const styles = await readFile(
|
|
new URL("../src/renderer/styles.css", import.meta.url),
|
|
"utf8",
|
|
);
|
|
assert.match(
|
|
styles,
|
|
/\.repo-workspace\s*\{[^}]*grid-template-rows:\s*auto auto auto 39px minmax\(0, 1fr\)/s,
|
|
);
|
|
assert.match(styles, /prefers-reduced-motion/);
|
|
assert.match(styles, /ForgeFlow 0\.8 premium visual system/);
|
|
});
|
|
|
|
test("interactive project illustrations are semantic, responsive and motion-safe", async () => {
|
|
const renderer = await rendererSource();
|
|
const styles = await readFile(
|
|
new URL("../src/renderer/styles.css", import.meta.url),
|
|
"utf8",
|
|
);
|
|
assert.match(renderer, /function projectIllustration/);
|
|
assert.match(renderer, /data-project-illustration/);
|
|
assert.match(renderer, /document\.addEventListener\("pointermove"/);
|
|
assert.match(styles, /\.project-illustration/);
|
|
assert.match(styles, /@keyframes signal-travel/);
|
|
assert.match(styles, /prefers-reduced-motion/);
|
|
assert.match(styles, /transform: none !important/);
|
|
});
|
|
|
|
test("the diff canvas uses a contextual and motion-safe code illustration", async () => {
|
|
const renderer = await rendererSource();
|
|
const styles = await readFile(
|
|
new URL("../src/renderer/styles.css", import.meta.url),
|
|
"utf8",
|
|
);
|
|
assert.match(renderer, /function diffAtmosphere/);
|
|
assert.match(renderer, /data-diff-atmosphere/);
|
|
assert.match(renderer, /--diff-tilt-x/);
|
|
assert.match(styles, /\.diff-atmosphere/);
|
|
assert.match(styles, /@keyframes code-packet-travel/);
|
|
assert.match(styles, /prefers-reduced-motion/);
|
|
});
|
|
|
|
test("Git Validator exposes scored best-practice checks and bounded repairs", async () => {
|
|
const renderer = await rendererSource();
|
|
const styles = await readFile(
|
|
new URL("../src/renderer/styles.css", import.meta.url),
|
|
"utf8",
|
|
);
|
|
const preload = await readFile(
|
|
new URL("../preload.cjs", import.meta.url),
|
|
"utf8",
|
|
);
|
|
assert.match(renderer, /function renderGitValidator/);
|
|
assert.match(renderer, /gitValidatorPreviewRepair/);
|
|
assert.match(renderer, /git-validator-suppress/);
|
|
assert.match(renderer, /git-validator-policy/);
|
|
assert.match(styles, /\.validator-score/);
|
|
assert.match(styles, /@container \(max-width: 900px\)/);
|
|
assert.match(preload, /gitValidatorScan/);
|
|
assert.match(preload, /gitValidatorRepair/);
|
|
assert.match(preload, /gitValidatorExport/);
|
|
});
|
|
|
|
test("renderer guards accessible names, labels and uncertain inventory evidence", async () => {
|
|
const renderer = await rendererSource();
|
|
const styles = await readFile(new URL("../src/renderer/styles.css", import.meta.url), "utf8");
|
|
assert.match(renderer, /button\.icon-button:not\(\[aria-label\]\)/);
|
|
assert.match(renderer, /\.field > label:not\(\[for\]\)/);
|
|
assert.match(renderer, /topCandidate\.confidence \|\| topCandidate\.status \|\| "review required"/);
|
|
assert.match(styles, /\.action-panel-body \.panel-callout > h2/);
|
|
assert.match(styles, /@media \(max-height: 760px\)/);
|
|
});
|