fix(agc): 运行不可用提示按清单事实分叉文案
This commit is contained in:
@@ -443,6 +443,7 @@ import {
|
||||
projectResourceSectionZoomFromWheel,
|
||||
} from './resourceSectionHeightModel';
|
||||
import { ResourceTypePanel } from './ResourceTypePanel';
|
||||
import { runUnavailableHintText } from './runUnavailableHint';
|
||||
import {
|
||||
describeProjectResourceCanvasLayoutRead,
|
||||
type ProjectResourceCanvasLayoutReadReport,
|
||||
@@ -10086,7 +10087,7 @@ export default function ProjectDevelopmentView({
|
||||
className="game-run-unavailable"
|
||||
role="status"
|
||||
>
|
||||
首个可运行原型尚未完成,运行视图暂不可用
|
||||
{runUnavailableHintText(manifest)}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import type { GameCreationAppManifest } from '../../../../../packages/shared/src/contracts/gameCreationApp';
|
||||
|
||||
/**
|
||||
* 「运行」页签不可用(`run-unavailable-hint`)时给用户看的文案。
|
||||
*
|
||||
* 同一句话对两种情况并不都成立,所以按清单里已有的事实分叉,而不是套一句万能的空话:
|
||||
*
|
||||
* - **全新项目**(清单里还没有任何工程内部版本):确实什么都还没产出,
|
||||
* 「首个可运行原型尚未完成,运行视图暂不可用」是准确的,保持原样。
|
||||
* - **已经有工程内容、但本机还没有可运行的构建产物**:典型是**从平台作品 Fork 来的工程源包
|
||||
* 项目**——包里就是作者的完整工程,但工程源包按规则不含构建产物(`project_bundle.rs` 排除
|
||||
* `game/dist`),AGC 也不在 Fork 时自动构建(与「不自动安装外部工程依赖」的既有策略一致)。
|
||||
* 这时原句会让人以为「什么都没有」,必须如实说清:内容已经在本地,只是还没有构建出可运行的
|
||||
* 原型,让智能体完成可运行原型之后即可运行。
|
||||
*
|
||||
* 判据只用清单里已有的字段(`versions` 非空 = 这个项目已经有工程内部版本记录)。渲染层拿不到
|
||||
* 「预览根有没有 index.html」这种文件事实(那是原生侧的门禁判据),所以这里不新造状态、也不
|
||||
* 额外调 IPC——它只决定**措辞**,能不能运行仍由运行门禁与原生预览决定。
|
||||
*/
|
||||
export function runUnavailableHintText(
|
||||
manifest: Pick<GameCreationAppManifest, 'versions'>,
|
||||
): string {
|
||||
const hasProjectVersions = (manifest.versions?.length ?? 0) > 0;
|
||||
return hasProjectVersions
|
||||
? '已有工程内容但还没有可运行的构建产物:让智能体完成可运行原型后即可运行'
|
||||
: '首个可运行原型尚未完成,运行视图暂不可用';
|
||||
}
|
||||
@@ -531,6 +531,134 @@ export function registerProjectWorkbenchFoundationTests() {
|
||||
expect(screen.getByRole('article', { name: /发布 Agent/ })).not.toBeNull();
|
||||
});
|
||||
|
||||
/**
|
||||
* Fork 建项之后运行视图必须直接可用。
|
||||
*
|
||||
* 原生侧(`game_fork.rs` 的 `register_forked_project_state_at`)在建项收口时登记「可运行
|
||||
* 原型」:成品包形态把发行产物铺进预览根并置 `code-prototype = completed`,同时写一条
|
||||
* `initial-1` 工程内部版本。这里钉的是渲染层的投影结果——那句「首个可运行原型尚未完成」
|
||||
* 必须消失,运行页签可点并真的发出播放请求。
|
||||
*/
|
||||
it('renders a forked package project as directly runnable with an initial version', () => {
|
||||
const manifest = createGameCreationAppManifest(
|
||||
'workbench-fork-package',
|
||||
'Fork 项目(成品包)',
|
||||
);
|
||||
const codePrototype = manifest.tasks.find(
|
||||
(task) => task.id === 'code-prototype',
|
||||
);
|
||||
if (!codePrototype) {
|
||||
throw new Error('fixture 必须包含 code-prototype seed 任务');
|
||||
}
|
||||
codePrototype.status = 'completed';
|
||||
manifest.versions = [
|
||||
{
|
||||
versionId: 'initial-1',
|
||||
parentVersionId: null,
|
||||
projectRevision: 1,
|
||||
resourceBindings: [],
|
||||
createdReason: 'initial',
|
||||
createdAt: 1_791_271_678,
|
||||
},
|
||||
];
|
||||
|
||||
const onPlay = vi.fn();
|
||||
render(
|
||||
React.createElement(ProjectDevelopmentView, {
|
||||
projectName: 'Fork 项目(成品包)',
|
||||
projectPath: '/tmp/workbench-fork-package',
|
||||
manifest,
|
||||
attachments: [],
|
||||
recentRunStatus: null,
|
||||
recentRunStopReason: null,
|
||||
chat: React.createElement(
|
||||
'div',
|
||||
{ 'aria-label': '测试项目总控' },
|
||||
'项目总控对话内容',
|
||||
),
|
||||
onHomeOpen: vi.fn(),
|
||||
onProjectsOpen: vi.fn(),
|
||||
onPlay,
|
||||
}),
|
||||
);
|
||||
|
||||
// 提示必须消失,页签必须不再是「不可用」态。
|
||||
expect(
|
||||
screen.queryByText('首个可运行原型尚未完成,运行视图暂不可用'),
|
||||
).toBeNull();
|
||||
expect(document.querySelector('#run-unavailable-hint')).toBeNull();
|
||||
const runTab = screen.getByRole('tab', {
|
||||
name: '运行',
|
||||
}) as HTMLButtonElement;
|
||||
expect(runTab.getAttribute('data-unavailable')).toBeNull();
|
||||
expect(runTab.getAttribute('aria-describedby')).toBeNull();
|
||||
|
||||
// 点运行 = 切视图 + 发起播放请求(就是 fork 之后用户看到的「直接能跑」)。
|
||||
fireEvent.click(runTab);
|
||||
expect(runTab.getAttribute('aria-selected')).toBe('true');
|
||||
expect(onPlay).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
/**
|
||||
* 工程源包 Fork 的如实表述:包里是作者的完整工程,但工程源包不含构建产物
|
||||
* (`project_bundle.rs` 排除 `game/dist`),AGC 也不在 Fork 时自动构建,所以本机还没有
|
||||
* 可运行的构建产物。此时不能再说「首个可运行原型尚未完成」(听着像什么都没有),
|
||||
* 也不能谎称已经可运行。
|
||||
*/
|
||||
it('tells the truth for a source-only fork that still needs a build', () => {
|
||||
const manifest = createGameCreationAppManifest(
|
||||
'workbench-fork-source',
|
||||
'Fork 项目(工程源包)',
|
||||
);
|
||||
manifest.versions = [
|
||||
{
|
||||
versionId: 'initial-1',
|
||||
parentVersionId: null,
|
||||
projectRevision: 1,
|
||||
resourceBindings: [],
|
||||
createdReason: 'initial',
|
||||
createdAt: 1_791_271_678,
|
||||
},
|
||||
];
|
||||
|
||||
const onPlay = vi.fn();
|
||||
render(
|
||||
React.createElement(ProjectDevelopmentView, {
|
||||
projectName: 'Fork 项目(工程源包)',
|
||||
projectPath: '/tmp/workbench-fork-source',
|
||||
manifest,
|
||||
attachments: [],
|
||||
recentRunStatus: null,
|
||||
recentRunStopReason: null,
|
||||
chat: React.createElement(
|
||||
'div',
|
||||
{ 'aria-label': '测试项目总控' },
|
||||
'项目总控对话内容',
|
||||
),
|
||||
onHomeOpen: vi.fn(),
|
||||
onProjectsOpen: vi.fn(),
|
||||
onPlay,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByText(
|
||||
'已有工程内容但还没有可运行的构建产物:让智能体完成可运行原型后即可运行',
|
||||
),
|
||||
).not.toBeNull();
|
||||
expect(
|
||||
screen.queryByText('首个可运行原型尚未完成,运行视图暂不可用'),
|
||||
).toBeNull();
|
||||
|
||||
const runTab = screen.getByRole('tab', {
|
||||
name: '运行',
|
||||
}) as HTMLButtonElement;
|
||||
expect(runTab.getAttribute('data-unavailable')).toBe('true');
|
||||
fireEvent.click(runTab);
|
||||
expect(runTab.getAttribute('aria-selected')).toBe('false');
|
||||
expect(onPlay).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('keeps the approval-mode choices reachable in their own dialog surface', () => {
|
||||
// 审批模式原来是长在会话列头部按钮里的对话框;面板顶部精简之后它由设置浮层里的
|
||||
// 「操作权限」行打开,但选项与「不可用项给出说明」的语义必须完整保留。
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { runUnavailableHintText } from '../src/view/project-development/runUnavailableHint';
|
||||
|
||||
/**
|
||||
* 「运行」页签不可用时的文案(`run-unavailable-hint`)。
|
||||
*
|
||||
* 为什么要有这条用例:同一句「首个可运行原型尚未完成」对**已有工程内容、只是本机还没构建出
|
||||
* 可运行产物**的情况(典型是从平台作品 Fork 来的工程源包项目)是误导——内容明明已经在本地。
|
||||
* 文案因此必须按清单事实分叉,而分叉判据只能是清单里已有的字段。
|
||||
*/
|
||||
describe('runUnavailableHintText', () => {
|
||||
it('全新项目(清单里还没有工程内部版本)沿用原有措辞', () => {
|
||||
expect(runUnavailableHintText({ versions: [] })).toBe(
|
||||
'首个可运行原型尚未完成,运行视图暂不可用',
|
||||
);
|
||||
});
|
||||
|
||||
it('versions 字段整体缺失时按「没有版本」处理,不抛错', () => {
|
||||
expect(runUnavailableHintText({} as { versions: [] })).toBe(
|
||||
'首个可运行原型尚未完成,运行视图暂不可用',
|
||||
);
|
||||
});
|
||||
|
||||
it('已有工程内容但没有可运行的构建产物时如实说明需要先完成原型', () => {
|
||||
const text = runUnavailableHintText({
|
||||
versions: [
|
||||
{
|
||||
versionId: 'initial-1',
|
||||
parentVersionId: null,
|
||||
projectRevision: 1,
|
||||
resourceBindings: [],
|
||||
createdReason: 'initial',
|
||||
createdAt: 1_791_271_678,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(text).toBe(
|
||||
'已有工程内容但还没有可运行的构建产物:让智能体完成可运行原型后即可运行',
|
||||
);
|
||||
// 不能再用「首个可运行原型尚未完成」那种"什么都还没有"的说法。
|
||||
expect(text).not.toContain('首个可运行原型尚未完成');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user