mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-21 16:18:55 +00:00
test(desktop): cover the Fallback Models editor
Asserts each {provider, model} entry renders as its own row (the bug
produced "[object Object]"), that removing a row emits the remaining
entries, that adding a blank row never persists a partial pair, and the
empty-state hint.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
21781d54ec
commit
bf3667aeec
1 changed files with 87 additions and 0 deletions
87
apps/desktop/src/app/settings/fallback-models-field.test.tsx
Normal file
87
apps/desktop/src/app/settings/fallback-models-field.test.tsx
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
// Radix Select calls scrollIntoView / pointer-capture APIs jsdom lacks.
|
||||
beforeAll(() => {
|
||||
Element.prototype.scrollIntoView = vi.fn()
|
||||
Element.prototype.hasPointerCapture = vi.fn(() => false)
|
||||
Element.prototype.releasePointerCapture = vi.fn()
|
||||
})
|
||||
|
||||
const getGlobalModelOptions = vi.fn()
|
||||
|
||||
vi.mock('@/hermes', () => ({
|
||||
getGlobalModelOptions: () => getGlobalModelOptions()
|
||||
}))
|
||||
|
||||
beforeEach(() => {
|
||||
getGlobalModelOptions.mockResolvedValue({
|
||||
providers: [
|
||||
{ name: 'GitHub Copilot', slug: 'copilot', models: ['gpt-5-mini', 'gpt-5.4-mini'] },
|
||||
{ name: 'OpenAI Codex', slug: 'openai-codex', models: ['gpt-5.4-mini'] },
|
||||
{ name: 'Nous', slug: 'nous', models: ['hermes-4'] }
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
cleanup()
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
async function renderField(value: unknown, onChange = vi.fn()) {
|
||||
const { FallbackModelsField } = await import('./fallback-models-field')
|
||||
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } })
|
||||
|
||||
render(
|
||||
<QueryClientProvider client={client}>
|
||||
<FallbackModelsField onChange={onChange} value={value} />
|
||||
</QueryClientProvider>
|
||||
)
|
||||
|
||||
return onChange
|
||||
}
|
||||
|
||||
const CHAIN = [
|
||||
{ provider: 'copilot', model: 'gpt-5-mini' },
|
||||
{ provider: 'openai-codex', model: 'gpt-5.4-mini' }
|
||||
]
|
||||
|
||||
describe('FallbackModelsField', () => {
|
||||
it('renders each {provider, model} entry as its own row (never "[object Object]")', async () => {
|
||||
await renderField(CHAIN)
|
||||
|
||||
// One Remove control per entry proves the object list became rows — the old
|
||||
// generic `list` input stringified the array to "[object Object]".
|
||||
expect(screen.getAllByLabelText('Remove')).toHaveLength(2)
|
||||
expect(screen.getByText('Add fallback')).toBeTruthy()
|
||||
expect(screen.queryByText(/\[object Object\]/)).toBeNull()
|
||||
await waitFor(() => expect(getGlobalModelOptions).toHaveBeenCalled())
|
||||
})
|
||||
|
||||
it('removing a row emits the remaining entries', async () => {
|
||||
const onChange = await renderField(CHAIN)
|
||||
|
||||
fireEvent.click(screen.getAllByLabelText('Remove')[0])
|
||||
|
||||
expect(onChange.mock.calls.at(-1)?.[0]).toEqual([{ provider: 'openai-codex', model: 'gpt-5.4-mini' }])
|
||||
})
|
||||
|
||||
it('adding a blank row does not persist a partial entry', async () => {
|
||||
const onChange = await renderField(CHAIN)
|
||||
|
||||
fireEvent.click(screen.getByText('Add fallback'))
|
||||
|
||||
// The new empty row stays in the UI but only complete pairs are emitted.
|
||||
expect(onChange.mock.calls.at(-1)?.[0]).toEqual(CHAIN)
|
||||
expect(screen.getAllByLabelText('Remove')).toHaveLength(3)
|
||||
})
|
||||
|
||||
it('shows an empty-state hint when there are no fallbacks', async () => {
|
||||
await renderField([])
|
||||
|
||||
expect(screen.getByText(/No fallback models/)).toBeTruthy()
|
||||
expect(screen.queryAllByLabelText('Remove')).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue