hermes-agent/ui-tui/src/__tests__/constants.test.ts
Brooklyn Nicholson 68ecdb6e26 refactor(tui): store-driven turn state + slash registry + module split
Hoist turn state from a 286-line hook into $turnState atom + turnController
singleton. createGatewayEventHandler becomes a typed dispatch over the
controller; its ctx shrinks from 30 fields to 5. Event-handler refs and 16
threaded actions are gone.

Fold three createSlash*Handler factories into a data-driven SlashCommand[]
registry under slash/commands/{core,session,ops}.ts. Aliases are data;
findSlashCommand does name+alias lookup. Shared guarded/guardedErr combinator
in slash/guarded.ts.

Split constants.ts + app/helpers.ts into config/ (timing/limits/env),
content/ (faces/placeholders/hotkeys/verbs/charms/fortunes), domain/ (roles/
details/messages/paths/slash/viewport/usage), protocol/ (interpolation/paste).

Type every RPC response in gatewayTypes.ts (26 new interfaces); drop all
`(r: any)` across slash + main app.

Shrink useMainApp from 1216 -> 646 lines by extracting useSessionLifecycle,
useSubmission, useConfigSync. Add <Fg> themed primitive and strip ~50
`as any` color casts.

Tests: 50 passing. Build + type-check clean.
2026-04-16 12:34:45 -05:00

47 lines
1.5 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { FACES } from '../content/faces.js'
import { HOTKEYS } from '../content/hotkeys.js'
import { PLACEHOLDERS } from '../content/placeholders.js'
import { TOOL_VERBS, VERBS } from '../content/verbs.js'
import { ROLE } from '../domain/roles.js'
import { ZERO } from '../domain/usage.js'
import { INTERPOLATION_RE } from '../protocol/interpolation.js'
import { DEFAULT_THEME } from '../theme.js'
describe('constants', () => {
it('ZERO', () => expect(ZERO).toEqual({ calls: 0, input: 0, output: 0, total: 0 }))
it('string arrays are populated', () => {
for (const arr of [FACES, PLACEHOLDERS, VERBS]) {
expect(arr.length).toBeGreaterThan(0)
arr.forEach(s => expect(typeof s).toBe('string'))
}
})
it('HOTKEYS are [key, desc] pairs', () => {
HOTKEYS.forEach(([k, d]) => {
expect(typeof k).toBe('string')
expect(typeof d).toBe('string')
})
})
it('TOOL_VERBS maps known tools (verb-only, no emoji)', () => {
expect(TOOL_VERBS.terminal).toBe('terminal')
expect(TOOL_VERBS.read_file).toBe('reading')
})
it('INTERPOLATION_RE matches {!cmd}', () => {
INTERPOLATION_RE.lastIndex = 0
expect(INTERPOLATION_RE.test('{!date}')).toBe(true)
INTERPOLATION_RE.lastIndex = 0
expect(INTERPOLATION_RE.test('plain')).toBe(false)
})
it('ROLE produces glyph/body/prefix per role', () => {
for (const role of ['assistant', 'system', 'tool', 'user'] as const) {
expect(ROLE[role](DEFAULT_THEME)).toHaveProperty('glyph')
}
})
})