mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
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>
128 lines
4.1 KiB
TypeScript
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');
|
|
});
|
|
});
|