mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-26 01:01:40 +00:00
feat: add tests and update mds
This commit is contained in:
parent
f226e6be10
commit
9d8f9765c1
11 changed files with 6013 additions and 4 deletions
43
ui-tui/src/__tests__/constants.test.ts
Normal file
43
ui-tui/src/__tests__/constants.test.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { FACES, HOTKEYS, INTERPOLATION_RE, PLACEHOLDERS, ROLE, TOOL_VERBS, VERBS, ZERO } from '../constants.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', () => {
|
||||
expect(TOOL_VERBS.terminal).toContain('terminal')
|
||||
expect(TOOL_VERBS.read_file).toContain('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')
|
||||
}
|
||||
})
|
||||
})
|
||||
25
ui-tui/src/__tests__/messages.test.ts
Normal file
25
ui-tui/src/__tests__/messages.test.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { upsert } from '../lib/messages.js'
|
||||
|
||||
|
||||
describe('upsert', () => {
|
||||
|
||||
it('appends when last role differs', () => {
|
||||
expect(upsert([{ role: 'user', text: 'hi' }], 'assistant', 'hello')).toHaveLength(2)
|
||||
})
|
||||
|
||||
it('replaces when last role matches', () => {
|
||||
expect(upsert([{ role: 'assistant', text: 'partial' }], 'assistant', 'full')[0]!.text).toBe('full')
|
||||
})
|
||||
|
||||
it('appends to empty', () => {
|
||||
expect(upsert([], 'user', 'first')).toEqual([{ role: 'user', text: 'first' }])
|
||||
})
|
||||
|
||||
it('does not mutate', () => {
|
||||
const prev = [{ role: 'user' as const, text: 'hi' }]
|
||||
upsert(prev, 'assistant', 'yo')
|
||||
expect(prev).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
112
ui-tui/src/__tests__/text.test.ts
Normal file
112
ui-tui/src/__tests__/text.test.ts
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import {
|
||||
compactPreview,
|
||||
estimateRows,
|
||||
fmtK,
|
||||
hasAnsi,
|
||||
hasInterpolation,
|
||||
pick,
|
||||
stripAnsi,
|
||||
userDisplay
|
||||
} from '../lib/text.js'
|
||||
|
||||
|
||||
describe('stripAnsi / hasAnsi', () => {
|
||||
|
||||
it('strips ANSI codes', () => {
|
||||
expect(stripAnsi('\x1b[31mred\x1b[0m')).toBe('red')
|
||||
})
|
||||
|
||||
it('passes plain text through', () => {
|
||||
expect(stripAnsi('hello')).toBe('hello')
|
||||
})
|
||||
|
||||
it('detects ANSI', () => {
|
||||
expect(hasAnsi('\x1b[1mbold\x1b[0m')).toBe(true)
|
||||
expect(hasAnsi('plain')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
describe('compactPreview', () => {
|
||||
|
||||
it('truncates with ellipsis', () => {
|
||||
expect(compactPreview('a'.repeat(100), 20)).toHaveLength(20)
|
||||
expect(compactPreview('a'.repeat(100), 20).at(-1)).toBe('…')
|
||||
})
|
||||
|
||||
it('returns short strings as-is', () => {
|
||||
expect(compactPreview('hello', 20)).toBe('hello')
|
||||
})
|
||||
|
||||
it('collapses whitespace', () => {
|
||||
expect(compactPreview(' a b ', 20)).toBe('a b')
|
||||
})
|
||||
|
||||
it('returns empty for whitespace-only', () => {
|
||||
expect(compactPreview(' ', 20)).toBe('')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
describe('estimateRows', () => {
|
||||
|
||||
it('single line', () => expect(estimateRows('hello', 80)).toBe(1))
|
||||
|
||||
it('wraps long lines', () => expect(estimateRows('a'.repeat(160), 80)).toBe(2))
|
||||
|
||||
it('counts newlines', () => expect(estimateRows('a\nb\nc', 80)).toBe(3))
|
||||
|
||||
it('skips table separators', () => {
|
||||
expect(estimateRows('| a | b |\n|---|---|\n| 1 | 2 |', 80)).toBe(2)
|
||||
})
|
||||
|
||||
it('handles code blocks', () => {
|
||||
expect(estimateRows('```python\nprint("hi")\n```', 80)).toBeGreaterThanOrEqual(2)
|
||||
})
|
||||
|
||||
it('compact mode skips empty lines', () => {
|
||||
expect(estimateRows('a\n\nb', 80, true)).toBe(2)
|
||||
expect(estimateRows('a\n\nb', 80, false)).toBe(3)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
describe('fmtK', () => {
|
||||
|
||||
it('formats thousands', () => expect(fmtK(1500)).toBe('1.5k'))
|
||||
|
||||
it('keeps small numbers', () => expect(fmtK(42)).toBe('42'))
|
||||
|
||||
it('boundary', () => {
|
||||
expect(fmtK(1000)).toBe('1.0k')
|
||||
expect(fmtK(999)).toBe('999')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
describe('hasInterpolation', () => {
|
||||
|
||||
it('detects {!cmd}', () => expect(hasInterpolation('echo {!date}')).toBe(true))
|
||||
|
||||
it('rejects plain text', () => expect(hasInterpolation('plain')).toBe(false))
|
||||
})
|
||||
|
||||
|
||||
describe('pick', () => {
|
||||
|
||||
it('returns element from array', () => {
|
||||
expect([1, 2, 3]).toContain(pick([1, 2, 3]))
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
describe('userDisplay', () => {
|
||||
|
||||
it('returns short messages as-is', () => expect(userDisplay('hello')).toBe('hello'))
|
||||
|
||||
it('truncates long messages', () => {
|
||||
expect(userDisplay('word '.repeat(100))).toContain('[long message]')
|
||||
})
|
||||
})
|
||||
52
ui-tui/src/__tests__/theme.test.ts
Normal file
52
ui-tui/src/__tests__/theme.test.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { DEFAULT_THEME, fromSkin } from '../theme.js'
|
||||
|
||||
|
||||
describe('DEFAULT_THEME', () => {
|
||||
|
||||
it('has brand defaults', () => {
|
||||
expect(DEFAULT_THEME.brand.name).toBe('Hermes Agent')
|
||||
expect(DEFAULT_THEME.brand.prompt).toBe('❯')
|
||||
expect(DEFAULT_THEME.brand.tool).toBe('┊')
|
||||
})
|
||||
|
||||
it('has color palette', () => {
|
||||
expect(DEFAULT_THEME.color.gold).toBe('#FFD700')
|
||||
expect(DEFAULT_THEME.color.error).toBe('#ef5350')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
describe('fromSkin', () => {
|
||||
|
||||
it('overrides banner colors', () => {
|
||||
expect(fromSkin({ banner_title: '#FF0000' }, {}).color.gold).toBe('#FF0000')
|
||||
})
|
||||
|
||||
it('preserves unset colors', () => {
|
||||
expect(fromSkin({ banner_title: '#FF0000' }, {}).color.amber).toBe(DEFAULT_THEME.color.amber)
|
||||
})
|
||||
|
||||
it('overrides branding', () => {
|
||||
const { brand } = fromSkin({}, { agent_name: 'TestBot', prompt_symbol: '$' })
|
||||
expect(brand.name).toBe('TestBot')
|
||||
expect(brand.prompt).toBe('$')
|
||||
})
|
||||
|
||||
it('defaults for empty skin', () => {
|
||||
expect(fromSkin({}, {}).color).toEqual(DEFAULT_THEME.color)
|
||||
expect(fromSkin({}, {}).brand.icon).toBe(DEFAULT_THEME.brand.icon)
|
||||
})
|
||||
|
||||
it('passes banner logo/hero', () => {
|
||||
expect(fromSkin({}, {}, 'LOGO', 'HERO').bannerLogo).toBe('LOGO')
|
||||
expect(fromSkin({}, {}, 'LOGO', 'HERO').bannerHero).toBe('HERO')
|
||||
})
|
||||
|
||||
it('maps ui_ color keys + cascades to status', () => {
|
||||
const { color } = fromSkin({ ui_ok: '#008000' }, {})
|
||||
expect(color.ok).toBe('#008000')
|
||||
expect(color.statusGood).toBe('#008000')
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue