hermes-agent/apps/desktop/src/lib/session-link-title.test.ts
hermes-seaeye[bot] 46815f4910
fmt(js): npm run fix on merge (#71532)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-25 19:11:39 +00:00

115 lines
4 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from 'vitest'
import { getSession } from '@/hermes'
import { $sessions } from '@/store/session'
import type { SessionInfo } from '@/types/hermes'
import { __resetSessionLinkTitleCache, fetchSessionLinkTitle, lookupLocalSessionTitle } from './session-link-title'
import { sessionRefCacheKey } from './session-refs'
vi.mock('@/hermes', () => ({
getSession: vi.fn()
}))
function makeSession(overrides: Partial<SessionInfo> = {}): SessionInfo {
return {
ended_at: null,
id: '20260101_abc123',
input_tokens: 0,
is_active: false,
last_active: 1_000,
message_count: 1,
model: null,
output_tokens: 0,
preview: null,
profile: 'default',
source: 'cli',
started_at: 1_000,
title: 'Research notes',
tool_call_count: 0,
...overrides
}
}
afterEach(() => {
__resetSessionLinkTitleCache()
$sessions.set([])
vi.mocked(getSession).mockReset()
})
describe('lookupLocalSessionTitle', () => {
it('reads from the in-memory session list', () => {
$sessions.set([makeSession({ profile: 'work', title: 'Branch plan' })])
expect(lookupLocalSessionTitle('work/20260101_abc123')).toBe('Branch plan')
})
it('matches the lineage root so a compressed chat still resolves', () => {
$sessions.set([makeSession({ _lineage_root_id: '20260101_abc123', id: '20260102_tip', title: 'Compressed chat' })])
expect(lookupLocalSessionTitle('20260101_abc123')).toBe('Compressed chat')
})
it('ignores a same-id row owned by another profile', () => {
$sessions.set([makeSession({ profile: 'work', title: 'Work chat' })])
expect(lookupLocalSessionTitle('personal/20260101_abc123')).toBe('')
})
it('returns empty for an untitled row so the caller can fall back to the id', () => {
$sessions.set([makeSession({ preview: null, title: null })])
expect(lookupLocalSessionTitle('default/20260101_abc123')).toBe('')
})
})
describe('fetchSessionLinkTitle', () => {
it('dedupes concurrent lookups', async () => {
vi.mocked(getSession).mockResolvedValue(makeSession({ title: 'From API' }))
const value = 'default/20260101_abc123'
const [first, second] = await Promise.all([fetchSessionLinkTitle(value), fetchSessionLinkTitle(value)])
expect(first).toBe('From API')
expect(second).toBe('From API')
expect(getSession).toHaveBeenCalledTimes(1)
expect(getSession).toHaveBeenCalledWith('20260101_abc123', 'default')
})
it('uses the local sidebar row before calling the API', async () => {
$sessions.set([makeSession({ title: 'Cached title' })])
await expect(fetchSessionLinkTitle('default/20260101_abc123')).resolves.toBe('Cached title')
expect(getSession).not.toHaveBeenCalled()
})
it('keeps separate cache entries per profile', async () => {
vi.mocked(getSession).mockImplementation(async (id, profile) =>
makeSession({ id, profile: profile ?? 'default', title: profile === 'work' ? 'Work chat' : 'Home chat' })
)
await expect(fetchSessionLinkTitle('default/20260101_abc123')).resolves.toBe('Home chat')
await expect(fetchSessionLinkTitle('work/20260101_abc123')).resolves.toBe('Work chat')
expect(sessionRefCacheKey('default/20260101_abc123')).not.toBe(sessionRefCacheKey('work/20260101_abc123'))
})
it('falls back to the preview when the session has no title', async () => {
vi.mocked(getSession).mockResolvedValue(makeSession({ preview: 'Summarize this repo', title: null }))
await expect(fetchSessionLinkTitle('20260101_abc123')).resolves.toBe('Summarize this repo')
})
it('resolves empty when the id is not on this backend', async () => {
vi.mocked(getSession).mockRejectedValue(new Error('Session not found'))
await expect(fetchSessionLinkTitle('default/missing')).resolves.toBe('')
})
it('resolves empty when the desktop bridge is unavailable', async () => {
vi.mocked(getSession).mockImplementation(() => {
throw new TypeError("Cannot read properties of undefined (reading 'api')")
})
await expect(fetchSessionLinkTitle('default/20260101_abc123')).resolves.toBe('')
})
})