Files
beacon-web/tests/features/packets/PacketTableRow.test.tsx
T
2026-10-02 22:38:54 -04:00

145 lines
7.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, it, expect, vi } from "vitest";
import { render, screen, fireEvent, within } from "@testing-library/react";
import { PacketTableRow } from "../../../src/features/packets/PacketTableRow";
import i18n from "../../../src/i18n";
import type { LatestObserver, PacketSummary, ResolvedHop } from "../../../src/types/api";
const pkt = (over: Partial<PacketSummary> = {}): PacketSummary => ({
packetHash: "AA11BB22", payloadType: 1, payloadTypeName: "ADVERT",
routeType: 1, routeTypeName: "FLOOD",
firstHeardAt: 1700000000, lastHeardAt: 1700000000, observationCount: 3, ...over,
});
const node = (name: string): ResolvedHop => ({
confidence: "high",
nodes: [{ id: "n-1", name, publicKey: "aabbccdd" }],
});
// Physical path metadata is independent of the logical source/destination chips.
const observer = (
over: { hopCount?: number; hashSize?: number } & Partial<Pick<LatestObserver, "resolvedSource" | "resolvedDestination">> = {},
): LatestObserver => {
const { hopCount = 2, hashSize = 1, ...rest } = over;
return { id: "abcdef1234", iata: "YVR", pathLength: { raw: "1e", hashSize, hopCount }, ...rest };
};
describe("PacketTableRow", () => {
it("prefers endpoints over a payload summary", () => {
render(<PacketTableRow packet={pkt({ payloadType: 2, summary: "Packet summary", latestObserver: observer({ resolvedSource: node("Source node"), resolvedDestination: node("Destination node") }) })} expanded={false} onToggle={() => {}} />);
expect(screen.queryByText("Packet summary")).not.toBeInTheDocument();
expect(screen.getByText("Source node")).toBeInTheDocument();
expect(screen.getByText("Destination node")).toBeInTheDocument();
});
it("exposes one button carrying the expansion state", () => {
render(<PacketTableRow packet={pkt()} expanded={false} onToggle={() => {}} />);
const btn = screen.getByRole("button");
expect(btn).toHaveAttribute("aria-expanded", "false");
});
it("toggles on click", () => {
const onToggle = vi.fn();
render(<PacketTableRow packet={pkt()} expanded={false} onToggle={onToggle} />);
fireEvent.click(screen.getByRole("button"));
expect(onToggle).toHaveBeenCalledOnce();
});
it("keeps packet content inside one row button", () => {
const { container } = render(<PacketTableRow packet={pkt()} expanded={false} onToggle={() => {}} />);
expect(container.querySelectorAll("button")).toHaveLength(1);
expect(screen.queryByText("latest")).not.toBeInTheDocument();
});
it("falls back to n/a in the hops, hash size, endpoint and IATA cells when there is no observer", () => {
render(<PacketTableRow packet={pkt()} expanded={false} onToggle={() => {}} />);
const row = within(screen.getByRole("button"));
expect(row.getAllByText("n/a")).toHaveLength(4);
});
it("shows the hash size alongside the hop count", () => {
render(<PacketTableRow packet={pkt({ observationCount: 9, latestObserver: observer({ hopCount: 5, hashSize: 3 }) })} expanded={false} onToggle={() => {}} />);
expect(screen.getByText("5")).toBeInTheDocument();
expect(screen.getByText("3")).toBeInTheDocument();
});
it("no longer shows the observer, which moved into the expansion", () => {
render(<PacketTableRow packet={pkt({ latestObserver: { id: "abcdef1234", displayName: "Cypress Peak", iata: "YVR" } })} expanded={false} onToggle={() => {}} />);
expect(screen.queryByText("Cypress Peak")).not.toBeInTheDocument();
expect(screen.queryByText("abcdef12")).not.toBeInTheDocument();
expect(screen.getByText("YVR")).toBeInTheDocument();
});
it("shows the hop count, which the REST list carries on every row", () => {
render(<PacketTableRow packet={pkt({ observationCount: 7, latestObserver: observer({ hopCount: 3 }) })} expanded={false} onToggle={() => {}} />);
expect(screen.getByText("3")).toBeInTheDocument();
});
it("renders resolved endpoints when the WS feed supplied them", () => {
render(
<PacketTableRow
packet={pkt({ latestObserver: observer({ resolvedSource: node("Laprairie"), resolvedDestination: node("YUL1") }) })}
expanded={false}
onToggle={() => {}}
/>,
);
expect(screen.getByText("Laprairie")).toBeInTheDocument();
expect(screen.getByText("YUL1")).toBeInTheDocument();
});
// The REST list leaves resolvedSource/Destination nil on purpose, so scrollback rows show one n/a
// for the pair rather than "n/a → n/a".
it("collapses the endpoint cell to a single n/a when neither endpoint resolved", () => {
render(<PacketTableRow packet={pkt({ latestObserver: observer({ hopCount: 2 }) })} expanded={false} onToggle={() => {}} />);
expect(screen.getAllByText("n/a")).toHaveLength(1);
});
it("still marks the missing half when only one endpoint resolved", () => {
render(<PacketTableRow packet={pkt({ latestObserver: observer({ resolvedSource: node("Laprairie") }) })} expanded={false} onToggle={() => {}} />);
expect(screen.getByText("Laprairie")).toBeInTheDocument();
expect(screen.getAllByText("n/a")).toHaveLength(1);
});
it("reflects the expanded state on the button and chevron", () => {
render(<PacketTableRow packet={pkt()} expanded={true} onToggle={() => {}} />);
expect(screen.getByRole("button")).toHaveAttribute("aria-expanded", "true");
expect(screen.getByText("›")).toHaveClass("rotate-90");
});
it("marks a fresh row with the pulse class", () => {
const { container } = render(<PacketTableRow packet={pkt()} expanded={false} isFresh onToggle={() => {}} />);
expect(container.querySelector(".packet-fresh")).toBeInTheDocument();
});
it("renders a scope tag when the packet has a scope", () => {
render(<PacketTableRow packet={pkt({ scope: "#bc" })} expanded={false} onToggle={() => {}} />);
expect(screen.getByText("#bc")).toBeInTheDocument();
});
it("falls back to the raw payload type name for an unrecognized payload type", () => {
render(<PacketTableRow packet={pkt({ payloadType: 99, payloadTypeName: "CUSTOM_99" })} expanded={false} onToggle={() => {}} />);
expect(screen.getByText("CUSTOM_99")).toBeInTheDocument();
});
it("falls back to Unknown when routeTypeName is empty", () => {
render(<PacketTableRow packet={pkt({ routeTypeName: "" })} expanded={false} onToggle={() => {}} />);
expect(screen.getByText("Unknown")).toBeInTheDocument();
});
it("keeps long route labels inside their dedicated track beside a scope", () => {
render(<PacketTableRow packet={pkt({ routeTypeName: "TRANSPORT_FLOOD", scope: "#ykf" })} expanded={false} onToggle={() => {}} />);
const route = screen.getByText("TRANSPORT_FLOOD");
expect(route).toHaveClass("truncate");
expect(route.parentElement).toHaveClass("min-w-0");
expect(screen.getByText("#ykf")).toBeInTheDocument();
});
});
describe("PacketTableRow in French", () => {
it("translates the unknown route and n/a fallbacks", async () => {
await i18n.changeLanguage("fr");
render(<PacketTableRow packet={pkt({ routeTypeName: "" })} expanded={false} onToggle={() => {}} />);
expect(screen.getByText("Inconnu")).toBeInTheDocument();
expect(screen.queryByText("n/a")).not.toBeInTheDocument();
});
});