From fe3399a44b80f25e2b5debc12be9f98117297e46 Mon Sep 17 00:00:00 2001
From: th
Date: Sat, 11 Apr 2026 20:19:22 +0200
Subject: [PATCH] tests: in-run progress strip + cache-bust runner.js across
files
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* runner.js: shows a 30px fixed-top progress strip during runs (label
"test x/total:", current suite/test name with ellipsis on the left,
fixed 50%-wide progress fill on the right that turns danger-red on
the first failure). The DOM node is created in loader.js so it appears
immediately on shortcut press; runner.js only updates it. The fill
width is fixed at flex:0 0 50% so it doesn't jiggle as the test name
changes length. Suites now live on window._testSuites as a defensive
fallback so two runner instances stay in sync.
* Cache invalidation: every test file's `from './runner.js'` is now
`from './runner.js?v=2'`, and loader.js imports the runner with the
same RUNNER_V suffix. Same URL → one module instance → suite
registration and runAll see the same array. Bumping RUNNER_V (in
loader.js + every test file in lock-step) is the way to force a
re-fetch when runner.js changes.
* loader.js: ensureProgressBar() draws the strip via setProperty(...,
'important') with z-index 2147483647 attached to documentElement so
nothing in user CSS, scanline overlay (z 9999) or modals can occlude
it. Auto-run path (?test=1) also pre-renders the strip.
chore: bump version to 0.9.1
Co-Authored-By: Claude Opus 4.6 (1M context)
---
config.php | 2 +-
public/plugins/tests/api-edge.test.js | 2 +-
public/plugins/tests/api.test.js | 2 +-
public/plugins/tests/board.test.js | 2 +-
public/plugins/tests/bucket-context.test.js | 2 +-
public/plugins/tests/buckets.test.js | 2 +-
public/plugins/tests/calendar-popup.test.js | 2 +-
public/plugins/tests/calendar.test.js | 2 +-
public/plugins/tests/canvas.test.js | 2 +-
.../plugins/tests/card-interactions.test.js | 2 +-
public/plugins/tests/context-menu.test.js | 2 +-
public/plugins/tests/datepicker.test.js | 2 +-
.../plugins/tests/detail-interactions.test.js | 2 +-
public/plugins/tests/detail.test.js | 2 +-
public/plugins/tests/editor-features.test.js | 2 +-
public/plugins/tests/editor-save.test.js | 2 +-
public/plugins/tests/editor-toolbar.test.js | 2 +-
public/plugins/tests/editor.test.js | 2 +-
public/plugins/tests/export.test.js | 2 +-
public/plugins/tests/favorites.test.js | 2 +-
public/plugins/tests/filtering.test.js | 2 +-
public/plugins/tests/gallery.test.js | 2 +-
public/plugins/tests/help.test.js | 2 +-
public/plugins/tests/image-fullscreen.test.js | 2 +-
public/plugins/tests/line-numbers.test.js | 2 +-
public/plugins/tests/loader.js | 49 +++++++++++++++++--
public/plugins/tests/markdown-edge.test.js | 2 +-
public/plugins/tests/markdown.test.js | 2 +-
public/plugins/tests/multiselect.test.js | 2 +-
public/plugins/tests/notifications.test.js | 2 +-
public/plugins/tests/quick-capture.test.js | 2 +-
public/plugins/tests/relay.test.js | 2 +-
public/plugins/tests/rrule.test.js | 2 +-
public/plugins/tests/runner.js | 47 +++++++++++++++++-
public/plugins/tests/search-logic.test.js | 2 +-
public/plugins/tests/search.test.js | 2 +-
public/plugins/tests/settings.test.js | 2 +-
public/plugins/tests/shell.test.js | 2 +-
public/plugins/tests/snippets.test.js | 2 +-
public/plugins/tests/sorting.test.js | 2 +-
public/plugins/tests/state-functions.test.js | 2 +-
public/plugins/tests/theme.test.js | 2 +-
public/plugins/tests/toolbar.test.js | 2 +-
public/plugins/tests/undo.test.js | 2 +-
44 files changed, 132 insertions(+), 48 deletions(-)
diff --git a/config.php b/config.php
index 6548c25..58a160b 100644
--- a/config.php
+++ b/config.php
@@ -1,7 +1,7 @@
{
it('create card with special characters in title', async () => {
diff --git a/public/plugins/tests/api.test.js b/public/plugins/tests/api.test.js
index 0dd7432..95958ff 100644
--- a/public/plugins/tests/api.test.js
+++ b/public/plugins/tests/api.test.js
@@ -1,4 +1,4 @@
-import { describe, it, expect, csrf } from './runner.js';
+import { describe, it, expect, csrf } from './runner.js?v=2';
describe('API: Cards CRUD', () => {
it('GET /api/cards returns array', async () => {
diff --git a/public/plugins/tests/board.test.js b/public/plugins/tests/board.test.js
index b89dd60..449578e 100644
--- a/public/plugins/tests/board.test.js
+++ b/public/plugins/tests/board.test.js
@@ -1,4 +1,4 @@
-import { describe, it, afterEach, expect, $, $$, click, sleep, closeAll, csrf } from './runner.js';
+import { describe, it, afterEach, expect, $, $$, click, sleep, closeAll, csrf } from './runner.js?v=2';
describe('Board Rendering', () => {
it('renders card elements', () => {
diff --git a/public/plugins/tests/bucket-context.test.js b/public/plugins/tests/bucket-context.test.js
index b357554..353c912 100644
--- a/public/plugins/tests/bucket-context.test.js
+++ b/public/plugins/tests/bucket-context.test.js
@@ -1,4 +1,4 @@
-import { describe, it, afterEach, expect, $, $$, click, sleep, closeAll, csrf } from './runner.js';
+import { describe, it, afterEach, expect, $, $$, click, sleep, closeAll, csrf } from './runner.js?v=2';
describe('Bucket Context Menu', () => {
afterEach(async () => {
diff --git a/public/plugins/tests/buckets.test.js b/public/plugins/tests/buckets.test.js
index 9c9bd20..d035066 100644
--- a/public/plugins/tests/buckets.test.js
+++ b/public/plugins/tests/buckets.test.js
@@ -1,4 +1,4 @@
-import { describe, it, afterEach, expect, $, $$, click, sleep, closeAll, csrf } from './runner.js';
+import { describe, it, afterEach, expect, $, $$, click, sleep, closeAll, csrf } from './runner.js?v=2';
import { state } from '../../js/modules/state.js';
describe('Bucket Bar', () => {
diff --git a/public/plugins/tests/calendar-popup.test.js b/public/plugins/tests/calendar-popup.test.js
index c3abe43..dbc3324 100644
--- a/public/plugins/tests/calendar-popup.test.js
+++ b/public/plugins/tests/calendar-popup.test.js
@@ -1,4 +1,4 @@
-import { describe, it, afterEach, expect, $, $$, click, sleep, closeAll } from './runner.js';
+import { describe, it, afterEach, expect, $, $$, click, sleep, closeAll } from './runner.js?v=2';
describe('Calendar: Day Click', () => {
afterEach(() => closeAll());
diff --git a/public/plugins/tests/calendar.test.js b/public/plugins/tests/calendar.test.js
index b76dc76..eca1234 100644
--- a/public/plugins/tests/calendar.test.js
+++ b/public/plugins/tests/calendar.test.js
@@ -1,4 +1,4 @@
-import { describe, it, afterEach, expect, $, click, sleep, closeAll } from './runner.js';
+import { describe, it, afterEach, expect, $, click, sleep, closeAll } from './runner.js?v=2';
describe('Calendar', () => {
afterEach(() => closeAll());
diff --git a/public/plugins/tests/canvas.test.js b/public/plugins/tests/canvas.test.js
index af65c7b..995b546 100644
--- a/public/plugins/tests/canvas.test.js
+++ b/public/plugins/tests/canvas.test.js
@@ -1,4 +1,4 @@
-import { describe, it, beforeEach, afterEach, expect, $, click, sleep, closeAll } from './runner.js';
+import { describe, it, beforeEach, afterEach, expect, $, click, sleep, closeAll } from './runner.js?v=2';
// ── Helpers ──
diff --git a/public/plugins/tests/card-interactions.test.js b/public/plugins/tests/card-interactions.test.js
index a7dac63..1a464c5 100644
--- a/public/plugins/tests/card-interactions.test.js
+++ b/public/plugins/tests/card-interactions.test.js
@@ -1,4 +1,4 @@
-import { describe, it, afterEach, expect, $, $$, click, rightClick, sleep, closeAll, csrf } from './runner.js';
+import { describe, it, afterEach, expect, $, $$, click, rightClick, sleep, closeAll, csrf } from './runner.js?v=2';
import { state } from '../../js/modules/state.js';
describe('Card: Tag Operations', () => {
diff --git a/public/plugins/tests/context-menu.test.js b/public/plugins/tests/context-menu.test.js
index 7884cda..19b98db 100644
--- a/public/plugins/tests/context-menu.test.js
+++ b/public/plugins/tests/context-menu.test.js
@@ -1,4 +1,4 @@
-import { describe, it, afterEach, expect, $, $$, click, rightClick, sleep, csrf } from './runner.js';
+import { describe, it, afterEach, expect, $, $$, click, rightClick, sleep, csrf } from './runner.js?v=2';
describe('Context Menu', () => {
afterEach(async () => {
diff --git a/public/plugins/tests/datepicker.test.js b/public/plugins/tests/datepicker.test.js
index 891ea8f..ee8031e 100644
--- a/public/plugins/tests/datepicker.test.js
+++ b/public/plugins/tests/datepicker.test.js
@@ -1,4 +1,4 @@
-import { describe, it, afterEach, expect, $, $$, click, sleep, closeAll } from './runner.js';
+import { describe, it, afterEach, expect, $, $$, click, sleep, closeAll } from './runner.js?v=2';
import { showDialog } from '../../js/modules/dialog.js';
describe('Datepicker: Rendering', () => {
diff --git a/public/plugins/tests/detail-interactions.test.js b/public/plugins/tests/detail-interactions.test.js
index 3ed24ca..7e22f2b 100644
--- a/public/plugins/tests/detail-interactions.test.js
+++ b/public/plugins/tests/detail-interactions.test.js
@@ -1,4 +1,4 @@
-import { describe, it, afterEach, expect, $, click, pressKey, sleep, closeAll, csrf } from './runner.js';
+import { describe, it, afterEach, expect, $, click, pressKey, sleep, closeAll, csrf } from './runner.js?v=2';
describe('Detail: Done Toggle', () => {
afterEach(() => closeAll());
diff --git a/public/plugins/tests/detail.test.js b/public/plugins/tests/detail.test.js
index e7972dd..44773af 100644
--- a/public/plugins/tests/detail.test.js
+++ b/public/plugins/tests/detail.test.js
@@ -1,4 +1,4 @@
-import { describe, it, afterEach, expect, $, click, pressKey, sleep, closeAll } from './runner.js';
+import { describe, it, afterEach, expect, $, click, pressKey, sleep, closeAll } from './runner.js?v=2';
describe('Detail View', () => {
afterEach(() => closeAll());
diff --git a/public/plugins/tests/editor-features.test.js b/public/plugins/tests/editor-features.test.js
index 3be7c64..2c6ac62 100644
--- a/public/plugins/tests/editor-features.test.js
+++ b/public/plugins/tests/editor-features.test.js
@@ -1,4 +1,4 @@
-import { describe, it, afterEach, expect, $, click, type, sleep, closeAll, csrf } from './runner.js';
+import { describe, it, afterEach, expect, $, click, type, sleep, closeAll, csrf } from './runner.js?v=2';
describe('Editor: Color Picker', () => {
afterEach(() => closeAll());
diff --git a/public/plugins/tests/editor-save.test.js b/public/plugins/tests/editor-save.test.js
index cafc43d..8fbd8e6 100644
--- a/public/plugins/tests/editor-save.test.js
+++ b/public/plugins/tests/editor-save.test.js
@@ -1,4 +1,4 @@
-import { describe, it, afterEach, expect, $, click, type, sleep, closeAll, csrf } from './runner.js';
+import { describe, it, afterEach, expect, $, click, type, sleep, closeAll, csrf } from './runner.js?v=2';
describe('Editor: Empty Title Prevention', () => {
afterEach(() => closeAll());
diff --git a/public/plugins/tests/editor-toolbar.test.js b/public/plugins/tests/editor-toolbar.test.js
index 805a627..d01982e 100644
--- a/public/plugins/tests/editor-toolbar.test.js
+++ b/public/plugins/tests/editor-toolbar.test.js
@@ -1,4 +1,4 @@
-import { describe, it, afterEach, expect, $, click, sleep, closeAll } from './runner.js';
+import { describe, it, afterEach, expect, $, click, sleep, closeAll } from './runner.js?v=2';
// Helper: set selection range in textarea
function selectRange(textarea, start, end) {
diff --git a/public/plugins/tests/editor.test.js b/public/plugins/tests/editor.test.js
index a00c825..aed39f6 100644
--- a/public/plugins/tests/editor.test.js
+++ b/public/plugins/tests/editor.test.js
@@ -1,4 +1,4 @@
-import { describe, it, afterEach, expect, $, click, pressKey, sleep, closeAll, csrf } from './runner.js';
+import { describe, it, afterEach, expect, $, click, pressKey, sleep, closeAll, csrf } from './runner.js?v=2';
describe('Editor: Structure', () => {
it('has all elements', () => {
diff --git a/public/plugins/tests/export.test.js b/public/plugins/tests/export.test.js
index 4bfe127..8f11cfe 100644
--- a/public/plugins/tests/export.test.js
+++ b/public/plugins/tests/export.test.js
@@ -1,4 +1,4 @@
-import { describe, it, expect, csrf } from './runner.js';
+import { describe, it, expect, csrf } from './runner.js?v=2';
describe('Export: JSON ZIP', () => {
it('export endpoint returns ZIP', async () => {
diff --git a/public/plugins/tests/favorites.test.js b/public/plugins/tests/favorites.test.js
index a2131ff..2686c57 100644
--- a/public/plugins/tests/favorites.test.js
+++ b/public/plugins/tests/favorites.test.js
@@ -1,4 +1,4 @@
-import { describe, it, afterEach, expect, $, $$, click, sleep, closeAll, csrf } from './runner.js';
+import { describe, it, afterEach, expect, $, $$, click, sleep, closeAll, csrf } from './runner.js?v=2';
import { state } from '../../js/modules/state.js';
describe('Favorites Bucket', () => {
diff --git a/public/plugins/tests/filtering.test.js b/public/plugins/tests/filtering.test.js
index 5166726..fd7fbc8 100644
--- a/public/plugins/tests/filtering.test.js
+++ b/public/plugins/tests/filtering.test.js
@@ -1,4 +1,4 @@
-import { describe, it, afterEach, expect, $, $$, click, sleep, closeAll, csrf } from './runner.js';
+import { describe, it, afterEach, expect, $, $$, click, sleep, closeAll, csrf } from './runner.js?v=2';
import { state, parseTags, sortedCards } from '../../js/modules/state.js';
describe('Filtering: Tag AND Logic', () => {
diff --git a/public/plugins/tests/gallery.test.js b/public/plugins/tests/gallery.test.js
index e1605cc..e25fbe4 100644
--- a/public/plugins/tests/gallery.test.js
+++ b/public/plugins/tests/gallery.test.js
@@ -1,4 +1,4 @@
-import { describe, it, afterEach, expect, $, click, sleep, closeAll } from './runner.js';
+import { describe, it, afterEach, expect, $, click, sleep, closeAll } from './runner.js?v=2';
describe('Gallery', () => {
afterEach(() => closeAll());
diff --git a/public/plugins/tests/help.test.js b/public/plugins/tests/help.test.js
index e17f2f4..605e7e7 100644
--- a/public/plugins/tests/help.test.js
+++ b/public/plugins/tests/help.test.js
@@ -1,4 +1,4 @@
-import { describe, it, afterEach, expect, $, $$, click, pressKey, sleep, closeAll } from './runner.js';
+import { describe, it, afterEach, expect, $, $$, click, pressKey, sleep, closeAll } from './runner.js?v=2';
describe('Help', () => {
afterEach(() => closeAll());
diff --git a/public/plugins/tests/image-fullscreen.test.js b/public/plugins/tests/image-fullscreen.test.js
index 43ab2f4..3d5f57a 100644
--- a/public/plugins/tests/image-fullscreen.test.js
+++ b/public/plugins/tests/image-fullscreen.test.js
@@ -1,4 +1,4 @@
-import { describe, it, afterEach, expect, $, click, pressKey, sleep, closeAll, csrf } from './runner.js';
+import { describe, it, afterEach, expect, $, click, pressKey, sleep, closeAll, csrf } from './runner.js?v=2';
describe('Image Fullscreen Viewer', () => {
afterEach(async () => {
diff --git a/public/plugins/tests/line-numbers.test.js b/public/plugins/tests/line-numbers.test.js
index ceb32f5..aa49c25 100644
--- a/public/plugins/tests/line-numbers.test.js
+++ b/public/plugins/tests/line-numbers.test.js
@@ -1,4 +1,4 @@
-import { describe, it, afterEach, expect, $, click, sleep, closeAll } from './runner.js';
+import { describe, it, afterEach, expect, $, click, sleep, closeAll } from './runner.js?v=2';
function enableLines() {
const gutter = $('#editor-gutter');
diff --git a/public/plugins/tests/loader.js b/public/plugins/tests/loader.js
index fab5a53..d283f29 100644
--- a/public/plugins/tests/loader.js
+++ b/public/plugins/tests/loader.js
@@ -16,23 +16,62 @@ const V = '?v=' + Date.now();
// Auto-load if ?test in query string
if (new URLSearchParams(location.search).has('test')) {
+ ensureProgressBar();
import('./tests.js' + V);
}
// Keyboard shortcut: Ctrl+Shift+Alt+T → open runner.
-// Import runner.js *without* the cache-busting query so we get the same
-// module instance that the test files register their suites into. With a
-// `?v=` suffix the browser treats it as a distinct module and we'd open
-// a fresh runner whose `suites` array is empty (→ "0/0 passed").
+//
+// runner.js is imported with the SAME `?v=2` query string the test
+// files use (see RUNNER_V below). Same URL → same module instance, so
+// suite registration in test files and runAll in the loader run against
+// the same `suites` array. Bump RUNNER_V whenever runner.js changes
+// in a way that needs cache invalidation across users.
document.addEventListener('keydown', e => {
if (e.ctrlKey && e.shiftKey && e.altKey && (e.key === 'T' || e.key === 't')) {
e.preventDefault();
+ ensureProgressBar();
import('./tests.js' + V).then(() =>
- import('./runner.js').then(m => m.open?.())
+ import('./runner.js' + RUNNER_V).then(m => m.open?.())
);
}
});
+// Must match the `?v=…` suffix used in test files' `from './runner.js?v=…'`
+// imports — bump both in lock-step to invalidate the runner.js cache.
+const RUNNER_V = '?v=2';
+
+function ensureProgressBar() {
+ if (document.getElementById('test-progress')) return;
+ const bar = document.createElement('div');
+ bar.id = 'test-progress';
+ const s = bar.style;
+ s.setProperty('position', 'fixed', 'important');
+ s.setProperty('top', '0', 'important');
+ s.setProperty('left', '0', 'important');
+ s.setProperty('right', '0', 'important');
+ s.setProperty('height', '30px', 'important');
+ s.setProperty('z-index', '2147483647', 'important');
+ s.setProperty('background', '#000', 'important');
+ s.setProperty('color', '#cbd5e1', 'important');
+ s.setProperty('font-family', 'monospace', 'important');
+ s.setProperty('font-size', '11px', 'important');
+ s.setProperty('display', 'flex', 'important');
+ s.setProperty('align-items', 'center', 'important');
+ s.setProperty('gap', '10px', 'important');
+ s.setProperty('padding', '0 12px', 'important');
+ s.setProperty('border-bottom', '1px solid #334155', 'important');
+ s.setProperty('pointer-events', 'none', 'important');
+ bar.innerHTML = `
+ tests starting…
+
+
+ `;
+ document.documentElement.appendChild(bar);
+}
+
hyprcrd?.registerPlugin?.({
name: 'tests',
version: '1.0.0',
diff --git a/public/plugins/tests/markdown-edge.test.js b/public/plugins/tests/markdown-edge.test.js
index 2b93ed9..fabead5 100644
--- a/public/plugins/tests/markdown-edge.test.js
+++ b/public/plugins/tests/markdown-edge.test.js
@@ -1,4 +1,4 @@
-import { describe, it, expect } from './runner.js';
+import { describe, it, expect } from './runner.js?v=2';
import { renderMd } from '../../js/modules/markdown.js';
describe('Markdown: Blank Lines', () => {
diff --git a/public/plugins/tests/markdown.test.js b/public/plugins/tests/markdown.test.js
index b1f1787..5e46bce 100644
--- a/public/plugins/tests/markdown.test.js
+++ b/public/plugins/tests/markdown.test.js
@@ -1,4 +1,4 @@
-import { describe, it, expect } from './runner.js';
+import { describe, it, expect } from './runner.js?v=2';
import { renderMd, formatTodoItems, stripMd } from '../../js/modules/markdown.js';
import { state } from '../../js/modules/state.js';
diff --git a/public/plugins/tests/multiselect.test.js b/public/plugins/tests/multiselect.test.js
index b27f5a2..627b2df 100644
--- a/public/plugins/tests/multiselect.test.js
+++ b/public/plugins/tests/multiselect.test.js
@@ -1,4 +1,4 @@
-import { describe, it, afterEach, expect, $, sleep, closeAll } from './runner.js';
+import { describe, it, afterEach, expect, $, sleep, closeAll } from './runner.js?v=2';
import { state } from '../../js/modules/state.js';
describe('Multi-Select', () => {
diff --git a/public/plugins/tests/notifications.test.js b/public/plugins/tests/notifications.test.js
index f74e66f..7bca614 100644
--- a/public/plugins/tests/notifications.test.js
+++ b/public/plugins/tests/notifications.test.js
@@ -1,4 +1,4 @@
-import { describe, it, expect, $ } from './runner.js';
+import { describe, it, expect, $ } from './runner.js?v=2';
describe('Notification Banner', () => {
it('banner element exists', () => {
diff --git a/public/plugins/tests/quick-capture.test.js b/public/plugins/tests/quick-capture.test.js
index d2b4f85..5c9fcca 100644
--- a/public/plugins/tests/quick-capture.test.js
+++ b/public/plugins/tests/quick-capture.test.js
@@ -1,4 +1,4 @@
-import { describe, it, afterEach, expect, $, type, pressKey, sleep, closeAll, csrf } from './runner.js';
+import { describe, it, afterEach, expect, $, type, pressKey, sleep, closeAll, csrf } from './runner.js?v=2';
describe('Quick Capture: Open/Close', () => {
afterEach(async () => {
diff --git a/public/plugins/tests/relay.test.js b/public/plugins/tests/relay.test.js
index 824fb56..5889e9c 100644
--- a/public/plugins/tests/relay.test.js
+++ b/public/plugins/tests/relay.test.js
@@ -1,4 +1,4 @@
-import { describe, it, expect, csrf } from './runner.js';
+import { describe, it, expect, csrf } from './runner.js?v=2';
describe('Relay: Push API', () => {
it('push without config returns error', async () => {
diff --git a/public/plugins/tests/rrule.test.js b/public/plugins/tests/rrule.test.js
index 90378f6..1f57297 100644
--- a/public/plugins/tests/rrule.test.js
+++ b/public/plugins/tests/rrule.test.js
@@ -1,4 +1,4 @@
-import { describe, it, expect } from './runner.js';
+import { describe, it, expect } from './runner.js?v=2';
import { state } from '../../js/modules/state.js';
// RRULE is set by CalDAV sync, not via PATCH. Test the data model and existing cards.
diff --git a/public/plugins/tests/runner.js b/public/plugins/tests/runner.js
index 0bc9abd..2ca7aed 100644
--- a/public/plugins/tests/runner.js
+++ b/public/plugins/tests/runner.js
@@ -7,7 +7,13 @@
import { getCsrf } from '../../js/modules/util.js';
import { stopBackgroundSync, startBackgroundSync } from '../../js/modules/caldav.js';
-const suites = [];
+// Suites live on window so two runner.js instances (one cached and loaded
+// by test files via bare imports, one fresh and loaded by loader.js with
+// ?v=) can share registration. The cached runner provides describe/it/
+// expect at module-load time; the fresh runner runs `runAll` against the
+// shared array. Without this we'd see "0/0 passed" whenever the cached
+// runner is older than the loader.
+const suites = (window._testSuites = window._testSuites || []);
let currentSuite = null;
// ── Public API ──
@@ -200,11 +206,48 @@ async function restoreBackup() {
// ── Runner ──
+// Fixed-top progress strip shown while tests are running. The main test
+// panel is hidden during the run so the suite can interact with the UI;
+// this strip gives the user a way to see how far along the run is.
+//
+// loader.js creates the DOM node when the user presses the shortcut so
+// it appears immediately (loader.js is cache-busted per page load via
+// the plugin manager's ?v=time()). The runner — which lives at a stable
+// URL so suite registration is consistent — only updates the existing
+// node. If runner.js is cached and stale, the placeholder still shows
+// up so the user can tell something happened.
+function getProgressBar() {
+ return document.getElementById('test-progress');
+}
+function updateProgress(current, total, name, failed) {
+ const bar = getProgressBar();
+ if (!bar) return;
+ const label = bar.querySelector('#test-progress-label');
+ const nameEl = bar.querySelector('#test-progress-name');
+ const fill = bar.querySelector('#test-progress-fill');
+ if (label) label.textContent = `test ${current}/${total}:`;
+ if (nameEl) nameEl.textContent = name;
+ if (fill) {
+ fill.style.width = (current / total * 100) + '%';
+ if (failed) fill.style.background = '#fb7185';
+ }
+}
+function hideProgress() {
+ getProgressBar()?.remove();
+}
+
async function runAll() {
const results = [];
+ const total = suites.reduce((n, s) => n + s.tests.length, 0);
+ // Initial label before any test has started
+ updateProgress(0, total, 'starting…', false);
+ let done = 0;
+ let anyFailed = false;
for (const suite of suites) {
const suiteResult = { name: suite.name, tests: [] };
for (const test of suite.tests) {
+ done++;
+ updateProgress(done, total, `${suite.name} — ${test.name}`, anyFailed);
const entry = { name: test.name, status: 'pass', error: null, time: 0 };
const t0 = performance.now();
try {
@@ -214,6 +257,7 @@ async function runAll() {
} catch (err) {
entry.status = 'fail';
entry.error = err.message;
+ anyFailed = true;
// Always run afterEach on failure too
try { if (suite.afterEach) await suite.afterEach(); } catch {}
}
@@ -222,6 +266,7 @@ async function runAll() {
}
results.push(suiteResult);
}
+ hideProgress();
return results;
}
diff --git a/public/plugins/tests/search-logic.test.js b/public/plugins/tests/search-logic.test.js
index c904d62..d995d4c 100644
--- a/public/plugins/tests/search-logic.test.js
+++ b/public/plugins/tests/search-logic.test.js
@@ -1,4 +1,4 @@
-import { describe, it, afterEach, expect, $, click, type, pressKey, sleep, closeAll, csrf } from './runner.js';
+import { describe, it, afterEach, expect, $, click, type, pressKey, sleep, closeAll, csrf } from './runner.js?v=2';
describe('Search: Tag + Text Combined', () => {
afterEach(() => closeAll());
diff --git a/public/plugins/tests/search.test.js b/public/plugins/tests/search.test.js
index a58b719..ee46377 100644
--- a/public/plugins/tests/search.test.js
+++ b/public/plugins/tests/search.test.js
@@ -1,4 +1,4 @@
-import { describe, it, afterEach, expect, $, click, type, pressKey, sleep, closeAll } from './runner.js';
+import { describe, it, afterEach, expect, $, click, type, pressKey, sleep, closeAll } from './runner.js?v=2';
describe('Search', () => {
afterEach(() => closeAll());
diff --git a/public/plugins/tests/settings.test.js b/public/plugins/tests/settings.test.js
index 8405cf5..e630c2c 100644
--- a/public/plugins/tests/settings.test.js
+++ b/public/plugins/tests/settings.test.js
@@ -1,4 +1,4 @@
-import { describe, it, afterEach, expect, $, $$, click, closeAll, sleep } from './runner.js';
+import { describe, it, afterEach, expect, $, $$, click, closeAll, sleep } from './runner.js?v=2';
describe('Settings: Open/Close', () => {
afterEach(() => closeAll());
diff --git a/public/plugins/tests/shell.test.js b/public/plugins/tests/shell.test.js
index db3a6a8..482f8e1 100644
--- a/public/plugins/tests/shell.test.js
+++ b/public/plugins/tests/shell.test.js
@@ -1,4 +1,4 @@
-import { describe, it, expect, $ } from './runner.js';
+import { describe, it, expect, $ } from './runner.js?v=2';
describe('App Shell', () => {
it('header with logo', () => {
diff --git a/public/plugins/tests/snippets.test.js b/public/plugins/tests/snippets.test.js
index 6cf4e87..80bb520 100644
--- a/public/plugins/tests/snippets.test.js
+++ b/public/plugins/tests/snippets.test.js
@@ -1,4 +1,4 @@
-import { describe, it, afterEach, expect, $, click, sleep, closeAll } from './runner.js';
+import { describe, it, afterEach, expect, $, click, sleep, closeAll } from './runner.js?v=2';
// Helper: simulate typing a snippet trigger in the editor body
function typeSnippet(bodyEl, trigger) {
diff --git a/public/plugins/tests/sorting.test.js b/public/plugins/tests/sorting.test.js
index 9f43cf8..f48ed4d 100644
--- a/public/plugins/tests/sorting.test.js
+++ b/public/plugins/tests/sorting.test.js
@@ -1,4 +1,4 @@
-import { describe, it, expect, csrf } from './runner.js';
+import { describe, it, expect, csrf } from './runner.js?v=2';
import { state, sortedCards } from '../../js/modules/state.js';
describe('Sorting: All Modes', () => {
diff --git a/public/plugins/tests/state-functions.test.js b/public/plugins/tests/state-functions.test.js
index e4f9435..840f50e 100644
--- a/public/plugins/tests/state-functions.test.js
+++ b/public/plugins/tests/state-functions.test.js
@@ -1,4 +1,4 @@
-import { describe, it, expect } from './runner.js';
+import { describe, it, expect } from './runner.js?v=2';
import { state, parseTags, sortedCards, getChildren, getGroupCards, getRecent, markRecent, getFavorites, toggleFavorite, isFavorite } from '../../js/modules/state.js';
describe('State: parseTags', () => {
diff --git a/public/plugins/tests/theme.test.js b/public/plugins/tests/theme.test.js
index 9fcdc86..504ba28 100644
--- a/public/plugins/tests/theme.test.js
+++ b/public/plugins/tests/theme.test.js
@@ -1,4 +1,4 @@
-import { describe, it, afterEach, expect, $, $$, click, closeAll, sleep } from './runner.js';
+import { describe, it, afterEach, expect, $, $$, click, closeAll, sleep } from './runner.js?v=2';
describe('Theme', () => {
it('CSS custom properties set', () => {
diff --git a/public/plugins/tests/toolbar.test.js b/public/plugins/tests/toolbar.test.js
index 1ff7c44..4c36dbf 100644
--- a/public/plugins/tests/toolbar.test.js
+++ b/public/plugins/tests/toolbar.test.js
@@ -1,4 +1,4 @@
-import { describe, it, afterEach, expect, $, click, sleep, closeAll } from './runner.js';
+import { describe, it, afterEach, expect, $, click, sleep, closeAll } from './runner.js?v=2';
import { state } from '../../js/modules/state.js';
describe('Toolbar: Filter Toggles', () => {
diff --git a/public/plugins/tests/undo.test.js b/public/plugins/tests/undo.test.js
index 80c081d..da4dc60 100644
--- a/public/plugins/tests/undo.test.js
+++ b/public/plugins/tests/undo.test.js
@@ -1,4 +1,4 @@
-import { describe, it, afterEach, expect, $, $$, click, type, rightClick, pressKey, sleep, closeAll, csrf } from './runner.js';
+import { describe, it, afterEach, expect, $, $$, click, type, rightClick, pressKey, sleep, closeAll, csrf } from './runner.js?v=2';
function ctrlZ() {
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'z', ctrlKey: true, bubbles: true }));
|