Files
hapi/web/src/chat/presentation.test.ts
T

142 lines
4.3 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { getEventPresentation, formatResetTime } from './presentation'
describe('getEventPresentation — limit-warning', () => {
it('formats five_hour warning', () => {
const result = getEventPresentation({
type: 'limit-warning',
utilization: 0.9,
endsAt: 1774278000,
limitType: 'five_hour',
})
expect(result.icon).toBe('⚠️')
expect(result.text).toMatch(/Usage limit 90% \(5-hour\)/)
expect(result.text).toMatch(/resets/)
})
it('formats seven_day warning', () => {
const result = getEventPresentation({
type: 'limit-warning',
utilization: 0.85,
endsAt: 1774850400,
limitType: 'seven_day',
})
expect(result.text).toMatch(/Usage limit 85% \(7-day\)/)
})
it('omits type label when limitType is empty', () => {
const result = getEventPresentation({
type: 'limit-warning',
utilization: 1,
endsAt: 1774278000,
limitType: '',
})
expect(result.text).toMatch(/^Usage limit 100% · resets/)
expect(result.text).not.toMatch(/\(/)
})
it('formats unknown limitType with underscore replacement', () => {
const result = getEventPresentation({
type: 'limit-warning',
utilization: 0.5,
endsAt: 1774278000,
limitType: 'thirty_day',
})
expect(result.text).toMatch(/\(thirty day\)/)
})
})
describe('getEventPresentation — limit-reached', () => {
it('shows limitType when present', () => {
const result = getEventPresentation({
type: 'limit-reached',
endsAt: 1774278000,
limitType: 'five_hour',
})
expect(result.icon).toBe('⏳')
expect(result.text).toMatch(/^Usage limit reached \(5-hour\) until/)
})
it('omits limitType when empty', () => {
const result = getEventPresentation({
type: 'limit-reached',
endsAt: 1774278000,
limitType: '',
})
expect(result.icon).toBe('⏳')
expect(result.text).toMatch(/^Usage limit reached until/)
expect(result.text).not.toMatch(/\(/)
})
})
describe('getEventPresentation — token-count', () => {
it('formats Codex token-count as compact context usage', () => {
const result = getEventPresentation({
type: 'token-count',
info: {
total: {
totalTokens: 23745,
inputTokens: 23631,
cachedInputTokens: 18176,
outputTokens: 114,
reasoningOutputTokens: 0
},
modelContextWindow: 258400
}
})
expect(result.icon).toBe('◷')
expect(result.text).toBe('Context 23.6k / 258.4k (9%) · out 114 · cached 18.2k')
})
})
describe('getEventPresentation — thread goals', () => {
it('formats goal status updates', () => {
const result = getEventPresentation({
type: 'thread-goal-updated',
goal: {
threadId: 'thread-1',
objective: 'ship goal support',
status: 'budgetLimited',
tokenBudget: 5000,
tokensUsed: 4100,
timeUsedSeconds: 0,
createdAt: 1,
updatedAt: 2
}
})
expect(result.text).toBe('Goal limited by budget · 4k / 5k')
})
it('formats goal clear events', () => {
const result = getEventPresentation({ type: 'thread-goal-cleared', threadId: 'thread-1' })
expect(result.text).toBe('Goal cleared')
})
})
describe('formatResetTime', () => {
it('formats a unix timestamp to a non-empty string', () => {
const result = formatResetTime(1774278000)
expect(result).toBeTruthy()
expect(typeof result).toBe('string')
})
it('handles millisecond timestamps', () => {
const result = formatResetTime(1774278000000)
expect(result).toBeTruthy()
})
it('returns raw value for invalid timestamps', () => {
const result = formatResetTime(NaN)
expect(result).toBeTruthy()
})
})