// @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);
});