From d083f9ade91dbcbc7aa577bbf7b2248ce21ebf09 Mon Sep 17 00:00:00 2001 From: n30nex Date: Sat, 31 Oct 2026 07:00:06 -0400 Subject: [PATCH] test: cover partial observer chart construction failures (#2354) Partial follow-up for #1778; addresses the chart-cleanup test gap identified in the review of #2339. The observer detail unit harness now throws during the second or fourth chart constructor. Both cases require existing chart instances to be destroyed, metadata/calibration to remain visible, the error to be rendered as text, and a later time-window retry to recover without extra requests. Production cleanup already behaves correctly; this PR adds coverage only. The parent-run ownership suite passes 30/30. Removing the actual cleanup produces exactly the two intended assertion failures. The remaining unit sources and runner match the current-master baseline whose 222 frontend suites passed; only this updated unit file differs within that test set. TDD scope: no production change or new behavior. The negative control removes existing cleanup to prove the new assertions detect its loss. No configuration, dependencies or runtime performance changes. Exact-head CI passed, including the browser shards. Independent adversarial, correctness and TDD reviews found no must-fix items. Co-authored-by: n30nex <6686206+n30nex@users.noreply.github.com> --- tests/unit/test-observer-detail-ownership.js | 32 ++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/tests/unit/test-observer-detail-ownership.js b/tests/unit/test-observer-detail-ownership.js index f091c847..621dbd59 100644 --- a/tests/unit/test-observer-detail-ownership.js +++ b/tests/unit/test-observer-detail-ownership.js @@ -5,10 +5,11 @@ const fs = require('fs'); const path = require('path'); const vm = require('vm'); -function harness() { +function harness(failChartAt = 0) { const elements = new Map(); const requests = []; const charts = []; + let chartAttempts = 0; let page; function element(id) { const children = []; @@ -36,7 +37,12 @@ function harness() { const app = element('app'); class Chart { static defaults = {}; - constructor(canvas, config) { this.config = config; this.destroyed = false; charts.push(this); } + constructor(canvas, config) { + if (++chartAttempts === failChartAt) throw new Error(''); + this.config = config; + this.destroyed = false; + charts.push(this); + } destroy() { this.destroyed = true; } } const context = vm.createContext({ @@ -147,6 +153,28 @@ async function test(name, fn) { }); } + for (const failedChart of [2, 4]) { + await test('chart construction failure ' + failedChart + ' cleans up partial analytics and permits retry', async () => { + const h = harness(failedChart); + complete(h.visit('A'), 'A'); + await settled(); + assert.strictEqual(h.charts.length, failedChart - 1, 'failure follows successful chart construction'); + assert.ok(h.charts.every(chart => chart.destroyed), 'already constructed charts must be destroyed'); + assert.strictEqual(h.snapshot().charts.length, 0, 'failed analytics must retain no active charts'); + assert.strictEqual(h.snapshot().title, 'A'); + assert.match(h.snapshot().html, /Test radio/); + assert.match(h.snapshot().html, /2 seconds/); + assert.strictEqual(h.elements.get('obsAnalytics').firstChild.textContent, 'Error: '); + assert.doesNotMatch(h.snapshot().html, //, 'construction error uses textContent'); + + complete(h.days(1), 'A', 'retry'); + await settled(); + assert.strictEqual(h.snapshot().charts.length, 4, 'a later load can construct its own charts'); + assert.ok(h.charts.slice(0, failedChart - 1).every(chart => chart.destroyed), 'retry cannot revive failed charts'); + assert.strictEqual(h.requests.length, 6, 'only the requested retry fetches again'); + }); + } + await test('identity failure stays visible when secondary responses settle later', async () => { const h = harness(); const pending = h.visit('A');