// @vitest-environment jsdom import { act, cleanup, fireEvent, render, screen, waitFor, within, } from '@testing-library/react'; import { useState } from 'react'; import { afterEach, beforeEach, expect, test, vi } from 'vitest'; import defaults from '../game-creator.config.json'; import type { GameCreatorAppConfig, GameCreatorLlmConfig, } from '../src/app/types'; import { CustomLlmSettings } from '../src/features/runtime-config/CustomLlmSettings'; import { RuntimeConfigDialog } from '../src/features/runtime-config/RuntimeConfigDialog'; const invoke = vi.fn(); const llm = { ...defaults.llm, customEnabled: true, baseUrl: 'https://models.example/v1', apiKey: 'test-custom-key', visibleModels: ['vendor/model.v1'], } as GameCreatorLlmConfig; function Harness() { const [draft, setDraft] = useState(llm); return ; } beforeEach(() => { invoke.mockReset(); window.__TAURI__ = { core: { invoke } }; }); afterEach(() => { cleanup(); delete window.__TAURI__; }); test('reads endpoint models, filters candidates, and previews only checked models', async () => { invoke.mockResolvedValue([ 'vendor/model.v1', 'vendor/fast:latest', 'hidden-model', ]); render(); fireEvent.click(screen.getByRole('button', { name: '读取模型列表' })); await screen.findByText('已读取 3 个模型'); expect(invoke).toHaveBeenCalledWith('discover_game_creator_llm_models', { llm, }); fireEvent.click(screen.getByRole('checkbox', { name: 'vendor/fast:latest' })); const preview = screen.getByRole('region', { name: '已勾选模型预览' }); expect( within(preview) .getAllByRole('listitem') .map((item) => item.textContent), ).toEqual(['vendor/model.v1 默认', 'vendor/fast:latest']); expect(within(preview).queryByText('hidden-model')).toBeNull(); fireEvent.change(screen.getByRole('textbox', { name: '搜索模型' }), { target: { value: 'FAST' }, }); expect(screen.getAllByRole('checkbox')).toHaveLength(1); fireEvent.click(screen.getByRole('checkbox', { name: 'vendor/fast:latest' })); expect(within(preview).getAllByRole('listitem')).toHaveLength(1); }); test('failed discovery preserves checked models and allows retry', async () => { invoke .mockRejectedValueOnce('模型列表读取失败(HTTP 401)') .mockResolvedValueOnce(['vendor/model.v1']); render(); fireEvent.click(screen.getByRole('button', { name: '读取模型列表' })); await screen.findByRole('alert'); expect( within(screen.getByRole('region', { name: '已勾选模型预览' })).getByText( /vendor\/model.v1/, ), ).not.toBeNull(); fireEvent.click(screen.getByRole('button', { name: '读取模型列表' })); await screen.findByText('已读取 1 个模型'); expect(screen.queryByRole('alert')).toBeNull(); }); test('changing endpoint discards an old in-flight result and clears old selections', async () => { let finish!: (models: string[]) => void; invoke.mockImplementation( () => new Promise((resolve) => { finish = resolve; }), ); render(); fireEvent.click(screen.getByRole('button', { name: '读取模型列表' })); fireEvent.change(screen.getByLabelText('自定义 LLM API 地址'), { target: { value: 'https://new.example/v1' }, }); await act(async () => finish(['old-model'])); expect(screen.queryByRole('checkbox', { name: 'old-model' })).toBeNull(); expect(screen.getByText('尚未勾选模型')).not.toBeNull(); expect(screen.getByRole('button', { name: '读取模型列表' })).toHaveProperty( 'disabled', false, ); }); test('settings save retains custom credentials and checked models, and reopening restores them', async () => { let config = { ...defaults, llm, agentLlm: {}, editorApi: { baseUrl: 'https://platform.example', apiKey: '' }, } as GameCreatorAppConfig; invoke.mockImplementation(async (command, args) => { if (command === 'write_game_creator_app_config') config = args.config; if ( command === 'read_game_creator_app_config' || command === 'write_game_creator_app_config' ) return { path: '/private/game-creator.config.json', config }; if (command === 'discover_game_creator_llm_models') return ['vendor/model.v1', 'vendor/fast:latest']; return []; }); const first = render( {}} />); await screen.findByLabelText('自定义 LLM API 地址'); expect(screen.getByLabelText('自定义 LLM API Key')).toHaveProperty( 'type', 'password', ); expect(screen.getByText('OpenAI Responses')).not.toBeNull(); expect(screen.getByText('最高')).not.toBeNull(); fireEvent.click(screen.getByRole('button', { name: '读取模型列表' })); await screen.findByText('已读取 2 个模型'); fireEvent.click(screen.getByRole('checkbox', { name: 'vendor/fast:latest' })); fireEvent.click(screen.getByRole('button', { name: '保存' })); await waitFor(() => expect(config.llm.visibleModels).toEqual([ 'vendor/model.v1', 'vendor/fast:latest', ]), ); expect(config.llm.apiKey).toBe('test-custom-key'); expect(config.llm.customEnabled).toBe(true); first.unmount(); render( {}} />); await screen.findByLabelText('自定义 LLM API 地址'); expect( within(screen.getByRole('region', { name: '已勾选模型预览' })).getAllByRole( 'listitem', ), ).toHaveLength(2); });