diff --git a/src/clipboard.test.ts b/src/clipboard.test.ts index c9ced55..1714bc7 100644 --- a/src/clipboard.test.ts +++ b/src/clipboard.test.ts @@ -1,4 +1,4 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vitest"; import { copyToClipboard } from "./clipboard"; describe("copyToClipboard", () => { diff --git a/src/test/setup.ts b/src/test/setup.ts index f149f27..e262193 100644 --- a/src/test/setup.ts +++ b/src/test/setup.ts @@ -1 +1,7 @@ import "@testing-library/jest-dom/vitest"; +import { cleanup } from "@testing-library/react"; +import { afterEach } from "vitest"; + +afterEach(() => { + cleanup(); +}); diff --git a/src/testkit.test.ts b/src/testkit.test.ts index f03c0b1..8a1ff81 100644 --- a/src/testkit.test.ts +++ b/src/testkit.test.ts @@ -1,5 +1,10 @@ import { describe, expect, it } from "vitest"; -import { buildCommand } from "./testkit"; +import { + buildCommand, + cliCompatibility, + formatCommandLine, + localFileGuides, +} from "./testkit"; describe("buildCommand", () => { it("builds a coverage command", () => { @@ -47,3 +52,146 @@ describe("buildCommand", () => { .toBe('soroban-testkit audit "./a b c"'); }); }); + +describe("formatCommandLine", () => { + it("returns single-line identical to buildCommand when multiline is false", () => { + const options = { + command: "coverage" as const, + path: ".", + format: "html" as const, + open: true, + fn: "", + ramp: "", + strict: false, + }; + expect(formatCommandLine(options, false)).toBe(buildCommand(options)); + }); + + it("formats multi-line coverage command with POSIX line continuations", () => { + const formatted = formatCommandLine( + { + command: "coverage", + path: "./contracts/escrow", + format: "lcov", + open: true, + fn: "", + ramp: "", + strict: false, + }, + true, + ); + + expect(formatted).toBe( + [ + "soroban-testkit coverage \\", + " ./contracts/escrow \\", + " --format lcov \\", + " --open", + ].join("\n"), + ); + }); + + it("formats multi-line limits command with quoted paths if needed", () => { + const formatted = formatCommandLine( + { + command: "limits", + path: "target/release/my contract.wasm", + format: "html", + open: false, + fn: "execute_equal", + ramp: "recipients", + strict: false, + }, + true, + ); + + expect(formatted).toBe( + [ + "soroban-testkit limits \\", + ' --contract "target/release/my contract.wasm" \\', + " --fn execute_equal \\", + " --ramp recipients", + ].join("\n"), + ); + }); + + it("formats multi-line audit command with strict flag", () => { + const formatted = formatCommandLine( + { + command: "audit", + path: "./my-contract/src", + format: "text", + open: false, + fn: "", + ramp: "", + strict: true, + }, + true, + ); + + expect(formatted).toBe( + [ + "soroban-testkit audit \\", + " ./my-contract/src \\", + " --strict", + ].join("\n"), + ); + }); + + it("formats multi-line audit command without strict flag", () => { + const formatted = formatCommandLine( + { + command: "audit", + path: "./src", + format: "text", + open: false, + fn: "", + ramp: "", + strict: false, + }, + true, + ); + + expect(formatted).toBe( + [ + "soroban-testkit audit \\", + " ./src", + ].join("\n"), + ); + }); +}); + +describe("cliCompatibility", () => { + it("provides version entries with required compatibility targets", () => { + expect(cliCompatibility.length).toBeGreaterThanOrEqual(3); + + const current = cliCompatibility.find((c) => c.status === "Current"); + expect(current).toBeDefined(); + expect(current?.sorobanSdk).toBe("27.0.x"); + expect(current?.rustTarget).toContain("wasm32v1-none"); + + for (const row of cliCompatibility) { + expect(row.version).toBeTruthy(); + expect(row.sorobanSdk).toBeTruthy(); + expect(row.stellarCli).toBeTruthy(); + expect(row.rustTarget).toBeTruthy(); + expect(row.status).toBeTruthy(); + expect(row.notes).toBeTruthy(); + } + }); +}); + +describe("localFileGuides", () => { + it("covers all commands with input/output documentation", () => { + for (const cmd of ["coverage", "limits", "audit"] as const) { + const guide = localFileGuides[cmd]; + expect(guide).toBeDefined(); + expect(guide.filesRead.length).toBeGreaterThan(0); + expect(guide.filesWritten.length).toBeGreaterThan(0); + expect(guide.offlineGuarantee).toContain("offline"); + expect(guide.resolvedPathExample("test-path")).toBeTruthy(); + } + }); +}); + + diff --git a/src/testkit.ts b/src/testkit.ts index 32bad1e..9263a2c 100644 --- a/src/testkit.ts +++ b/src/testkit.ts @@ -54,3 +54,164 @@ export function buildCommand(options: CommandOptions): string { .filter(Boolean) .join(" "); } + +export function formatCommandLine( + options: CommandOptions, + multiline = false, +): string { + if (!multiline) { + return buildCommand(options); + } + + if (options.command === "coverage") { + const args: string[] = []; + if (options.path) args.push(quote(options.path)); + args.push(`--format ${options.format}`); + if (options.open) args.push("--open"); + + if (args.length === 0) { + return "soroban-testkit coverage"; + } + + return [ + "soroban-testkit coverage \\", + ...args.map((arg, index) => ` ${arg}${index < args.length - 1 ? " \\" : ""}`), + ].join("\n"); + } + + if (options.command === "limits") { + const contractPath = quote( + options.path || "target/wasm32v1-none/release/contract.wasm", + ); + const fn = options.fn || "batch_payout"; + const ramp = options.ramp || "recipients"; + + return [ + "soroban-testkit limits \\", + ` --contract ${contractPath} \\`, + ` --fn ${fn} \\`, + ` --ramp ${ramp}`, + ].join("\n"); + } + + // audit + const auditPath = quote(options.path || "./src"); + if (options.strict) { + return [ + "soroban-testkit audit \\", + ` ${auditPath} \\`, + " --strict", + ].join("\n"); + } + + return ["soroban-testkit audit \\", ` ${auditPath}`].join("\n"); +} + +export interface CliCompatibility { + version: string; + sorobanSdk: string; + stellarCli: string; + rustTarget: string; + status: "Current" | "Supported" | "Upcoming"; + notes: string; +} + +export const cliCompatibility: readonly CliCompatibility[] = [ + { + version: "v0.2.0", + sorobanSdk: "27.0.x", + stellarCli: ">= 22.0.0", + rustTarget: "wasm32v1-none (Rust >= 1.84)", + status: "Current", + notes: + "AuthMatrix multi-actor enforcement, auth-mocked limits ramp, SAC heuristic mocking, cargo-llvm-cov integration.", + }, + { + version: "v0.1.0", + sorobanSdk: "22.x - 26.x", + stellarCli: ">= 21.0.0", + rustTarget: "wasm32-unknown-unknown / wasm32v1-none", + status: "Supported", + notes: + "Deterministic TestEnv, ledger timestamp/sequence warping, conservation balance checks, AST source code audit scanner.", + }, + { + version: "v0.3.0", + sorobanSdk: "28.0.x+", + stellarCli: ">= 23.0.0", + rustTarget: "wasm32v1-none (Rust 2024)", + status: "Upcoming", + notes: + "Extended persistent TTL storage simulation, multi-contract cross-call limits ramp, and gas telemetry export.", + }, +]; + +export interface LocalFileGuide { + command: CommandName; + title: string; + summary: string; + defaultPath: string; + resolvedPathExample: (path: string) => string; + filesRead: string[]; + filesWritten: string[]; + offlineGuarantee: string; +} + +export const localFileGuides: Record = { + coverage: { + command: "coverage", + title: "Contract Coverage (cargo-llvm-cov)", + summary: + "Instruments contract crates and runs local test suites to compute exact source and branch coverage.", + defaultPath: ".", + resolvedPathExample: (p) => (p || ".").replace(/\/$/, "") + "/Cargo.toml", + filesRead: [ + "Cargo.toml / Cargo.lock (crate dependencies and test harness)", + "src/**/*.rs (contract source files)", + "tests/**/*.rs (integration test suites)", + ], + filesWritten: [ + "target/llvm-cov/html/** (interactive report when --format html)", + "target/llvm-cov/lcov.info (coverage trace when --format lcov)", + "None (stdout summary only when --format text)", + ], + offlineGuarantee: + "Execution is 100% offline. No contract bytecode or source files are ever transmitted remotely.", + }, + limits: { + command: "limits", + title: "Resource Limit Ramp (soroban-testkit limits)", + summary: + "Probes compiled WASM bytecode across stepped parameter values to find exact instruction and memory boundaries.", + defaultPath: "target/wasm32v1-none/release/contract.wasm", + resolvedPathExample: (p) => + p || "target/wasm32v1-none/release/contract.wasm", + filesRead: [ + "Pre-compiled contract WebAssembly bytecode (.wasm artifact)", + "Local function export table and symbol signatures", + ], + filesWritten: [ + "None (metrics and resource ceilings stream directly to terminal stdout)", + ], + offlineGuarantee: + "100% offline. Probes execute in isolated local child processes using in-memory mock ledger state with zero network RPC calls.", + }, + audit: { + command: "audit", + title: "Static Source Audit (soroban-testkit audit)", + summary: + "Performs AST-level static security checks for authentication gating, arithmetic safety, and TTL state.", + defaultPath: "./src", + resolvedPathExample: (p) => (p || "./src").replace(/\/$/, "") + "/*.rs", + filesRead: [ + "Rust source files (*.rs) in the specified project directory", + "Syntax trees for require_auth, arithmetic operations, and storage keys", + ], + filesWritten: [ + "None (findings and warning locations stream directly to terminal stdout)", + ], + offlineGuarantee: + "100% offline, read-only and non-destructive. Source code is parsed locally without external telemetry.", + }, +}; + diff --git a/tests/visual/__screenshots__/commands-audit.png b/tests/visual/__screenshots__/commands-audit.png new file mode 100644 index 0000000..d04a558 Binary files /dev/null and b/tests/visual/__screenshots__/commands-audit.png differ diff --git a/tests/visual/__screenshots__/commands-coverage.png b/tests/visual/__screenshots__/commands-coverage.png new file mode 100644 index 0000000..8efa941 Binary files /dev/null and b/tests/visual/__screenshots__/commands-coverage.png differ diff --git a/tests/visual/__screenshots__/commands-limits.png b/tests/visual/__screenshots__/commands-limits.png new file mode 100644 index 0000000..7e5162e Binary files /dev/null and b/tests/visual/__screenshots__/commands-limits.png differ diff --git a/tests/visual/__screenshots__/full-page.png b/tests/visual/__screenshots__/full-page.png new file mode 100644 index 0000000..6ec1d5d Binary files /dev/null and b/tests/visual/__screenshots__/full-page.png differ diff --git a/tests/visual/__screenshots__/hero.png b/tests/visual/__screenshots__/hero.png new file mode 100644 index 0000000..c86ed82 Binary files /dev/null and b/tests/visual/__screenshots__/hero.png differ diff --git a/tests/visual/__screenshots__/modules.png b/tests/visual/__screenshots__/modules.png new file mode 100644 index 0000000..cf26324 Binary files /dev/null and b/tests/visual/__screenshots__/modules.png differ diff --git a/vite.config.ts b/vite.config.ts index d31dddc..8034a56 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -6,5 +6,6 @@ export default defineConfig({ test: { environment: "jsdom", setupFiles: "./src/test/setup.ts", + exclude: ["tests/visual/**", "node_modules/**"], }, });