import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { useState, type ReactNode } from "react"; import { act, fireEvent, render, screen, waitFor, within } from "@testing-library/react"; import { useSearchParams } from "react-router-dom"; import { App } from "../src/App"; import { useRegionSelection } from "../src/hooks/useRegion"; import type { PacketDetail } from "../src/types/api"; vi.mock("../src/api/ws-manager", () => ({ WsManager: class { connect() {} disconnect() {} updateSubscription() {} } })); vi.mock("../src/api/client", () => ({ getRegions: async () => [], getRegion: async () => ({ iatas: [] }), getBrokers: async () => [], getScopes: async () => [] })); vi.mock("../src/components/SplashScreen", () => ({ SplashScreen: () => null })); vi.mock("../src/components/AppShell", () => ({ AppShell: ({ children, onTabChange }: { children: ReactNode; onTabChange: (tab: string) => void }) => { const { setSelection } = useRegionSelection(); return <>{children}; } })); vi.mock("../src/features/routes/RouteTable", () => ({ RouteTable: ({ onViewObserver, onAnalyzePacket, onViewNode }: { onViewObserver: (id: string) => void; onAnalyzePacket: (hash: string, id: number) => void; onViewNode: (id: string) => void }) => { const [filter, setFilter] = useState(""); const [params] = useSearchParams(); return
setFilter(e.target.value)} />{params.toString()}
Routes
; } })); vi.mock("../src/features/observers/ObserverSidebar", () => ({ ObserverSidebar: () =>

Observer directory

})); vi.mock("../src/features/observers/useObserverDirectory", () => ({ useObserverDirectory: () => ({ observers: [], observerTypes: [], isPending: false, isError: false, unsupported: false, hasNextPage: false, isFetchingNextPage: false, maxObservationCount: null, resetKey: "test", retry: () => {}, refresh: () => {}, loadMore: () => {} }) })); vi.mock("../src/features/stats/MeshTab", () => ({ MeshTab: ({ onSelectObserver }: { onSelectObserver: (id: string) => void }) => { const [value, setValue] = useState(""); return <> setValue(e.target.value)} />; } })); vi.mock("../src/features/stats/ObserverTab", () => ({ ObserverTab: ({ selectedObserverId, range, actions }: { selectedObserverId: string; range: string; actions?: ReactNode }) => <>

Dashboard {selectedObserverId} {range}

{actions} })); // Exercise the real analytics shell without loading charts unrelated to its observer action. vi.mock("../src/features/stats/TrafficTab", () => ({ TrafficTab: () => null })); vi.mock("../src/features/stats/SignalTab", () => ({ SignalTab: () => null })); vi.mock("../src/features/stats/PathsTab", () => ({ PathsTab: () => null })); vi.mock("../src/features/stats/ScopesTab", () => ({ ScopesTab: () => null })); vi.mock("../src/features/stats/TalkersTab", () => ({ TalkersTab: () => null })); vi.mock("../src/features/stats/ClockDriftTab", () => ({ ClockDriftTab: () => null })); vi.mock("../src/features/stats/CompareObserversTab", () => ({ CompareObserversTab: () => null })); vi.mock("../src/features/stats/NeighbourGraphTab", () => ({ NeighbourGraphTab: () => null })); vi.mock("../src/features/observers/ObserverAdverts", () => ({ ObserverAdverts: ({ onAnalyzePacket }: { onAnalyzePacket: (hash: string, id: number) => void }) => })); vi.mock("../src/features/nodes/NodeDetailPanel", () => ({ NodeDetailPanel: ({ nodeId, onClose, onViewObserver }: { nodeId: string; onClose: () => void; onViewObserver: (id: string) => void }) => <>

Node {nodeId}

})); vi.mock("../src/features/packets/usePacketDetail", () => ({ usePacketDetail: (hash: string | null) => ({ data: hash ? { packetHash: hash, observations: [], header: { payloadType: 1 } } as unknown as PacketDetail : undefined, isLoading: false }) })); vi.mock("../src/features/packets/PacketAnalyzerDrawer", () => ({ PacketAnalyzerDrawer: ({ detail, onClose, onViewObserver, onViewNode, onViewPath, selectedObservationId, onSelectObservation }: { detail: PacketDetail; onClose: () => void; onViewObserver: (id: string) => void; onViewNode: (id: string) => void; onViewPath: () => void; selectedObservationId: number | null; onSelectObservation: (id: number) => void }) => <>

Packet {detail?.packetHash} report {selectedObservationId}

})); vi.mock("../src/features/map/PacketPathMap", () => ({ PacketPathMap: () =>
Path map
})); beforeEach(() => { window.history.replaceState({}, "", "/?tab=Routes&route=full-route&routeIata=YOW"); vi.stubGlobal("localStorage", { getItem: () => null, setItem: () => {}, removeItem: () => {} }); }); afterEach(() => vi.unstubAllGlobals()); const click = (name: string) => { const button = screen.getByRole("button", { name, exact: true }); button.focus(); fireEvent.click(button); }; const escape = () => fireEvent.keyDown(screen.getByRole("dialog"), { key: "Escape" }); const browserBack = async () => { await act(async () => { window.history.back(); }); await waitFor(() => expect(window.location.search).toContain("tab=Routes")); }; describe("observer investigation windows", () => { const dashboard = async (id = "o1") => { expect(await screen.findByRole("heading", { level: 1 })).toHaveTextContent(`Dashboard ${id}`); const params = new URLSearchParams(window.location.search); expect(params.get("tab")).toBe("Observers"); expect(params.get("observer")).toBe(id); }; it("opens the dashboard straight from the route panel", async () => { render(); click("Route observer"); await dashboard(); expect(screen.queryByRole("dialog")).not.toBeInTheDocument(); }); it("moves a stacked packet into the side drawer beside the dashboard, keeping its report", async () => { render(); click("Route packet"); click("Select report 8"); click("Packet observer"); await dashboard(); expect(screen.queryByRole("dialog")).not.toBeInTheDocument(); expect(screen.getByRole("heading", { name: /^Packet / })).toHaveTextContent("Packet aa"); const params = new URLSearchParams(window.location.search); expect(params.get("hash")).toBe("aa"); expect(params.get("analyze")).toBe("1"); expect(params.get("observation")).toBe("8"); }); it("keeps the side drawer open when its observer is inspected", async () => { window.history.replaceState({}, "", "/?tab=Routes&hash=aa&analyze=1"); render(); click("Packet observer"); await dashboard(); expect(screen.getByRole("heading", { name: /^Packet / })).toHaveTextContent("Packet aa"); }); it("closes stacked node windows when going to the dashboard", async () => { render(); click("Route packet"); click("Packet node"); click("Node observer"); await dashboard(); expect(screen.queryByRole("dialog")).not.toBeInTheDocument(); }); it("opens an advert from the dashboard in the side drawer", async () => { window.history.replaceState({}, "", "/?tab=Observers&observer=o1"); render(); click("Advert packet"); expect(await screen.findByRole("heading", { name: /^Packet / })).toHaveTextContent("Packet bb"); expect(new URLSearchParams(window.location.search).get("observation")).toBe("3"); }); it("keeps the node and packet stack when Escape is pressed", () => { render(); click("Route packet"); click("Packet node"); escape(); expect(within(screen.getByRole("dialog")).getByRole("heading")).toHaveTextContent("Packet aa"); click("Packet map"); escape(); expect(within(screen.getByRole("dialog")).getByRole("heading")).toHaveTextContent("Packet aa"); }); it("returns to the previous tab with browser Back", async () => { render(); click("Route observer"); await screen.findByRole("heading", { level: 1 }); await browserBack(); expect(screen.getByTestId("route-origin")).toBeInTheDocument(); }); it("keeps the packet drawer and stacked windows across tabs, but not into Analytics", async () => { await import("../src/features/stats/StatsOverview"); window.history.replaceState({}, "", "/?tab=Routes&hash=aa&analyze=1"); render(); expect(screen.getByRole("heading", { name: /Packet aa/ })).toBeInTheDocument(); click("Packet node"); click("Observer tab"); expect(await screen.findByRole("heading", { level: 2, name: /Packet aa/ })).toBeInTheDocument(); expect(within(screen.getByRole("dialog")).getByRole("heading")).toHaveTextContent("Node n1"); click("Analytics tab"); await screen.findByRole("button", { name: "Leaderboard observer" }); expect(screen.queryByRole("heading", { name: /Packet aa/ })).not.toBeInTheDocument(); expect(screen.queryByRole("dialog")).not.toBeInTheDocument(); click("Route tab"); expect(await screen.findByRole("heading", { name: /Packet aa/ })).toBeInTheDocument(); expect(screen.queryByRole("dialog")).not.toBeInTheDocument(); }); it("opens the Observers dashboard from the leaderboard", async () => { await import("../src/features/stats/StatsOverview"); window.history.replaceState({}, "", "/?tab=Analytics&statsTab=mesh&range=24h"); render(); await screen.findByRole("button", { name: "Leaderboard observer" }); click("Leaderboard observer"); expect(await screen.findByRole("heading", { level: 1 })).toHaveTextContent("Dashboard o1 24h"); expect(window.location.search).toContain("tab=Observers"); expect(window.location.search).toContain("observer=o1"); expect(window.location.search).not.toContain("statsTab"); }); it("redirects legacy Analytics observer links to the Observers dashboard", async () => { window.history.replaceState({}, "", "/?tab=Analytics&statsTab=observer&observerId=o2&range=7d"); render(); expect(await screen.findByRole("heading", { level: 1 })).toHaveTextContent("Dashboard o2 7d"); expect(window.location.search).toContain("tab=Observers"); expect(window.location.search).not.toContain("statsTab"); }); it("closes the stacked windows when the region changes", async () => { render(); click("Route packet"); click("Packet node"); click("Change region"); await waitFor(() => expect(screen.queryByRole("dialog")).not.toBeInTheDocument()); }); it("clears comparison params from both the observer dashboard and analytics compare tabs on tab change", () => { window.history.replaceState({}, "", "/?tab=Observers&observer=o1&compareWith=o2&compareUntil=1700000000000&compareA=x&compareB=y&compareSince=1"); render(); click("Route tab"); expect(window.location.search).toContain("tab=Routes"); for (const key of ["observer", "compareWith", "compareUntil", "compareA", "compareB", "compareSince"]) { expect(window.location.search).not.toContain(key); } }); it("keeps analytics compare params when re-clicking the already-active Analytics tab", async () => { await import("../src/features/stats/StatsOverview"); window.history.replaceState({}, "", "/?tab=Analytics&statsTab=compare&compareA=a&compareB=b&compareSince=1&compareUntil=2"); render(); click("Analytics tab"); for (const pair of ["compareA=a", "compareB=b", "compareSince=1", "compareUntil=2"]) { expect(window.location.search).toContain(pair); } }); it("keeps the observer dashboard's compare params when re-clicking the already-active Observer tab", () => { window.history.replaceState({}, "", "/?tab=Observers&observer=o1&compareWith=o2&compareUntil=2"); render(); click("Observer tab"); for (const pair of ["observer=o1", "compareWith=o2", "compareUntil=2"]) { expect(window.location.search).toContain(pair); } }); }); describe("observer investigation on mobile", () => { beforeEach(() => vi.stubGlobal("matchMedia", (query: string) => ({ matches: /max-width/.test(query), media: query, addEventListener: () => {}, removeEventListener: () => {}, addListener: () => {}, removeListener: () => {} }))); it("closes an inspected observer back to the packet it came from", async () => { render(); click("Route packet"); click("Select report 8"); click("Packet observer"); expect(await screen.findByRole("heading", { level: 1 })).toHaveTextContent("Dashboard o1"); expect(screen.queryByRole("heading", { name: /^Packet / })).not.toBeInTheDocument(); click("Back to observers"); await waitFor(() => expect(new URLSearchParams(window.location.search).get("tab")).toBe("Routes")); expect(await screen.findByRole("heading", { name: /^Packet / })).toHaveTextContent("Packet aa"); expect(new URLSearchParams(window.location.search).get("observation")).toBe("8"); }); it("closes a dashboard opened from the list back to the list", async () => { window.history.replaceState({}, "", "/?tab=Observers&observer=o1"); render(); await screen.findByRole("heading", { level: 1 }); click("Back to observers"); await waitFor(() => expect(screen.queryByRole("heading", { level: 1 })).not.toBeInTheDocument()); expect(new URLSearchParams(window.location.search).get("tab")).toBe("Observers"); }); });