Files
mdg-filius/packages/editor/test/textBinding.test.ts
T
julian.kiedaischandClaude Opus 5.5 2c7733b575 feat: F4.6f2 the text editor on the document's Y.Text, files both ways while the simulation runs
Oracle TextEditorWindowRun uses Java's text editor window and its file
dialog as a person does. The open file's text is its Y.Text in the
document (plan 7.3): typed is in the file at once, for everyone and for
the running engine. Core openText/saveTextAs without Java's mistakes
(lost saves after save-as onto a file or a removed file, files named
'ordner/d.txt' or ''); doc files.ts (changeFiles, fileText, nodeFiles,
readFilesInto); the room takes a person's files into the engine at once
and writes the engine's files back at the tick they changed; the editor's
window, Java's dialog, the text area following others' changes with the
caret in place, its own undo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 17:50:27 +00:00

57 lines
2.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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);
});
});