Files
longhaul.cpp/tools/ui/tests/client/update-message-in-place.svelte.test.ts
T
2026-07-29 01:00:10 -05:00

65 lines
2.4 KiB
TypeScript

// Pins the contract that makes streaming cheap: updateMessageAtIndex mutates the
// existing message object instead of replacing it.
//
// Replacing it (`{ ...old, ...updates }`) changes the array slot, which
// invalidates every consumer that merely walks the list - ChatMessages'
// `displayMessages` rebuilds entries for EVERY message in the conversation. That
// made per-token cost scale with conversation length (1.26ms at 1 prior message
// -> 3.07ms at 40). Mutating in place keeps it flat.
import { describe, it, expect } from 'vitest';
import { conversationsStore } from '$lib/stores/conversations.svelte';
import type { DatabaseMessage } from '$lib/types';
import { MessageRole } from '$lib/enums';
function makeMessage(id: string): DatabaseMessage {
return {
id,
convId: 'c1',
type: 'text',
timestamp: 0,
role: MessageRole.ASSISTANT,
content: '',
parent: null,
children: []
} as DatabaseMessage;
}
describe('conversationsStore.updateMessageAtIndex', () => {
it('mutates in place, preserving object identity', () => {
conversationsStore.activeMessages = [makeMessage('a'), makeMessage('b')];
const before = conversationsStore.activeMessages[1];
conversationsStore.updateMessageAtIndex(1, { content: 'hello' });
expect(conversationsStore.activeMessages[1].content).toBe('hello');
expect(conversationsStore.activeMessages[1]).toBe(before);
conversationsStore.activeMessages = [];
});
it('leaves other messages and unrelated fields untouched', () => {
conversationsStore.activeMessages = [makeMessage('a'), makeMessage('b')];
const untouched = conversationsStore.activeMessages[0];
conversationsStore.updateMessageAtIndex(1, { content: 'x', model: 'm1' });
expect(conversationsStore.activeMessages[0]).toBe(untouched);
expect(conversationsStore.activeMessages[0].content).toBe('');
expect(conversationsStore.activeMessages[1].model).toBe('m1');
expect(conversationsStore.activeMessages[1].id).toBe('b');
conversationsStore.activeMessages = [];
});
it('is a no-op for an index of -1 or out of range', () => {
conversationsStore.activeMessages = [makeMessage('a')];
expect(() => conversationsStore.updateMessageAtIndex(-1, { content: 'x' })).not.toThrow();
expect(() => conversationsStore.updateMessageAtIndex(9, { content: 'x' })).not.toThrow();
expect(conversationsStore.activeMessages[0].content).toBe('');
conversationsStore.activeMessages = [];
});
});