25c5ecdf6e
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m58s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 6m34s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 5m29s
Project CI / Backend tests (pull_request) Failing after 4m38s
Project CI / Frontend tests (pull_request) Successful in 3m33s
Project CI / Repository checks (pull_request) Failing after 4m4s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m13s
Project CI / Native shell tests (pull_request) Successful in 6m35s
- 78 处内联 window.__TAURI__ 赋值改为 installTauriRuntime,覆盖 appSurface 之外的 37 个测试文件 - 31 处 delete window.__TAURI__ / window.__TAURI__ = undefined 复位改为 resetTauriRuntime - 删除 recentProjectsHook、projectCreationDirectory、projectResourceLiveIntegration、useProjectResourceCardPreviews、resourceVersionReplacementModel、unityProjectOpen 里的本地 asTauriInvoke / asTauriListen 适配器与相关类型别名 - designProjectRestore 的 setup 参数由 ReturnType<typeof vi.fn> 收窄为具体 invoke 签名 - 迁移后仅 tests/tauriRuntimeFake.ts 直接读写 window.__TAURI__
159 lines
5.5 KiB
TypeScript
159 lines
5.5 KiB
TypeScript
// @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';
|
||
import { installTauriRuntime, resetTauriRuntime } from './tauriRuntimeFake';
|
||
|
||
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 <CustomLlmSettings llm={draft} disabled={false} onChange={setDraft} />;
|
||
}
|
||
|
||
beforeEach(() => {
|
||
invoke.mockReset();
|
||
installTauriRuntime({ core: { invoke } });
|
||
});
|
||
afterEach(() => {
|
||
cleanup();
|
||
resetTauriRuntime();
|
||
});
|
||
|
||
test('reads endpoint models, filters candidates, and previews only checked models', async () => {
|
||
invoke.mockResolvedValue([
|
||
'vendor/model.v1',
|
||
'vendor/fast:latest',
|
||
'hidden-model',
|
||
]);
|
||
render(<Harness />);
|
||
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(<Harness />);
|
||
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(<Harness />);
|
||
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(<RuntimeConfigDialog onClose={() => {}} />);
|
||
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(<RuntimeConfigDialog onClose={() => {}} />);
|
||
await screen.findByLabelText('自定义 LLM API 地址');
|
||
expect(
|
||
within(screen.getByRole('region', { name: '已勾选模型预览' })).getAllByRole(
|
||
'listitem',
|
||
),
|
||
).toHaveLength(2);
|
||
});
|