diff --git a/apps/web/register-dom.ts b/apps/web/register-dom.ts
new file mode 100644
index 0000000..a2fb3a1
--- /dev/null
+++ b/apps/web/register-dom.ts
@@ -0,0 +1,2 @@
+import { GlobalRegistrator } from '@happy-dom/global-registrator';
+GlobalRegistrator.register();
diff --git a/apps/web/setup-tests.ts b/apps/web/setup-tests.ts
index 96a8606..a77137d 100644
--- a/apps/web/setup-tests.ts
+++ b/apps/web/setup-tests.ts
@@ -1,12 +1,10 @@
+import "./register-dom"
import "@testing-library/jest-dom/vitest"
-import { GlobalRegistrator } from '@happy-dom/global-registrator';
import { afterAll, afterEach, beforeAll } from "vitest";
import { server } from "./test/msw/server";
+import { cleanup } from "@testing-library/react";
-GlobalRegistrator.register();
-
-afterEach(async () => {
- const { cleanup } = await import("@testing-library/react");
+afterEach(() => {
cleanup();
});
diff --git a/apps/web/src/features/wallet/components/AccountBadge.tsx b/apps/web/src/features/wallet/components/AccountBadge.tsx
index e012229..cb160ad 100644
--- a/apps/web/src/features/wallet/components/AccountBadge.tsx
+++ b/apps/web/src/features/wallet/components/AccountBadge.tsx
@@ -22,7 +22,7 @@ export function AccountBadge({ address, className, ...props }: AccountBadgeProps
const balanceData = useBalance()
const balance = balanceData?.xlm
const isLoading = balanceData?.isLoading ?? false
- const { isMainnet } = useNetwork()
+ const { displayLabel } = useNetwork()
useEffect(() => {
if (!open) return
@@ -103,7 +103,7 @@ export function AccountBadge({ address, className, ...props }: AccountBadgeProps
Network
- {isMainnet ? "Mainnet" : "Testnet"}
+ {displayLabel}
diff --git a/apps/web/src/features/wallet/components/ConnectButton.test.tsx b/apps/web/src/features/wallet/components/ConnectButton.test.tsx
index 8e3c174..af98b58 100644
--- a/apps/web/src/features/wallet/components/ConnectButton.test.tsx
+++ b/apps/web/src/features/wallet/components/ConnectButton.test.tsx
@@ -109,7 +109,7 @@ describe("ConnectButton - Disconnected State", () => {
const dialog = screen.getByRole("dialog")
expect(dialog).toBeInTheDocument()
- const dialogTitle = screen.getByText("Connect Wallet")
+ const dialogTitle = screen.getByRole("heading", { name: "Connect Wallet" })
expect(dialogTitle).toBeInTheDocument()
})
@@ -259,7 +259,7 @@ describe("ConnectButton - Disconnected State", () => {
render()
// Account badge should be rendered instead
- expect(screen.getByText(/GAAAAAA/)).toBeInTheDocument()
+ expect(screen.getByText(/GAAAAA/)).toBeInTheDocument()
})
})
@@ -356,7 +356,7 @@ describe("ConnectButton - Disconnected State", () => {
expect(
screen.queryByRole("button", { name: /Connect wallet/i }),
).not.toBeInTheDocument()
- expect(screen.getByText(/GAAAAAA/)).toBeInTheDocument()
+ expect(screen.getByText(/GAAAAA/)).toBeInTheDocument()
})
})
diff --git a/apps/web/src/features/wallet/components/NetworkMismatchBanner.tsx b/apps/web/src/features/wallet/components/NetworkMismatchBanner.tsx
index 7ca1c4e..892e6a5 100644
--- a/apps/web/src/features/wallet/components/NetworkMismatchBanner.tsx
+++ b/apps/web/src/features/wallet/components/NetworkMismatchBanner.tsx
@@ -8,7 +8,7 @@ const SESSION_KEY = "so4-network-mismatch-dismissed"
export function NetworkMismatchBanner() {
const { pathname } = useLocation()
- const { mismatch, network } = useNetwork()
+ const { mismatch, displayLabel } = useNetwork()
const { status } = useWalletStore()
const [dismissed, setDismissed] = useState(
() => sessionStorage.getItem(SESSION_KEY) === "1"
@@ -18,7 +18,7 @@ export function NetworkMismatchBanner() {
if (pathname === "/") return null
if (!mismatch || status !== "connected" || dismissed) return null
- const walletLabel = network === "mainnet" ? "Mainnet" : "Testnet"
+ const walletLabel = displayLabel
const appLabel = NETWORK.name === "mainnet" ? "Mainnet" : "Testnet"
function dismiss() {
diff --git a/apps/web/src/features/wallet/hooks/useNetwork.test.ts b/apps/web/src/features/wallet/hooks/useNetwork.test.ts
new file mode 100644
index 0000000..00bfbb8
--- /dev/null
+++ b/apps/web/src/features/wallet/hooks/useNetwork.test.ts
@@ -0,0 +1,132 @@
+import { describe, expect, it, beforeEach } from "vitest"
+import { renderHook } from "@testing-library/react"
+import { useNetwork, normalizeNetwork } from "./useNetwork"
+import { useWalletStore } from "../store/wallet-store"
+import { NETWORK } from "@/app/config/network"
+
+describe("normalizeNetwork", () => {
+ it("should normalize Stellar Testnet strings", () => {
+ expect(normalizeNetwork("testnet")).toBe("testnet")
+ expect(normalizeNetwork("TESTNET")).toBe("testnet")
+ expect(normalizeNetwork("Test SDF Network ; September 2015")).toBe("testnet")
+ expect(normalizeNetwork(" testnet ")).toBe("testnet")
+ })
+
+ it("should normalize Stellar Mainnet/Public strings", () => {
+ expect(normalizeNetwork("public")).toBe("mainnet")
+ expect(normalizeNetwork("mainnet")).toBe("mainnet")
+ expect(normalizeNetwork("PUBLIC")).toBe("mainnet")
+ expect(normalizeNetwork("Public Global Stellar Network ; September 2015")).toBe("mainnet")
+ expect(normalizeNetwork(" public ")).toBe("mainnet")
+ })
+
+ it("should return unknown for other strings", () => {
+ expect(normalizeNetwork("unknown")).toBe("unknown")
+ expect(normalizeNetwork("custom")).toBe("unknown")
+ expect(normalizeNetwork("arbitrary")).toBe("unknown")
+ })
+
+ it("should return unknown for empty or missing inputs", () => {
+ expect(normalizeNetwork(null)).toBe("unknown")
+ expect(normalizeNetwork(undefined)).toBe("unknown")
+ expect(normalizeNetwork("")).toBe("unknown")
+ expect(normalizeNetwork(" ")).toBe("unknown")
+ })
+})
+
+describe("useNetwork Hook", () => {
+ beforeEach(() => {
+ useWalletStore.setState({
+ address: null,
+ walletId: null,
+ status: "disconnected",
+ pendingTransactionXdr: null,
+ network: "testnet",
+ })
+ })
+
+ describe("when status is disconnected", () => {
+ it("should always return mismatch as false", () => {
+ // Set network to a mismatching network but status disconnected
+ const testNetwork = NETWORK.name === "mainnet" ? "testnet" : "mainnet"
+ useWalletStore.setState({ status: "disconnected", network: testNetwork })
+
+ const { result } = renderHook(() => useNetwork())
+
+ expect(result.current.mismatch).toBe(false)
+ })
+ })
+
+ describe("when status is connected", () => {
+ it("should return mismatch as false if wallet network matches app network", () => {
+ useWalletStore.setState({ status: "connected", network: NETWORK.name })
+
+ const { result } = renderHook(() => useNetwork())
+
+ expect(result.current.mismatch).toBe(false)
+ })
+
+ it("should return mismatch as true if wallet network does not match app network", () => {
+ const opposingNetwork = NETWORK.name === "mainnet" ? "testnet" : "mainnet"
+ useWalletStore.setState({ status: "connected", network: opposingNetwork })
+
+ const { result } = renderHook(() => useNetwork())
+
+ expect(result.current.mismatch).toBe(true)
+ })
+
+ it("should return mismatch as true for unknown wallet networks", () => {
+ useWalletStore.setState({ status: "connected", network: "custom-network" })
+
+ const { result } = renderHook(() => useNetwork())
+
+ expect(result.current.mismatch).toBe(true)
+ })
+ })
+
+ describe("network classification and labels", () => {
+ it("should expose correct details for testnet value", () => {
+ useWalletStore.setState({ network: "Test SDF Network ; September 2015" })
+
+ const { result } = renderHook(() => useNetwork())
+
+ expect(result.current.normalizedNetwork).toBe("testnet")
+ expect(result.current.isTestnet).toBe(true)
+ expect(result.current.isMainnet).toBe(false)
+ expect(result.current.displayLabel).toBe("Testnet")
+ })
+
+ it("should expose correct details for mainnet/public value", () => {
+ useWalletStore.setState({ network: "Public Global Stellar Network ; September 2015" })
+
+ const { result } = renderHook(() => useNetwork())
+
+ expect(result.current.normalizedNetwork).toBe("mainnet")
+ expect(result.current.isTestnet).toBe(false)
+ expect(result.current.isMainnet).toBe(true)
+ expect(result.current.displayLabel).toBe("Mainnet")
+ })
+
+ it("should expose correct details for unknown value", () => {
+ useWalletStore.setState({ network: "unknown" })
+
+ const { result } = renderHook(() => useNetwork())
+
+ expect(result.current.normalizedNetwork).toBe("unknown")
+ expect(result.current.isTestnet).toBe(false)
+ expect(result.current.isMainnet).toBe(false)
+ expect(result.current.displayLabel).toBe("Unknown")
+ })
+
+ it("should expose correct details for missing/null value", () => {
+ useWalletStore.setState({ network: null })
+
+ const { result } = renderHook(() => useNetwork())
+
+ expect(result.current.normalizedNetwork).toBe("unknown")
+ expect(result.current.isTestnet).toBe(false)
+ expect(result.current.isMainnet).toBe(false)
+ expect(result.current.displayLabel).toBe("Unknown")
+ })
+ })
+})
diff --git a/apps/web/src/features/wallet/hooks/useNetwork.ts b/apps/web/src/features/wallet/hooks/useNetwork.ts
index 8d196d0..b600a90 100644
--- a/apps/web/src/features/wallet/hooks/useNetwork.ts
+++ b/apps/web/src/features/wallet/hooks/useNetwork.ts
@@ -1,13 +1,52 @@
import { useWalletStore } from "../store/wallet-store"
import { NETWORK } from "@/app/config/network"
+export function normalizeNetwork(network: string | null | undefined): "testnet" | "mainnet" | "unknown" {
+ if (!network) return "unknown"
+
+ const normalized = network.trim().toLowerCase()
+
+ if (
+ normalized === "testnet" ||
+ normalized === "test sdf network ; september 2015"
+ ) {
+ return "testnet"
+ }
+
+ if (
+ normalized === "public" ||
+ normalized === "mainnet" ||
+ normalized === "public global stellar network ; september 2015"
+ ) {
+ return "mainnet"
+ }
+
+ return "unknown"
+}
+
export function useNetwork() {
const { network, status } = useWalletStore()
- const isTestnet = network === "testnet"
- const isMainnet = network === "mainnet"
+ const normalizedNetwork = normalizeNetwork(network)
+ const isTestnet = normalizedNetwork === "testnet"
+ const isMainnet = normalizedNetwork === "mainnet"
+
// Mismatch only meaningful when a wallet is connected
- const mismatch = status === "connected" && network !== NETWORK.name
+ const mismatch = status === "connected" && normalizedNetwork !== NETWORK.name
+
+ const displayLabel =
+ normalizedNetwork === "testnet"
+ ? "Testnet"
+ : normalizedNetwork === "mainnet"
+ ? "Mainnet"
+ : "Unknown"
- return { network, isTestnet, isMainnet, mismatch }
+ return {
+ network,
+ normalizedNetwork,
+ isTestnet,
+ isMainnet,
+ mismatch,
+ displayLabel,
+ }
}
diff --git a/apps/web/src/features/wallet/store/wallet-store.ts b/apps/web/src/features/wallet/store/wallet-store.ts
index cc19ba0..885f9bf 100644
--- a/apps/web/src/features/wallet/store/wallet-store.ts
+++ b/apps/web/src/features/wallet/store/wallet-store.ts
@@ -2,7 +2,7 @@ import { create } from "zustand"
import { persist } from "zustand/middleware"
type WalletStatus = "disconnected" | "connecting" | "connected" | "error"
-type Network = "testnet" | "mainnet"
+type Network = string | null
type WalletStore = {
address: string | null
@@ -17,7 +17,7 @@ type WalletStore = {
}
const DEFAULT_NETWORK: Network =
- (import.meta.env.VITE_NETWORK as Network) === "mainnet" ? "mainnet" : "testnet"
+ (import.meta.env.VITE_NETWORK as string) === "mainnet" ? "mainnet" : "testnet"
export const useWalletStore = create()(
persist(