Files
jarrodwatts--claude-hud/tests/usage-coverage.test.js
T
wehub-resource-sync 7088f49b01
Build dist / build (push) Has been cancelled
CI / test (18.x) (push) Has been cancelled
CI / test (20.x) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:39:33 +08:00

292 lines
11 KiB
JavaScript

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { renderUsageLine } from '../dist/render/lines/usage.js';
function stripAnsi(str) {
return str
.replace(/\x1b\[[0-9;]*m/g, '')
.replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, '');
}
function baseContext() {
return {
stdin: {
model: { display_name: 'Opus' },
context_window: {
context_window_size: 200000,
current_usage: {
input_tokens: 10000,
cache_creation_input_tokens: 0,
cache_read_input_tokens: 0,
},
},
},
transcript: { tools: [], skills: [], mcpServers: [], agents: [], todos: [], sessionTokens: undefined },
claudeMdCount: 0,
rulesCount: 0,
mcpCount: 0,
hooksCount: 0,
sessionDuration: '',
gitStatus: null,
usageData: {
fiveHour: 25,
sevenDay: null,
fiveHourResetAt: new Date(Date.now() + 3 * 60 * 60 * 1000),
sevenDayResetAt: null,
balanceLabel: null,
},
memoryUsage: null,
config: {
lineLayout: 'compact',
showSeparators: false,
pathLevels: 1,
elementOrder: ['project', 'context', 'usage'],
gitStatus: { enabled: true, showDirty: true, showAheadBehind: false, showFileStats: false, branchOverflow: 'truncate', pushWarningThreshold: 0, pushCriticalThreshold: 0 },
display: { showModel: true, showProject: true, showContextBar: true, contextValue: 'percent', showConfigCounts: true, showCost: false, showDuration: true, showSpeed: false, showTokenBreakdown: true, showUsage: true, usageValue: 'percent', usageBarEnabled: false, showResetLabel: true, showTools: true, showSkills: false, showMcp: false, showAgents: true, showTodos: true, showSessionTokens: false, showSessionName: false, showClaudeCodeVersion: false, showMemoryUsage: false, showPromptCache: false, promptCacheTtlSeconds: 300, showOutputStyle: false, mergeGroups: [['context', 'usage']], autocompactBuffer: 'enabled', usageThreshold: 0, sevenDayThreshold: 80, environmentThreshold: 0, customLine: '' },
colors: {
context: 'green',
usage: 'brightBlue',
warning: 'yellow',
usageWarning: 'brightMagenta',
critical: 'red',
model: 'cyan',
project: 'yellow',
git: 'magenta',
gitBranch: 'cyan',
label: 'dim',
custom: 208,
},
},
};
}
test('renderUsageLine returns null when showUsage is false', () => {
const ctx = baseContext();
ctx.config.display.showUsage = false;
assert.equal(renderUsageLine(ctx), null);
});
test('renderUsageLine returns null when usageData is null', () => {
const ctx = baseContext();
ctx.usageData = null;
assert.equal(renderUsageLine(ctx), null);
});
test('renderUsageLine returns null when usage below threshold', () => {
const ctx = baseContext();
ctx.config.display.usageThreshold = 50;
ctx.usageData.fiveHour = 25;
assert.equal(renderUsageLine(ctx), null);
});
test('renderUsageLine shows balance label when below threshold', () => {
const ctx = baseContext();
ctx.config.display.usageThreshold = 50;
ctx.usageData.fiveHour = 25;
ctx.usageData.balanceLabel = '¥6.35';
const line = stripAnsi(renderUsageLine(ctx) ?? '');
assert.ok(line.includes('¥6.35'));
});
test('renderUsageLine shows limit reached warning', () => {
const ctx = baseContext();
ctx.usageData.fiveHour = 100;
ctx.usageData.fiveHourResetAt = new Date(Date.now() + 60 * 60 * 1000);
const line = stripAnsi(renderUsageLine(ctx) ?? '');
assert.ok(line.includes('Limit reached'));
});
test('renderUsageLine shows limit reached in compact mode', () => {
const ctx = baseContext();
ctx.config.display.usageCompact = true;
ctx.usageData.fiveHour = 100;
ctx.usageData.fiveHourResetAt = new Date(Date.now() + 60 * 60 * 1000);
const line = stripAnsi(renderUsageLine(ctx) ?? '');
assert.ok(line.includes('Limit'));
});
test('renderUsageLine shows limit reached with balance label', () => {
const ctx = baseContext();
ctx.usageData.fiveHour = 100;
ctx.usageData.fiveHourResetAt = new Date(Date.now() + 60 * 60 * 1000);
ctx.usageData.balanceLabel = '$10.00';
const line = stripAnsi(renderUsageLine(ctx) ?? '');
assert.ok(line.includes('Limit reached'));
assert.ok(line.includes('$10.00'));
});
test('renderUsageLine compact mode with fiveHour only', () => {
const ctx = baseContext();
ctx.config.display.usageCompact = true;
ctx.usageData.fiveHour = 50;
ctx.usageData.fiveHourResetAt = new Date(Date.now() + 2 * 60 * 60 * 1000);
const line = stripAnsi(renderUsageLine(ctx) ?? '');
assert.ok(line.includes('5h:'));
assert.ok(line.includes('50%'));
});
test('renderUsageLine compact mode with both windows', () => {
const ctx = baseContext();
ctx.config.display.usageCompact = true;
ctx.usageData.fiveHour = 60;
ctx.usageData.sevenDay = 85;
ctx.usageData.fiveHourResetAt = new Date(Date.now() + 2 * 60 * 60 * 1000);
ctx.usageData.sevenDayResetAt = new Date(Date.now() + 3 * 24 * 60 * 60 * 1000);
const line = stripAnsi(renderUsageLine(ctx) ?? '');
assert.ok(line.includes('5h:'));
assert.ok(line.includes('7d:'));
});
test('renderUsageLine compact mode returns null when no window data qualifies', () => {
const ctx = baseContext();
ctx.config.display.usageCompact = true;
ctx.usageData.fiveHour = null;
ctx.usageData.sevenDay = null;
ctx.usageData.fiveHourResetAt = null;
ctx.usageData.sevenDayResetAt = null;
const result = renderUsageLine(ctx);
assert.equal(result, null);
});
test('renderUsageLine shows seven-day only when fiveHour is null', () => {
const ctx = baseContext();
ctx.usageData.fiveHour = null;
ctx.usageData.sevenDay = 45;
ctx.usageData.sevenDayResetAt = new Date(Date.now() + 5 * 24 * 60 * 60 * 1000);
const line = stripAnsi(renderUsageLine(ctx) ?? '');
assert.ok(line.includes('Weekly'));
});
test('renderUsageLine shows both windows when sevenDay exceeds threshold', () => {
const ctx = baseContext();
ctx.usageData.fiveHour = 40;
ctx.usageData.sevenDay = 85;
ctx.usageData.fiveHourResetAt = new Date(Date.now() + 3 * 60 * 60 * 1000);
ctx.usageData.sevenDayResetAt = new Date(Date.now() + 5 * 24 * 60 * 60 * 1000);
const line = stripAnsi(renderUsageLine(ctx) ?? '');
assert.ok(line.includes('40%'));
assert.ok(line.includes('Weekly'));
});
test('renderUsageLine with bar enabled', () => {
const ctx = baseContext();
ctx.config.display.usageBarEnabled = true;
ctx.usageData.fiveHour = 60;
const line = renderUsageLine(ctx);
// Bar includes ANSI codes; just check it renders something
assert.ok(line);
});
test('renderUsageLine absolute time format', () => {
const ctx = baseContext();
ctx.config.display.timeFormat = 'absolute';
ctx.usageData.fiveHour = 50;
ctx.usageData.fiveHourResetAt = new Date(Date.now() + 2 * 60 * 60 * 1000);
const line = stripAnsi(renderUsageLine(ctx) ?? '');
assert.ok(line.includes('resets at'));
});
test('renderUsageLine elapsed time format', () => {
const ctx = baseContext();
ctx.config.display.timeFormat = 'elapsed';
ctx.usageData.fiveHour = 50;
ctx.usageData.fiveHourResetAt = new Date(Date.now() + 2 * 60 * 60 * 1000);
const line = stripAnsi(renderUsageLine(ctx) ?? '');
assert.ok(line.includes('elapsed'));
});
test('renderUsageLine elapsedAndAbsolute time format', () => {
const ctx = baseContext();
ctx.config.display.timeFormat = 'elapsedAndAbsolute';
ctx.usageData.fiveHour = 50;
ctx.usageData.fiveHourResetAt = new Date(Date.now() + 2 * 60 * 60 * 1000);
const line = stripAnsi(renderUsageLine(ctx) ?? '');
assert.ok(line.includes('elapsed'));
});
test('renderUsageLine remaining usage value mode', () => {
const ctx = baseContext();
ctx.config.display.usageValue = 'remaining';
ctx.usageData.fiveHour = 40;
const line = stripAnsi(renderUsageLine(ctx) ?? '');
// remaining = 100 - 40 = 60%
assert.ok(line.includes('60%'));
});
test('renderUsageLine hides reset label when showResetLabel is false', () => {
const ctx = baseContext();
ctx.config.display.showResetLabel = false;
ctx.usageData.fiveHour = 50;
ctx.usageData.fiveHourResetAt = new Date(Date.now() + 2 * 60 * 60 * 1000);
const line = stripAnsi(renderUsageLine(ctx) ?? '');
assert.ok(!line.includes('Resets in'));
});
test('renderUsageLine with only balanceLabel and no window data', () => {
const ctx = baseContext();
ctx.usageData.fiveHour = null;
ctx.usageData.sevenDay = null;
ctx.usageData.fiveHourResetAt = null;
ctx.usageData.sevenDayResetAt = null;
ctx.usageData.balanceLabel = '$5.00';
const line = stripAnsi(renderUsageLine(ctx) ?? '');
assert.ok(line.includes('$5.00'));
});
test('renderUsageLine with null percent shows -- in compact mode', () => {
const ctx = baseContext();
ctx.config.display.usageCompact = true;
ctx.usageData.fiveHour = null;
ctx.usageData.sevenDay = 85;
ctx.usageData.sevenDayResetAt = new Date(Date.now() + 3 * 24 * 60 * 60 * 1000);
const line = stripAnsi(renderUsageLine(ctx) ?? '');
assert.ok(line.includes('7d:'));
});
test('renderUsageLine limit reached with sevenDay at 100', () => {
const ctx = baseContext();
ctx.usageData.fiveHour = 80;
ctx.usageData.sevenDay = 100;
ctx.usageData.sevenDayResetAt = new Date(Date.now() + 5 * 24 * 60 * 60 * 1000);
const line = stripAnsi(renderUsageLine(ctx) ?? '');
assert.ok(line.includes('Limit reached'));
});
test('renderUsageLine limit reached compact mode without reset time', () => {
const ctx = baseContext();
ctx.config.display.usageCompact = true;
ctx.usageData.fiveHour = 100;
ctx.usageData.fiveHourResetAt = null;
const line = stripAnsi(renderUsageLine(ctx) ?? '');
assert.ok(line.includes('Limit'));
});
test('renderUsageLine limit reached with balance in compact mode', () => {
const ctx = baseContext();
ctx.config.display.usageCompact = true;
ctx.usageData.fiveHour = 100;
ctx.usageData.fiveHourResetAt = new Date(Date.now() + 60 * 60 * 1000);
ctx.usageData.balanceLabel = '$2.50';
const line = stripAnsi(renderUsageLine(ctx) ?? '');
assert.ok(line.includes('$2.50'));
});
test('renderUsageLine elapsed format for limit reached uses relative', () => {
const ctx = baseContext();
ctx.config.display.timeFormat = 'elapsed';
ctx.usageData.fiveHour = 100;
ctx.usageData.fiveHourResetAt = new Date(Date.now() + 2 * 60 * 60 * 1000);
const line = stripAnsi(renderUsageLine(ctx) ?? '');
assert.ok(line.includes('Limit reached'));
});
test('renderUsageLine elapsedAndAbsolute format for limit uses absolute', () => {
const ctx = baseContext();
ctx.config.display.timeFormat = 'elapsedAndAbsolute';
ctx.usageData.fiveHour = 100;
ctx.usageData.fiveHourResetAt = new Date(Date.now() + 2 * 60 * 60 * 1000);
const line = stripAnsi(renderUsageLine(ctx) ?? '');
assert.ok(line.includes('Limit reached'));
assert.ok(line.includes('resets at'));
});