/* Shared fixtures for the component tests. * * `renderWith` supplies the two contexts every screen needs — a router and the * model status — so a test can render one component rather than the whole app. * The model status is faked at the `api` boundary rather than by mocking the * hook, so the provider's own logic (which of the five states a given settings * row and connection result produce) is exercised by the tests that depend on * it instead of being assumed. */ import { act, render } from '@testing-library/react' import { MemoryRouter } from 'react-router-dom' import { vi } from 'vitest' import { ModelStatusProvider } from '../modelStatus' import { ToastHost } from '../components' /** A settings row with the shape the real API returns. */ export const SETTINGS = { endpoint_url: 'http://127.0.0.1:11434/v1', model: 'qwen2.5:3b-instruct', api_mode: 'chat', temperature: 0.2, max_output_tokens: 400, context_token_budget: 8000, model_timeout_seconds: 600, // Null is the shipped default: nobody has declared a window, so the server is // asked for one. See `contextwindow.py`. context_window_override: null, summary_model: '', embedding_model: 'nomic-embed-text', memory_bank_capacity: 200, memory_top_k: 5, narrator_prompt: 'You are the narrator.', } /** * Points `api.getSettings` and `api.testConnection` at a given world. * * `models` defaults to a listing containing the configured model, which is the * ordinary "everything works" case. */ export function mockModelStatus(api, { settings = SETTINGS, ok = true, models = ['qwen2.5:3b-instruct', 'nomic-embed-text'], detail = '', warning = '', } = {}) { vi.spyOn(api, 'getSettings').mockResolvedValue(settings) vi.spyOn(api, 'testConnection').mockResolvedValue( ok ? { ok: true, models, ...(warning ? { warning } : {}) } : { ok: false, detail }, ) } /** * Renders inside the router and the model-status provider. * * Async because the provider fetches settings and runs a connection test on * mount; awaiting here lets those promises settle inside `act`, so a test sees * the resolved status rather than "checking" and React emits no warning about * an unwrapped update. */ export async function renderWith(ui, { route = '/' } = {}) { let view await act(async () => { view = render( {ui} , ) }) return view } /** * An accepted narrator turn, in the shape the actions API returns. * * **`ActionOut` carries no `branch_id`, and this must not either.** A fixture * that supplies one models a payload the server never sends, and it hid a real * defect: the take pager compared `target.branch_id !== action.branch_id`, * which against the real payload is always `number !== undefined` and therefore * always true. With a `branch_id` in the fixture the comparison looked correct * and the tests passed against broken code. */ export function narratorAction(overrides = {}) { return { id: 2, type: 'ai', text: 'The tavern is warm and loud.', reasoning: '', world_changes: null, take_count: 1, take_index: 0, ...overrides, } } /** An accepted player turn. */ export function playerAction(overrides = {}) { return { id: 1, type: 'do', text: 'I walk into the tavern.', reasoning: '', take_count: 1, take_index: 0, ...overrides, } }