Files
DeskClaw/tests/unit/chat-event-dedupe.test.ts
2026-04-19 19:36:33 +08:00

141 lines
3.6 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest';
import { extractText } from '@/pages/Chat/message-utils';
const { gatewayRpcMock, hostApiFetchMock, agentsState } = vi.hoisted(() => ({
gatewayRpcMock: vi.fn(),
hostApiFetchMock: vi.fn(),
agentsState: {
agents: [] as Array<Record<string, unknown>>,
},
}));
vi.mock('@/stores/gateway', () => ({
useGatewayStore: {
getState: () => ({
status: { state: 'running', port: 18789 },
rpc: gatewayRpcMock,
}),
},
}));
vi.mock('@/stores/agents', () => ({
useAgentsStore: {
getState: () => agentsState,
},
}));
vi.mock('@/lib/host-api', () => ({
hostApiFetch: (...args: unknown[]) => hostApiFetchMock(...args),
}));
describe('chat event dedupe', () => {
beforeEach(() => {
vi.resetModules();
window.localStorage.clear();
gatewayRpcMock.mockReset();
hostApiFetchMock.mockReset();
agentsState.agents = [];
});
it('keeps processing delta events without seq for the same run', async () => {
const { useChatStore } = await import('@/stores/chat');
useChatStore.setState({
currentSessionKey: 'agent:main:main',
currentAgentId: 'main',
sessions: [{ key: 'agent:main:main' }],
messages: [],
sessionLabels: {},
sessionLastActivity: {},
sending: false,
activeRunId: null,
streamingText: '',
streamingMessage: null,
streamingTools: [],
pendingFinal: true,
lastUserMessageAt: null,
pendingToolImages: [],
error: null,
loading: false,
thinkingLevel: null,
});
useChatStore.getState().handleChatEvent({
state: 'delta',
runId: 'run-no-seq',
sessionKey: 'agent:main:main',
message: {
role: 'assistant',
id: 'reply-stream',
content: [{ type: 'text', text: 'Checked X.' }],
},
});
useChatStore.getState().handleChatEvent({
state: 'delta',
runId: 'run-no-seq',
sessionKey: 'agent:main:main',
message: {
role: 'assistant',
id: 'reply-stream',
content: [
{ type: 'text', text: 'Checked X.' },
{ type: 'text', text: 'Checked X. Here is the summary.' },
],
},
});
expect(extractText(useChatStore.getState().streamingMessage)).toBe('Checked X. Here is the summary.');
});
it('still dedupes repeated delta events when seq matches', async () => {
const { useChatStore } = await import('@/stores/chat');
useChatStore.setState({
currentSessionKey: 'agent:main:main',
currentAgentId: 'main',
sessions: [{ key: 'agent:main:main' }],
messages: [],
sessionLabels: {},
sessionLastActivity: {},
sending: false,
activeRunId: null,
streamingText: '',
streamingMessage: null,
streamingTools: [],
pendingFinal: false,
lastUserMessageAt: null,
pendingToolImages: [],
error: null,
loading: false,
thinkingLevel: null,
});
useChatStore.getState().handleChatEvent({
state: 'delta',
runId: 'run-with-seq',
sessionKey: 'agent:main:main',
seq: 3,
message: {
role: 'assistant',
id: 'reply-stream',
content: [{ type: 'text', text: 'first version' }],
},
});
useChatStore.getState().handleChatEvent({
state: 'delta',
runId: 'run-with-seq',
sessionKey: 'agent:main:main',
seq: 3,
message: {
role: 'assistant',
id: 'reply-stream',
content: [{ type: 'text', text: 'duplicate version should be ignored' }],
},
});
expect(extractText(useChatStore.getState().streamingMessage)).toBe('first version');
});
});