sourcebot/packages/web/src/features/chat/utils.test.ts

241 lines
6 KiB
TypeScript
Raw Normal View History

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();
});