import { describe, expect, it } from 'vitest'; import { buildQuestionFormKey, mergeServerMessagesIntoConversation, } from '../../src/components/ProjectView'; import type { ChatMessage, ProjectFile } from '../../src/types'; describe('buildQuestionFormKey', () => { it('is stable across a streaming form-id change (no remount mid-answer)', () => { // The streaming preview shows the `discovery` fallback id until the body id // streams in; a form that emits answerable questions before its id flips // the parsed id late. The React key must NOT change across that flip, or // the panel remounts and drops in-progress selections. Same conversation + // message ⇒ same key regardless of the parsed id. const early = buildQuestionFormKey('conv-1', 'msg-1', true); const settled = buildQuestionFormKey('conv-1', 'msg-1', true); expect(early).toBe('conv-1:msg-1'); expect(settled).toBe(early); }); it('gives a distinct key to a later form in a different assistant message', () => { // A second discovery form (same `discovery` template id) lives in its own // assistant message, so it still gets its own key and replays the reveal — // without folding the id into the key. expect(buildQuestionFormKey('conv-1', 'msg-1', true)).not.toBe( buildQuestionFormKey('conv-1', 'msg-2', true), ); }); it('returns null until a form, conversation, and message are all present', () => { expect(buildQuestionFormKey(null, 'msg-1', true)).toBeNull(); expect(buildQuestionFormKey('conv-1', null, true)).toBeNull(); expect(buildQuestionFormKey('conv-1', 'msg-1', false)).toBeNull(); }); }); describe('mergeServerMessagesIntoConversation', () => { it('adds server-created CTA messages while preserving local produced files', () => { const producedFile: ProjectFile = { name: 'deck.html', size: 1024, mtime: 1, kind: 'html', mime: 'text/html', }; const localMessages: ChatMessage[] = [ { id: 'user-1', role: 'user', content: 'Use this SKILL.md', }, { id: 'assistant-1', role: 'assistant', content: 'Done', runStatus: 'succeeded', producedFiles: [producedFile], }, ]; const serverMessages: ChatMessage[] = [ { id: 'user-1', role: 'user', content: 'Use this SKILL.md', }, { id: 'assistant-1', role: 'assistant', content: 'Done', runStatus: 'succeeded', }, { id: 'cta-1', role: 'assistant', content: '', events: [ { kind: 'plugin_candidate', candidateId: 'candidate-1', title: 'Main', description: 'This repo looks like a plugin.', }, ], }, ]; const merged = mergeServerMessagesIntoConversation(localMessages, serverMessages); expect(merged.map((message) => message.id)).toEqual(['user-1', 'assistant-1', 'cta-1']); expect(merged[1]?.producedFiles).toEqual([producedFile]); }); });