diff --git a/apps/web/src/features/wallet/components/NetworkMismatchBanner.test.tsx b/apps/web/src/features/wallet/components/NetworkMismatchBanner.test.tsx new file mode 100644 index 0000000..c0bcdb0 --- /dev/null +++ b/apps/web/src/features/wallet/components/NetworkMismatchBanner.test.tsx @@ -0,0 +1,125 @@ +import { describe, it, expect, beforeEach, afterEach, beforeAll, vi } from "vitest" +import { render, screen, fireEvent } from "@testing-library/react" +import { useWalletStore } from "../store/wallet-store" +import { NetworkMismatchBanner } from "./NetworkMismatchBanner" +import { NETWORK } from "@/app/config/network" + +// ── Mock router ────────────────────────────────────────────────────────────── +const mockLocation = { pathname: "/faucet" } + +vi.mock("@tanstack/react-router", () => ({ + useLocation: () => mockLocation, +})) + +describe("NetworkMismatchBanner (#211)", () => { + let originalNetworkName: "testnet" | "mainnet" + + beforeAll(() => { + originalNetworkName = NETWORK.name + }) + + beforeEach(() => { + // Clear sessionStorage and reset mock defaults + sessionStorage.clear() + mockLocation.pathname = "/faucet" + NETWORK.name = "testnet" + + // Default wallet state: connected to testnet + useWalletStore.setState({ + address: "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF", + walletId: "freighter", + status: "connected", + pendingTransactionXdr: null, + network: "testnet", + }) + }) + + afterEach(() => { + vi.restoreAllMocks() + NETWORK.name = originalNetworkName + }) + + it("does not render when networks match (both testnet)", () => { + render() + expect(screen.queryByRole("alert")).not.toBeInTheDocument() + }) + + it("does not render when wallet is disconnected", () => { + useWalletStore.setState({ + address: null, + walletId: null, + status: "disconnected", + network: "testnet", + }) + render() + expect(screen.queryByRole("alert")).not.toBeInTheDocument() + }) + + it("does not render on landing page (pathname = '/') even with mismatch", () => { + mockLocation.pathname = "/" + NETWORK.name = "mainnet" // Mismatch: app mainnet vs wallet testnet + render() + expect(screen.queryByRole("alert")).not.toBeInTheDocument() + }) + + it("renders warning banner when app is testnet and wallet is mainnet", () => { + // Wallet mainnet, App testnet + useWalletStore.setState({ + address: "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF", + walletId: "freighter", + status: "connected", + network: "mainnet", + }) + + render() + + const alert = screen.getByRole("alert") + expect(alert).toBeInTheDocument() + expect( + screen.getByText( + /Your wallet is connected to Mainnet but this app is running on Testnet\. Please switch networks in your wallet\./i, + ), + ).toBeInTheDocument() + }) + + it("renders warning banner when app is mainnet and wallet is testnet", () => { + // Wallet testnet, App mainnet + NETWORK.name = "mainnet" + + render() + + const alert = screen.getByRole("alert") + expect(alert).toBeInTheDocument() + expect( + screen.getByText( + /Your wallet is connected to Testnet but this app is running on Mainnet\. Please switch networks in your wallet\./i, + ), + ).toBeInTheDocument() + }) + + it("dismisses the banner when the Dismiss button is clicked", () => { + NETWORK.name = "mainnet" // Wallet testnet vs App mainnet + + const { rerender } = render() + + expect(screen.getByRole("alert")).toBeInTheDocument() + + const dismissButton = screen.getByRole("button", { name: /Dismiss/i }) + fireEvent.click(dismissButton) + + // Alert should immediately disappear from DOM + rerender() + expect(screen.queryByRole("alert")).not.toBeInTheDocument() + + // Assert that sessionStorage was updated + expect(sessionStorage.getItem("so4-network-mismatch-dismissed")).toBe("1") + }) + + it("respects pre-existing dismissal state from sessionStorage", () => { + NETWORK.name = "mainnet" // Wallet testnet vs App mainnet + sessionStorage.setItem("so4-network-mismatch-dismissed", "1") + + render() + expect(screen.queryByRole("alert")).not.toBeInTheDocument() + }) +})