Files
geointel/frontend/src/hooks/useGeoAssistant.test.tsx
T

118 lines
3.5 KiB
TypeScript

import { act, renderHook, waitFor } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { AssistantQueryResponse } from '../types'
const mocks = vi.hoisted(() => ({
status: vi.fn(),
models: vi.fn(),
query: vi.fn(),
}))
vi.mock('../services/api/assistant', () => ({
assistantApi: mocks,
}))
import { useGeoAssistant } from './useGeoAssistant'
function deferred<T>() {
let resolve!: (value: T) => void
let reject!: (reason?: unknown) => void
const promise = new Promise<T>((resolvePromise, rejectPromise) => {
resolve = resolvePromise
reject = rejectPromise
})
return { promise, resolve, reject }
}
function response(answer: string): AssistantQueryResponse {
return {
answer,
model: 'geo-model',
scope_label: 'testgebied',
context_metrics: [],
temporal_series: [],
source_dataset_ids: [],
warnings: [],
generated_at: '2026-08-23T12:00:00Z',
}
}
describe('useGeoAssistant request scope', () => {
beforeEach(() => {
window.localStorage.clear()
mocks.status.mockResolvedValue({
enabled: true,
reachable: true,
status: 'ready',
base_url: 'http://localhost',
default_model: 'geo-model',
model_count: 1,
limitation_message: '',
})
mocks.models.mockResolvedValue({
items: [{ name: 'geo-model', capabilities: ['chat'] }],
total: 1,
default_model: 'geo-model',
})
})
it('ignores an answer that returns after the active project changed', async () => {
const pending = deferred<AssistantQueryResponse>()
mocks.query.mockReturnValueOnce(pending.promise)
const { result, rerender } = renderHook(
({ projectId }) => useGeoAssistant({
selectedProjectId: projectId,
selectedAreaId: null,
selectionBbox: null,
}),
{ initialProps: { projectId: 'project-1' } },
)
await waitFor(() => expect(result.current.selectedModel).toBe('geo-model'))
let request!: Promise<boolean>
act(() => {
request = result.current.ask('Wat staat hier?')
})
rerender({ projectId: 'project-2' })
await act(async () => {
pending.resolve(response('antwoord uit project 1'))
await request
})
expect(result.current.messages).toEqual([])
expect(result.current.loading).toBe(false)
expect(result.current.error).toBeNull()
})
it('lets only the newest request update a conversation', async () => {
const older = deferred<AssistantQueryResponse>()
const newer = deferred<AssistantQueryResponse>()
mocks.query
.mockReturnValueOnce(older.promise)
.mockReturnValueOnce(newer.promise)
const { result } = renderHook(() => useGeoAssistant({
selectedProjectId: 'project-1',
selectedAreaId: null,
selectionBbox: null,
}))
await waitFor(() => expect(result.current.selectedModel).toBe('geo-model'))
let olderRequest!: Promise<boolean>
let newerRequest!: Promise<boolean>
act(() => { olderRequest = result.current.ask('Eerste vraag') })
act(() => { newerRequest = result.current.ask('Tweede vraag') })
await act(async () => {
newer.resolve(response('nieuwste antwoord'))
await newerRequest
})
await act(async () => {
older.resolve(response('verouderd antwoord'))
await olderRequest
})
const assistantMessages = result.current.messages.filter((message) => message.role === 'assistant')
expect(assistantMessages.map((message) => message.content)).toEqual(['nieuwste antwoord'])
expect(result.current.loading).toBe(false)
})
})