6981648796
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m56s
Project CI / Backend tests (pull_request) Failing after 12s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 3m48s
Project CI / Native shell tests (pull_request) Failing after 45s
Project CI / Repository checks (pull_request) Failing after 13s
Project CI / Frontend tests (pull_request) Failing after 1m52s
Project CI / AI game creator shell web tests (pull_request) Failing after 1m42s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Failing after 6m57s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Failing after 8m1s
- 解决 refactor/split-direct-project 与 origin/master 在 App.tsx、立项策划聊天视图、Direct composer/引用输入区、styles.css、Rust direct user item 与 appSurface 用例上的冲突,按「Supervisor 永久退役」口径保留 DirectProject 独立聊天容器与 Design Agent 两条产品路径 - 采纳 master 的策划 Agent V1/V2 退役:删除 GDD 审批卡、策划输入卡、planningLane、planningSessionV2、planningSessionContract、规划展示适配与 Rust planning_*_v2 命令、模块、契约及对应用例,不保留兼容别名或双跑路径 - 把 master「折叠思考显示单行预览」的目的落到当前结构:新增共享表现 chat/components/AgentReasoning/AgentReasoning.tsx(折叠态单行纯文本预览 + 箭头、展开态安全 Markdown),DirectProject 回合与策划回合共用,删掉两处写死的 pre 折叠实现 - 把 master「策划入口可选模型 / 推理档」的目的接到当前策划输入盒:复用 ConversationModelSelect 与 ComposerReasoningEffortSelect,配置写回仍走客户端配置通道 - App.tsx 删除只服务退役 Supervisor / 策划 V2 的 state、ref、effect、回调与死参数,并删除两条读路径都退役后的 workspaceProjectKind;openWorkspace 的工程类型入参保留为未使用契约 - Rust 侧保留本分支 canonical→wire 投影、无审计 Direct 回合与 direct user item 严格校验,并入 master 的 prepare_new_web_project_at 前置复核 - 更新 ADR 与 shared-memory 决策记录:策划当前只有 Design Agent、两条路径的共享表现清单,以及本次合并的口径、代价与验证证据 - 验证:AGC 与仓库 typecheck、check:encoding、check:doc-index、git diff --check、改动文件 eslint 0 error;AGC vitest 168 个文件中除 5 个 jsdom localStorage 环境失败文件与本分支既有 resourceTagStatsRefresh 失败外全绿,appSurface 198 passed / 13 skipped;Rust 定向用例 direct_codex_user_item、skill_pack、sessions 全过(整套分片在本容器受 /sbin -> usr/bin 触发沙箱预检失败,与本合并无关)
210 lines
7.3 KiB
TypeScript
210 lines
7.3 KiB
TypeScript
/** @vitest-environment jsdom */
|
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
import type { GameCreationAppAssetKind } from '../../../packages/shared/src/contracts/gameCreationApp';
|
|
import {
|
|
createGameCreationAppManifest,
|
|
fireEvent,
|
|
installResizeObserverStub,
|
|
ProjectDevelopmentView,
|
|
React,
|
|
render,
|
|
screen,
|
|
waitFor,
|
|
within,
|
|
} from './appSurface/harness';
|
|
|
|
vi.mock('@tauri-apps/api/core', async () => ({
|
|
...(await vi.importActual<typeof import('@tauri-apps/api/core')>(
|
|
'@tauri-apps/api/core',
|
|
)),
|
|
invoke: (command: string, args?: Record<string, unknown>) =>
|
|
window.__TAURI__!.core.invoke(command, args),
|
|
}));
|
|
|
|
const projectId = 'json-canvas';
|
|
const projectPath = '/tmp/json-canvas';
|
|
const emptyState = {
|
|
ui_trees: [],
|
|
ui_design_images: {},
|
|
sprite_assets: {},
|
|
font_assets: {},
|
|
};
|
|
|
|
async function mountJsonCanvas(
|
|
kind: GameCreationAppAssetKind = 'ui-design',
|
|
rejectPreview = false,
|
|
) {
|
|
installResizeObserverStub();
|
|
const manifest = createGameCreationAppManifest(projectId, 'JSON 资源测试');
|
|
manifest.assets = [
|
|
{ id: 'design', kind, localPath: 'ui/design.json' },
|
|
{ id: 'ordinary', kind: 'unknown', localPath: 'assets/ordinary.json' },
|
|
{ id: 'spoof', kind: 'unknown', localPath: 'ui/spoof.json' },
|
|
].map((asset) => ({
|
|
...asset,
|
|
mediaType: 'application/json',
|
|
category: 'document' as const,
|
|
source: { kind: 'generated' as const },
|
|
}));
|
|
const invoke = vi.fn(
|
|
async (command: string, args?: Record<string, unknown>) => {
|
|
if (command === 'read_local_project_resource_graph') {
|
|
const ids = (args?.resources as Array<{ resourceId: string }>).map(
|
|
(item) => item.resourceId,
|
|
);
|
|
return {
|
|
resourceIds: ids,
|
|
referenceEdges: [],
|
|
taskFlows: [],
|
|
producerAssignments: [],
|
|
dependencyDepths: ids.map((resourceId) => ({
|
|
resourceId,
|
|
dependencyDepth: 0,
|
|
})),
|
|
connectionIndex: ids.map((resourceId) => ({
|
|
resourceId,
|
|
upstreamReferenceResourceIds: [],
|
|
downstreamReferenceResourceIds: [],
|
|
referenceEdgeIds: [],
|
|
taskFlowIds: [],
|
|
})),
|
|
unresolvedReferenceResourceIds: [],
|
|
cyclicResourceIds: [],
|
|
cyclicTaskIds: [],
|
|
producerMappingTruncated: false,
|
|
};
|
|
}
|
|
if (
|
|
command === 'read_local_project_resource_canvas_layout' ||
|
|
command === 'update_local_project_resource_canvas_layout'
|
|
) {
|
|
const layout = {
|
|
schemaVersion: 'game-creator-resource-layout.v1',
|
|
projectId,
|
|
mode: args?.mode,
|
|
revision: Number(args?.expectedRevision ?? 0) + 1,
|
|
positions: args?.positions ?? [],
|
|
updatedAt: 1,
|
|
};
|
|
return command.startsWith('update_')
|
|
? { status: 'updated', layout }
|
|
: layout;
|
|
}
|
|
if (command === 'read_local_project_text_preview') {
|
|
if (rejectPreview) throw new Error('文档读取失败');
|
|
const path = args?.relativePath;
|
|
return {
|
|
path,
|
|
mediaType: 'application/json',
|
|
byteLen: 20,
|
|
content:
|
|
path === 'assets/ordinary.json'
|
|
? '{"text":"# 不应当作标题"}'
|
|
: JSON.stringify({
|
|
schemaVersion: 'game-creator-ui-design-state.v1',
|
|
state: emptyState,
|
|
}),
|
|
...(path === 'ui/design.json' ? { uiDesignAssetId: 'design' } : {}),
|
|
};
|
|
}
|
|
if (command === 'load_ui_design_state')
|
|
return { revision: 0, state: emptyState };
|
|
if (
|
|
command === 'list_pending_local_project_resource_edits' ||
|
|
command === 'list_local_project_asset_generations'
|
|
)
|
|
return [];
|
|
throw new Error(`unexpected command ${command}`);
|
|
},
|
|
);
|
|
window.__TAURI__ = { core: { invoke } } as unknown as typeof window.__TAURI__;
|
|
const onManifestChange = vi.fn();
|
|
render(
|
|
<ProjectDevelopmentView
|
|
projectPath={projectPath}
|
|
projectName={manifest.name}
|
|
manifest={manifest}
|
|
attachments={[]}
|
|
recentRunStatus={null}
|
|
recentRunStopReason={null}
|
|
chat={<div>对话</div>}
|
|
onManifestChange={onManifestChange}
|
|
/>,
|
|
);
|
|
fireEvent.click(await screen.findByRole('button', { name: '打开文档' }));
|
|
const card = (id: string) =>
|
|
document.querySelector<HTMLElement>(
|
|
`.game-resource-book-scene-card.is-expanded [data-resource-card-id="asset:${id}"]`,
|
|
)!;
|
|
await waitFor(() =>
|
|
expect(card('design')?.getAttribute('data-preview-status')).toBe(
|
|
rejectPreview ? 'failed' : 'loaded',
|
|
),
|
|
);
|
|
return { card, invoke, onManifestChange };
|
|
}
|
|
|
|
afterEach(() => {
|
|
delete window.__TAURI__;
|
|
});
|
|
|
|
describe('JSON 画布卡片与入口', () => {
|
|
it.each(['ui-design', 'document'] as const)(
|
|
'合法 State 的 %s 登记显示 UI 设计,进入现有编辑器且不修改 manifest',
|
|
async (kind) => {
|
|
const { card, invoke, onManifestChange } = await mountJsonCanvas(kind);
|
|
const design = card('design');
|
|
expect(design.getAttribute('data-json-presentation')).toBe('ui-design');
|
|
expect(within(design).getByText('UI 设计')).not.toBeNull();
|
|
expect(within(design).getByText('UI 编辑器')).not.toBeNull();
|
|
expect(design.textContent).not.toContain('schemaVersion');
|
|
fireEvent.click(design.querySelector('button')!);
|
|
fireEvent.click(await screen.findByRole('button', { name: 'UI 编辑器' }));
|
|
await waitFor(() =>
|
|
expect(invoke).toHaveBeenCalledWith('load_ui_design_state', {
|
|
input: {
|
|
projectPath,
|
|
expectedProjectId: projectId,
|
|
assetId: 'design',
|
|
},
|
|
}),
|
|
);
|
|
expect(onManifestChange).not.toHaveBeenCalled();
|
|
},
|
|
);
|
|
|
|
it('普通 JSON 和伪 UI 文本保持 JSON 展示,无编辑器入口,详情按代码渲染', async () => {
|
|
const { card } = await mountJsonCanvas();
|
|
for (const id of ['ordinary', 'spoof']) {
|
|
const element = card(id);
|
|
expect(element.getAttribute('data-json-presentation')).toBe('json');
|
|
expect(
|
|
element.querySelector('[data-resource-type="JSON"]'),
|
|
).not.toBeNull();
|
|
expect(element.textContent).not.toContain('schemaVersion');
|
|
fireEvent.click(element.querySelector('button')!);
|
|
expect(screen.queryByRole('button', { name: 'UI 编辑器' })).toBeNull();
|
|
}
|
|
fireEvent.click(card('ordinary').querySelector('button')!);
|
|
fireEvent.click(await screen.findByRole('button', { name: '预览' }));
|
|
const dialog = await screen.findByRole('dialog', { name: '文档预览' });
|
|
await waitFor(() =>
|
|
expect(dialog.querySelector('pre code')?.textContent).toContain(
|
|
'{"text":"# 不应当作标题"}',
|
|
),
|
|
);
|
|
expect(
|
|
within(dialog).queryByRole('heading', { name: '不应当作标题' }),
|
|
).toBeNull();
|
|
});
|
|
|
|
it('JSON 读取失败不授予编辑入口,仍可查看明确的读取错误', async () => {
|
|
const { card } = await mountJsonCanvas('unknown', true);
|
|
fireEvent.click(card('design').querySelector('button')!);
|
|
expect(screen.queryByRole('button', { name: 'UI 编辑器' })).toBeNull();
|
|
fireEvent.click(screen.getByRole('button', { name: '预览' }));
|
|
expect(await screen.findByRole('alert')).not.toBeNull();
|
|
});
|
|
});
|