import { render, screen } from "@testing-library/react"; import type { Address, Hex } from "viem"; import { describe, expect, it } from "vitest"; import shellHtml from "../index.html?raw"; import { App } from "./App"; import type { DashboardState } from "./hooks/useBankDashboard"; import type { DashboardSnapshot, DeploymentManifest } from "./types/dashboard"; const address = (digit: string) => `0x${digit.repeat(40)}` as Address; const transactionHash = (digit: string) => `0x${digit.repeat(64)}` as Hex; const localManifest: DeploymentManifest = { schemaVersion: 1, network: "anvil", chainId: 31337, deploymentBlock: 3n, rpcUrl: "http://127.0.0.1:8545", token: address("1"), proxy: address("2"), implementation: address("3"), owner: address("4"), actors: [ { label: "Alice", address: address("5") }, { label: "Bob", address: address("6") }, ], }; const snapshot: DashboardSnapshot = { blockNumber: 18n, synchronizedAt: new Date("2026-08-21T10:00:00.000Z"), version: 1, paused: true, asset: localManifest.token, owner: localManifest.owner, proxy: localManifest.proxy, implementation: localManifest.implementation, reserves: 1_500_000_000n, liabilities: 1_400_000_000n, surplus: 100_000_000n, actors: [ { label: "Alice", address: address("5"), balance: 900_000_000n }, { label: "Bob", address: address("6"), balance: 500_000_000n }, ], activity: [ { kind: "deposit", account: address("5"), amount: 1_000_000_000n, blockNumber: 4n, logIndex: 0, transactionHash: transactionHash("a") }, { kind: "withdrawal", account: address("5"), amount: 100_000_000n, blockNumber: 5n, logIndex: 0, transactionHash: transactionHash("b") }, { kind: "paused", account: address("4"), blockNumber: 6n, logIndex: 0, transactionHash: transactionHash("c") }, { kind: "ownershipTransferred", previousOwner: address("7"), newOwner: address("4"), blockNumber: 3n, logIndex: 1, transactionHash: transactionHash("d") }, { kind: "upgraded", implementation: address("3"), blockNumber: 3n, logIndex: 0, transactionHash: transactionHash("d") }, ], diagnostics: [{ blockNumber: 7n, logIndex: 2, transactionHash: transactionHash("e"), message: "Could not decode a proxy event log." }], }; function ready(manifest = localManifest, value = snapshot): DashboardState { return { status: "ready", manifest, snapshot: value }; } describe("read-only operations console", () => { it("loads the console entry point from the browser document", () => { const document = new DOMParser().parseFromString(shellHtml, "text/html"); expect(document.querySelector('script[type="module"]')?.getAttribute("src")).toBe("/src/main.tsx"); }); it("keeps the exact educational funds warning and complete trust disclosure visible", () => { render(); expect(screen.getByText("Educational demo — mock token — never use real funds.")).toBeTruthy(); expect(screen.getByText(/educational and unaudited/i)).toBeTruthy(); expect(screen.getByText(/owner can pause customer actions and install arbitrary future logic/i)).toBeTruthy(); expect(screen.getByText(/UUPS mistakes can corrupt state or permanently brick upgradeability/i)).toBeTruthy(); expect(screen.getByText(/professional audits, operational key controls, multisig or timelocked governance, incident procedures, legal advice, and jurisdiction-specific compliance/i)).toBeTruthy(); }); it("renders network, synchronization, lifecycle, pause, and V1 status", () => { render(); expect(screen.getByText("Local Anvil")).toBeTruthy(); expect(screen.getByText("Ready")).toBeTruthy(); expect(screen.getByText("Block 18")).toBeTruthy(); expect(screen.getByText("Paused")).toBeTruthy(); expect(screen.getByText("Version 1")).toBeTruthy(); expect(screen.getByText(/Aug 21, 2026/)).toBeTruthy(); }); it("renders accounting, contract identity, and local tracked accounts", () => { render(); expect(screen.getByText("1,500.00 mUSDC")).toBeTruthy(); expect(screen.getByText("1,400.00 mUSDC")).toBeTruthy(); expect(screen.getByText("100.00 mUSDC")).toBeTruthy(); expect(screen.getByText("107.14%")).toBeTruthy(); expect(screen.getByText("Proxy")).toBeTruthy(); expect(screen.getByText("Implementation")).toBeTruthy(); expect(screen.getByText("Token")).toBeTruthy(); expect(screen.getByText("Owner")).toBeTruthy(); expect(screen.getByText("Deployment block")).toBeTruthy(); expect(screen.getByText("Alice")).toBeTruthy(); expect(screen.getByText("Bob")).toBeTruthy(); expect(screen.getByText("900.00 mUSDC")).toBeTruthy(); expect(screen.getByText("500.00 mUSDC")).toBeTruthy(); }); it("makes every learning abbreviation keyboard-focusable with its explanation intact", () => { const { container } = render(); const abbreviations = Array.from(container.querySelectorAll("abbr")); expect(abbreviations).toHaveLength(5); for (const abbreviation of abbreviations) { expect(abbreviation.getAttribute("title")?.trim().length).toBeGreaterThan(0); expect(abbreviation.getAttribute("tabindex")).toBe("0"); (abbreviation as HTMLElement).focus(); expect(document.activeElement).toBe(abbreviation); } }); it("renders valid V1 activity newest first beside an isolated decode warning", () => { render(); const timeline = screen.getByLabelText("Proxy activity"); expect(timeline.textContent).toContain("Deposited"); expect(timeline.textContent).toContain("Withdrawn"); expect(timeline.textContent).toContain("Paused by"); expect(timeline.textContent).toContain("Ownership transferred"); expect(timeline.textContent).toContain("Implementation upgraded"); expect(timeline.textContent).toContain("Block 7"); expect(timeline.textContent).toContain("Could not decode a proxy event log."); expect(timeline.textContent?.indexOf("Paused by")).toBeLessThan(timeline.textContent?.indexOf("Withdrawn") ?? 0); expect(timeline.textContent).toContain("0xcccc…cccc"); }); it("uses validated Base Sepolia explorer links and public shortened account labels", () => { const baseManifest: DeploymentManifest = { ...localManifest, network: "baseSepolia", chainId: 84532, rpcUrl: "https://sepolia.base.org", explorerBaseUrl: "https://sepolia.basescan.org", }; render(); expect(screen.getByText("Base Sepolia")).toBeTruthy(); expect(screen.queryByText("Alice")).toBeNull(); expect(screen.getAllByText("0x5555…5555").length).toBeGreaterThan(0); const proxyLink = screen.getByRole("link", { name: /proxy.*0x2222…2222/i }); expect(proxyLink.getAttribute("href")).toBe(`https://sepolia.basescan.org/address/${snapshot.proxy}`); const transactionLink = screen.getByRole("link", { name: /transaction 0xcccc…cccc/i }); expect(transactionLink.getAttribute("href")).toBe(`https://sepolia.basescan.org/tx/${transactionHash("c")}`); }); it("does not create explorer links for local Anvil", () => { render(); expect(screen.queryAllByRole("link")).toHaveLength(0); }); it("shows No deposits instead of dividing zero liabilities", () => { render(); expect(screen.getByText("No deposits")).toBeTruthy(); }); it("distinguishes stale and disconnected conditions without hiding the persistent warnings", () => { const { rerender } = render(); expect(screen.getByText("Stale")).toBeTruthy(); expect(screen.getByText(/RPC timeout/)).toBeTruthy(); expect(screen.getByText("1,500.00 mUSDC")).toBeTruthy(); rerender(); expect(screen.getAllByText("Disconnected")).toHaveLength(2); expect(screen.getByText(/connection refused/)).toBeTruthy(); expect(screen.getByText("Educational demo — mock token — never use real funds.")).toBeTruthy(); expect(screen.getByText(/educational and unaudited/i)).toBeTruthy(); }); it("renders explanatory invalid-manifest and chain-mismatch terminal screens", () => { const { rerender } = render(); expect(screen.getAllByText("Invalid manifest")).toHaveLength(2); expect(screen.getByText(/missing required field proxy/)).toBeTruthy(); rerender(); expect(screen.getAllByText("Chain mismatch")).toHaveLength(2); expect(screen.getByText(/84532.*31337/)).toBeTruthy(); }); it("exposes no forms, buttons, wallet connection, signing, or transaction controls", () => { const { container } = render(); expect(container.querySelector("button")).toBeNull(); expect(container.querySelector("form")).toBeNull(); expect(container.textContent).not.toMatch(/connect wallet|sign transaction|submit transaction|deposit funds|withdraw funds/i); }); });