diff --git a/packages/ckeditor5-find-and-replace/src/replaceallcommand.ts b/packages/ckeditor5-find-and-replace/src/replaceallcommand.ts index 5bce774445c..ccd961baf1b 100644 --- a/packages/ckeditor5-find-and-replace/src/replaceallcommand.ts +++ b/packages/ckeditor5-find-and-replace/src/replaceallcommand.ts @@ -50,9 +50,12 @@ export default class ReplaceAllCommand extends ReplaceCommandBase { ) ), null as Collection | null )!; if ( results.length ) { - [ ...results ].forEach( searchResult => { - // Just reuse logic from the replace command to replace a single match. - this._replace( newText, searchResult ); + // Wrapped in single change will batch it into one transaction. + model.change( () => { + [ ...results ].forEach( searchResult => { + // Just reuse logic from the replace command to replace a single match. + this._replace( newText, searchResult ); + } ); } ); } } diff --git a/packages/ckeditor5-find-and-replace/tests/replaceallcommand.js b/packages/ckeditor5-find-and-replace/tests/replaceallcommand.js index 5ba6e77c46c..8fbce50cefe 100644 --- a/packages/ckeditor5-find-and-replace/tests/replaceallcommand.js +++ b/packages/ckeditor5-find-and-replace/tests/replaceallcommand.js @@ -66,7 +66,7 @@ describe( 'ReplaceAllCommand', () => { expect( editor.getData() ).to.equal( '

Foo bar baz

Foo bar baz

' ); } ); - it( 'should replace all passed results in the document document', () => { + it( 'should replace all passed results in the document', () => { setData( model, 'Foo bar [b]az[Foo] bar baz' ); const ranges = editor.model.document.selection.getRanges(); @@ -154,5 +154,47 @@ describe( 'ReplaceAllCommand', () => { expect( getData( editor.model, { withoutSelection: true } ) ).to.equal( 'Aaa Boo Coo Daa' ); } ); + + it( 'should restore every text occurrences replaced by `replace all` in the document at one undo step', () => { + setData( model, 'Foo bar bazFoo bar bazFoo bar baz' ); + + editor.execute( 'replaceAll', 'new', 'bar' ); + + expect( editor.getData() ).to.equal( '

Foo new baz

Foo new baz

Foo new baz

' ); + + editor.execute( 'undo' ); + + expect( editor.getData() ).to.equal( '

Foo bar baz

Foo bar baz

Foo bar baz

' ); + } ); + + it( 'should restore every text occurrences replaced by `replace all` in multiple roots at one undo step', async () => { + class MultiRootEditor extends ModelTestEditor { + constructor( config ) { + super( config ); + + this.model.document.createRoot( '$root', 'second' ); + } + } + + const multiRootEditor = await MultiRootEditor + .create( { plugins: [ FindAndReplaceEditing, Paragraph, UndoEditing ] } ); + + setData( multiRootEditor.model, 'Foo bar baz', { rootName: 'main' } ); + setData( multiRootEditor.model, 'Ra baz baz', { rootName: 'second' } ); + + const { results } = multiRootEditor.execute( 'find', 'z' ); + + multiRootEditor.execute( 'replaceAll', 'r', results ); + + expect( multiRootEditor.getData( { rootName: 'main' } ) ).to.equal( '

Foo bar bar

' ); + expect( multiRootEditor.getData( { rootName: 'second' } ) ).to.equal( '

Ra bar bar

' ); + + multiRootEditor.execute( 'undo' ); + + expect( multiRootEditor.getData( { rootName: 'main' } ) ).to.equal( '

Foo bar baz

' ); + expect( multiRootEditor.getData( { rootName: 'second' } ) ).to.equal( '

Ra baz baz

' ); + + await multiRootEditor.destroy(); + } ); } ); } );