// @vitest-environment jsdom import { cleanup, fireEvent, render, screen, waitFor, } from '@testing-library/react'; import { afterEach, beforeEach, expect, test, vi } from 'vitest'; import { resolveTauriInvoke } from '../src/app/tauri'; import { ConversationModelSelect } from '../src/features/project-workspace/ConversationModelSelect'; import { loadClientLlmModels } from '../src/services/clientApi'; vi.mock('../src/app/tauri', () => ({ resolveTauriInvoke: vi.fn() })); vi.mock('../src/services/clientApi', () => ({ loadClientLlmModels: vi.fn() })); const invoke = vi.fn(); beforeEach(() => { vi.clearAllMocks(); vi.mocked(resolveTauriInvoke).mockReturnValue(invoke); vi.mocked(loadClientLlmModels).mockResolvedValue({ defaultModelId: 'quality', models: [ { id: 'quality', displayName: '高质量' }, { id: 'fast', displayName: '快速' }, ], }); invoke.mockImplementation(async (command, input) => ({ config: { selectedModelId: command === 'select_game_creator_model' ? input.modelId : 'quality', }, })); }); afterEach(cleanup); test('only displays aliases and persists selection through the native command', async () => { const onReady = vi.fn(); render(); await screen.findByRole('button', { name: '对话模型' }); expect(screen.queryByText('gpt-6-astra')).toBeNull(); fireEvent.click(screen.getByRole('button', { name: '对话模型' })); fireEvent.click(screen.getByRole('option', { name: '快速' })); await waitFor(() => expect(invoke).toHaveBeenCalledWith('select_game_creator_model', { modelId: 'fast', }), ); await waitFor(() => expect(onReady).toHaveBeenLastCalledWith(true)); expect( screen.getByRole('button', { name: '对话模型' }).textContent, ).toContain('快速'); }); test('does not mark a removed selection ready or expose the old identifier', async () => { invoke.mockResolvedValue({ config: { selectedModelId: 'private-old-model' }, }); const onReady = vi.fn(); render(); await screen.findByText('请选择可用模型'); expect(onReady).toHaveBeenLastCalledWith(false); expect(screen.queryByText('private-old-model')).toBeNull(); }); test('failed catalog can be refreshed without enabling submission', async () => { vi.mocked(loadClientLlmModels).mockRejectedValueOnce(new Error('offline')); const onReady = vi.fn(); render(); await screen.findByText('模型列表加载失败'); expect(onReady).toHaveBeenLastCalledWith(false); fireEvent.click(screen.getByRole('button', { name: '对话模型' })); fireEvent.click(screen.getByRole('button', { name: '刷新模型列表' })); await waitFor(() => expect(onReady).toHaveBeenLastCalledWith(true)); }); test('a failed save keeps submission unavailable', async () => { invoke.mockImplementation(async (command) => { if (command === 'select_game_creator_model') throw new Error('disk full'); return { config: { selectedModelId: 'quality' } }; }); const onReady = vi.fn(); render(); await screen.findByRole('button', { name: '对话模型' }); fireEvent.click(screen.getByRole('button', { name: '对话模型' })); fireEvent.click(screen.getByRole('option', { name: '快速' })); await screen.findByText('模型选择保存失败'); expect(onReady).toHaveBeenLastCalledWith(false); });