79 lines
2.8 KiB
JavaScript
79 lines
2.8 KiB
JavaScript
import { test } from 'node:test';
|
|||
|
|
import assert from 'node:assert/strict';
|
||
|
|
|
||
|
|
import { reconcileTimelineItems } from '../app/src/renderer/src/session-timeline-items.mjs';
|
||
|
|
|
||
|
|
function message(uuid, overrides = {}) {
|
||
|
|
return {
|
||
|
|
uuid,
|
||
|
|
type: 'assistant',
|
||
|
|
text: 'message',
|
||
|
|
tool_calls: [],
|
||
|
|
...overrides,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
test('timeline items preserve the rendered order and identity of every navigable root', () => {
|
||
|
|
const messages = [
|
||
|
|
message('meta', { is_meta: 1 }),
|
||
|
|
message('workflow', {
|
||
|
|
text: '',
|
||
|
|
tool_calls: [
|
||
|
|
{ id: 'workflow-call', name: 'Workflow', workflow: { workflow_name: 'Build' } },
|
||
|
|
{ id: 'bash-call', name: 'Bash' },
|
||
|
|
],
|
||
|
|
}),
|
||
|
|
message('skill', {
|
||
|
|
text: '',
|
||
|
|
tool_calls: [{ id: 'skill-call', name: 'Skill' }],
|
||
|
|
}),
|
||
|
|
message('thinking', { content_type: 'thinking' }),
|
||
|
|
message('normal'),
|
||
|
|
];
|
||
|
|
|
||
|
|
const items = reconcileTimelineItems([], messages);
|
||
|
|
|
||
|
|
assert.deepEqual(items.map(item => ({
|
||
|
|
key: item.key,
|
||
|
|
kind: item.kind,
|
||
|
|
anchorUuid: item.anchorUuid,
|
||
|
|
messageUuid: item.messageUuid,
|
||
|
|
})), [
|
||
|
|
{ key: 'meta:meta', kind: 'meta', anchorUuid: 'meta', messageUuid: 'meta' },
|
||
|
|
{ key: 'workflow:workflow', kind: 'workflow', anchorUuid: 'workflow', messageUuid: 'workflow' },
|
||
|
|
{ key: 'workflow-tools:workflow', kind: 'workflow-tools', anchorUuid: 'workflow-tools', messageUuid: 'workflow' },
|
||
|
|
{ key: 'skill:skill', kind: 'skill', anchorUuid: 'skill', messageUuid: 'skill' },
|
||
|
|
{ key: 'thinking:thinking', kind: 'thinking', anchorUuid: 'thinking', messageUuid: 'thinking' },
|
||
|
|
{ key: 'message:normal', kind: 'message', anchorUuid: 'normal', messageUuid: 'normal' },
|
||
|
|
]);
|
||
|
|
assert.equal(items[1].workflowCall.id, 'workflow-call');
|
||
|
|
assert.deepEqual(items[2].toolCalls.map(call => call.id), ['bash-call']);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('snapshot reconciliation reuses unchanged timeline items and replaces only updated roots', () => {
|
||
|
|
const first = message('first');
|
||
|
|
const second = message('second');
|
||
|
|
const initial = reconcileTimelineItems([], [first, second]);
|
||
|
|
const updatedSecond = { ...second, text: 'updated' };
|
||
|
|
|
||
|
|
const reconciled = reconcileTimelineItems(initial, [first, updatedSecond]);
|
||
|
|
|
||
|
|
assert.equal(reconciled[0], initial[0]);
|
||
|
|
assert.notEqual(reconciled[1], initial[1]);
|
||
|
|
assert.equal(reconciled[1].message, updatedSecond);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('tail appends do not rebuild existing timeline items', () => {
|
||
|
|
const existingMessages = Array.from({ length: 1000 }, (_, index) => message(`message-${index}`));
|
||
|
|
const initial = reconcileTimelineItems([], existingMessages);
|
||
|
|
const appended = reconcileTimelineItems(initial, [
|
||
|
|
...existingMessages,
|
||
|
|
message('message-1000'),
|
||
|
|
]);
|
||
|
|
|
||
|
|
assert.equal(appended.length, 1001);
|
||
|
|
assert.equal(appended[0], initial[0]);
|
||
|
|
assert.equal(appended[999], initial[999]);
|
||
|
|
assert.equal(appended[1000].key, 'message:message-1000');
|
||
|
|
});
|