Files
beacon-web/tests/features/packets/PathData.test.tsx
T

99 lines
3.9 KiB
TypeScript

import { describe, it, expect, vi, afterEach } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import { ResolvedHopBlock } from "../../../src/features/packets/PathData";
import type { ResolvedHop } from "../../../src/types/api";
// mobile/touch == no hover-capable pointer; desktop == has hover. Interaction modality keys off
// (hover: hover), not viewport width.
function setMobile(mobile: boolean) {
window.matchMedia = vi.fn().mockImplementation((query: string) => ({
matches: /hover: hover/.test(query) ? !mobile : mobile,
media: query,
onchange: null,
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
addListener: vi.fn(),
removeListener: vi.fn(),
dispatchEvent: vi.fn(),
})) as unknown as typeof window.matchMedia;
}
afterEach(() => vi.restoreAllMocks());
const singleHop: ResolvedHop = {
confidence: "high",
nodes: [{ id: "node-1", name: "Repeater A", publicKey: "deadbeefcafe" }],
};
describe("ResolvedHopBlock per-hop SNR", () => {
it("shows the formatted SNR in the popover when the hop carries one", () => {
setMobile(true);
render(<ResolvedHopBlock hop={{ ...singleHop, snr: 10.75 }} label="ABC1" />);
fireEvent.click(screen.getByText("ABC1"));
expect(screen.getByText(/SNR/)).toBeInTheDocument();
expect(screen.getByText("10.75")).toBeInTheDocument();
});
it("shows no SNR line when the hop has none", () => {
setMobile(true);
render(<ResolvedHopBlock hop={singleHop} label="ABC1" />);
fireEvent.click(screen.getByText("ABC1"));
expect(screen.queryByText(/SNR/)).not.toBeInTheDocument();
});
});
describe("ResolvedHopBlock (desktop)", () => {
it("opens the node directly when the single-match block is clicked", () => {
setMobile(false);
const onViewNode = vi.fn();
render(<ResolvedHopBlock hop={singleHop} label="ABC1" onViewNode={onViewNode} />);
fireEvent.click(screen.getByText("ABC1"));
expect(onViewNode).toHaveBeenCalledWith("node-1");
});
});
describe("ResolvedHopBlock (mobile)", () => {
it("reveals the name popover on tap instead of navigating", () => {
setMobile(true);
const onViewNode = vi.fn();
render(<ResolvedHopBlock hop={singleHop} label="ABC1" onViewNode={onViewNode} />);
fireEvent.click(screen.getByText("ABC1"));
// popover shows the resolved name, and we did NOT jump straight into the node
expect(screen.getByText("Repeater A")).toBeInTheDocument();
expect(onViewNode).not.toHaveBeenCalled();
});
it("opens the node when the name in the popover is tapped", () => {
setMobile(true);
const onViewNode = vi.fn();
render(<ResolvedHopBlock hop={singleHop} label="ABC1" onViewNode={onViewNode} />);
fireEvent.click(screen.getByText("ABC1"));
fireEvent.click(screen.getByText("Repeater A"));
expect(onViewNode).toHaveBeenCalledWith("node-1");
});
// A touch device wider than the mobile breakpoint (a tablet, or Chrome's device emulation, which
// reports no hover even at tablet widths) must still tap-to-toggle — not hover — so the popover
// doesn't dismiss before the resolved name can be tapped. Modality keys off (hover: hover), not width.
it("taps to open on a hover-less device even at desktop width", () => {
window.matchMedia = vi.fn().mockImplementation((query: string) => ({
matches: false, // no hover, and not below the mobile width
media: query,
onchange: null,
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
addListener: vi.fn(),
removeListener: vi.fn(),
dispatchEvent: vi.fn(),
})) as unknown as typeof window.matchMedia;
const onViewNode = vi.fn();
render(<ResolvedHopBlock hop={singleHop} label="ABC1" onViewNode={onViewNode} />);
fireEvent.click(screen.getByText("ABC1"));
expect(screen.getByText("Repeater A")).toBeInTheDocument();
expect(onViewNode).not.toHaveBeenCalled();
});
});