// subtask-placeholder.test.mjs — placeholder text for empty subtask rollup // // Run with: node --test web/test/subtask-placeholder.test.mjs import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; // ── Logic under test ────────────────────────────────────────────────────────── // // When a task is BLOCKED/READY and the subtask list is empty, the rollup shows // meaningful content instead of a generic placeholder: // 1. task.description truncated to ~120 chars (word boundary) // 2. fallback to task.name if no description // 3. fallback to 'Waiting for subtasks…' if neither function truncateToWordBoundary(text, maxLen = 120) { if (!text || text.length <= maxLen) return text; const cut = text.lastIndexOf(' ', maxLen); return (cut > 0 ? text.slice(0, cut) : text.slice(0, maxLen)) + '…'; } function getSubtaskPlaceholder(task) { const blurb = task.description || task.name; return blurb ? truncateToWordBoundary(blurb) : 'Waiting for subtasks…'; } // ── Tests ───────────────────────────────────────────────────────────────────── describe('truncateToWordBoundary', () => { it('returns text unchanged when shorter than maxLen', () => { assert.equal(truncateToWordBoundary('hello world', 120), 'hello world'); }); it('returns text unchanged when exactly maxLen', () => { const text = 'a'.repeat(120); assert.equal(truncateToWordBoundary(text, 120), text); }); it('truncates at word boundary and appends ellipsis', () => { const text = 'Fix the login bug that causes users to be logged out unexpectedly when they navigate between pages in the application user interface'; const result = truncateToWordBoundary(text, 120); assert.ok(result.length <= 121, `result too long: ${result.length}`); assert.ok(result.endsWith('…'), 'should end with ellipsis'); assert.ok(result.startsWith('Fix the'), 'should keep beginning of text'); }); it('truncates at character boundary when no word boundary exists', () => { const text = 'a'.repeat(200); const result = truncateToWordBoundary(text, 120); assert.equal(result, 'a'.repeat(120) + '…'); }); it('returns empty string unchanged', () => { assert.equal(truncateToWordBoundary(''), ''); }); it('returns null unchanged', () => { assert.equal(truncateToWordBoundary(null), null); }); }); describe('getSubtaskPlaceholder', () => { it('uses task.description when available', () => { const task = { description: 'Fix auth bug', name: 'auth-fix' }; assert.equal(getSubtaskPlaceholder(task), 'Fix auth bug'); }); it('truncates task.description at 120 chars', () => { const longDesc = 'This is a very long description that exceeds the limit. ' + 'It goes on and on describing what the task is about in great detail. ' + 'Eventually it reaches the maximum allowed length.'; const task = { description: longDesc, name: 'long-task' }; const result = getSubtaskPlaceholder(task); assert.ok(result.endsWith('…'), 'should end with ellipsis'); assert.ok(result.length <= 122, `result too long: ${result.length}`); }); it('falls back to task.name when description is absent', () => { const task = { name: 'deploy-frontend' }; assert.equal(getSubtaskPlaceholder(task), 'deploy-frontend'); }); it('falls back to task.name when description is empty string', () => { const task = { description: '', name: 'deploy-frontend' }; assert.equal(getSubtaskPlaceholder(task), 'deploy-frontend'); }); it('falls back to generic text when both description and name are absent', () => { const task = {}; assert.equal(getSubtaskPlaceholder(task), 'Waiting for subtasks…'); }); });