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… + +