The previous reservation set the left box width but everything still
shared one flex row, so the lower-priority tail + cwd could still shrink
`ready`/model down to fragments ("re"). Pin the essentials (indicator +
model + context) in a non-shrinking group, and render the tail segments
(bar, duration, compressions, voice, session count, bg, cost) only when
the whole segment fits in the leftover space — in priority order — so
nothing truncates mid-segment and the low-value tail drops first.
Also shrink the cwd/branch label (max 40 → 28) so it stops dominating the
bar on roomy-but-not-huge terminals.
114 lines
3.1 KiB
TypeScript
114 lines
3.1 KiB
TypeScript
import React from 'react'
|
|
import { describe, expect, it, vi } from 'vitest'
|
|
|
|
import { StatusRule } from '../components/appChrome.js'
|
|
import { DEFAULT_THEME } from '../theme.js'
|
|
|
|
type ReactNodeLike = React.ReactNode
|
|
|
|
const textContent = (node: ReactNodeLike): string => {
|
|
if (node === null || node === undefined || typeof node === 'boolean') {
|
|
return ''
|
|
}
|
|
|
|
if (typeof node === 'string' || typeof node === 'number') {
|
|
return String(node)
|
|
}
|
|
|
|
if (Array.isArray(node)) {
|
|
return node.map(textContent).join('')
|
|
}
|
|
|
|
if (React.isValidElement(node)) {
|
|
return textContent(node.props.children)
|
|
}
|
|
|
|
return ''
|
|
}
|
|
|
|
const findClickableWithText = (node: ReactNodeLike, needle: string): React.ReactElement | null => {
|
|
if (node === null || node === undefined || typeof node === 'boolean') {
|
|
return null
|
|
}
|
|
|
|
if (Array.isArray(node)) {
|
|
for (const child of node) {
|
|
const found = findClickableWithText(child, needle)
|
|
|
|
if (found) {
|
|
return found
|
|
}
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
if (!React.isValidElement(node)) {
|
|
return null
|
|
}
|
|
|
|
if (typeof node.props.onClick === 'function' && textContent(node).includes(needle)) {
|
|
return node
|
|
}
|
|
|
|
return findClickableWithText(node.props.children, needle)
|
|
}
|
|
|
|
describe('StatusRule session count click target', () => {
|
|
it('makes the live session count itself clickable', () => {
|
|
const openSwitcher = vi.fn()
|
|
const element = StatusRule({
|
|
bgCount: 0,
|
|
busy: false,
|
|
cols: 100,
|
|
cwdLabel: '~/repo',
|
|
liveSessionCount: 1,
|
|
model: 'kimi-k2.6',
|
|
onSessionCountClick: openSwitcher,
|
|
sessionStartedAt: null,
|
|
showCost: false,
|
|
status: 'ready',
|
|
statusColor: DEFAULT_THEME.color.ok,
|
|
t: DEFAULT_THEME,
|
|
turnStartedAt: null,
|
|
usage: { total: 0 },
|
|
voiceLabel: ''
|
|
})
|
|
|
|
const clickableSessionCount = findClickableWithText(element, '1 session')
|
|
|
|
expect(clickableSessionCount).not.toBeNull()
|
|
clickableSessionCount!.props.onClick({ stopImmediatePropagation: vi.fn() })
|
|
expect(openSwitcher).toHaveBeenCalledOnce()
|
|
})
|
|
|
|
it('keeps status + model and drops the low-value tail on a narrow terminal', () => {
|
|
const element = StatusRule({
|
|
bgCount: 0,
|
|
busy: false,
|
|
cols: 44,
|
|
cwdLabel: '~/src/hermes-agent/apps/desktop (bb/tui-statusbar-responsive)',
|
|
liveSessionCount: 3,
|
|
model: 'opus-4.8',
|
|
onSessionCountClick: vi.fn(),
|
|
sessionStartedAt: Date.now() - 60_000,
|
|
showCost: true,
|
|
status: 'ready',
|
|
statusColor: DEFAULT_THEME.color.ok,
|
|
t: DEFAULT_THEME,
|
|
turnStartedAt: null,
|
|
usage: { context_max: 200_000, context_percent: 25, context_used: 50_000, cost_usd: 0.5, total: 50_000 },
|
|
voiceLabel: 'voice off'
|
|
})
|
|
|
|
const rendered = textContent(element)
|
|
|
|
// Must-keep essentials survive intact …
|
|
expect(rendered).toContain('ready')
|
|
expect(rendered).toContain('opus 4.8')
|
|
// … while the low-value tail (session count, cost) is dropped, not truncated.
|
|
expect(rendered).not.toContain('3 sessions')
|
|
expect(rendered).not.toContain('$0.5000')
|
|
})
|
|
})
|