test fixes: dialog selector, closeAll scope, copy filter, runner ?v=3

* card-interactions.test.js: group delete tests used `.dialog-ok`
  (class selector) but the button has `id="dialog-ok"`. querySelector
  returned null, the click was skipped, the dialog stayed open and
  blocked every downstream test. Fixed to getElementById.
* runner.js closeAll(): added #dialog, #bucket-super-popover (remove),
  and #gitea-overlay to the cleanup list. A failed test that leaves any
  of these open no longer poisons the rest of the suite.
* runner.js copy handler: when there are failures or flaky tests, the
  "copy" button now builds the clipboard text from lastResults —
  only failed + flaky lines (suite name, test name, timing, error,
  original error for retried). When everything passed, copies the
  full output as before. lastResults is set in renderResults().
* bucket-context.test.js: move-left test used `[data-bucket]` to find
  user bucket tabs, which misses super-bucket tiles (they carry
  `data-bucket-super`). Changed to `[data-bucket-drag]` which is on
  both regular and super-bucket tiles but not on fixed filters.
* context-menu.test.js: make-ticket pre-fill assertion now polls up
  to 2s for the title input to be populated (async openCreateForm
  repo-fetch was racing against the immediate assertion).
* card-join.test.js: clickJoin sleep bumped from 400ms → 800ms.
  joinSelectedCards does 3+ serial API calls (fetch bodies, PATCH
  winner, N×DELETE losers, _reloadAll) which sometimes exceeded the
  old 400ms window on slow runs.
* Runner cache: RUNNER_V bumped from ?v=2 → ?v=3 in loader.js + all
  42 test files so the browser picks up the new runner.js content
  (copy handler, closeAll, retry progress bar) without a hard reload.

super-buckets: remember last selection per group

* buckets.js: module-scope lastSuperSelection Map tracks the last
  active member per super-bucket parent. Updated in renderBar()
  whenever activeBucket matches a group member. tileFor() uses it
  as a fallback when the user is on a different bucket — the tile
  shows the last-picked child's name and color instead of reverting
  to the parent. The dropdown popover also highlights the last
  selection when no member is currently active.

chore: bump version to 0.9.11

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
thandClaude Opus 4.6 committed 2026-04-12 10:05:38 +02:00
1 parent 56c90225cb
commit c8835825e7
47 files changed
+126 -56

No files matched your search

+33 -3
View File
@@ -171,7 +171,7 @@ export function csrf() {
}
export async function closeAll() {
for (const id of ['settings-overlay', 'help', 'search', 'calendar', 'gallery', 'detail', 'modal', 'tasks']) {
for (const id of ['settings-overlay', 'help', 'search', 'calendar', 'gallery', 'detail', 'modal', 'tasks', 'dialog']) {
const el = document.getElementById(id);
if (el?.classList.contains('open')) el.classList.remove('open');
}
@@ -179,6 +179,8 @@ export async function closeAll() {
document.querySelector('#select-bar')?.classList.add('hidden');
document.querySelector('.bucket-picker')?.remove();
document.querySelector('.orphan-lightbox')?.remove();
document.getElementById('bucket-super-popover')?.remove();
document.getElementById('gitea-overlay')?.classList.remove('open');
// Clear multi-select
document.querySelectorAll('.card-selected').forEach(el => {
el.classList.remove('card-selected');
@@ -289,6 +291,7 @@ async function runAll(only = null) {
// ── UI Panel ──
let panel = null;
let lastResults = null;
function createPanel() {
if (panel) { panel.remove(); panel = null; }
@@ -309,9 +312,35 @@ function createPanel() {
document.body.appendChild(panel);
panel.querySelector('#test-close-btn').addEventListener('click', close);
panel.querySelector('#test-run-btn').addEventListener('click', run);
// Copy: when failures exist, only copy the failed + flaky tests so
// the user can paste the relevant part without wading through 200+
// lines of green. When everything passed, copy the full output.
panel.querySelector('#test-copy-btn').addEventListener('click', () => {
const output = panel.querySelector('#test-output');
const text = output.innerText;
let text;
if (lastResults) {
const hasFailures = lastResults.some(s => s.tests.some(t => t.status === 'fail' || t.retried));
if (hasFailures) {
const lines = [];
for (const s of lastResults) {
const relevant = s.tests.filter(t => t.status === 'fail' || t.retried);
if (!relevant.length) continue;
lines.push(s.name);
for (const t of relevant) {
const icon = t.status === 'fail' ? '✗' : '⚠';
const note = t.retried ? (t.status === 'pass' ? ' (flaky — passed on retry)' : ' (failed twice)') : '';
lines.push(`${icon} ${t.name} ${t.time}ms${note}`);
if (t.error) lines.push(` ${t.error}`);
if (t.originalError) lines.push(` first run: ${t.originalError}`);
}
lines.push('');
}
text = lines.join('\n').trim();
} else {
text = panel.querySelector('#test-output').innerText;
}
} else {
text = panel.querySelector('#test-output').innerText;
}
navigator.clipboard.writeText(text).then(() => {
const btn = panel.querySelector('#test-copy-btn');
btn.textContent = 'copied';
@@ -322,6 +351,7 @@ function createPanel() {
}
function renderResults(results) {
lastResults = results;
const output = panel.querySelector('#test-output');
const summary = panel.querySelector('#test-summary');
let total = 0, passed = 0, failed = 0, flaky = 0, totalTime = 0;