import { expect, test, vi } from 'vitest' import { fileReferenceToString, getAnswerPartFromAssistantMessage, groupMessageIntoSteps } from './utils' import { FILE_REFERENCE_REGEX, ANSWER_TAG } from './constants'; import { SBChatMessage, SBChatMessagePart } from './types'; // Mock the env module vi.mock('@/env.mjs', () => ({ env: { SOURCEBOT_CHAT_FILE_MAX_CHARACTERS: 4000, } })); test('fileReferenceToString formats file references correctly', () => { expect(fileReferenceToString({ fileName: 'auth.ts' })).toBe('@file:{auth.ts}'); expect(fileReferenceToString({ fileName: 'auth.ts', range: { startLine: 45, endLine: 60, } })).toBe('@file:{auth.ts:45-60}'); }); test('fileReferenceToString matches FILE_REFERENCE_REGEX', () => { expect(FILE_REFERENCE_REGEX.test(fileReferenceToString({ fileName: 'auth.ts' }))).toBe(true); FILE_REFERENCE_REGEX.lastIndex = 0; expect(FILE_REFERENCE_REGEX.test(fileReferenceToString({ fileName: 'auth.ts', range: { startLine: 45, endLine: 60, } }))).toBe(true); }); test('groupMessageIntoSteps returns an empty array when there are no parts', () => { const parts: SBChatMessagePart[] = [] const steps = groupMessageIntoSteps(parts); expect(steps).toEqual([]); }); test('groupMessageIntoSteps returns a single group when there is only one step-start part', () => { const parts: SBChatMessagePart[] = [ { type: 'step-start', }, { type: 'text', text: 'Hello, world!', } ] const steps = groupMessageIntoSteps(parts); expect(steps).toEqual([ [ { type: 'step-start', }, { type: 'text', text: 'Hello, world!', } ] ]); }); test('groupMessageIntoSteps returns a multiple groups when there is multiple step-start parts', () => { const parts: SBChatMessagePart[] = [ { type: 'step-start', }, { type: 'text', text: 'Hello, world!', }, { type: 'step-start', }, { type: 'text', text: 'Ok lets go', }, ] const steps = groupMessageIntoSteps(parts); expect(steps).toEqual([ [ { type: 'step-start', }, { type: 'text', text: 'Hello, world!', } ], [ { type: 'step-start', }, { type: 'text', text: 'Ok lets go', } ] ]); }); test('groupMessageIntoSteps returns a single group when there is no step-start part', () => { const parts: SBChatMessagePart[] = [ { type: 'text', text: 'Hello, world!', }, { type: 'text', text: 'Ok lets go', }, ] const steps = groupMessageIntoSteps(parts); expect(steps).toEqual([ [ { type: 'text', text: 'Hello, world!', }, { type: 'text', text: 'Ok lets go', } ] ]); }); test('getAnswerPartFromAssistantMessage returns text part when it starts with ANSWER_TAG while not streaming', () => { const message: SBChatMessage = { role: 'assistant', parts: [ { type: 'text', text: 'Some initial text' }, { type: 'text', text: `${ANSWER_TAG}This is the answer to your question.` } ] } as SBChatMessage; const result = getAnswerPartFromAssistantMessage(message, false); expect(result).toEqual({ type: 'text', text: `${ANSWER_TAG}This is the answer to your question.` }); }); test('getAnswerPartFromAssistantMessage returns text part when it starts with ANSWER_TAG while streaming', () => { const message: SBChatMessage = { role: 'assistant', parts: [ { type: 'text', text: 'Some initial text' }, { type: 'text', text: `${ANSWER_TAG}This is the answer to your question.` } ] } as SBChatMessage; const result = getAnswerPartFromAssistantMessage(message, true); expect(result).toEqual({ type: 'text', text: `${ANSWER_TAG}This is the answer to your question.` }); }); test('getAnswerPartFromAssistantMessage returns last text part as fallback when not streaming and no ANSWER_TAG', () => { const message: SBChatMessage = { role: 'assistant', parts: [ { type: 'text', text: 'First text part' }, { type: 'tool-call', id: 'call-1', name: 'search', args: {} }, { type: 'text', text: 'This is the last text part without answer tag' } ] } as SBChatMessage; const result = getAnswerPartFromAssistantMessage(message, false); expect(result).toEqual({ type: 'text', text: 'This is the last text part without answer tag' }); }); test('getAnswerPartFromAssistantMessage returns undefined when streaming and no ANSWER_TAG', () => { const message: SBChatMessage = { role: 'assistant', parts: [ { type: 'text', text: 'Some text without answer tag' }, { type: 'text', text: 'Another text part' } ] } as SBChatMessage; const result = getAnswerPartFromAssistantMessage(message, true); expect(result).toBeUndefined(); });