Files
hyprcrd/public/plugins/tests/detail.test.js
T
thandClaude Opus 4.6 c934673c4a detail: optional bucket-switch on open from search / link
New toggle under Settings → Behavior → Interactions: "Switch to
card's bucket on open (search / link)". When enabled, openDetail
calls activateBucket(card.bucket_id) so the board and bucket bar
update to show the card's context. Skipped when already on 'all'
or the correct bucket. Default off.

Uses activateBucket() (from buckets.js) instead of raw state
assignment so the bucket-bar highlight tracks the switch.

Tests: two cases — setting on switches activeBucket + verifies the
bucket-tab.active in the bar matches; setting off leaves the
bucket unchanged. Both create/delete probe cards in try/finally
and restore the setting + activeBucket in afterEach.

Bumps to 1.9.27.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 23:39:36 +02:00

152 lines
5.5 KiB
JavaScript

import { describe, it, afterEach, expect, $, click, pressKey, sleep, closeAll, csrf } from './runner.js?v=4';
describe('Detail View', () => {
afterEach(() => closeAll());
it('has structural elements', () => {
for (const id of ['detail', 'detail-title', 'detail-body', 'detail-meta', 'detail-tags']) {
expect($(`#${id}`)).toExist();
}
});
it('has control buttons', () => {
for (const id of ['detail-edit', 'detail-delete', 'detail-done', 'detail-zen', 'detail-close']) {
expect($(`#${id}`)).toExist();
}
});
it('opens on card click (non-split)', async () => {
if (!$('#board-preview')?.classList.contains('hidden')) {
click('#board-split-toggle');
await sleep(100);
}
const card = $('#board [data-id]');
if (!card) return;
click(card);
await sleep(300);
expect($('#detail')).toBeVisible();
});
it('shows card title', async () => {
if (!$('#board-preview')?.classList.contains('hidden')) {
click('#board-split-toggle');
await sleep(100);
}
const card = $('#board [data-id]');
if (!card) return;
click(card);
await sleep(300);
expect($('#detail-title').textContent.length).toBeGreaterThan(0);
});
it('closes on ESC', async () => {
if (!$('#board-preview')?.classList.contains('hidden')) {
click('#board-split-toggle');
await sleep(100);
}
const card = $('#board [data-id]');
if (!card) return;
click(card);
await sleep(300);
if (!$('#detail')?.classList.contains('open')) return;
pressKey('Escape');
for (let i = 0; i < 20; i++) {
if (!$('#detail')?.classList.contains('open')) break;
await sleep(50);
}
expect($('#detail')).toBeHidden();
});
it('closes on close button', async () => {
if (!$('#board-preview')?.classList.contains('hidden')) {
click('#board-split-toggle');
await sleep(100);
}
const card = $('#board [data-id]');
if (!card) return;
click(card);
await sleep(300);
click('#detail-close');
await sleep(150);
expect($('#detail')).toBeHidden();
});
});
describe('Detail: open-switches-bucket', () => {
const SETTING_KEY = 'hyprcrd-open-switches-bucket';
const BUCKET_KEY = 'hyprcrd-active-bucket';
afterEach(async () => {
await closeAll();
// Restore runner baseline.
window.state.activeBucket = 'all';
localStorage.setItem(BUCKET_KEY, 'all');
});
it('switches to the card bucket when the setting is on', async () => {
const buckets = await fetch('api/buckets').then(r => r.json());
if (!Array.isArray(buckets) || !buckets.length) return;
const b = buckets[0];
const token = csrf();
const h = { 'Content-Type': 'application/json', 'X-CSRF-Token': token };
let card;
const prev = localStorage.getItem(SETTING_KEY);
try {
card = await fetch('api/cards', { method: 'POST', headers: h,
body: JSON.stringify({ title: '__test_switch_bucket__', body: '', bucket_id: b.id })
}).then(r => r.json());
await window._reloadAll();
await sleep(100);
// Start on a DIFFERENT bucket so the switch is visible.
const other = buckets.length > 1 ? buckets[1] : buckets[0];
window.state.activeBucket = String(other.id);
// Enable the setting.
localStorage.setItem(SETTING_KEY, '1');
window._openDetail(card.id);
await sleep(150);
expect(String(window.state.activeBucket)).toBe(String(b.id));
// Bucket bar must highlight the switched-to bucket.
const activeTab = document.querySelector('#bucket-bar .bucket-tab.active[data-bucket]');
expect(activeTab).toExist();
expect(activeTab.dataset.bucket).toBe(String(b.id));
} finally {
localStorage.setItem(SETTING_KEY, prev ?? '0');
if (card?.id) await fetch(`api/cards/${card.id}`, { method: 'DELETE', headers: { 'X-CSRF-Token': token } });
await window._reloadAll();
}
});
it('does NOT switch when the setting is off', async () => {
const buckets = await fetch('api/buckets').then(r => r.json());
if (!Array.isArray(buckets) || buckets.length < 2) return;
const token = csrf();
const h = { 'Content-Type': 'application/json', 'X-CSRF-Token': token };
let card;
const prev = localStorage.getItem(SETTING_KEY);
try {
card = await fetch('api/cards', { method: 'POST', headers: h,
body: JSON.stringify({ title: '__test_no_switch__', body: '', bucket_id: buckets[0].id })
}).then(r => r.json());
await window._reloadAll();
await sleep(100);
window.state.activeBucket = String(buckets[1].id);
localStorage.setItem(SETTING_KEY, '0');
window._openDetail(card.id);
await sleep(100);
// Should stay on the other bucket.
expect(String(window.state.activeBucket)).toBe(String(buckets[1].id));
} finally {
localStorage.setItem(SETTING_KEY, prev ?? '0');
if (card?.id) await fetch(`api/cards/${card.id}`, { method: 'DELETE', headers: { 'X-CSRF-Token': token } });
await window._reloadAll();
}
});
});