Files
aiturk-hermes-ide/apps/desktop/src/app/settings/memory/provider-config-modal.test.tsx
T

118 lines
3.6 KiB
TypeScript

import { cleanup, fireEvent, screen, waitFor } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { MemoryProviderConfig, MemoryProviderField } from '@/types/hermes'
import { renderWithEnglish as render } from '../test-locale'
const saveMemoryProviderConfig = vi.fn()
vi.mock('@/hermes', () => ({
saveMemoryProviderConfig: (provider: string, values: unknown) => saveMemoryProviderConfig(provider, values)
}))
vi.mock('@/store/profile', async () => {
const { atom } = await import('nanostores')
return { $activeGatewayProfile: atom('default') }
})
vi.mock('@/store/notifications', () => ({
notify: vi.fn(),
notifyError: vi.fn()
}))
function field(
overrides: Partial<MemoryProviderField> & Pick<MemoryProviderField, 'key' | 'kind'>
): MemoryProviderField {
return {
label: overrides.key,
value: '',
description: '',
placeholder: '',
is_set: false,
inline: false,
group: 'Other',
options: [],
...overrides
}
}
function schema(): MemoryProviderConfig {
return {
name: 'honcho',
label: 'Honcho',
docs_url: 'https://docs.honcho.dev/v3/guides/integrations/hermes',
fields: [
field({ key: 'workspace', kind: 'text', label: 'Workspace', value: 'myws', inline: true, group: 'Connection' }),
field({ key: 'saveMessages', kind: 'bool', label: 'Save messages', value: 'true', group: 'Message writing' }),
field({ key: 'dialecticMaxChars', kind: 'number', label: 'Max result chars', value: '1200', group: 'Dialectic' }),
field({
key: 'userPeerAliases',
kind: 'json',
label: 'User peer aliases',
value: '{"t":"eri"}',
group: 'Identity'
})
]
}
}
beforeEach(() => {
saveMemoryProviderConfig.mockResolvedValue({ ok: true })
})
afterEach(() => {
cleanup()
vi.clearAllMocks()
})
async function renderModal(open = true) {
const { ProviderConfigModal } = await import('./provider-config-modal')
const onOpenChange = vi.fn()
const onSaved = vi.fn().mockResolvedValue(undefined)
const result = render(
<ProviderConfigModal
config={schema()}
onOpenChange={onOpenChange}
onSaved={onSaved}
open={open}
provider="honcho"
/>
)
return { ...result, onOpenChange, onSaved }
}
describe('ProviderConfigModal', () => {
it('renders every field grouped, including inline ones, with kind-specific controls', async () => {
await renderModal()
expect(await screen.findByText('Message writing')).toBeTruthy()
expect(screen.getByText('Dialectic')).toBeTruthy()
// bool -> switch, number -> spinbutton, json/text -> textbox
expect(screen.getByRole('switch')).toBeTruthy()
expect(screen.getByDisplayValue('1200')).toBeTruthy()
expect(screen.getByDisplayValue('myws')).toBeTruthy()
expect(screen.getByDisplayValue('{"t":"eri"}')).toBeTruthy()
})
it('saves only edited fields, serializing the toggled bool to "false"', async () => {
const { onSaved, onOpenChange } = await renderModal()
fireEvent.click(await screen.findByRole('switch'))
fireEvent.click(screen.getByRole('button', { name: 'Save changes' }))
// A save must never ratify rendered defaults the backend does not store.
await waitFor(() => expect(saveMemoryProviderConfig).toHaveBeenCalledWith('honcho', { saveMessages: 'false' }))
await waitFor(() => expect(onSaved).toHaveBeenCalled())
expect(onOpenChange).toHaveBeenCalledWith(false)
})
it('renders nothing while closed', async () => {
await renderModal(false)
expect(screen.queryByText('Message writing')).toBeNull()
})
})