Files
Genarrative/apps/ai-game-creator-shell/tests/pluginHost.test.ts
T
kdletters c1d26f11df 移除 Cocos 和 Unity 插件的工程类型限制
统一插件列表、启动、面板和 Agent 工具的可用性判断,保留开关与平台约束

调整前端自动启动并修复 Cocos 项目切换的旧连接与订阅快照竞态

补充插件宿主、工具目录和前端回归测试,同步插件技术规范与共享决策
2026-09-20 10:53:16 +08:00

135 lines
4.4 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from 'vitest';
import {
setAgcPluginProjectPath,
startAvailableAgcEditorPlugins,
} from '../src/services/pluginHost';
afterEach(() => vi.unstubAllGlobals());
const editorPlugins = ['agc-cocos-editor', 'agc-unity-editor'].map((id) => ({
id,
name: id === 'agc-cocos-editor' ? 'Cocos Creator' : 'Unity',
builtin: true,
enabled: true,
hasRuntime: true,
status: 'stopped',
}));
describe('插件自动启动使用后端能力投影', () => {
it.each(
[
[],
[
{
...editorPlugins[0],
enabled: false,
hasRuntime: true,
status: 'stopped',
},
],
[
{
...editorPlugins[0],
enabled: true,
hasRuntime: false,
status: 'package',
},
],
[
{
...editorPlugins[0],
enabled: true,
hasRuntime: true,
status: 'invalid',
},
],
[{ ...editorPlugins[0], status: 'running' }],
[{ ...editorPlugins[0], status: 'failed' }],
[{ ...editorPlugins[0], status: 'disabled' }],
[{ ...editorPlugins[0], builtin: false }],
[{ ...editorPlugins[0], id: 'other-plugin' }],
].map((plugins) => ({ plugins })),
)('只启动宿主投影允许自动启动的内置编辑器插件(%j)', async ({ plugins }) => {
const invoke = vi.fn(async () => plugins);
vi.stubGlobal('window', { __TAURI__: { core: { invoke } } });
await startAvailableAgcEditorPlugins();
expect(invoke).toHaveBeenCalledTimes(1);
expect(invoke).toHaveBeenCalledWith('list_agc_plugins');
});
it.each(['stopped', 'discovered'])(
'从同一份宿主列表启动所有可用的编辑器插件:%s',
async (status) => {
const invoke = vi.fn(async (command: string) =>
command === 'list_agc_plugins'
? editorPlugins.map((plugin) => ({ ...plugin, status }))
: {},
);
vi.stubGlobal('window', { __TAURI__: { core: { invoke } } });
await startAvailableAgcEditorPlugins();
expect(invoke.mock.calls).toEqual([
['list_agc_plugins'],
['start_agc_plugin', { id: 'agc-cocos-editor' }],
['start_agc_plugin', { id: 'agc-unity-editor' }],
]);
},
);
it('一个编辑器插件启动失败仍启动另一个,且不自动重试', async () => {
const invoke = vi.fn(async (command: string, params?: { id: string }) => {
if (command === 'list_agc_plugins') return editorPlugins;
if (params?.id === 'agc-cocos-editor') throw new Error('进程已退出');
return {};
});
vi.stubGlobal('window', { __TAURI__: { core: { invoke } } });
await expect(startAvailableAgcEditorPlugins()).rejects.toThrow(
'Cocos Creator:进程已退出',
);
expect(invoke.mock.calls).toEqual([
['list_agc_plugins'],
['start_agc_plugin', { id: 'agc-cocos-editor' }],
['start_agc_plugin', { id: 'agc-unity-editor' }],
]);
});
it('项目切换后迟到的插件列表不会启动旧项目的插件', async () => {
let finishList!: (plugins: typeof editorPlugins) => void;
const invoke = vi.fn(
() =>
new Promise<typeof editorPlugins>((resolve) => {
finishList = resolve;
}),
);
vi.stubGlobal('window', { __TAURI__: { core: { invoke } } });
let active = true;
const start = startAvailableAgcEditorPlugins(() => active);
active = false;
finishList(editorPlugins);
await start;
expect(invoke).toHaveBeenCalledTimes(1);
});
it('快速切换项目时旧 cleanup 不能晚于新项目设置抵达宿主', async () => {
const received: Array<string | null> = [];
let finishFirst: () => void = () => undefined;
const invoke = vi.fn(
async (_command: string, params: { projectPath: string | null }) => {
if (params.projectPath === 'C:/A')
await new Promise<void>((resolve) => {
finishFirst = resolve;
});
received.push(params.projectPath);
},
);
vi.stubGlobal('window', { __TAURI__: { core: { invoke } } });
const first = setAgcPluginProjectPath('C:/A');
const cleanup = setAgcPluginProjectPath(null);
const second = setAgcPluginProjectPath('C:/B');
await vi.waitFor(() => expect(invoke).toHaveBeenCalledTimes(1));
finishFirst();
await Promise.all([first, cleanup, second]);
expect(received).toEqual(['C:/A', null, 'C:/B']);
});
});