mirror of
https://github.com/MeshCore-Beacon/beacon-web.git
synced 2026-10-20 07:22:03 +00:00
114 lines
7.1 KiB
TypeScript
114 lines
7.1 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { act, render, screen, within } from "@testing-library/react";
|
|
import { TrafficTab } from "../../../src/features/stats/TrafficTab";
|
|
import { useStatsObservations } from "../../../src/features/stats/useStats";
|
|
import { EChart } from "../../../src/features/stats/EChart";
|
|
import i18n from "../../../src/i18n";
|
|
|
|
const query = { data: [{ hour: Date.UTC(2026, 8, 19, 12), iata: "YOW", observationCount: 1200, uniquePackets: 9999, activeObservers: 88 }], dataUpdatedAt: Date.UTC(2026, 8, 19, 12, 30), isPending: false, isLoading: false, isPlaceholderData: false, isError: false, isFetching: false, refetch: vi.fn() };
|
|
const originalData = query.data;
|
|
vi.mock("../../../src/features/stats/useStats", () => ({ useStatsObservations: vi.fn(() => query) }));
|
|
vi.mock("../../../src/features/stats/EChart", () => ({ EChart: vi.fn(() => <div data-testid="chart" />) }));
|
|
|
|
beforeEach(() => {
|
|
query.data = originalData; query.isPending = false; query.isLoading = false; query.isPlaceholderData = false; query.isError = false; query.isFetching = false;
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
describe("Traffic page", () => {
|
|
it("translates the page and exact table without changing reception counts, IATA codes or ranges", async () => {
|
|
await act(() => i18n.changeLanguage("fr"));
|
|
render(<TrafficTab range="7d" />);
|
|
expect(screen.queryByRole("heading")).not.toBeInTheDocument();
|
|
expect(useStatsObservations).toHaveBeenCalledWith("7d");
|
|
expect(screen.getByText("Activité horaire · 7 j")).toBeInTheDocument();
|
|
const table = screen.getByRole("table", { name: "Observations par zone" });
|
|
expect(within(table).getByRole("row", { name: /YOW.*1,200.*100.0%/ })).toBeInTheDocument();
|
|
expect(screen.queryByText("9,999")).not.toBeInTheDocument();
|
|
expect(screen.getByText(/Heures en UTC/)).toHaveTextContent("pas les paquets uniques");
|
|
expect(screen.getByText(/pas une panne/)).toBeInTheDocument();
|
|
expect(screen.getByRole("button", { name: /pas une panne/ })).toBeInTheDocument();
|
|
expect(screen.queryByText(/pas une panne/, { selector: "p" })).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("redraws grouped and unassigned chart labels on language change without altering data", async () => {
|
|
const row = originalData[0]!;
|
|
query.data = [row, { ...row, iata: "", observationCount: 9000 }, ...["YVR", "YYZ", "YUL", "YHZ", "YYC", "YEG", "YQB", "YXE"].map((iata, i) => ({ ...row, iata, observationCount: i + 1 }))];
|
|
const chartOptions = () => vi.mocked(EChart).mock.calls.slice(-3).map(([{ option }]) => option);
|
|
const numbers = (options: ReturnType<typeof chartOptions>) => options.map((option) =>
|
|
(Array.isArray(option.series) ? option.series : [option.series]).map((series) => series?.data?.map((entry) =>
|
|
entry && typeof entry === "object" && !Array.isArray(entry) && "value" in entry ? entry.value : entry)));
|
|
render(<TrafficTab range="24h" />);
|
|
const before = chartOptions(), values = numbers(before);
|
|
await act(() => i18n.changeLanguage("fr"));
|
|
const after = chartOptions();
|
|
expect(numbers(after)).toEqual(values);
|
|
after.forEach((option, i) => expect(option.aria).not.toEqual(before[i]!.aria));
|
|
expect(after[0]).toMatchObject({ series: expect.arrayContaining([expect.objectContaining({ name: "Non attribué" }), expect.objectContaining({ name: "Autres zones" })]) });
|
|
expect(after[2]).toMatchObject({ series: [{ data: expect.arrayContaining([expect.objectContaining({ name: "Autres zones", value: 6 }), expect.objectContaining({ name: "Non attribué", value: 9000 })]) }] });
|
|
expect(screen.getByRole("row", { name: /Non attribué.*9,000/ })).toBeInTheDocument();
|
|
expect(screen.getByRole("row", { name: /YOW.*1,200/ })).toBeInTheDocument();
|
|
expect(screen.queryByRole("row", { name: /Autres zones/ })).not.toBeInTheDocument();
|
|
expect(query.refetch).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it.each(["isPending", "isPlaceholderData", "isError"] as const)("translates %s and hides prior-region data", async (state) => {
|
|
query[state] = true;
|
|
await act(() => i18n.changeLanguage("fr"));
|
|
render(<TrafficTab range="30d" />);
|
|
expect(screen.queryByText("YOW")).not.toBeInTheDocument();
|
|
expect(screen.queryByRole("table")).not.toBeInTheDocument();
|
|
expect(screen.queryAllByTestId("chart")).toHaveLength(0);
|
|
if (state === "isError") {
|
|
expect(screen.getByRole("alert")).toHaveTextContent("Impossible de charger le trafic");
|
|
expect(screen.getByText("Données indisponibles")).toBeInTheDocument();
|
|
} else expect(screen.getByText("Chargement du trafic…")).toBeInTheDocument();
|
|
});
|
|
|
|
it("translates the empty view without inventing receptions or implying an outage", async () => {
|
|
query.data = [];
|
|
await act(() => i18n.changeLanguage("fr"));
|
|
render(<TrafficTab range="24h" />);
|
|
expect(screen.getByText("Aucune observation dans cette période.")).toBeInTheDocument();
|
|
expect(screen.getByText(/pas une panne/)).toBeInTheDocument();
|
|
expect(screen.getByRole("button", { name: /pas une panne/ })).toBeInTheDocument();
|
|
expect(screen.queryByText(/pas une panne/, { selector: "p" })).not.toBeInTheDocument();
|
|
expect(screen.getAllByText("Aucune donnée")).toHaveLength(3);
|
|
expect(screen.queryByRole("table")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("shows exact reception counts and UTC/missing-history guidance without a refresh action", () => {
|
|
render(<TrafficTab range="24h" />);
|
|
expect(useStatsObservations).toHaveBeenCalledWith("24h");
|
|
const table = screen.getByRole("table", { name: "Observations by area" });
|
|
expect(within(table).getByRole("row", { name: /YOW.*1,200.*100.0%/ })).toBeInTheDocument();
|
|
expect(screen.queryByText("9,999")).not.toBeInTheDocument();
|
|
expect(screen.getByText(/not unique packets/)).toHaveTextContent(/UTC.*outage/);
|
|
expect(screen.queryByRole("button", { name: /Refresh/ })).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("does not display previous-region data while a new query is loading", () => {
|
|
query.isPlaceholderData = true;
|
|
render(<TrafficTab range="7d" />);
|
|
expect(screen.queryByText("YOW")).not.toBeInTheDocument();
|
|
expect(screen.queryByRole("table")).not.toBeInTheDocument();
|
|
expect(screen.queryAllByTestId("chart")).toHaveLength(0);
|
|
});
|
|
|
|
it("provides a visible error state without stale chart values", () => {
|
|
query.isError = true;
|
|
render(<TrafficTab range="30d" />);
|
|
expect(screen.getByRole("alert")).toHaveTextContent("Could not load traffic");
|
|
expect(screen.queryByText("YOW")).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it("draws hourly sparks for observations, areas and the busiest hour, and a presence strip for active hours", () => {
|
|
const at = (h: number, iata: string, n: number) => ({ hour: Date.UTC(2026, 8, 19, h), iata, observationCount: n, uniquePackets: 0, activeObservers: 0 });
|
|
query.data = [at(10, "YOW", 5), at(11, "YOW", 9), at(11, "YUL", 2), at(12, "YOW", 3)];
|
|
const { container } = render(<TrafficTab range="24h" />);
|
|
expect(container.querySelectorAll("polyline")).toHaveLength(3);
|
|
expect(container.querySelectorAll("[data-peak]")).toHaveLength(1);
|
|
expect(container.querySelectorAll("rect")).toHaveLength(1);
|
|
});
|