Files
beacon-web/tests/features/nodes/NodeTable.test.tsx
T
MrAlders0n 3e43cfd009 fix: follow up on last week's merges (#55-#101) (#102)
Bugs: clear compare params on tab change, recover channel live messages after a 429,
observer copy link keeps only region params, route table no longer navigates on region
change with nothing selected, map borders memo on data identity, live overview can't
resurrect a failed query, French relative times, no language write on first visit,
0/0 node locations hidden outside the map, path/signal stats use the shared retry rule.

i18n: node/packet/mesh/observer strings that were still hardcoded after #62.

Cleanup: formatUtc, ACTION_BUTTON_CLASS, observer-stats, useWindowedStats, observer-id;
observer pages use StatCard/Card/Field/Segmented/SearchBar/SelectDropdown; one details
chrome on the stats tabs; comment and translations-guide trims; localStorage stub for
Node 26 in the test setup.
2026-09-30 10:19:32 -04:00

77 lines
3.0 KiB
TypeScript

import { describe, expect, it, vi, beforeEach } from "vitest";
import { render, screen, within, fireEvent } from "@testing-library/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { NodeTable } from "../../../src/features/nodes/NodeTable";
import { getNodesPage } from "../../../src/api/client";
import i18n from "../../../src/i18n";
import type { NodeSummary } from "../../../src/features/nodes/types";
import type { WsManager } from "../../../src/api/ws-manager";
const region = { iatas: ["YVR"], regionKey: "YVR", isResolved: true };
vi.mock("../../../src/hooks/useRegion", () => ({ useRegion: () => region }));
vi.mock("../../../src/hooks/useScopes", () => ({ useScopes: () => [] }));
vi.mock("../../../src/api/client", () => ({ getNodesPage: vi.fn() }));
const fakeWsManager = { onNodeUpdate: () => () => {} } as unknown as WsManager;
const node = (over: Partial<NodeSummary>): NodeSummary => ({
id: "node-a",
publicKey: "aabbccdd",
nodeType: 2,
nodeTypeName: "REPEATER",
name: "Node A",
lat: null,
lng: null,
iatas: [],
knownNeighborCount: 0,
...over,
});
function mount(items: NodeSummary[]) {
vi.mocked(getNodesPage).mockResolvedValue({ items, nextCursor: null, hasMore: false });
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
render(
<QueryClientProvider client={client}>
<NodeTable wsManager={fakeWsManager} selectedNodeId={null} onSelectNode={vi.fn()} />
</QueryClientProvider>,
);
}
beforeEach(() => {
vi.mocked(getNodesPage).mockReset();
});
describe("NodeTable location column", () => {
it("shows a dash for an explicit 0/0 advert reset, not the coordinates", async () => {
mount([node({ id: "node-zero", name: "Zeroed node", lat: 0, lng: 0 })]);
const nameCell = await screen.findByText("Zeroed node");
const row = nameCell.closest("tr")!;
const cells = within(row).getAllByRole("cell");
expect(cells[cells.length - 1]).toHaveTextContent("—");
expect(screen.queryByText("0.00, 0.00")).not.toBeInTheDocument();
});
it("still shows coordinates when only one axis is zero", async () => {
mount([node({ id: "node-partial", name: "Partial node", lat: 0, lng: 10 })]);
await screen.findByText("Partial node");
expect(screen.getByText("0.00, 10.00")).toBeInTheDocument();
});
});
describe("NodeTable IATA badge tooltip", () => {
it("shows a translated last-heard label, in English and French", async () => {
const lastHeard = Date.now() - 7 * 86_400_000;
mount([node({ id: "node-iata", name: "IATA node", iatas: [{ iata: "YOW", lastHeard }] })]);
const badge = await screen.findByText("YOW");
const trigger = badge.parentElement!;
fireEvent.mouseEnter(trigger);
expect(screen.getByRole("tooltip")).toHaveTextContent("last heard 7d ago");
fireEvent.mouseLeave(trigger);
await i18n.changeLanguage("fr");
fireEvent.mouseEnter(trigger);
expect(screen.getByRole("tooltip")).toHaveTextContent("dernier contact il y a 7 j");
});
});