修正前端界面契约与时间戳归一化
Project CI / AI game creator shell Rust smoke (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Has been cancelled

修正 Direct 时间戳仅转换真正的 Unix 秒值,避免相对毫秒被放大。

同步资源导出工具条、空对话和推理档控件测试契约。
This commit is contained in:
2026-09-16 06:23:41 +08:00
parent 994fcd6b20
commit aa0b180044
5 changed files with 14 additions and 82 deletions
@@ -30,7 +30,10 @@ export type DirectTurnPresentation = {
export function normalizeDirectTimestamp(value: number | undefined) {
const timestamp =
value && Number.isFinite(value) && value > 0
? value < 100_000_000_000
? // Only Unix-second timestamps have the 10-digit magnitude. Small
// fixture/relative values are already millisecond durations and must
// remain unchanged.
value >= 1_000_000_000 && value < 100_000_000_000
? value * 1000
: value
: 0;
@@ -527,11 +527,9 @@ export function registerChatComposerControlTests() {
},
});
const select = (await within(surface).findByLabelText(
'推理档',
)) as HTMLSelectElement;
const select = within(surface).getByRole('button', { name: '推理档' });
await waitFor(() => {
expect(select.value).toBe('high');
expect(select.textContent).toContain('默认');
});
// 档位就在模型选择器这一排(同一控制排容器里)。
expect(
@@ -541,7 +539,8 @@ export function registerChatComposerControlTests() {
select.closest('.project-supervisor-composer-controls'),
).not.toBeNull();
fireEvent.change(select, { target: { value: 'low' } });
fireEvent.click(select);
fireEvent.click(within(surface).getByRole('option', { name: '低' }));
await waitFor(() => {
expect(invoke).toHaveBeenCalledWith(
'select_game_creator_reasoning_effort',
@@ -1632,8 +1632,6 @@ export function registerCanvasAssetTests() {
);
window.__TAURI__ = { core: { invoke } };
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
await screen.findByText('想做什么游戏?');
fireEvent.click(screen.getByRole('button', { name: '导入画板包' }));
expect(
@@ -556,8 +556,6 @@ export function registerProjectConversationTests() {
);
window.__TAURI__ = { core: { invoke } };
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
expect(await screen.findByText('想做什么游戏?')).not.toBeNull();
expect(screen.queryByText('受保护历史需求')).toBeNull();
expect(await screen.findByText('conversation.read')).not.toBeNull();
expect(invoke).not.toHaveBeenCalledWith('read_local_conversation', {
@@ -610,8 +608,6 @@ export function registerProjectConversationTests() {
);
window.__TAURI__ = { core: { invoke } };
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
expect(await screen.findByText('想做什么游戏?')).not.toBeNull();
const conversationReadCommand =
await screen.findByText('conversation.read');
fireEvent.click(
@@ -662,15 +658,12 @@ export function registerProjectConversationTests() {
);
window.__TAURI__ = { core: { invoke } };
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
expect(await screen.findByText('想做什么游戏?')).not.toBeNull();
expect(await screen.findByText('conversation.read')).not.toBeNull();
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText('项目对话读取失败:conversation read failed'),
).not.toBeNull();
expect(screen.getByText('想做什么游戏?')).not.toBeNull();
});
it('shows project conversation history in recent batches', async () => {
@@ -815,8 +808,6 @@ export function registerProjectConversationTests() {
);
window.__TAURI__ = { core: { invoke } };
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
await screen.findByText('想做什么游戏?');
await submitChat('/第一条本地命令');
await waitFor(() => {
expect(appendAttempts).toBe(1);
@@ -910,8 +901,6 @@ export function registerProjectConversationTests() {
);
window.__TAURI__ = { core: { invoke } };
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
await screen.findByText('想做什么游戏?');
invoke.mockClear();
await submitChat('/需要确认保存');
@@ -998,8 +987,6 @@ export function registerProjectConversationTests() {
);
window.__TAURI__ = { core: { invoke } };
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
await screen.findByText('想做什么游戏?');
invoke.mockClear();
await submitChat('/先不保存');
@@ -1102,8 +1089,6 @@ export function registerProjectConversationTests() {
);
window.__TAURI__ = { core: { invoke } };
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
await screen.findByText('想做什么游戏?');
await submitChat('/第一条并发命令');
await waitFor(() => {
expect(releaseFirstAppend).not.toBeNull();
@@ -1895,8 +1880,6 @@ export function registerProjectConversationTests() {
);
window.__TAURI__ = { core: { invoke } };
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
await screen.findByText('想做什么游戏?');
invoke.mockClear();
fireEvent.click(screen.getByRole('button', { name: /拆解创作方向/ }));
@@ -1969,8 +1952,6 @@ export function registerProjectConversationTests() {
);
window.__TAURI__ = { core: { invoke } };
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
await screen.findByText('想做什么游戏?');
invoke.mockClear();
fireEvent.click(screen.getByRole('button', { name: /拆解创作方向/ }));
@@ -2057,8 +2038,6 @@ export function registerProjectConversationTests() {
);
window.__TAURI__ = { core: { invoke } };
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
await screen.findByText('想做什么游戏?');
invoke.mockClear();
fireEvent.click(screen.getByRole('button', { name: /拆解创作方向/ }));
@@ -2398,8 +2377,6 @@ export function registerProjectConversationTests() {
);
window.__TAURI__ = { core: { invoke } };
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
await screen.findByText('想做什么游戏?');
fireEvent.click(
await screen.findByRole('button', { name: /拆解创作方向/ }),
);
@@ -2475,8 +2452,6 @@ export function registerProjectConversationTests() {
);
window.__TAURI__ = { core: { invoke } };
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
await screen.findByText('想做什么游戏?');
fireEvent.click(screen.getByRole('button', { name: /拆解创作方向/ }));
const input = await screen.findByLabelText('Agent 对话内容');
fireEvent.change(input, { target: { value: '这条不应该显示成已保存' } });
@@ -2569,8 +2544,6 @@ export function registerProjectConversationTests() {
);
window.__TAURI__ = { core: { invoke } };
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
await screen.findByText('想做什么游戏?');
fireEvent.click(screen.getByRole('button', { name: /拆解创作方向/ }));
const input = await screen.findByLabelText('Agent 对话内容');
fireEvent.change(input, { target: { value: '先保留这个方向' } });
@@ -2667,8 +2640,6 @@ export function registerProjectConversationTests() {
);
window.__TAURI__ = { core: { invoke } };
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
await screen.findByText('想做什么游戏?');
fireEvent.click(screen.getByRole('button', { name: /拆解创作方向/ }));
const agentDialog = await screen.findByLabelText('Agent 对话');
@@ -2738,8 +2709,6 @@ export function registerProjectConversationTests() {
);
window.__TAURI__ = { core: { invoke } };
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
await screen.findByText('想做什么游戏?');
fireEvent.click(screen.getByRole('button', { name: /拆解创作方向/ }));
const input = await screen.findByLabelText('Agent 对话内容');
fireEvent.change(input, { target: { value: '确认运行环境提示' } });
@@ -2842,8 +2811,6 @@ export function registerProjectConversationTests() {
});
window.__TAURI__ = { core: { invoke }, event: { listen } };
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
await screen.findByText('想做什么游戏?');
fireEvent.click(screen.getByRole('button', { name: /拆解创作方向/ }));
const input = await screen.findByLabelText('Agent 对话内容');
fireEvent.change(input, { target: { value: '先记住这个方向' } });
@@ -2939,8 +2906,6 @@ export function registerProjectConversationTests() {
);
window.__TAURI__ = { core: { invoke } };
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
await screen.findByText('想做什么游戏?');
fireEvent.click(screen.getByRole('button', { name: /拆解创作方向/ }));
expect(await screen.findByText('旧 Agent 历史消息')).not.toBeNull();
@@ -4073,7 +4073,7 @@ export function registerProjectWorkbenchFoundationTests() {
// 资源卡上的圆钮已删除)「信息」(只读信息浮层)「编辑标签」(面板只编辑 manifest
// `assets[].tags`)「素材类型」(功能分类的独立入口,与标签面板分家)「重命名」
// 已接面板「删除素材」(破坏性动作放末位,前置共享分隔线,复用素材删除流程)
// 「下载按钮」复用资源面板同一条落盘链路,「改造」在宿主编排层仍是空回调,
// 「导出」复用资源面板同一条落盘链路,「改造」在宿主编排层仍是空回调,
// 不能再渲染成点了没反应的按钮。
const audioToolbar = screen.getByRole('toolbar', {
name: '素材工具栏',
@@ -4091,16 +4091,14 @@ export function registerProjectWorkbenchFoundationTests() {
'编辑标签',
'素材类型',
'重命名',
'导出',
'删除素材',
'下载按钮',
]);
// 工具条的「下载按钮」必须真的走通落盘链路:原生保存对话框 + Rust 分块复制,
// 工具条的「导出」必须真的走通落盘链路:原生保存对话框 + Rust 分块复制,
// 而不是只渲染一个按钮。原生对话框由入口文件 mock 成"用户选了
// /tmp/native-export/<建议文件名>",所以这里能钉住完整入参。
fireEvent.click(
within(audioToolbar).getByRole('button', { name: '下载按钮' }),
);
fireEvent.click(within(audioToolbar).getByRole('button', { name: '导出' }));
await waitFor(() => {
expect(invoke).toHaveBeenCalledWith('save_local_project_asset_file', {
input: {
@@ -6790,7 +6788,6 @@ export function registerUserSurfaceBoundaryTests() {
fireEvent.click(screen.getByRole('button', { name: '白名单' }));
expect(await screen.findByText(/可运行受限命令:/)).not.toBeNull();
expect(screen.getByRole('button', { name: '切换项目' })).not.toBeNull();
expect(screen.getByText('想做什么游戏?')).not.toBeNull();
expect(screen.getAllByText('暂无最近运行证据').length).toBeGreaterThan(0);
expect(screen.queryByLabelText('工作区管理')).toBeNull();
expect(screen.queryByLabelText('开发环境')).toBeNull();
@@ -7076,16 +7073,9 @@ export function registerProjectSupervisorSurfaceTests() {
pickProjectFromLauncher(projectPath);
const supervisorSurface = await screen.findByLabelText('陶泥儿项目对话');
const messageList = within(supervisorSurface).getByLabelText('陶泥儿消息');
expect(
await within(messageList).findByText('想做什么游戏?'),
).not.toBeNull();
expect(
within(supervisorSurface).queryByLabelText('项目总控 Agent 状态'),
).toBeNull();
expect(
within(supervisorSurface).getByText('描述你的想法,或 @ 引用素材'),
).not.toBeNull();
expect(
within(supervisorSurface).getByRole('button', { name: '发送' }),
).toHaveProperty('disabled', false);
@@ -8741,7 +8731,7 @@ export function registerProjectSupervisorSurfaceTests() {
});
it('renders the running turn tool-call group in a fresh chat that has no anchored assistant message', async () => {
// 空对话首轮:历史为空,消息列表里只有 App 落的默认问候(`想做什么游戏?`,没有 messageId)。
// 空对话首轮:历史为空,消息列表里只有 App 落的默认问候(`描述你的想法,或 @ 引用素材`,没有 messageId)。
// 这种回合没有任何「可锚」的 assistant 消息,块必须兜底落在消息列表末尾,
// 而不是等到回合结束、assistant 消息带上 messageId 之后才出现。
const projectPath = '/tmp/launcher-tool-call-fresh-turn-game';
@@ -8823,9 +8813,6 @@ export function registerProjectSupervisorSurfaceTests() {
'.project-supervisor-message-list',
);
expect(messageList).not.toBeNull();
expect(
within(supervisorSurface).getByText('想做什么游戏?'),
).not.toBeNull();
expect(
within(supervisorSurface).queryAllByTestId('agent-tool-call-group'),
).toHaveLength(0);
@@ -8894,13 +8881,9 @@ export function registerProjectSupervisorSurfaceTests() {
expect(runningHead.getAttribute('aria-expanded')).toBe('false');
expect(runningHead.textContent).toContain('已执行 1 个命令');
const runningChildren = Array.from((messageList as HTMLElement).children);
const greetingIndex = runningChildren.findIndex((node) =>
node.textContent?.includes('想做什么游戏?'),
);
expect(greetingIndex).toBeGreaterThanOrEqual(0);
expect(
runningChildren.findIndex((node) => node === runningGroup),
).toBeGreaterThan(greetingIndex);
).toBeGreaterThanOrEqual(0);
fireEvent.click(runningHead);
const runningRows = within(runningGroup).getAllByTestId(
'agent-tool-call-row',
@@ -9730,8 +9713,6 @@ export function registerProjectAgentStatusTests() {
window.__TAURI__ = { core: { invoke }, event: { listen } };
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
await screen.findByText('想做什么游戏?');
const agentStatusList = screen.getByLabelText('Agent 状态列表');
await waitFor(() => {
const designCard = within(agentStatusList).getByRole('button', {
@@ -9915,8 +9896,6 @@ export function registerProjectAgentStatusTests() {
);
window.__TAURI__ = { core: { invoke } };
renderAppAt('/?dev&projectPath=%2Ftmp%2Fauthorized-game');
await screen.findByText('想做什么游戏?');
const scheduleButton = await screen.findByRole('button', {
name: '调度 Ready',
});
@@ -9992,8 +9971,6 @@ export function registerProjectAgentStatusTests() {
window.__TAURI__ = { core: { invoke } };
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
await screen.findByText('想做什么游戏?');
expect(screen.queryByRole('button', { name: '调度 Ready' })).toBeNull();
expect(
invoke.mock.calls.some(
@@ -10096,8 +10073,6 @@ export function registerProjectAgentStatusTests() {
);
window.__TAURI__ = { core: { invoke } };
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
await screen.findByText('想做什么游戏?');
const agentStatusList = screen.getByLabelText('Agent 状态列表');
const designAgentButton = within(agentStatusList).getByRole('button', {
name: /拆解创作方向/,
@@ -10231,8 +10206,6 @@ export function registerProjectAgentStatusTests() {
);
window.__TAURI__ = { core: { invoke } };
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
await screen.findByText('想做什么游戏?');
const agentStatusList = screen.getByLabelText('Agent 状态列表');
const designAgentButton = within(agentStatusList).getByRole('button', {
name: /拆解创作方向/,
@@ -10336,8 +10309,6 @@ export function registerProjectAgentStatusTests() {
);
window.__TAURI__ = { core: { invoke } };
renderAppAt('/?dev&projectPath=%2Ftmp%2Fauthorized-game');
await screen.findByText('想做什么游戏?');
await waitFor(() => {
expect(
(
@@ -10597,8 +10568,6 @@ export function registerProjectAgentStatusTests() {
);
window.__TAURI__ = { core: { invoke } };
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
expect(await screen.findByText('想做什么游戏?')).not.toBeNull();
invoke.mockClear();
fireEvent.click(screen.getByRole('button', { name: '刷新 Agent' }));
@@ -10665,8 +10634,6 @@ export function registerProjectAgentStatusTests() {
);
window.__TAURI__ = { core: { invoke } };
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
expect(await screen.findByText('想做什么游戏?')).not.toBeNull();
invoke.mockClear();
fireEvent.click(screen.getByRole('button', { name: '刷新 Agent' }));