// // Copyright 2026 DXOS.org // import { EditorState } from '@codemirror/state'; import { describe, test } from 'vitest'; import { PROMPT_ELEMENT, createTurnSource } from './turns.ts'; describe('createTurnSource', () => { test('folds the response below a prompt', ({ expect }) => { const turns = scan(['Hi', 'Answer.'].join('\n')); expect(turns).toHaveLength(1); expect(turns[0].head).toBe('Hi'); expect(turns[0].folded).toBe('Answer.'); }); test('anchors the gutter marker on the first line of a multi-line head', ({ expect }) => { const doc = ['line one', 'line two', 'Answer.'].join('\n'); const turns = scan(doc); expect(turns[0].headLine).toBe(0); expect(turns[0].folded).toBe('Answer.'); }); // A toolbar rendered under the prompt belongs to the head: folding the response must not hide the // control that folds it. test('keeps a trailing self-closing toolbar tag out of the fold range', ({ expect }) => { const doc = ['Hi', '', '', 'Answer.'].join('\n'); const turns = scan(doc); expect(turns).toHaveLength(1); expect(turns[0].head).toContain(' { const doc = ['Hi', '', '', 'Answer.'].join('\n'); const turns = scan(doc); expect(turns[0].folded).toBe('Answer.'); }); test('does not absorb a paired tag that opens the response', ({ expect }) => { const doc = ['Hi', 'Thinking', 'Answer.'].join('\n'); const turns = scan(doc); expect(turns[0].head).toBe('Hi'); expect(turns[0].folded).toBe(['Thinking', 'Answer.'].join('\n')); }); test('ends a turn where the next prompt begins, trailing whitespace trimmed', ({ expect }) => { const doc = ['One', 'First.', '', 'Two', 'Second.', ''].join('\n'); const turns = scan(doc); expect(turns).toHaveLength(2); expect(turns[0].folded).toBe('First.'); expect(turns[1].folded).toBe('Second.'); }); test('yields no turn for a prompt with an empty response', ({ expect }) => { expect(scan('Hi')).toHaveLength(0); expect(scan(['Hi', ''].join('\n'))).toHaveLength(0); }); }); /** Resolves each turn into the text it treats as head vs. the text it would hide. */ const scan = (doc: string) => { const state = EditorState.create({ doc }); return createTurnSource(PROMPT_ELEMENT)(state).map((turn) => ({ head: state.doc.sliceString(turn.headLineFrom, turn.from).trim(), folded: state.doc.sliceString(turn.from, turn.to).trim(), headLine: state.doc.lineAt(turn.headLineFrom).number - 1, })); };