mirror of
https://github.com/MeshCore-Beacon/beacon-web.git
synced 2026-10-18 19:31:27 +00:00
127 lines
7.7 KiB
TypeScript
127 lines
7.7 KiB
TypeScript
import { beforeEach, expect, it, vi } from "vitest";
|
|
import { act, fireEvent, render, screen, within } from "@testing-library/react";
|
|
import { ScopesTab } from "../../../src/features/stats/ScopesTab";
|
|
import { EChart } from "../../../src/features/stats/EChart";
|
|
import i18n from "../../../src/i18n";
|
|
const query = { data: [
|
|
{ name: "#west", packetCount: 3, observerCount: 2, nodeCount: 2 },
|
|
{ name: "#east", packetCount: 2, observerCount: 2, nodeCount: 3 },
|
|
], isPending: false, isLoading: false, isPlaceholderData: false, isError: false, isFetching: false, refetch: vi.fn() };
|
|
const originalData = query.data;
|
|
const H = 3_600_000;
|
|
const seriesQuery = { data: { hours: [0, 1, 2].map((i) => ({ hour: i * H, status: "complete", values: null })) }, isSuccess: true, isPlaceholderData: false };
|
|
vi.mock("../../../src/features/stats/useStats", () => ({ useScopes: () => query, useStatsSeries: () => seriesQuery }));
|
|
vi.mock("../../../src/features/stats/EChart", () => ({ EChart: vi.fn(() => <div data-testid="chart" />) }));
|
|
beforeEach(() => { vi.clearAllMocks(); query.data = originalData; query.isError = false; query.isPending = false; query.isPlaceholderData = false; });
|
|
|
|
it("translates scope counts and membership guidance without changing scope names or values", async () => {
|
|
await act(() => i18n.changeLanguage("fr"));
|
|
render(<ScopesTab range="24h" />);
|
|
expect(screen.queryByRole("heading")).not.toBeInTheDocument();
|
|
const table = screen.getByRole("table", { name: "Effectifs des scopes" });
|
|
expect(within(table).getByRole("row", { name: /#west.*3.*2.*2/ })).toBeInTheDocument();
|
|
expect(within(table).getByRole("row", { name: /#east.*2.*2.*3/ })).toBeInTheDocument();
|
|
expect(screen.getByText("Observateurs par scope")).toBeInTheDocument();
|
|
expect(screen.getByText(/plusieurs scopes/)).toHaveTextContent("couvrent la période sélectionnée");
|
|
expect(screen.getByText("2 sur 2 scopes")).toBeInTheDocument();
|
|
expect(screen.queryByRole("button", { name: /Actualiser/ })).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("keeps the current search and chart values when changing language", async () => {
|
|
render(<ScopesTab range="24h" />);
|
|
fireEvent.change(screen.getByRole("searchbox", { name: "Find a scope" }), { target: { value: " EAST " } });
|
|
const charts = () => vi.mocked(EChart).mock.calls.slice(-3).map(([{ option }]) => option);
|
|
const bars = (chart: ReturnType<typeof charts>[number]) => (Array.isArray(chart.series) ? chart.series : [chart.series]).map((series) => series?.data);
|
|
const before = charts();
|
|
await act(() => i18n.changeLanguage("fr"));
|
|
expect(screen.getByRole("searchbox", { name: "Rechercher un scope" })).toHaveValue(" EAST ");
|
|
expect(screen.getByText("1 sur 2 scopes")).toBeInTheDocument();
|
|
expect(screen.queryByText("#west")).not.toBeInTheDocument();
|
|
expect(screen.getByRole("row", { name: /#east.*2.*2.*3/ })).toBeInTheDocument();
|
|
charts().forEach((chart, i) => {
|
|
expect(bars(chart)).toEqual(bars(before[i]!));
|
|
expect(chart.yAxis).toEqual(before[i]!.yAxis);
|
|
expect(chart.aria).not.toEqual(before[i]!.aria);
|
|
});
|
|
expect(query.refetch).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it.each(["isPending", "isPlaceholderData", "isError"] as const)("translates %s and hides stale regional scopes", async (state) => {
|
|
query[state] = true;
|
|
await act(() => i18n.changeLanguage("fr"));
|
|
render(<ScopesTab range="24h" />);
|
|
expect(screen.queryByRole("table")).not.toBeInTheDocument();
|
|
expect(screen.queryAllByTestId("chart")).toHaveLength(0);
|
|
expect(screen.queryByText("#west")).not.toBeInTheDocument();
|
|
if (state === "isError") {
|
|
expect(screen.getByRole("alert")).toHaveTextContent("Impossible de charger les scopes");
|
|
expect(screen.getByText("Données indisponibles")).toBeInTheDocument();
|
|
} else expect(screen.getByText("Chargement des scopes…")).toBeInTheDocument();
|
|
});
|
|
|
|
it("distinguishes a translated empty dataset from a search with no match", async () => {
|
|
await act(() => i18n.changeLanguage("fr"));
|
|
const { rerender } = render(<ScopesTab range="24h" />);
|
|
const search = screen.getByRole("searchbox", { name: "Rechercher un scope" });
|
|
fireEvent.change(search, { target: { value: "#absent" } });
|
|
expect(screen.getByText("Aucun scope ne correspond à cette recherche.")).toBeInTheDocument();
|
|
expect(screen.getByText("0 sur 2 scopes")).toBeInTheDocument();
|
|
expect(screen.queryByRole("table")).not.toBeInTheDocument();
|
|
fireEvent.change(search, { target: { value: "" } });
|
|
query.data = []; rerender(<ScopesTab range="24h" />);
|
|
expect(screen.getByText("Aucune donnée de scope disponible.")).toBeInTheDocument();
|
|
expect(screen.getAllByText("Aucune donnée")).toHaveLength(3);
|
|
expect(screen.queryAllByTestId("chart")).toHaveLength(0);
|
|
});
|
|
|
|
it("shows exact scope counts, keeps membership semantics explicit and filters the whole view", () => {
|
|
render(<ScopesTab range="24h" />);
|
|
const table = screen.getByRole("table", { name: "Scope counts" });
|
|
expect(within(table).getByRole("row", { name: /#west.*3.*2.*2/ })).toBeInTheDocument();
|
|
expect(screen.getByText("Observers by scope")).toBeInTheDocument();
|
|
expect(screen.getByRole("button", { name: /an observer can belong/ })).toBeInTheDocument();
|
|
expect(screen.queryByText(/an observer can belong/, { selector: "p" })).not.toBeInTheDocument();
|
|
expect(screen.getByText(/Scope activity/)).toBeInTheDocument();
|
|
fireEvent.change(screen.getByRole("searchbox", { name: "Find a scope" }), { target: { value: "WEST" } });
|
|
expect(screen.queryByText("#east")).not.toBeInTheDocument();
|
|
expect(screen.getByText(/1 of 2 scopes/)).toBeInTheDocument();
|
|
expect(screen.queryByRole("button", { name: /Refresh/ })).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("hides stale values while a region change is pending and on errors", () => {
|
|
query.isPlaceholderData = true;
|
|
const { rerender } = render(<ScopesTab range="24h" />);
|
|
expect(screen.queryByRole("table")).not.toBeInTheDocument();
|
|
expect(screen.queryAllByTestId("chart")).toHaveLength(0);
|
|
query.isPlaceholderData = false; query.isError = true; rerender(<ScopesTab range="24h" />);
|
|
expect(screen.getByRole("alert")).toHaveTextContent("Could not load scopes");
|
|
expect(screen.queryByText("#west")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("draws hourly sparks on all four scope cards from the scopes left after searching", () => {
|
|
query.data = [
|
|
{ name: "#west", packetCount: 3, observerCount: 2, nodeCount: 2, hourly: [{ hour: 0, packets: 1, observers: 1, nodes: 1 }, { hour: 2 * H, packets: 2, observers: 2, nodes: 0 }] },
|
|
{ name: "#east", packetCount: 2, observerCount: 2, nodeCount: 3, hourly: [{ hour: H, packets: 2, observers: 1, nodes: 3 }] },
|
|
] as typeof originalData;
|
|
const { container } = render(<ScopesTab range="24h" />);
|
|
const points = () => [...container.querySelectorAll("polyline")].map((l) => l.getAttribute("points"));
|
|
expect(points()).toHaveLength(4);
|
|
expect(screen.getByText("Line: hearing per hour")).toBeInTheDocument();
|
|
expect(screen.getByText("Line: advertising per hour")).toBeInTheDocument();
|
|
const before = points();
|
|
fireEvent.change(screen.getByRole("searchbox", { name: "Find a scope" }), { target: { value: "east" } });
|
|
expect(points()).toHaveLength(4);
|
|
expect(points()).not.toEqual(before);
|
|
});
|
|
|
|
it("shows no scoped packet total before any hour is rolled, keeping the membership counts", async () => {
|
|
await act(() => i18n.changeLanguage("en"));
|
|
const saved = seriesQuery.data;
|
|
seriesQuery.data = { ...saved, completeHours: 0 } as typeof saved;
|
|
try {
|
|
render(<ScopesTab range="24h" />);
|
|
expect(within(screen.getByText("Scoped packets").parentElement!.parentElement!).getByText("—")).toBeInTheDocument();
|
|
expect(within(screen.getAllByText("Observers")[0]!.parentElement!.parentElement!).getByText("4")).toBeInTheDocument();
|
|
} finally { seriesQuery.data = saved; }
|
|
});
|