'use strict'; /** * Unit tests for status-formatter.js */ const { describe, it } = require('node:test'); const assert = require('node:assert/strict'); const { format, formatElapsed, formatTokens, statusIcon, truncateLine, extractAgentId, } = require('../../src/status-formatter'); const NOW = Date.now(); function makeState(overrides = {}) { return { sessionKey: 'agent:main:mattermost:channel:abc:thread:xyz', status: 'active', startTime: NOW - 38000, // 38s ago lines: [], children: [], agentId: 'main', depth: 0, tokenCount: 0, ...overrides, }; } describe('status-formatter.js', () => { describe('format()', () => { it('formats active session with header', () => { const state = makeState(); const result = format(state); assert.ok(result.includes('[ACTIVE]')); assert.ok(result.includes('main')); assert.ok(result.match(/\d+s/)); }); it('formats done session with footer', () => { const state = makeState({ status: 'done' }); const result = format(state); assert.ok(result.includes('[DONE]')); }); it('formats error session', () => { const state = makeState({ status: 'error' }); const result = format(state); assert.ok(result.includes('[ERROR]')); }); it('formats interrupted session', () => { const state = makeState({ status: 'interrupted' }); const result = format(state); assert.ok(result.includes('[INTERRUPTED]')); }); it('includes status lines', () => { const state = makeState({ lines: ['Reading files...', ' exec: ls [OK]', 'Writing results...'], }); const result = format(state); assert.ok(result.includes('Reading files...')); assert.ok(result.includes('exec: ls [OK]')); assert.ok(result.includes('Writing results...')); }); it('limits status lines to maxLines', () => { const lines = Array.from({ length: 30 }, (_, i) => `Line ${i + 1}`); const state = makeState({ lines }); const result = format(state, { maxLines: 5 }); // Only last 5 lines should appear assert.ok(result.includes('Line 26')); assert.ok(result.includes('Line 30')); assert.ok(!result.includes('Line 1')); }); it('includes token count in done footer', () => { const state = makeState({ status: 'done', tokenCount: 12400 }); const result = format(state); assert.ok(result.includes('12.4k')); }); it('no token count in footer when zero', () => { const state = makeState({ status: 'done', tokenCount: 0 }); const result = format(state); // Should not include "tokens" for zero count assert.ok(!result.includes('tokens')); }); it('renders nested child sessions', () => { const child = makeState({ sessionKey: 'agent:main:subagent:uuid-1', agentId: 'proj035-planner', depth: 1, status: 'done', lines: ['Reading protocol...'], }); const parent = makeState({ lines: ['Starting plan...'], children: [child], }); const result = format(parent); assert.ok(result.includes('proj035-planner')); assert.ok(result.includes('Reading protocol...')); // Child should be indented — top-level uses blockquote prefix ("> ") so child // lines appear as "> ..." ("> " + depth*2 spaces). Verify indentation exists. const childLine = result.split('\n').find((l) => l.includes('proj035-planner')); assert.ok(childLine && /^> {2}/.test(childLine)); }); it('active session has no done footer', () => { const state = makeState({ status: 'active' }); const result = format(state); const lines = result.split('\n'); // No line should contain [DONE], [ERROR], [INTERRUPTED] assert.ok(!lines.some((l) => /\[(DONE|ERROR|INTERRUPTED)\]/.test(l))); }); }); describe('formatElapsed()', () => { it('formats seconds', () => { assert.equal(formatElapsed(0), '0s'); assert.equal(formatElapsed(1000), '1s'); assert.equal(formatElapsed(59000), '59s'); }); it('formats minutes', () => { assert.equal(formatElapsed(60000), '1m0s'); assert.equal(formatElapsed(90000), '1m30s'); assert.equal(formatElapsed(3599000), '59m59s'); }); it('formats hours', () => { assert.equal(formatElapsed(3600000), '1h0m'); assert.equal(formatElapsed(7260000), '2h1m'); }); it('handles negative values', () => { assert.equal(formatElapsed(-1000), '0s'); }); }); describe('formatTokens()', () => { it('formats small counts', () => { assert.equal(formatTokens(0), '0'); assert.equal(formatTokens(999), '999'); }); it('formats thousands', () => { assert.equal(formatTokens(1000), '1.0k'); assert.equal(formatTokens(12400), '12.4k'); assert.equal(formatTokens(999900), '999.9k'); }); it('formats millions', () => { assert.equal(formatTokens(1000000), '1.0M'); assert.equal(formatTokens(2500000), '2.5M'); }); }); describe('statusIcon()', () => { it('returns correct icons', () => { assert.equal(statusIcon('active'), '[ACTIVE]'); assert.equal(statusIcon('done'), '[DONE]'); assert.equal(statusIcon('error'), '[ERROR]'); assert.equal(statusIcon('interrupted'), '[INTERRUPTED]'); assert.equal(statusIcon('unknown'), '[UNKNOWN]'); assert.equal(statusIcon(''), '[UNKNOWN]'); }); }); describe('truncateLine()', () => { it('does not truncate short lines', () => { const line = 'Short line'; assert.equal(truncateLine(line), line); }); it('truncates long lines', () => { const line = 'x'.repeat(200); const result = truncateLine(line); assert.ok(result.length <= 120); assert.ok(result.endsWith('...')); }); }); describe('extractAgentId()', () => { it('extracts agent ID from session key', () => { assert.equal(extractAgentId('agent:main:mattermost:channel:abc'), 'main'); assert.equal(extractAgentId('agent:coder-agent:session:123'), 'coder-agent'); }); it('handles non-standard keys', () => { assert.equal(extractAgentId('main'), 'main'); assert.equal(extractAgentId(''), 'unknown'); }); it('handles null/undefined', () => { assert.equal(extractAgentId(null), 'unknown'); assert.equal(extractAgentId(undefined), 'unknown'); }); }); });