119 lines
4.2 KiB
TypeScript
119 lines
4.2 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { buildStateContext } from '@/lib/orchestration/summarizers/state-context';
|
|
import type { StatelessChatRequest } from '@/lib/types/chat';
|
|
|
|
function makeStoreState(whiteboardElements: unknown[]): StatelessChatRequest['storeState'] {
|
|
return {
|
|
stage: {
|
|
id: 'stage-1',
|
|
name: 'Code lesson',
|
|
whiteboard: [{ id: 'whiteboard-1', elements: whiteboardElements }],
|
|
},
|
|
scenes: [],
|
|
currentSceneId: null,
|
|
mode: 'autonomous',
|
|
whiteboardOpen: true,
|
|
} as unknown as StatelessChatRequest['storeState'];
|
|
}
|
|
|
|
function bigCodeBlock(id: string, lineCount: number) {
|
|
return {
|
|
id,
|
|
type: 'code',
|
|
language: 'python',
|
|
fileName: `${id}.py`,
|
|
lines: Array.from({ length: lineCount }, (_, i) => ({
|
|
id: `${id}-L${i}`,
|
|
content: `${id}_line_${i} = ${i}`,
|
|
})),
|
|
};
|
|
}
|
|
|
|
describe('buildStateContext whiteboard code summary', () => {
|
|
it('bounds a single very large code block instead of dumping every line', () => {
|
|
const context = buildStateContext(makeStoreState([bigCodeBlock('code', 5000)]));
|
|
|
|
// The tail is an omitted count, not a line-by-line dump...
|
|
expect(context).toContain('more line(s) omitted');
|
|
// ...and a late line's content is not rendered.
|
|
expect(context).not.toContain('code_line_4999 = 4999');
|
|
// The whole state context stays within a deterministic bound driven by the
|
|
// shared code-line budget plus the small non-code overhead.
|
|
expect(context.length).toBeLessThan(3000);
|
|
});
|
|
|
|
it('shares one deterministic budget across multiple large code blocks', () => {
|
|
const oneBlock = buildStateContext(makeStoreState([bigCodeBlock('a', 2000)]));
|
|
const fourBlocks = buildStateContext(
|
|
makeStoreState([
|
|
bigCodeBlock('a', 2000),
|
|
bigCodeBlock('b', 2000),
|
|
bigCodeBlock('c', 2000),
|
|
bigCodeBlock('d', 2000),
|
|
]),
|
|
);
|
|
|
|
// Four large blocks do not multiply the code dump: the budget is shared, so
|
|
// going from one to four blocks only adds bounded per-block headers, not
|
|
// four full code listings.
|
|
expect(fourBlocks.length - oneBlock.length).toBeLessThan(600);
|
|
expect(fourBlocks.length).toBeLessThan(3500);
|
|
expect(fourBlocks).toContain('more line(s) omitted');
|
|
});
|
|
|
|
it('renders a normal small code block in full', () => {
|
|
const context = buildStateContext(
|
|
makeStoreState([
|
|
{
|
|
id: 'small',
|
|
type: 'code',
|
|
language: 'python',
|
|
lines: [
|
|
{ id: 'L1', content: 'x = 1' },
|
|
{ id: 'L2', content: 'y = 2' },
|
|
],
|
|
},
|
|
]),
|
|
);
|
|
|
|
expect(context).toContain('L1: x = 1');
|
|
expect(context).toContain('L2: y = 2');
|
|
expect(context).not.toContain('more line(s) omitted');
|
|
});
|
|
|
|
it('gives a newer persisted code block budget priority over an older large one', () => {
|
|
// stage.whiteboard elements are appended in creation order, so an OLD large
|
|
// block sits before a NEWER small one. With source-order budgeting the old
|
|
// block would starve the new one; newest-first budgeting must let the newer
|
|
// persisted block keep its element id, line ids, and content so a later
|
|
// agent can edit it — while the display order stays board order.
|
|
const context = buildStateContext(
|
|
makeStoreState([
|
|
bigCodeBlock('old', 2000),
|
|
{
|
|
id: 'new',
|
|
type: 'code',
|
|
language: 'python',
|
|
lines: [
|
|
{ id: 'N1', content: 'fresh = 1' },
|
|
{ id: 'N2', content: 'fresh = 2' },
|
|
],
|
|
},
|
|
]),
|
|
);
|
|
|
|
// The newer small block is fully editable despite being created after a
|
|
// much larger block.
|
|
expect(context).toContain('[id:new]');
|
|
expect(context).toContain('N1: fresh = 1');
|
|
expect(context).toContain('N2: fresh = 2');
|
|
// The older large block is announced but squeezed to the omitted tail.
|
|
expect(context).toContain('[id:old]');
|
|
expect(context).toContain('more line(s) omitted');
|
|
expect(context).not.toContain('old_line_1999 = 1999');
|
|
|
|
// Display order is unchanged: the old block still renders before the new one.
|
|
expect(context.indexOf('[id:old]')).toBeLessThan(context.indexOf('[id:new]'));
|
|
});
|
|
});
|