diff --git a/src/meshcore_hub/web/static/js/spa-react/pages/Routes.test.tsx b/src/meshcore_hub/web/static/js/spa-react/pages/Routes.test.tsx index 0c171da..b97c844 100644 --- a/src/meshcore_hub/web/static/js/spa-react/pages/Routes.test.tsx +++ b/src/meshcore_hub/web/static/js/spa-react/pages/Routes.test.tsx @@ -74,6 +74,52 @@ describe("Routes", () => { ).not.toBeNull(); }); + it("renders matched/threshold/degraded stat with dash separator", async () => { + const data = { + items: [ + { + ...ROUTES.items[0], + route_result: { + quality: "clear", + state: "healthy", + matched_count: 3, + threshold: 5, + effective_clear: 2, + }, + }, + ], + }; + vi.spyOn(api, "apiGet").mockImplementation(async (path) => { + if (path === "/api/v1/routes") return data; + if (path.match(/\/api\/v1\/routes\/[^/]+$/)) return ROUTE_DETAIL; + if (path.includes("/history")) return ROUTE_HISTORY; + throw new Error(`Unexpected: ${path}`); + }); + renderWithProviders(); + await waitFor(() => { + expect(screen.getByText("3/5-2")).toBeInTheDocument(); + }); + }); + + it("shows Now label for the last history chart entry", async () => { + const historyWithData = { + data: [ + { date: "2026-07-20", matched: 0, total: 0 }, + { date: "2026-07-21", matched: 1, total: 1 }, + ], + }; + vi.spyOn(api, "apiGet").mockImplementation(async (path) => { + if (path === "/api/v1/routes") return ROUTES; + if (path.match(/\/api\/v1\/routes\/[^/]+$/)) return ROUTE_DETAIL; + if (path.includes("/history")) return historyWithData; + throw new Error(`Unexpected: ${path}`); + }); + renderWithProviders(); + await waitFor(() => { + expect(screen.getByText("common.now")).toBeInTheDocument(); + }); + }); + it("shows an error on fetch failure", async () => { vi.spyOn(api, "apiGet").mockRejectedValue(new Error("routes error")); renderWithProviders(); diff --git a/src/meshcore_hub/web/static/js/spa-react/pages/Routes.tsx b/src/meshcore_hub/web/static/js/spa-react/pages/Routes.tsx index e44187b..4a8fed4 100644 --- a/src/meshcore_hub/web/static/js/spa-react/pages/Routes.tsx +++ b/src/meshcore_hub/web/static/js/spa-react/pages/Routes.tsx @@ -310,7 +310,7 @@ function StatsRow({ route }: { route: RouteItem }) { {matched}/{threshold} - {"\u2192"} + {"-"} {degraded} @@ -473,7 +473,7 @@ function DetailContent({ {historyData.map((d, i) => ( {i === historyData.length - 1 - ? t("routes.last_n_hours", { n: route.window_hours }) + ? t("common.now") : new Date(`${d.date}T00:00:00`).toLocaleDateString( undefined, { day: "2-digit", month: "2-digit" }, diff --git a/src/meshcore_hub/web/static/locales/en.json b/src/meshcore_hub/web/static/locales/en.json index deb2d85..3a0aab6 100644 --- a/src/meshcore_hub/web/static/locales/en.json +++ b/src/meshcore_hub/web/static/locales/en.json @@ -117,7 +117,8 @@ "unnamed_node": "Unnamed Node", "validation_invalid_number": "Value must be a valid number", "validation_invalid_boolean": "Value must be true, false, yes, no, 1, or 0", - "sort_by": "Sort by" + "sort_by": "Sort by", + "now": "Now" }, "links": { "website": "Website", @@ -359,7 +360,6 @@ "quality_no_coverage": "No Coverage", "quality_unknown": "Unknown", "recent_packets": "Recent Packets", - "last_n_hours": "Last {{n}}h", "min_nodes_error": "At least 2 path nodes are required.", "other_routes": "Other routes", "filter_mine": "Show only my routes" diff --git a/src/meshcore_hub/web/static/locales/nl.json b/src/meshcore_hub/web/static/locales/nl.json index 014b915..a041a19 100644 --- a/src/meshcore_hub/web/static/locales/nl.json +++ b/src/meshcore_hub/web/static/locales/nl.json @@ -108,7 +108,8 @@ "filter_operator_label": "Operator", "filter_observer_label": "Waarnemer", "filter_observer_enable": "Klik om deze waarnemer te tonen", - "filter_observer_disable": "Klik om deze waarnemer te verbergen" + "filter_observer_disable": "Klik om deze waarnemer te verbergen", + "now": "Nu" }, "links": { "website": "Website", @@ -282,7 +283,6 @@ "quality_no_coverage": "Geen dekking", "quality_unknown": "Onbekend", "recent_packets": "Recente packets", - "last_n_hours": "Laatste {{n}}u", "min_nodes_error": "Minimaal 2 padknooppunten zijn vereist.", "other_routes": "Overige routes", "filter_mine": "Toon alleen mijn routes"