// Where the caret and the selection go when another changes the open file's // text (textBinding.ts): moved by what was inserted or deleted before them, // left before what another inserts just at them — the deltas as Yjs gives them. import { describe, expect, it } from 'vitest'; import * as Y from 'yjs'; import { mapIndex, type TextDelta } from '../src/desktop/textBinding.js'; /** The delta of a change to a text that held `before`. */ function deltaOf(before: string, change: (text: Y.Text) => void): TextDelta { const doc = new Y.Doc(); const text = doc.getText('t'); text.insert(0, before); let delta: TextDelta = []; text.observe((event) => (delta = event.delta as TextDelta)); change(text); return delta; } describe('a place in the text after another’s change', () => { it('moves by what was inserted before it, not by what came after it or just at it', () => { const insert = deltaOf('Hallo Welt', (text) => text.insert(6, 'schöne ')); expect([0, 5, 6, 7, 10].map((index) => mapIndex(index, insert))).toEqual([0, 5, 6, 14, 17]); expect( mapIndex( 3, deltaOf('abc', (text) => text.insert(0, 'xy')), ), ).toBe(5); expect( mapIndex( 0, deltaOf('abc', (text) => text.insert(0, 'xy')), ), ).toBe(0); }); it('moves back by what was deleted before it, to where the deletion began when it lay inside', () => { const removed = deltaOf('Hallo schöne Welt', (text) => text.delete(6, 7)); expect([0, 6, 9, 13, 17].map((index) => mapIndex(index, removed))).toEqual([0, 6, 6, 6, 10]); }); it('takes a replacement as its deletion and then its insertion: a place at the end of what was replaced stays before the new text', () => { const replaced = deltaOf('Erster Stand', (text) => { text.doc!.transact(() => { text.delete(0, 6); text.insert(0, 'Zweiter'); }); }); expect(replaced).toEqual([{ delete: 6 }, { insert: 'Zweiter' }]); expect([0, 3, 6, 7, 12].map((index) => mapIndex(index, replaced))).toEqual([0, 0, 0, 8, 13]); // An embedded object counts as one place. expect(mapIndex(2, [{ insert: { image: 'x' } }])).toBe(3); expect(mapIndex(2, [{ retain: 2 }, { insert: { image: 'x' } }])).toBe(2); }); });