688cf7a971
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m28s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m59s
Project CI / Backend tests (pull_request) Successful in 5m1s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 8m7s
Project CI / Native shell tests (pull_request) Successful in 6m17s
Project CI / Frontend tests (pull_request) Successful in 2m9s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 9m29s
Project CI / AI game creator shell web tests (pull_request) Successful in 1m42s
Project CI / Repository checks (pull_request) Successful in 2m18s
按消息 ID 合并实时正文与正式消息,保留逐步显示进度 分离请求收尾与动画结束,隔离旧回合事件及异步返回 独立展示工具状态,保留 Provider 重试的正文重置语义 补充重复终态、整块回复、多消息及回合隔离回归测试 同步更新策划展示规范与排障记录
100 lines
3.4 KiB
TypeScript
100 lines
3.4 KiB
TypeScript
// @vitest-environment jsdom
|
|
import { act, cleanup, renderHook } from '@testing-library/react';
|
|
import { afterEach, beforeEach, expect, it, vi } from 'vitest';
|
|
|
|
import type { DesignView } from '../src/app/types';
|
|
import { useDesignReplyAnimation } from '../src/view/project-development/planning/useDesignReplyAnimation';
|
|
|
|
beforeEach(() => vi.useFakeTimers());
|
|
afterEach(() => {
|
|
cleanup();
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
function view(messages: DesignView['messages'], running = false): DesignView {
|
|
return {
|
|
session: {
|
|
sessionId: 's',
|
|
projectId: 'p',
|
|
currentPhase: 'concept',
|
|
approvedPhases: [],
|
|
pendingApproval: null,
|
|
pendingClarification: null,
|
|
turnIndex: 1,
|
|
lastError: null,
|
|
},
|
|
messages,
|
|
running,
|
|
canRetry: false,
|
|
};
|
|
}
|
|
|
|
it('keeps each reply progress across tool snapshots and repeated completion', () => {
|
|
const { result } = renderHook(useDesignReplyAnimation);
|
|
const history = { id: 'history', role: 'assistant', text: '历史' };
|
|
const first = { id: 't:response:0', role: 'assistant', text: '第一条回复' };
|
|
const second = { id: 't:response:1', role: 'assistant', text: '第二条回复' };
|
|
act(() => {
|
|
result.current.reset([history.id]);
|
|
result.current.receiveText(first.id, first.text);
|
|
vi.advanceTimersByTime(100);
|
|
});
|
|
expect(result.current.replies[0].visible).toBe('第一');
|
|
act(() =>
|
|
result.current.receiveView(
|
|
view(
|
|
[history, first, { id: 'tool', role: 'tool', text: '工具已完成' }],
|
|
true,
|
|
),
|
|
),
|
|
);
|
|
expect(result.current.replies).toHaveLength(1);
|
|
expect(result.current.replies[0].visible).toBe('第一');
|
|
act(() => result.current.receiveText(second.id, second.text));
|
|
const terminal = view([history, first, second]);
|
|
act(() => {
|
|
result.current.receiveView(terminal);
|
|
result.current.receiveView(terminal);
|
|
});
|
|
expect(result.current.replies.map((reply) => reply.visible)).toEqual([
|
|
'第一',
|
|
'',
|
|
]);
|
|
act(() => vi.advanceTimersByTime(500));
|
|
act(() => result.current.receiveView(terminal));
|
|
expect(result.current.replies.map((reply) => reply.visible)).toEqual([
|
|
first.text,
|
|
second.text,
|
|
]);
|
|
});
|
|
|
|
it('resets only an unpersisted retry attempt and discards failed partial output', () => {
|
|
const { result } = renderHook(useDesignReplyAnimation);
|
|
act(() => {
|
|
result.current.receiveText('t:response:0', '尝试失败');
|
|
vi.advanceTimersByTime(100);
|
|
});
|
|
expect(result.current.replies[0].visible).toBe('尝试');
|
|
act(() => result.current.receiveText('t:response:0', ''));
|
|
expect(result.current.replies[0].visible).toBe('');
|
|
const saved = { id: 't:response:0', role: 'assistant', text: '成功' };
|
|
act(() => {
|
|
result.current.receiveText(saved.id, saved.text);
|
|
result.current.receiveView(view([saved], true));
|
|
vi.advanceTimersByTime(100);
|
|
result.current.receiveText(saved.id, '');
|
|
result.current.receiveText('t:response:1', '未被接受的文本');
|
|
result.current.receiveView(view([saved]));
|
|
});
|
|
expect(result.current.replies).toEqual([
|
|
{ messageId: saved.id, target: '成功', visible: '成功', persisted: true },
|
|
]);
|
|
act(() => {
|
|
result.current.receiveText('t:response:2', '连接中断');
|
|
result.current.discardUnpersisted();
|
|
});
|
|
expect(result.current.replies).toHaveLength(1);
|
|
act(() => result.current.reset([saved.id]));
|
|
expect(result.current.replies).toEqual([]);
|
|
});
|