9eb537c6ff
- index.tsx:新增 autoRunPreviewProjectRef 记账,站在运行视图却没有可嵌入预览且可运行时自动补发一次 onPlay,同一个项目只补一次 - index.tsx:showRunView 先记账再发播放,避免点页签时被自动补发重复触发一次 - 用例:新增 runAutoLoadOnEnter(切项目自动补发一次、同项目不重复、点页签只发一次、有活体预览时不补发) - 文档:PRD 顶栏条款、验收用例 S17 判据、decision-log 同步自动载入口径
183 lines
5.5 KiB
TypeScript
183 lines
5.5 KiB
TypeScript
/** @vitest-environment jsdom */
|
|
import { cleanup, fireEvent, render, screen } from '@testing-library/react';
|
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
import {
|
|
createGameCreationAppManifest,
|
|
type GameCreationAppPreviewState,
|
|
} from '../../../packages/shared/src/contracts/gameCreationApp';
|
|
import ProjectDevelopmentView from '../src/view/project-development';
|
|
import { installTauriRuntime } from './tauriRuntimeFake';
|
|
|
|
/**
|
|
* 「进入项目时已经站在运行页,却没有画面」要自动载入一次。
|
|
*
|
|
* 现场:从另一个项目切过来时工作台不重挂(`mode` 是工作台自己的 state),上一条项目留下的运行
|
|
* 视图会原样保留,画面却已经没了;用户看到的就是「客户端运行画面尚未载入 + 点击顶部「运行」
|
|
* 页签」,像坏了一样。
|
|
*/
|
|
const OFFLINE_PREVIEW: GameCreationAppPreviewState = {
|
|
status: 'running',
|
|
url: 'https://example.com/game',
|
|
port: 443,
|
|
};
|
|
|
|
/** 有完成的代码原型才算「可运行」——运行页签才会真的发播放请求。 */
|
|
function buildManifest(id: string, name: string) {
|
|
const manifest = createGameCreationAppManifest(id, name);
|
|
manifest.tasks = manifest.tasks.map((task) =>
|
|
task.id === 'code-prototype' ? { ...task, status: 'completed' } : task,
|
|
);
|
|
return manifest;
|
|
}
|
|
|
|
function installInvoke() {
|
|
installTauriRuntime({
|
|
core: {
|
|
invoke: vi.fn(async (command: string, args?: Record<string, unknown>) => {
|
|
if (command === 'read_local_project_resource_graph') {
|
|
return {
|
|
resourceIds: [],
|
|
referenceEdges: [],
|
|
taskFlows: [],
|
|
categories: [],
|
|
diagnostics: [],
|
|
};
|
|
}
|
|
if (command === 'read_local_project_resource_canvas_layout') {
|
|
return {
|
|
schemaVersion: 'game-creator-resource-layout.v1',
|
|
projectId: args?.expectedProjectId,
|
|
mode: args?.mode,
|
|
revision: 0,
|
|
positions: [],
|
|
updatedAt: 0,
|
|
};
|
|
}
|
|
throw new Error(`unexpected invoke ${command}`);
|
|
}),
|
|
},
|
|
});
|
|
}
|
|
|
|
function renderWorkbench({
|
|
projectPath,
|
|
name,
|
|
preview = OFFLINE_PREVIEW,
|
|
onPlay,
|
|
}: {
|
|
projectPath: string;
|
|
name: string;
|
|
preview?: GameCreationAppPreviewState;
|
|
onPlay: () => void;
|
|
}) {
|
|
installInvoke();
|
|
return render(
|
|
<ProjectDevelopmentView
|
|
projectName={name}
|
|
projectPath={projectPath}
|
|
manifest={buildManifest('run-auto-load', name)}
|
|
attachments={[]}
|
|
recentRunStatus="completed"
|
|
recentRunStopReason={null}
|
|
preview={preview}
|
|
chat={null}
|
|
onHomeOpen={vi.fn()}
|
|
onProjectsOpen={vi.fn()}
|
|
onNotice={vi.fn()}
|
|
onPlay={onPlay}
|
|
/>,
|
|
);
|
|
}
|
|
|
|
afterEach(() => {
|
|
cleanup();
|
|
vi.clearAllMocks();
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
describe('运行视图没有画面时的自动载入', () => {
|
|
it('切到另一个项目时自动补一次播放,同一个项目不重复发', async () => {
|
|
const onPlay = vi.fn();
|
|
const view = renderWorkbench({
|
|
projectPath: '/tmp/project-a',
|
|
name: '项目 A',
|
|
onPlay,
|
|
});
|
|
|
|
// 先在「项目 A」上把视图切到运行(没有可嵌入的预览:切过去只有空态)。
|
|
fireEvent.click(screen.getByRole('tab', { name: '运行' }));
|
|
await screen.findByText('客户端运行画面尚未载入');
|
|
expect(onPlay).toHaveBeenCalledTimes(1);
|
|
|
|
// 工作台不重挂,只换项目:运行视图原样留下,画面是空的 → 自动补一次。
|
|
view.rerender(
|
|
<ProjectDevelopmentView
|
|
projectName="项目 B"
|
|
projectPath="/tmp/project-b"
|
|
manifest={buildManifest('run-auto-load-b', '项目 B')}
|
|
attachments={[]}
|
|
recentRunStatus={null}
|
|
recentRunStopReason={null}
|
|
preview={OFFLINE_PREVIEW}
|
|
chat={null}
|
|
onHomeOpen={vi.fn()}
|
|
onProjectsOpen={vi.fn()}
|
|
onNotice={vi.fn()}
|
|
onPlay={onPlay}
|
|
/>,
|
|
);
|
|
expect(onPlay).toHaveBeenCalledTimes(2);
|
|
|
|
// 同一个项目再渲染一次(manifest 刷新、对话更新之类)不该反复发播放请求。
|
|
view.rerender(
|
|
<ProjectDevelopmentView
|
|
projectName="项目 B"
|
|
projectPath="/tmp/project-b"
|
|
manifest={buildManifest('run-auto-load-b', '项目 B 更新')}
|
|
attachments={[]}
|
|
recentRunStatus={null}
|
|
recentRunStopReason={null}
|
|
preview={OFFLINE_PREVIEW}
|
|
chat={null}
|
|
onHomeOpen={vi.fn()}
|
|
onProjectsOpen={vi.fn()}
|
|
onNotice={vi.fn()}
|
|
onPlay={onPlay}
|
|
/>,
|
|
);
|
|
expect(onPlay).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it('点「运行」页签本身只发一次播放,不被自动载入重复触发', async () => {
|
|
const onPlay = vi.fn();
|
|
renderWorkbench({
|
|
projectPath: '/tmp/project-a',
|
|
name: '项目 A',
|
|
onPlay,
|
|
});
|
|
|
|
fireEvent.click(screen.getByRole('tab', { name: '运行' }));
|
|
|
|
expect(onPlay).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('有活体预览时不补发:画面已经在,没必要再启动一次', async () => {
|
|
const onPlay = vi.fn();
|
|
renderWorkbench({
|
|
projectPath: '/tmp/project-a',
|
|
name: '项目 A',
|
|
preview: {
|
|
status: 'running',
|
|
url: 'http://127.0.0.1:4173/',
|
|
port: 4173,
|
|
},
|
|
onPlay,
|
|
});
|
|
|
|
// 会话内确认过的预览会自动切到运行视图;此时画面在,谁都不该发播放请求。
|
|
await screen.findByTitle('项目 A 游戏运行画面');
|
|
expect(onPlay).not.toHaveBeenCalled();
|
|
});
|
|
});
|