// task-panel-summary.test.mjs — verifies task summary renders exactly once in panel. // // Run with: node --test web/test/task-panel-summary.test.mjs import { describe, it, beforeEach } from 'node:test'; import assert from 'node:assert/strict'; // ── Minimal DOM mock ────────────────────────────────────────────────────────── function makeMockDOM() { const elements = {}; function createElement(tag) { const el = { tag, className: '', textContent: '', innerHTML: '', hidden: false, children: [], dataset: {}, _listeners: {}, appendChild(child) { this.children.push(child); return child; }, prepend(...nodes) { this.children.unshift(...nodes); }, append(...nodes) { nodes.forEach(n => this.children.push(n)); }, querySelector(sel) { const cls = sel.startsWith('.') ? sel.slice(1) : null; function search(el) { if (cls && el.className && el.className.split(' ').includes(cls)) return el; for (const c of el.children || []) { const found = search(c); if (found) return found; } return null; } return search(this); }, querySelectorAll(sel) { const cls = sel.startsWith('.') ? sel.slice(1) : null; const results = []; function search(el) { if (cls && el.className && el.className.split(' ').includes(cls)) results.push(el); for (const c of el.children || []) search(c); } search(this); return results; }, addEventListener(ev, fn) {}, }; return el; } // Named panel elements referenced by getElementById const panelTitle = createElement('h2'); const panelContent = createElement('div'); elements['task-panel-title'] = panelTitle; elements['task-panel-content'] = panelContent; const doc = { createElement, getElementById(id) { return elements[id] || null; }, }; return { doc, panelContent }; } // ── Import renderTaskPanel ──────────────────────────────────────────────────── import { renderTaskPanel } from '../app.js'; // ── Tests ───────────────────────────────────────────────────────────────────── describe('renderTaskPanel summary rendering', () => { it('renders task summary exactly once for a COMPLETED task', () => { const { doc, panelContent } = makeMockDOM(); // Must set global document before calling renderTaskPanel global.document = doc; const task = { id: 'task-1', name: 'Fix the bug', state: 'COMPLETED', summary: 'Resolved the nil pointer in the payment handler.', priority: 'normal', created_at: '2026-03-17T10:00:00Z', updated_at: '2026-03-17T10:05:00Z', tags: [], }; renderTaskPanel(task, []); // Count all elements with class 'task-summary' or 'task-summary-text' const summaryEls = panelContent.querySelectorAll('.task-summary'); const summaryTextEls = panelContent.querySelectorAll('.task-summary-text'); const total = summaryEls.length + summaryTextEls.length; assert.equal(total, 1, `Expected exactly 1 summary element, got ${total} (task-summary: ${summaryEls.length}, task-summary-text: ${summaryTextEls.length})`); }); it('uses task-summary class (not task-summary-text) for good contrast', () => { const { doc, panelContent } = makeMockDOM(); global.document = doc; const task = { id: 'task-2', name: 'Another task', state: 'COMPLETED', summary: 'All done.', priority: 'high', created_at: '2026-03-17T10:00:00Z', updated_at: '2026-03-17T10:05:00Z', tags: [], }; renderTaskPanel(task, []); const summaryEls = panelContent.querySelectorAll('.task-summary'); assert.equal(summaryEls.length, 1, 'Expected .task-summary element'); assert.equal(summaryEls[0].textContent, task.summary); }); it('renders no summary section when task has no summary', () => { const { doc, panelContent } = makeMockDOM(); global.document = doc; const task = { id: 'task-3', name: 'Pending task', state: 'PENDING', summary: null, priority: 'normal', created_at: '2026-03-17T10:00:00Z', updated_at: '2026-03-17T10:00:00Z', tags: [], }; renderTaskPanel(task, []); const summaryEls = panelContent.querySelectorAll('.task-summary'); const summaryTextEls = panelContent.querySelectorAll('.task-summary-text'); assert.equal(summaryEls.length + summaryTextEls.length, 0, 'Expected no summary when task.summary is null'); }); });