import assert from "node:assert/strict"; import test from "node:test"; import React from "react"; import { renderToStaticMarkup } from "react-dom/server"; import { VaultImportDestinationControls, VaultImportProgressPanel, VaultImportProgressView, } from "./ImportVaultDialog.tsx"; const messages: Record = { "vault.import.progress.title": "Importing hosts", "vault.import.progress.reading": "Reading file", "vault.import.progress.parsing": "Parsing hosts", "vault.import.progress.preparing": "Preparing changes", "vault.import.progress.saving": "Saving hosts", "vault.import.progress.complete": "Import complete", "vault.import.progress.failed": "Import failed", "vault.import.progress.summary": "Imported {count} hosts; skipped {skipped}; duplicates {duplicates}.", "vault.import.progress.keepOpen": "You can keep using Netcatty while this runs.", "vault.import.progress.fileSummary": "{name} · {count} files", "vault.import.progress.fileCount": "{completed} of {total} files", "common.close": "Close", "common.cancel": "Cancel", }; const t = (key: string, values?: Record) => { let value = messages[key] ?? key; for (const [name, replacement] of Object.entries(values ?? {})) { value = value.replace(`{${name}}`, String(replacement)); } return value; }; test("vault import progress renders the current background stage and percent", () => { const html = renderToStaticMarkup( {}} onCancel={() => {}} t={t} />, ); assert.match(html, /Importing hosts/); assert.match(html, /hosts\.csv/); assert.match(html, /Parsing hosts/); assert.match(html, /aria-valuenow="55"/); assert.match(html, /role="status"/); assert.match(html, /aria-live="polite"/); assert.match(html, /]*role="status"[^>]*>Parsing hosts<\/span>/); assert.match(html, />CancelClose { const html = renderToStaticMarkup( {}} t={t} />, ); assert.match(html, /Import complete/); assert.match(html, /Imported 8000 hosts; skipped 3; duplicates 2\./); assert.match( html, /]*role="status"[^>]*>Import complete\. Imported 8000 hosts; skipped 3; duplicates 2\.<\/span>/, ); assert.match(html, /aria-valuenow="100"/); assert.match(html, />Close { const html = renderToStaticMarkup( {}} t={t} />, ); assert.match( html, /]*role="status"[^>]*>Import failed\. Saved Vault data is unreadable<\/span>/, ); }); test("vault import progress shows SecureCRT batch file progress", () => { const html = renderToStaticMarkup( {}} t={t} />, ); assert.match(html, /Sessions · 3 files/); assert.match(html, /2 of 3 files/); assert.match(html, /DB\.ini/); }); test("vault import progress is shown in a non-blocking floating panel", () => { const html = renderToStaticMarkup( {}} onCancel={() => {}} t={t} />, ); assert.match(html, /data-vault-import-progress-panel/); assert.doesNotMatch(html, /role="dialog"/); assert.match(html, /fixed/); assert.match(html, /max-h-\[calc\(100vh-2rem\)\]/); assert.match(html, /overflow-y-auto/); }); test("vault import destination controls offer preserve, existing, and new groups", () => { const html = renderToStaticMarkup( {}} groups={["Production", "Staging"]} existingGroup="Production" onExistingGroupChange={() => {}} newGroup="" onNewGroupChange={() => {}} t={t} />, ); assert.equal((html.match(/data-import-destination-mode=/g) ?? []).length, 3); assert.match(html, /Production/); assert.doesNotMatch(html, /Staging/); assert.match(html, /vault\.import\.destination\.preserve/); assert.match(html, /vault\.import\.destination\.existing/); assert.match(html, /vault\.import\.destination\.new/); }); test("import format step keeps destination settings off the main card grid", async () => { // Source-level guard: format tiles must not embed SecureCRT copy, and the // destination picker lives behind a dedicated footer entry point. const { readFileSync } = await import("node:fs"); const source = readFileSync( new URL("./ImportVaultDialog.tsx", import.meta.url), "utf8", ); assert.match(source, /data-import-destination-settings="true"/); assert.match(source, /data-import-securecrt-prompt="true"/); assert.match(source, /step === "destination"/); assert.match(source, /FolderTree/); assert.doesNotMatch( source, /data-import-format=\{opt\.format\}[\s\S]*securecrt\.directoryHint/, ); }); test("vault import destination search caps very large group suggestions", () => { const groups = Array.from({ length: 1000 }, (_, index) => `Group ${index}`); const html = renderToStaticMarkup( {}} groups={groups} existingGroup="" onExistingGroupChange={() => {}} newGroup="" onNewGroupChange={() => {}} t={t} />, ); assert.equal((html.match(/