309 lines
12 KiB
JavaScript
309 lines
12 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("Gitea workspace sync is preview-driven, recoverable and never deletes ignored runtime data", async () => {
|
|
const renderer = await rendererSource();
|
|
const preload = await readFile(new URL("../preload.cjs", import.meta.url), "utf8");
|
|
const ipc = await ipcSource();
|
|
assert.match(renderer, /Gitea workspace sync/);
|
|
assert.match(renderer, /preview-workspace-sync/);
|
|
assert.match(renderer, /confirm-workspace-sync/);
|
|
assert.match(renderer, /Ignored runtime files remain in place/);
|
|
assert.match(renderer, /recovery branch/);
|
|
assert.match(renderer, /named Git stash/);
|
|
assert.match(renderer, /Gitea fetch interval/);
|
|
assert.match(preload, /previewWorkspaceSync/);
|
|
assert.match(preload, /applyWorkspaceSync/);
|
|
assert.match(ipc, /repository:workspace-sync-preview/);
|
|
assert.match(ipc, /repository:workspace-sync-apply/);
|
|
});
|
|
|
|
test("demo bridge implements the complete Git recovery flow", async () => {
|
|
const source = await readFile(
|
|
new URL("../src/renderer/mock-repository-bridge.js", import.meta.url),
|
|
"utf8",
|
|
);
|
|
for (const method of [
|
|
"gitRecoveryStatus",
|
|
"reconcileRepository",
|
|
"repairGitLocks",
|
|
"repairRepositorySync",
|
|
]) {
|
|
assert.match(source, new RegExp(`async ${method}\\(`));
|
|
}
|
|
});
|
|
|
|
test("Git tools rows retain their content height inside the scrollable tab", async () => {
|
|
const styles = await readFile(
|
|
new URL("../src/renderer/styles.css", import.meta.url),
|
|
"utf8",
|
|
);
|
|
assert.match(
|
|
styles,
|
|
/\.git-tools-grid\s*\{[^}]*grid-auto-rows:\s*max-content/s,
|
|
);
|
|
});
|
|
|
|
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 groups variable context above a stable tab row", async () => {
|
|
const renderer = await rendererSource();
|
|
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 39px minmax\(0, 1fr\)/s,
|
|
);
|
|
assert.match(renderer, /<div class="repo-context">/);
|
|
assert.match(styles, /\.tabs\s*\{[^}]*overflow-x:\s*auto/s);
|
|
assert.match(styles, /\.tab\s*\{[^}]*white-space:\s*nowrap/s);
|
|
assert.match(styles, /prefers-reduced-motion/);
|
|
assert.match(styles, /ForgeFlow 0\.8 premium visual system/);
|
|
});
|
|
|
|
test("help center explains core workflows and supports contextual searchable guidance", async () => {
|
|
const renderer = await rendererSource();
|
|
assert.match(renderer, /function renderHelp\(\)/);
|
|
assert.match(renderer, /id: "workspace-sync"/);
|
|
assert.match(renderer, /id: "deployment-linking"/);
|
|
assert.match(renderer, /id: "deploy-keys"/);
|
|
assert.match(renderer, /id: "git-validator"/);
|
|
assert.match(renderer, /id="help-search"/);
|
|
assert.match(renderer, /data-action="open-context-help" data-topic="workspace-sync"/);
|
|
assert.match(renderer, /ui\.currentView === "help"/);
|
|
});
|
|
|
|
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\)/);
|
|
});
|