Files
edentext/tests/unit/track-changes-replace.test.ts
Steffen Becker 96863395f3 fix: recording a replacement keeps the text it replaced
Only a pure delete was turned into a deletion mark; typing or pasting over
a selection ran as it was, so the replaced text left the document unmarked
and rejecting the change lost the original for good. A one-step replacement
now marks its range deleted and puts the new content after it, as both word
processors do.
2026-08-16 02:36:31 +02:00

57 lines
2.2 KiB
TypeScript
Raw Permalink 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.
// Typing or pasting over a selection is a deletion and an insertion at once. Recording
// it as the insertion alone dropped the replaced text, so rejecting lost the original.
import { describe, it, expect } from 'vitest';
import { Editor } from '@tiptap/core';
import Document from '@tiptap/extension-document';
import Paragraph from '@tiptap/extension-paragraph';
import Text from '@tiptap/extension-text';
import { TextSelection } from '@tiptap/pm/state';
import { TrackChanges, Insertion, Deletion, revisions } from '../../src/lib/editor/extensions/trackChanges';
function editorOver(text: string) {
const el = document.createElement('div');
document.body.appendChild(el);
return new Editor({
element: el,
extensions: [Document, Paragraph, Text, Insertion, Deletion,
TrackChanges.configure({ recording: () => true, author: () => 'Ada' })],
content: `<p>${text}</p>`,
});
}
// Replace "quick" (positions 5–10 in "The quick fox") with "slow".
async function replaceQuick(editor: Editor) {
const { state, view } = editor;
view.dispatch(state.tr
.setSelection(TextSelection.create(state.doc, 5, 10))
.replaceSelectionWith(state.schema.text('slow'), false));
await Promise.resolve();
}
describe('recording a replacement', () => {
it('keeps the replaced text as a deletion beside the insertion', async () => {
const editor = editorOver('The quick fox');
await replaceQuick(editor);
expect(editor.state.doc.textContent).toBe('The quickslow fox');
expect(revisions(editor.state.doc).map((r) => [r.kind, r.text]))
.toEqual([['deletion', 'quick'], ['insertion', 'slow']]);
editor.destroy();
});
it('restores the original on reject', async () => {
const editor = editorOver('The quick fox');
await replaceQuick(editor);
editor.commands.rejectRevisions(true);
expect(editor.state.doc.textContent).toBe('The quick fox');
editor.destroy();
});
it('keeps only the new text on accept', async () => {
const editor = editorOver('The quick fox');
await replaceQuick(editor);
editor.commands.acceptRevisions(true);
expect(editor.state.doc.textContent).toBe('The slow fox');
editor.destroy();
});
});