Files
beacon-web/tests/features/map/PacketPathMapModal.test.tsx
T

110 lines
5.0 KiB
TypeScript

import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import type { PacketDetail } from "../../../src/types/api";
import { PayloadType } from "../../../src/types/enums";
// stub the WebGL map; the modal's own logic is the selector + selection state
vi.mock("../../../src/features/map/PacketPathMap", () => ({
PacketPathMap: ({ selectedKey }: { selectedKey: string | null }) => (
<div data-testid="mini-map">{selectedKey ?? "all"}</div>
),
}));
import { PacketPathMapModal } from "../../../src/features/map/PacketPathMapModal";
// this Node/jsdom combo leaves window.localStorage unavailable; stub it so the modal's
// style-preference read doesn't throw.
beforeEach(() => {
const store = new Map<string, string>();
vi.stubGlobal("localStorage", {
getItem: (k: string) => store.get(k) ?? null,
setItem: (k: string, v: string) => store.set(k, v),
});
});
afterEach(() => vi.unstubAllGlobals());
const hop = (id: string, lng: number, lat: number) => ({ confidence: "high" as const, nodes: [{ id, publicKey: "pk", longitude: lng, latitude: lat }] });
const detail = {
packetHash: "aabbccdd",
header: { payloadType: PayloadType.TEXT, routeType: 1 },
observations: [
{ id: 1, observerId: "obs-alpha", observerName: "Alpha", iata: "YYZ", heardAt: 0, sourceBroker: "b", pathLength: { raw: "", hashSize: 1, hopCount: 2 }, resolvedPath: [hop("a", -79, 43), hop("b", -75, 45)], propagationTimeMs: 100 },
{ id: 2, observerId: "obs-bravo", observerName: "Bravo", iata: "YOW", heardAt: 0, sourceBroker: "b", pathLength: { raw: "", hashSize: 1, hopCount: 2 }, resolvedPath: [hop("c", -80, 44), hop("d", -76, 46)], propagationTimeMs: 480 },
],
} as unknown as PacketDetail;
describe("PacketPathMapModal", () => {
it("lists All paths plus a row per observer and starts on All", () => {
render(<PacketPathMapModal detail={detail} onClose={() => {}} />);
expect(screen.getByText("All paths")).toBeInTheDocument();
expect(screen.getByText("Alpha")).toBeInTheDocument();
expect(screen.getByText("Bravo")).toBeInTheDocument();
expect(screen.getByTestId("mini-map")).toHaveTextContent("all");
});
it("isolates a path when its row is clicked", () => {
render(<PacketPathMapModal detail={detail} onClose={() => {}} />);
fireEvent.click(screen.getByText("Bravo"));
expect(screen.getByTestId("mini-map")).toHaveTextContent("obs-bravo");
});
it("shows each observer's propagation", () => {
render(<PacketPathMapModal detail={detail} onClose={() => {}} />);
expect(screen.getByText("0.100s")).toBeInTheDocument(); // formatPropagation(100)
expect(screen.getByText("0.480s")).toBeInTheDocument();
});
it("closes from the close button", () => {
const onClose = vi.fn();
render(<PacketPathMapModal detail={detail} onClose={onClose} />);
fireEvent.click(screen.getByLabelText("Close path map"));
expect(onClose).toHaveBeenCalledOnce();
});
it("pre-selects the observer from initialSelectedKey", () => {
render(<PacketPathMapModal detail={detail} onClose={() => {}} initialSelectedKey="obs-bravo" />);
expect(screen.getByTestId("mini-map")).toHaveTextContent("obs-bravo");
});
it("falls back to All when initialSelectedKey isn't a known path", () => {
render(<PacketPathMapModal detail={detail} onClose={() => {}} initialSelectedKey="nope" />);
expect(screen.getByTestId("mini-map")).toHaveTextContent("all");
});
it("renders a copy-link button", () => {
render(<PacketPathMapModal detail={detail} onClose={() => {}} />);
expect(screen.getByRole("button", { name: "Copy path link" })).toBeInTheDocument();
});
describe("copy path link", () => {
const writeText = vi.fn();
beforeEach(() => {
Object.defineProperty(navigator, "clipboard", { value: { writeText }, writable: true, configurable: true });
writeText.mockClear();
});
afterEach(() => window.history.replaceState({}, "", "/"));
it("copies the selected path and strips the analyzer", () => {
window.history.replaceState({}, "", "/?tab=Packets&hash=aabb&analyze=1");
render(<PacketPathMapModal detail={detail} onClose={() => {}} />);
fireEvent.click(screen.getByText("Bravo"));
fireEvent.click(screen.getByRole("button", { name: "Copy path link" }));
const copied = new URL(writeText.mock.calls[0]![0] as string);
expect(copied.searchParams.get("tab")).toBe("Packets");
expect(copied.searchParams.get("hash")).toBe(detail.packetHash);
expect(copied.searchParams.get("path")).toBe("obs-bravo");
expect(copied.searchParams.has("analyze")).toBe(false); // path and analyze are exclusive
});
it("copies path=all when nothing is isolated", () => {
render(<PacketPathMapModal detail={detail} onClose={() => {}} />);
fireEvent.click(screen.getByRole("button", { name: "Copy path link" }));
expect(new URL(writeText.mock.calls[0]![0] as string).searchParams.get("path")).toBe("all");
});
});
});