594063a1e0
- 按 vitest 0.34 的参数元组口径改写 mock 泛型 - 修正 noUncheckedIndexedAccess 下的下标访问 - 补齐与当前类型脱节的测试桩字段 - 按 scripts/*.mjs 的运行时契约补局部类型声明
102 lines
3.4 KiB
TypeScript
102 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([]);
|
|
});
|