From e43a71011495ac2dab2308599d494d655afd81c3 Mon Sep 17 00:00:00 2001 From: clawbot <35+clawbot@noreply.example.org> Date: Sun, 18 Oct 2026 23:00:35 +0200 Subject: [PATCH] Summary reads --/-- after an interval change, not 0/26 in red (closes #140) The interval menu resets every host to pending and updates the summary, which counted the WAN hosts with a latency recorded, none, and showed 0/26 in red until the second round after the change ended. While every WAN host still waits for its first check, the Reachable slot now reads --/-- in white, as at page load. updateSummary is exported for a unit test that resets the hosts as the interval menu does; a second test checks that the slot still reads 0/26 in red once every check failed. Model: opus-5-5 --- TODO.md | 8 +++++++ test/unit/main.test.js | 47 ++++++++++++++++++++++++++++++++++++++++++ web/src/main.js | 23 ++++++++++++++------- 3 files changed, 70 insertions(+), 8 deletions(-) diff --git a/TODO.md b/TODO.md index 302faf8..0cfec79 100644 --- a/TODO.md +++ b/TODO.md @@ -21,6 +21,14 @@ Run `make frontend-viewport-test` in CI as its own step; it is not part of # Completed Steps +- 2026-10-18: after an interval change, the summary's Reachable slot reads + `--/--` in white, as at page load, until the second round since the change + ends ([#140](https://git.eeqj.de/sneak/netwatch/issues/140)). It used to read + 0/26 in red from the change on, as no WAN host had a check recorded, for up to + two intervals with the network fine. The summary update now writes `--/--` + while every WAN host still waits for its first check. A unit test resets the + hosts as the interval menu does and checks the slot; another checks that it + reads 0/26 in red once every WAN host's check has failed - 2026-10-14: stopping the server waits for a report file write already under way, the periodic flush's or the one `Append` starts when 10 MiB are waiting, before the final flush diff --git a/test/unit/main.test.js b/test/unit/main.test.js index dcdb1a6..0201e85 100644 --- a/test/unit/main.test.js +++ b/test/unit/main.test.js @@ -17,6 +17,7 @@ import { measureLatency, Reporter, tick, + updateSummary, } from "../../web/src/main.js"; // There is no page here, so the tests stand in for it. The debug log looks @@ -654,6 +655,52 @@ test("the summary's min, max, median and average latency over the WAN targets' l }); }); +// In the next two tests each element of the summary that updateSummary +// writes into is a plain object, made the first time it is looked up and +// kept in elements under "#" and its id. + +// The interval menu resets each target and counts the rounds from 0 again, +// then updates the summary, before any check since the change is recorded. +test("after an interval change, the summary's Reachable slot reads --/-- in white, as at page load", (t) => { + t.mock.method( + globalThis.document, + "getElementById", + (id) => (elements[`#${id}`] ??= {}), + ); + const state = new AppState([]); + for (const host of state.wan) { + host.pushSample(Date.now(), { latency: 30, error: null }); + } + state.tickCount = 5; + + for (const host of state.allHosts) host.reset(); + state.tickCount = 0; + updateSummary(state); + + assert.equal(elements["#summary-reachable"].textContent, "--/--"); + assert.equal(elements["#summary-reachable"].className, "text-white"); +}); + +test("once every WAN target's check has failed, the summary's Reachable slot reads 0 of them, in red", (t) => { + t.mock.method( + globalThis.document, + "getElementById", + (id) => (elements[`#${id}`] ??= {}), + ); + const state = new AppState([]); + for (const host of state.wan) { + host.pushSample(Date.now(), { latency: null, error: "timeout" }); + } + + updateSummary(state); + + assert.equal( + elements["#summary-reachable"].textContent, + `0/${state.wan.length}`, + ); + assert.equal(elements["#summary-reachable"].className, "text-red-400"); +}); + // An app state in which, of the WAN targets, the first timedOut timed out, // the next unreachable were found unreachable, the next answered answered // after latency ms, and the rest have not been checked yet. diff --git a/web/src/main.js b/web/src/main.js index 065dc26..ab40d2d 100644 --- a/web/src/main.js +++ b/web/src/main.js @@ -995,7 +995,7 @@ function updateHostRow(host, index) { SparklineRenderer.draw(canvas, host.history); } -function updateSummary(state) { +export function updateSummary(state) { const stats = state.wanStats(); const hstats = state.wanHistoryStats(); @@ -1014,13 +1014,20 @@ function updateSummary(state) { const hmaxUnitEl = document.getElementById("summary-hmax-unit"); if (!reachableEl) return; - reachableEl.textContent = `${stats.reachable}/${stats.total}`; - reachableEl.className = - stats.reachable === stats.total - ? "text-green-400" - : stats.reachable === 0 - ? "text-red-400" - : "text-yellow-400"; + // Before any WAN host's check is recorded, as after an interval change, + // no host has answered or failed, so the slot reads as at page load. + if (state.wan.every((h) => h.status === "pending")) { + reachableEl.textContent = "--/--"; + reachableEl.className = "text-white"; + } else { + reachableEl.textContent = `${stats.reachable}/${stats.total}`; + reachableEl.className = + stats.reachable === stats.total + ? "text-green-400" + : stats.reachable === 0 + ? "text-red-400" + : "text-yellow-400"; + } if (stats.min !== null) { minEl.textContent = `${stats.min}`;