Spaces:
Paused
Paused
| import { describe, expect, it } from 'vitest'; | |
| import type { ChatMessage } from './contracts'; | |
| import { | |
| normalizeConversationToolsEnabled, | |
| normalizeConversationToolEvents, | |
| planMessageRegeneration, | |
| planUserMessageEdit, | |
| } from './conversations'; | |
| const messages: ChatMessage[] = [ | |
| { id: 'u1', role: 'user', content: 'First', timestamp: '10:00' }, | |
| { id: 'a1', role: 'assistant', content: 'First answer', timestamp: '10:01' }, | |
| { id: 'u2', role: 'user', content: 'Second', timestamp: '10:02' }, | |
| { id: 'a2', role: 'assistant', content: 'Second answer', timestamp: '10:03' }, | |
| { id: 'u3', role: 'user', content: 'Third', timestamp: '10:04' }, | |
| { id: 'a3', role: 'assistant', content: 'Third answer', timestamp: '10:05' }, | |
| ]; | |
| describe('planMessageRegeneration', () => { | |
| it('keeps the selected user message and truncates everything after it', () => { | |
| expect(planMessageRegeneration(messages, 'u2')).toEqual({ | |
| baseMessages: messages.slice(0, 2), | |
| userMessage: messages[2], | |
| discardedMessageIds: ['a2', 'u3', 'a3'], | |
| }); | |
| }); | |
| it('replaces the selected assistant answer from its preceding user turn', () => { | |
| expect(planMessageRegeneration(messages, 'a2')).toEqual({ | |
| baseMessages: messages.slice(0, 2), | |
| userMessage: messages[2], | |
| discardedMessageIds: ['a2', 'u3', 'a3'], | |
| }); | |
| }); | |
| it('does not regenerate unknown or system-only turns', () => { | |
| expect(planMessageRegeneration(messages, 'missing')).toBeNull(); | |
| expect(planMessageRegeneration([ | |
| { id: 'system', role: 'system', content: 'Rules', timestamp: '10:00' }, | |
| ], 'system')).toBeNull(); | |
| }); | |
| }); | |
| describe('planUserMessageEdit', () => { | |
| it('keeps earlier history, edits the selected user turn, and discards everything after it', () => { | |
| expect(planUserMessageEdit(messages, 'u2', ' Revised second ', 123)).toEqual({ | |
| baseMessages: messages.slice(0, 2), | |
| userMessage: { ...messages[2], content: 'Revised second', editedAt: 123 }, | |
| discardedMessageIds: ['a2', 'u3', 'a3'], | |
| }); | |
| }); | |
| it('rejects assistant, missing, and empty edits', () => { | |
| expect(planUserMessageEdit(messages, 'a2', 'No')).toBeNull(); | |
| expect(planUserMessageEdit(messages, 'missing', 'No')).toBeNull(); | |
| expect(planUserMessageEdit(messages, 'u2', ' ')).toBeNull(); | |
| }); | |
| }); | |
| describe('normalizeConversationToolEvents', () => { | |
| it('migrates old conversations by rebuilding the log from persisted tool runs', () => { | |
| const withTools: ChatMessage[] = [{ | |
| id: 'assistant', | |
| role: 'assistant', | |
| content: 'Done', | |
| timestamp: '10:01', | |
| tools: [ | |
| { id: 'complete', name: 'artifact_deploy', input: '{}', output: '{"ok":true}', state: 'complete' }, | |
| { id: 'interrupted', name: 'artifact_write', input: '{}', state: 'running' }, | |
| ], | |
| }]; | |
| expect(normalizeConversationToolEvents(undefined, withTools)).toEqual([ | |
| { | |
| id: 'complete', | |
| timestamp: '10:01', | |
| label: 'artifact_deploy', | |
| detail: 'local result restored from chat history', | |
| state: 'complete', | |
| }, | |
| { | |
| id: 'interrupted', | |
| timestamp: '10:01', | |
| label: 'artifact_write', | |
| detail: 'interrupted before completion', | |
| state: 'error', | |
| }, | |
| ]); | |
| }); | |
| it('prefers saved live-event details for a known tool call', () => { | |
| const withTools: ChatMessage[] = [{ | |
| id: 'assistant', | |
| role: 'assistant', | |
| content: '', | |
| timestamp: '10:01', | |
| tools: [{ id: 'call', name: 'artifact_test', input: '{}', output: '{}', state: 'complete' }], | |
| }]; | |
| expect(normalizeConversationToolEvents([{ | |
| id: 'call', | |
| timestamp: '10:01:01.500', | |
| label: 'artifact_test', | |
| detail: 'local result returned to model', | |
| state: 'complete', | |
| }], withTools)).toEqual([{ | |
| id: 'call', | |
| timestamp: '10:01:01.500', | |
| label: 'artifact_test', | |
| detail: 'local result returned to model', | |
| state: 'complete', | |
| }]); | |
| }); | |
| }); | |
| describe('normalizeConversationToolsEnabled', () => { | |
| it('restores an enabled chat and keeps older records safely disabled', () => { | |
| expect(normalizeConversationToolsEnabled(true)).toBe(true); | |
| expect(normalizeConversationToolsEnabled(false)).toBe(false); | |
| expect(normalizeConversationToolsEnabled(undefined)).toBe(false); | |
| }); | |
| }); | |