From 1a889ea4351b4bddd5f7ce3556cc4726131b2c0f Mon Sep 17 00:00:00 2001
From: th
Date: Wed, 15 Apr 2026 19:02:58 +0200
Subject: [PATCH] tests: snapshot and reset view state around runs
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Tests assume a normal board — no super-view, no bucket filter, manual
sort, no hides. When the runner fired with the app left in super-view
or with filters on, selectors like `.card[data-id]` missed the
`.superview-item` DOM and multi-select / due-date assertions read the
wrong initial values.
Snapshot the relevant bits of window.state before the run, force a
clean baseline, then restore the user's view afterwards so opening the
runner doesn't silently wipe their context.
Co-Authored-By: Claude Opus 4.6 (1M context)
---
public/plugins/tests/runner.js | 84 ++++++++++++++++++++++++++++++++++
1 file changed, 84 insertions(+)
diff --git a/public/plugins/tests/runner.js b/public/plugins/tests/runner.js
index 3d8139e..79b93de 100644
--- a/public/plugins/tests/runner.js
+++ b/public/plugins/tests/runner.js
@@ -170,6 +170,71 @@ export function csrf() {
return getCsrf();
}
+// Snapshot the view-affecting bits of window.state so we can force a
+// neutral baseline (normal board, no filter, manual sort, no hides)
+// for the test run and restore the user's view afterwards. Tests were
+// flaky when the runner fired while the board was in super-view: DOM
+// selectors like `.card[data-id]` don't match `.superview-item`, so
+// multi-select and due-date tests read wrong initial values.
+export function snapshotViewState() {
+ const s = window.state;
+ if (!s) return null;
+ return {
+ superView: s.superView,
+ activeBucket: s.activeBucket,
+ activeTags: [...(s.activeTags || [])],
+ activeGroup: s.activeGroup,
+ sortMode: s.sortMode,
+ hideTermine: s.hideTermine,
+ hideDone: s.hideDone,
+ hideTagged: s.hideTagged,
+ boardSplitOpen: s.boardSplitOpen,
+ };
+}
+
+export function resetViewState() {
+ const s = window.state;
+ if (!s) return;
+ s.superView = false;
+ s.activeBucket = 'all';
+ s.activeTags = [];
+ s.activeGroup = null;
+ s.sortMode = 'manual';
+ s.hideTermine = false;
+ s.hideDone = false;
+ s.hideTagged = false;
+ s.boardSplitOpen = false;
+ s.selectedIds?.clear?.();
+ // Sync super-view toggle visuals with the reset state so tests that
+ // inspect the button see the correct pose.
+ const btn = document.getElementById('superview-toggle');
+ const icon = btn?.querySelector('i');
+ if (icon) icon.className = 'ph ph-list text-sm';
+ btn?.classList.remove('active');
+ window._renderBoard?.();
+}
+
+export function restoreViewState(snap) {
+ const s = window.state;
+ if (!s || !snap) return;
+ Object.assign(s, {
+ superView: snap.superView,
+ activeBucket: snap.activeBucket,
+ activeTags: snap.activeTags,
+ activeGroup: snap.activeGroup,
+ sortMode: snap.sortMode,
+ hideTermine: snap.hideTermine,
+ hideDone: snap.hideDone,
+ hideTagged: snap.hideTagged,
+ boardSplitOpen: snap.boardSplitOpen,
+ });
+ const btn = document.getElementById('superview-toggle');
+ const icon = btn?.querySelector('i');
+ if (icon) icon.className = `ph ${snap.superView ? 'ph-squares-four' : 'ph-list'} text-sm`;
+ btn?.classList.toggle('active', !!snap.superView);
+ window._renderBoard?.();
+}
+
export async function closeAll() {
document.getElementById('hterm')?.classList.add('hidden');
for (const id of ['settings-overlay', 'help', 'shortcuts', 'search', 'calendar', 'gallery', 'detail', 'modal', 'tasks', 'dialog']) {
@@ -452,6 +517,15 @@ async function run() {
stopBackgroundSync();
window._testRunning = true;
+ // 2b. Snapshot + reset view state. Tests assume a normal board with
+ // no filters / super-view off / manual sort — if the user left the
+ // app in super-view or with a bucket filter active, assertions on
+ // .card[data-id], card count, etc. read wrong initial values.
+ const viewSnap = snapshotViewState();
+ await closeAll();
+ resetViewState();
+ await sleep(50);
+
// 3. Hide panel so tests run against visible UI
panel.style.display = 'none';
await sleep(100);
@@ -476,6 +550,11 @@ async function run() {
await window._reloadAll?.();
await sleep(200);
} catch {}
+ // Re-reset the view in case a failing test left it in super-view,
+ // a bucket filter, or a sorted mode — otherwise the retry runs
+ // against the same wrong baseline that flaked it the first time.
+ resetViewState();
+ await sleep(50);
const retryResults = await runAll(failedKeys);
const retryMap = new Map();
for (const sr of retryResults) {
@@ -508,6 +587,11 @@ async function run() {
await sleep(200);
} catch {}
+ // 6b. Put the view back the way the user left it (super-view, sort,
+ // filters, …) so launching the test runner doesn't silently wipe
+ // their working context.
+ restoreViewState(viewSnap);
+
// 7. Resume background sync (delay to avoid immediate re-sync creating duplicates)
window._testRunning = false;
setTimeout(() => startBackgroundSync(), 60000);
|