Files
Codeman/test/tui/tui-digest.test.ts
T
Codeman maintainer 8b5fc974b0 fix: cover tui in the CLI inventory and drop the em-dashes it printed
The inventory test predates the `tui` command, so a rename or an
accidental removal would have gone unnoticed: it now asserts the command,
its `-l`/`--list` flag and its optional position operand.

The digest and search-result lines joined their halves with an em-dash,
which the repo's own convention rules out, so both now use the middle dot
the surrounding lines already use. The one em-dash left in `src/tui/` is
load-bearing: `search-service.ts` builds a session snippet with it, and
the pattern that strips the repeated label has to match it.

Also moves `buildSearchEntries`'s doc comment back onto
`buildSearchEntries`; it had ended up stacked above a helper.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-22 14:13:58 +02:00

128 lines
4.1 KiB
TypeScript

/**
* @fileoverview Unit tests for the away digest's compact rendering.
*
* The digest is read top-down and never studied, so the promises worth pinning
* are: the counts sit in the first line, every entry is exactly one line, and a
* long section is capped with a tail rather than pushing the next section off
* the overlay.
*/
import { describe, it, expect } from 'vitest';
import { formatAwayDigest } from '../../src/tui/tui-digest.js';
import type { AwayDigestItem, AwayDigestResponse } from '../../src/web/away-digest.js';
const NOW = 1_700_000_000_000;
function entry(overrides: Partial<AwayDigestItem> = {}): AwayDigestItem {
return {
id: 'e1',
timestamp: NOW - 120_000,
category: 'needs_attention',
severity: 'warning',
title: 'permission prompt',
source: 'lifecycle',
sessionName: 'w4-api',
...overrides,
};
}
function digest(overrides: Partial<AwayDigestResponse> = {}): AwayDigestResponse {
return {
range: { range: '24h', since: NOW - 86_400_000, until: NOW },
generatedAt: NOW,
dataFreshness: {
lifecyclePersisted: true,
tokenStatsPersisted: true,
runSummariesLiveOnly: true,
subagentsLiveOnly: true,
},
totals: {
sessionsCreated: 3,
sessionsExited: 1,
activeSessions: 2,
needsAttention: 1,
completed: 1,
errors: 0,
warnings: 1,
tokenWindowPrecision: 'day',
},
sections: { needsAttention: [], completed: [], stillRunning: [], idle: [], informational: [] },
...overrides,
};
}
describe('formatAwayDigest', () => {
it('opens with the range and the counts', () => {
const lines = formatAwayDigest(digest(), { now: NOW });
expect(lines[0]).toBe('the last 24 hours · 3 started · 1 exited · 2 running');
});
it('names the range the way the API labels it', () => {
const since = digest({ range: { range: 'since-last-visit', since: NOW - 1000, until: NOW } });
expect(formatAwayDigest(since, { now: NOW })[0]).toContain('since your last visit');
});
it('gives every entry one line, with its age and session', () => {
const lines = formatAwayDigest(
digest({
sections: {
needsAttention: [entry({ detail: 'Bash(git push)' })],
completed: [],
stillRunning: [],
idle: [],
informational: [],
},
}),
{ now: NOW }
);
expect(lines).toContain('NEEDS ATTENTION (1)');
expect(lines).toContain(' 2m w4-api permission prompt · Bash(git push)');
});
it('caps a long section instead of burying the next one', () => {
const many = Array.from({ length: 9 }, (_, i) => entry({ id: `e${i}`, title: `event ${i}` }));
const lines = formatAwayDigest(
digest({
sections: {
needsAttention: many,
completed: [entry({ id: 'c1', category: 'completed', title: 'finished' })],
stillRunning: [],
idle: [],
informational: [],
},
}),
{ now: NOW, sectionLimit: 3 }
);
expect(lines).toContain('NEEDS ATTENTION (9)');
expect(lines).toContain(' … 6 more');
expect(lines).toContain('COMPLETED (1)');
});
it('says so when nothing happened', () => {
expect(formatAwayDigest(digest(), { now: NOW })).toContain('nothing happened while you were away');
});
it('adds the token totals only when the range had any', () => {
expect(formatAwayDigest(digest(), { now: NOW }).join('\n')).not.toContain('tokens:');
const withTokens = digest({
totals: { ...digest().totals, inputTokens: 45_200, outputTokens: 12_100, estimatedCost: 1.234 },
});
expect(formatAwayDigest(withTokens, { now: NOW })).toContain('tokens: 45.2k in · 12.1k out · $1.23');
});
it('drops the age column for an entry with no usable timestamp', () => {
const lines = formatAwayDigest(
digest({
sections: {
needsAttention: [entry({ timestamp: 0, sessionName: undefined, sessionId: 'abcdef1234' })],
completed: [],
stillRunning: [],
idle: [],
informational: [],
},
}),
{ now: NOW }
);
expect(lines).toContain(' abcdef12 permission prompt');
});
});