118 lines
3.5 KiB
TypeScript
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)
|
|
})
|
|
})
|