mirror of
https://github.com/nexu-io/open-design.git
synced 2026-07-06 22:31:53 +08:00
94 lines
3.0 KiB
TypeScript
94 lines
3.0 KiB
TypeScript
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]);
|
|
});
|
|
});
|