Browse Source

Code style: wraped tests in the main describe().

Piotrek Koszuliński 9 years ago
parent
commit
37c48900eb

+ 11 - 11
packages/ckeditor5-undo/tests/basecommand.js

@@ -6,22 +6,22 @@
 import ModelTestEditor from 'tests/core/_utils/modeltesteditor.js';
 import BaseCommand from 'ckeditor5/undo/basecommand.js';
 
-let editor, doc, root, base;
+describe( 'BaseCommand', () => {
+	let editor, doc, root, base;
 
-beforeEach( () => {
-	editor = new ModelTestEditor();
-	base = new BaseCommand( editor );
+	beforeEach( () => {
+		editor = new ModelTestEditor();
+		base = new BaseCommand( editor );
 
-	doc = editor.document;
+		doc = editor.document;
 
-	root = doc.getRoot();
-} );
+		root = doc.getRoot();
+	} );
 
-afterEach( () => {
-	base.destroy();
-} );
+	afterEach( () => {
+		base.destroy();
+	} );
 
-describe( 'BaseCommand', () => {
 	describe( 'constructor()', () => {
 		it( 'should create command with empty batch stack', () => {
 			expect( base._checkEnabled() ).to.be.false;

+ 222 - 220
packages/ckeditor5-undo/tests/redocommand.js

@@ -10,244 +10,246 @@ import UndoCommand from 'ckeditor5/undo/undocommand.js';
 import RedoCommand from 'ckeditor5/undo/redocommand.js';
 import { itemAt, getText } from 'tests/engine/model/_utils/utils.js';
 
-let editor, doc, root, redo, undo;
+describe( 'RedoCommand', () => {
+	let editor, doc, root, redo, undo;
 
-beforeEach( () => {
-	editor = new ModelTestEditor();
-	redo = new RedoCommand( editor );
+	beforeEach( () => {
+		editor = new ModelTestEditor();
+		redo = new RedoCommand( editor );
 
-	doc = editor.document;
+		doc = editor.document;
 
-	root = doc.getRoot();
-} );
+		root = doc.getRoot();
+	} );
 
-afterEach( () => {
-	redo.destroy();
-} );
+	afterEach( () => {
+		redo.destroy();
+	} );
 
-describe( 'RedoCommand', () => {
-	describe( '_execute', () => {
-		const p = pos => new Position( root, [].concat( pos ) );
-		const r = ( a, b ) => new Range( p( a ), p( b ) );
-
-		let batch0, batch1, batch2;
-		let batches = new Set();
-
-		beforeEach( () => {
-			undo = new UndoCommand( editor );
-
-			// Simple integration with undo.
-			undo.on( 'revert', ( evt, undoneBatch, undoingBatch ) => {
-				if ( !batches.has( undoingBatch ) ) {
-					redo.addBatch( undoingBatch );
-					batches.add( undoingBatch );
-				}
+	describe( 'RedoCommand', () => {
+		describe( '_execute', () => {
+			const p = pos => new Position( root, [].concat( pos ) );
+			const r = ( a, b ) => new Range( p( a ), p( b ) );
+
+			let batch0, batch1, batch2;
+			let batches = new Set();
+
+			beforeEach( () => {
+				undo = new UndoCommand( editor );
+
+				// Simple integration with undo.
+				undo.on( 'revert', ( evt, undoneBatch, undoingBatch ) => {
+					if ( !batches.has( undoingBatch ) ) {
+						redo.addBatch( undoingBatch );
+						batches.add( undoingBatch );
+					}
+				} );
+
+				/*
+				 [root]
+				 - {}
+				 */
+				editor.document.selection.setRanges( [ r( 0, 0 ) ] );
+				batch0 = doc.batch();
+				undo.addBatch( batch0 );
+				batch0.insert( p( 0 ), 'foobar' );
+				/*
+				 [root]
+				 - f
+				 - o
+				 - o
+				 - b
+				 - a
+				 - r{}
+				 */
+				// Let's make things spicy and this time, make a backward selection.
+				editor.document.selection.setRanges( [ r( 2, 4 ) ], true );
+				batch1 = doc.batch();
+				undo.addBatch( batch1 );
+				batch1.setAttribute( r( 2, 4 ), 'key', 'value' );
+				/*
+				 [root]
+				 - f
+				 - o
+				 - {o (key: value)
+				 - b} (key: value)
+				 - a
+				 - r
+				 */
+				editor.document.selection.setRanges( [ r( 1, 3 ) ] );
+				batch2 = doc.batch();
+				undo.addBatch( batch2 );
+				batch2.move( r( 1, 3 ), p( 6 ) );
+				/*
+				 [root]
+				 - f
+				 - b (key: value)
+				 - a
+				 - r
+				 - {o
+				 - o} (key: value)
+				 */
 			} );
 
-			/*
-			 [root]
-			 - {}
-			 */
-			editor.document.selection.setRanges( [ r( 0, 0 ) ] );
-			batch0 = doc.batch();
-			undo.addBatch( batch0 );
-			batch0.insert( p( 0 ), 'foobar' );
-			/*
-			 [root]
-			 - f
-			 - o
-			 - o
-			 - b
-			 - a
-			 - r{}
-			 */
-			// Let's make things spicy and this time, make a backward selection.
-			editor.document.selection.setRanges( [ r( 2, 4 ) ], true );
-			batch1 = doc.batch();
-			undo.addBatch( batch1 );
-			batch1.setAttribute( r( 2, 4 ), 'key', 'value' );
-			/*
-			 [root]
-			 - f
-			 - o
-			 - {o (key: value)
-			 - b} (key: value)
-			 - a
-			 - r
-			 */
-			editor.document.selection.setRanges( [ r( 1, 3 ) ] );
-			batch2 = doc.batch();
-			undo.addBatch( batch2 );
-			batch2.move( r( 1, 3 ), p( 6 ) );
-			/*
-			 [root]
-			 - f
-			 - b (key: value)
-			 - a
-			 - r
-			 - {o
-			 - o} (key: value)
-			 */
-		} );
-
-		it( 'should redo batch undone by undo command', () => {
-			undo._execute( batch2 );
-
-			redo._execute();
-			// Should be back at original state:
-			/*
-			 [root]
-			 - f
-			 - b (key: value)
-			 - a
-			 - r
-			 - {o
-			 - o} (key: value)
-			 */
-			expect( getText( root ) ).to.equal( 'fbaroo' );
-			expect( itemAt( root, 1 ).getAttribute( 'key' ) ).to.equal( 'value' );
-			expect( itemAt( root, 5 ).getAttribute( 'key' ) ).to.equal( 'value' );
-
-			expect( editor.document.selection.getRanges().next().value.isEqual( r( 4, 6 ) ) ).to.be.true;
-			expect( editor.document.selection.isBackward ).to.be.false;
-		} );
+			it( 'should redo batch undone by undo command', () => {
+				undo._execute( batch2 );
+
+				redo._execute();
+				// Should be back at original state:
+				/*
+				 [root]
+				 - f
+				 - b (key: value)
+				 - a
+				 - r
+				 - {o
+				 - o} (key: value)
+				 */
+				expect( getText( root ) ).to.equal( 'fbaroo' );
+				expect( itemAt( root, 1 ).getAttribute( 'key' ) ).to.equal( 'value' );
+				expect( itemAt( root, 5 ).getAttribute( 'key' ) ).to.equal( 'value' );
+
+				expect( editor.document.selection.getRanges().next().value.isEqual( r( 4, 6 ) ) ).to.be.true;
+				expect( editor.document.selection.isBackward ).to.be.false;
+			} );
 
-		it( 'should redo series of batches undone by undo command', () => {
-			undo._execute();
-			undo._execute();
-			undo._execute();
-
-			redo._execute();
-			// Should be like after applying `batch0`:
-			/*
-			 [root]
-			 - f
-			 - o
-			 - o
-			 - b
-			 - a
-			 - r{}
-			 */
-			expect( getText( root ) ).to.equal( 'foobar' );
-			expect( Array.from( root.getChildren() ).find( node => node.hasAttribute( 'key' ) ) ).to.be.undefined;
-
-			expect( editor.document.selection.getRanges().next().value.isEqual( r( 6, 6 ) ) ).to.be.true;
-			expect( editor.document.selection.isBackward ).to.be.false;
-
-			redo._execute();
-			// Should be like after applying `batch1`:
-			/*
-			 [root]
-			 - f
-			 - o
-			 - {o (key: value)
-			 - b} (key: value)
-			 - a
-			 - r
-			 */
-			expect( getText( root ) ).to.equal( 'foobar' );
-			expect( itemAt( root, 2 ).getAttribute( 'key' ) ).to.equal( 'value' );
-			expect( itemAt( root, 3 ).getAttribute( 'key' ) ).to.equal( 'value' );
-
-			expect( editor.document.selection.getRanges().next().value.isEqual( r( 2, 4 ) ) ).to.be.true;
-			expect( editor.document.selection.isBackward ).to.be.true;
-
-			redo._execute();
-			// Should be like after applying `batch2`:
-			/*
-			 [root]
-			 - f
-			 - b (key: value)
-			 - a
-			 - r
-			 - {o
-			 - o} (key: value)
-			 */
-			expect( getText( root ) ).to.equal( 'fbaroo' );
-			expect( itemAt( root, 1 ).getAttribute( 'key' ) ).to.equal( 'value' );
-			expect( itemAt( root, 5 ).getAttribute( 'key' ) ).to.equal( 'value' );
-
-			expect( editor.document.selection.getRanges().next().value.isEqual( r( 4, 6 ) ) ).to.be.true;
-			expect( editor.document.selection.isBackward ).to.be.false;
-		} );
+			it( 'should redo series of batches undone by undo command', () => {
+				undo._execute();
+				undo._execute();
+				undo._execute();
+
+				redo._execute();
+				// Should be like after applying `batch0`:
+				/*
+				 [root]
+				 - f
+				 - o
+				 - o
+				 - b
+				 - a
+				 - r{}
+				 */
+				expect( getText( root ) ).to.equal( 'foobar' );
+				expect( Array.from( root.getChildren() ).find( node => node.hasAttribute( 'key' ) ) ).to.be.undefined;
+
+				expect( editor.document.selection.getRanges().next().value.isEqual( r( 6, 6 ) ) ).to.be.true;
+				expect( editor.document.selection.isBackward ).to.be.false;
+
+				redo._execute();
+				// Should be like after applying `batch1`:
+				/*
+				 [root]
+				 - f
+				 - o
+				 - {o (key: value)
+				 - b} (key: value)
+				 - a
+				 - r
+				 */
+				expect( getText( root ) ).to.equal( 'foobar' );
+				expect( itemAt( root, 2 ).getAttribute( 'key' ) ).to.equal( 'value' );
+				expect( itemAt( root, 3 ).getAttribute( 'key' ) ).to.equal( 'value' );
+
+				expect( editor.document.selection.getRanges().next().value.isEqual( r( 2, 4 ) ) ).to.be.true;
+				expect( editor.document.selection.isBackward ).to.be.true;
+
+				redo._execute();
+				// Should be like after applying `batch2`:
+				/*
+				 [root]
+				 - f
+				 - b (key: value)
+				 - a
+				 - r
+				 - {o
+				 - o} (key: value)
+				 */
+				expect( getText( root ) ).to.equal( 'fbaroo' );
+				expect( itemAt( root, 1 ).getAttribute( 'key' ) ).to.equal( 'value' );
+				expect( itemAt( root, 5 ).getAttribute( 'key' ) ).to.equal( 'value' );
+
+				expect( editor.document.selection.getRanges().next().value.isEqual( r( 4, 6 ) ) ).to.be.true;
+				expect( editor.document.selection.isBackward ).to.be.false;
+			} );
 
-		it( 'should redo batch selectively undone by undo command', () => {
-			undo._execute( batch0 );
-			redo._execute();
-
-			// Should be back to original state:
-			/*
-			 [root]
-			 - f
-			 - b (key: value)
-			 - a
-			 - r
-			 - o
-			 - o{} (key: value)
-			 */
-			expect( getText( root ) ).to.equal( 'fbaroo' );
-			expect( itemAt( root, 1 ).getAttribute( 'key' ) ).to.equal( 'value' );
-			expect( itemAt( root, 5 ).getAttribute( 'key' ) ).to.equal( 'value' );
-
-			expect( editor.document.selection.getRanges().next().value.isEqual( r( 6, 6 ) ) ).to.be.true;
-			expect( editor.document.selection.isBackward ).to.be.false;
-		} );
+			it( 'should redo batch selectively undone by undo command', () => {
+				undo._execute( batch0 );
+				redo._execute();
+
+				// Should be back to original state:
+				/*
+				 [root]
+				 - f
+				 - b (key: value)
+				 - a
+				 - r
+				 - o
+				 - o{} (key: value)
+				 */
+				expect( getText( root ) ).to.equal( 'fbaroo' );
+				expect( itemAt( root, 1 ).getAttribute( 'key' ) ).to.equal( 'value' );
+				expect( itemAt( root, 5 ).getAttribute( 'key' ) ).to.equal( 'value' );
+
+				expect( editor.document.selection.getRanges().next().value.isEqual( r( 6, 6 ) ) ).to.be.true;
+				expect( editor.document.selection.isBackward ).to.be.false;
+			} );
 
-		it( 'should redo batch selectively undone by undo command #2', () => {
-			undo._execute( batch1 );
-			undo._execute( batch2 );
-			redo._execute();
-			redo._execute();
-
-			// Should be back to original state:
-			/*
-			 [root]
-			 - f
-			 - {b} (key: value)
-			 - a
-			 - r
-			 - o
-			 - o (key: value)
-			 */
-			expect( getText( root ) ).to.equal( 'fbaroo' );
-			expect( itemAt( root, 1 ).getAttribute( 'key' ) ).to.equal( 'value' );
-			expect( itemAt( root, 5 ).getAttribute( 'key' ) ).to.equal( 'value' );
-
-			expect( editor.document.selection.getRanges().next().value.isEqual( r( 1, 2 ) ) ).to.be.true;
-			expect( editor.document.selection.isBackward ).to.be.true;
-		} );
+			it( 'should redo batch selectively undone by undo command #2', () => {
+				undo._execute( batch1 );
+				undo._execute( batch2 );
+				redo._execute();
+				redo._execute();
+
+				// Should be back to original state:
+				/*
+				 [root]
+				 - f
+				 - {b} (key: value)
+				 - a
+				 - r
+				 - o
+				 - o (key: value)
+				 */
+				expect( getText( root ) ).to.equal( 'fbaroo' );
+				expect( itemAt( root, 1 ).getAttribute( 'key' ) ).to.equal( 'value' );
+				expect( itemAt( root, 5 ).getAttribute( 'key' ) ).to.equal( 'value' );
+
+				expect( editor.document.selection.getRanges().next().value.isEqual( r( 1, 2 ) ) ).to.be.true;
+				expect( editor.document.selection.isBackward ).to.be.true;
+			} );
 
-		it( 'should transform redo batch by changes written in history that happened after undo but before redo #2', () => {
-			// Now it is "fBaroO".
-			// Undo moving "oo" to the end of string. Now it is "foOBar". Capitals mean set attribute.
-			undo._execute();
+			it( 'should transform redo batch by changes written in history that happened after undo but before redo #2', () => {
+				// Now it is "fBaroO".
+				// Undo moving "oo" to the end of string. Now it is "foOBar". Capitals mean set attribute.
+				undo._execute();
 
-			// Remove "ar".
-			doc.batch().remove( r( 4, 6 ) );
+				// Remove "ar".
+				doc.batch().remove( r( 4, 6 ) );
 
-			// Undo setting attribute on "ob". Now it is "foob".
-			undo._execute();
+				// Undo setting attribute on "ob". Now it is "foob".
+				undo._execute();
 
-			// Append "xx" at the beginning. Now it is "xxfoob".
-			doc.batch().insert( p( 0 ), 'xx' );
+				// Append "xx" at the beginning. Now it is "xxfoob".
+				doc.batch().insert( p( 0 ), 'xx' );
 
-			// Redo setting attribute on "ob". Now it is "xxfoOB".
-			redo._execute();
+				// Redo setting attribute on "ob". Now it is "xxfoOB".
+				redo._execute();
 
-			expect( getText( root ) ).to.equal( 'xxfoob' );
-			expect( itemAt( root, 4 ).getAttribute( 'key' ) ).to.equal( 'value' );
-			expect( itemAt( root, 5 ).getAttribute( 'key' ) ).to.equal( 'value' );
-			expect( editor.document.selection.getFirstRange().isEqual( r( 4, 6 ) ) ).to.be.true;
-			expect( editor.document.selection.isBackward ).to.be.true;
+				expect( getText( root ) ).to.equal( 'xxfoob' );
+				expect( itemAt( root, 4 ).getAttribute( 'key' ) ).to.equal( 'value' );
+				expect( itemAt( root, 5 ).getAttribute( 'key' ) ).to.equal( 'value' );
+				expect( editor.document.selection.getFirstRange().isEqual( r( 4, 6 ) ) ).to.be.true;
+				expect( editor.document.selection.isBackward ).to.be.true;
 
-			// Redo moving "oo". Now it is "xxfBoO". Selection is expected to be on just moved "oO".
-			redo._execute();
+				// Redo moving "oo". Now it is "xxfBoO". Selection is expected to be on just moved "oO".
+				redo._execute();
 
-			expect( getText( root ) ).to.equal( 'xxfboo' );
-			expect( itemAt( root, 3 ).getAttribute( 'key' ) ).to.equal( 'value' );
-			expect( itemAt( root, 5 ).getAttribute( 'key' ) ).to.equal( 'value' );
-			expect( editor.document.selection.getFirstRange().isEqual( r( 4, 6 ) ) ).to.be.true;
-			expect( editor.document.selection.isBackward ).to.be.false;
+				expect( getText( root ) ).to.equal( 'xxfboo' );
+				expect( itemAt( root, 3 ).getAttribute( 'key' ) ).to.equal( 'value' );
+				expect( itemAt( root, 5 ).getAttribute( 'key' ) ).to.equal( 'value' );
+				expect( editor.document.selection.getFirstRange().isEqual( r( 4, 6 ) ) ).to.be.true;
+				expect( editor.document.selection.isBackward ).to.be.false;
+			} );
 		} );
 	} );
 } );

+ 340 - 338
packages/ckeditor5-undo/tests/undocommand.js

@@ -11,356 +11,358 @@ import UndoCommand from 'ckeditor5/undo/undocommand.js';
 import AttributeDelta from 'ckeditor5/engine/model/delta/attributedelta.js';
 import { itemAt, getText } from 'tests/engine/model/_utils/utils.js';
 
-let editor, doc, root, undo;
-
-beforeEach( () => {
-	editor = new ModelTestEditor();
-	undo = new UndoCommand( editor );
-
-	doc = editor.document;
-
-	root = doc.getRoot();
-} );
-
-afterEach( () => {
-	undo.destroy();
-} );
-
 describe( 'UndoCommand', () => {
-	const p = pos => new Position( root, [].concat( pos ) );
-	const r = ( a, b ) => new Range( p( a ), p( b ) );
-
-	describe( '_execute', () => {
-		let batch0, batch1, batch2, batch3;
-
-		beforeEach( () => {
-			/*
-			 [root]
-			 - {}
-			 */
-			editor.document.selection.setRanges( [ r( 0, 0 ) ] );
-			batch0 = doc.batch();
-			undo.addBatch( batch0 );
-			batch0.insert( p( 0 ), 'foobar' );
-			/*
-			 [root]
-			 - f
-			 - o
-			 - o
-			 - b
-			 - a
-			 - r{}
-			 */
-			// Let's make things spicy and this time, make a backward selection.
-			editor.document.selection.setRanges( [ r( 2, 4 ) ], true );
-			batch1 = doc.batch();
-			undo.addBatch( batch1 );
-			batch1.setAttribute( r( 2, 4 ), 'key', 'value' );
-			/*
-			 [root]
-			 - f
-			 - o
-			 - {o (key: value)
-			 - b} (key: value)
-			 - a
-			 - r
-			 */
-			editor.document.selection.setRanges( [ r( 1, 3 ) ] );
-			batch2 = doc.batch();
-			undo.addBatch( batch2 );
-			batch2.move( r( 1, 3 ), p( 6 ) );
-			/*
-			 [root]
-			 - f
-			 - b (key: value)
-			 - a
-			 - r
-			 - {o
-			 - o} (key: value)
-			 */
-			editor.document.selection.setRanges( [ r( 1, 4 ) ] );
-			batch3 = doc.batch();
-			undo.addBatch( batch3 );
-			batch3.wrap( r( 1, 4 ), 'p' );
-			/*
-			 [root]
-			 - f
-			 - [p]
-			 	- {b (key: value)
-			 	- a
-			 	- r}
-			 - o
-			 - o (key: value)
-			 */
-			editor.document.selection.setRanges( [ r( 0, 1 ) ] );
-			batch2.move( r( 0, 1 ), p( 3 ) );
-			/*
-			 [root]
-			 - [p]
-			 	- b (key: value)
-			 	- a
-			 	- r
-			 - o
-			 - f
-			 - o{} (key: value)
-			 */
-			editor.document.selection.setRanges( [ r( 4, 4 ) ] );
-		} );
-
-		it( 'should revert changes done by deltas from the batch that was most recently added to the command stack', () => {
-			undo._execute();
-
-			// Selection is restored. Wrap is removed:
-			/*
-			 [root]
-			 - {b (key: value)
-			 - a
-			 - r}
-			 - o
-			 - f
-			 - o (key: value)
-			 */
-
-			expect( getText( root ) ).to.equal( 'barofo' );
-			expect( itemAt( root, 0 ).getAttribute( 'key' ) ).to.equal( 'value' );
-			expect( itemAt( root, 5 ).getAttribute( 'key' ) ).to.equal( 'value' );
-
-			expect( editor.document.selection.getFirstRange().isEqual( r( 0, 3 ) ) ).to.be.true;
-			expect( editor.document.selection.isBackward ).to.be.false;
-
-			undo._execute();
-
-			// Two moves are removed:
-			/*
-			 [root]
-			 - f
-			 - {o
-			 - o} (key: value)
-			 - b (key: value)
-			 - a
-			 - r
-			 */
-
-			expect( getText( root ) ).to.equal( 'foobar' );
-			expect( itemAt( root, 2 ).getAttribute( 'key' ) ).to.equal( 'value' );
-			expect( itemAt( root, 3 ).getAttribute( 'key' ) ).to.equal( 'value' );
-
-			expect( editor.document.selection.getFirstRange().isEqual( r( 1, 3 ) ) ).to.be.true;
-			expect( editor.document.selection.isBackward ).to.be.false;
-
-			undo._execute();
-
-			// Set attribute is undone:
-			/*
-			 [root]
-			 - f
-			 - o
-			 - {o
-			 - b}
-			 - a
-			 - r
-			 */
-
-			expect( getText( root ) ).to.equal( 'foobar' );
-			expect( itemAt( root, 2 ).hasAttribute( 'key' ) ).to.be.false;
-			expect( itemAt( root, 3 ).hasAttribute( 'key' ) ).to.be.false;
-
-			expect( editor.document.selection.getFirstRange().isEqual( r( 2, 4 ) ) ).to.be.true;
-			expect( editor.document.selection.isBackward ).to.be.true;
-
-			undo._execute();
-
-			// Insert is undone:
-			/*
-			 [root]
-			 */
-
-			expect( root.childCount ).to.equal( 0 );
-			expect( editor.document.selection.getFirstRange().isEqual( r( 0, 0 ) ) ).to.be.true;
-		} );
-
-		it( 'should revert changes done by deltas from given batch, if parameter was passed (test: revert set attribute)', () => {
-			undo._execute( batch1 );
-			// Remove attribute:
-			/*
-			 [root]
-			 - [p]
-			 	- b
-			 	- a
-			 	- r
-			 - o
-			 - f
-			 - o
-			 */
-
-			expect( itemAt( root, 0 ).name ).to.equal( 'p' );
-			expect( getText( root.getChild( 0 ) ) ).to.equal( 'bar' );
-			expect( root.getChild( 1 ).data ).to.equal( 'ofo' );
-
-			expect( itemAt( root.getChild( 0 ), 0 ).hasAttribute( 'key' ) ).to.be.false;
-			expect( itemAt( root, 2 ).hasAttribute( 'key' ) ).to.be.false;
-			expect( itemAt( root, 3 ).hasAttribute( 'key' ) ).to.be.false;
-
-			// Selection is only partially restored because the range got broken.
-			// The selection would have to best on letter "b" and letter "o", but it is set only on letter "b".
-			expect( editor.document.selection.getFirstRange().isEqual( r( [ 0, 0 ], [ 0, 1 ] ) ) ).to.be.true;
-			expect( editor.document.selection.isBackward ).to.be.true;
-		} );
-
-		it( 'should revert changes done by deltas from given batch, if parameter was passed (test: revert insert foobar)', () => {
-			undo._execute( batch0 );
-			// Remove foobar:
-			/*
-			 [root]
-			 - [p]
-			 */
-
-			// The `P` element wasn't removed because it wasn`t added by undone batch.
-			// It would be perfect if the `P` got removed aswell because wrapping was on removed nodes.
-			// But this would need a lot of logic / hardcoded ifs or a post-fixer.
-			expect( root.childCount ).to.equal( 1 );
-			expect( itemAt( root, 0 ).name ).to.equal( 'p' );
-
-			expect( editor.document.selection.getFirstRange().isEqual( r( 0, 0 ) ) ).to.be.true;
-			expect( editor.document.selection.isBackward ).to.be.false;
-
-			undo._execute( batch1 );
-			// Remove attributes.
-			// This does nothing in the `root` because attributes were set on nodes that already got removed.
-			// But those nodes should change in the graveyard and we can check them there.
+	let editor, doc, root, undo;
 
-			expect( root.childCount ).to.equal( 1 );
-			expect( itemAt( root, 0 ).name ).to.equal( 'p' );
+	beforeEach( () => {
+		editor = new ModelTestEditor();
+		undo = new UndoCommand( editor );
 
-			// Operations for undoing that batch were working on graveyard so document selection should not change.
-			expect( editor.document.selection.getFirstRange().isEqual( r( 0, 0 ) ) ).to.be.true;
-			expect( editor.document.selection.isBackward ).to.be.false;
+		doc = editor.document;
 
-			expect( doc.graveyard.getChild( 0 ).maxOffset ).to.equal( 6 );
-
-			for ( let char of doc.graveyard._children ) {
-				expect( char.hasAttribute( 'key' ) ).to.be.false;
-			}
-
-			// Let's undo wrapping. This should leave us with empty root.
-			undo._execute( batch3 );
-			expect( root.maxOffset ).to.equal( 0 );
-
-			// Once again transformed range ends up in the graveyard.
-			expect( editor.document.selection.getFirstRange().isEqual( r( 0, 0 ) ) ).to.be.true;
-			expect( editor.document.selection.isBackward ).to.be.false;
-		} );
+		root = doc.getRoot();
 	} );
 
-	// Some tests to ensure 100% CC and proper behavior in edge cases.
-	describe( 'edge cases', () => {
-		function getCaseText( root ) {
-			let text = '';
-
-			for ( let i = 0; i < root.childCount; i++ ) {
-				let node = root.getChild( i );
-				text += node.getAttribute( 'uppercase' ) ? node.data.toUpperCase() : node.data;
-			}
-
-			return text;
-		}
-
-		it( 'correctly handles deltas in compressed history that were earlier updated into multiple deltas (or split when undoing)', () => {
-			// In this case we assume that one of the deltas in compressed history was updated to two deltas.
-			// This is a tricky edge case because it is almost impossible to come up with convincing scenario that produces it.
-			// At the moment of writing this test and comment, only Undo feature uses `CompressedHistory#updateDelta`.
-			// Because deltas that "stays" in history are transformed with `isStrong` flag set to `false`, `MoveOperation`
-			// won't get split and `AttributeDelta` can hold multiple `AttributeOperation` in it. So using most common deltas
-			// (`InsertDelta`, `RemoveDelta`, `MoveDelta`, `AttributeDelta`) and undo it's impossible to get to this edge case.
-			// Still there might be some weird scenarios connected with OT / Undo / Collaborative Editing / other deltas /
-			// fancy 3rd party plugin where it may come up, so it's better to be safe than sorry.
-
-			root.appendChildren( new Text( 'abcdef' ) );
-			expect( getCaseText( root ) ).to.equal( 'abcdef' );
-
-			editor.document.selection.setRanges( [ r( 1, 4 ) ] );
-			let batch0 = doc.batch();
-			undo.addBatch( batch0 );
-			batch0.move( r( 1, 4 ), p( 5 ) );
-			expect( getCaseText( root ) ).to.equal( 'aebcdf' );
-
-			editor.document.selection.setRanges( [ r( 1, 1 ) ]  );
-			let batch1 = doc.batch();
-			undo.addBatch( batch1 );
-			batch1.remove( r( 0, 1 ) );
-			expect( getCaseText( root ) ).to.equal( 'ebcdf' );
-
-			editor.document.selection.setRanges( [ r( 0, 3 ) ] );
-			let batch2 = doc.batch();
-			undo.addBatch( batch2 );
-			batch2.setAttribute( r( 0, 3 ), 'uppercase', true );
-			expect( getCaseText( root ) ).to.equal( 'EBCdf' );
-
-			undo._execute( batch0 );
-			expect( getCaseText( root ) ).to.equal( 'BCdEf' );
-
-			// Let's simulate splitting the delta by updating the history by hand.
-			let attrHistoryDelta = doc.history.getDelta( 2 )[ 0 ];
-			let attrDelta1 = new AttributeDelta();
-			attrDelta1.addOperation( attrHistoryDelta.operations[ 0 ] );
-			let attrDelta2 = new AttributeDelta();
-			attrDelta2.addOperation( attrHistoryDelta.operations[ 1 ] );
-			doc.history.updateDelta( 2, [ attrDelta1, attrDelta2 ] );
-
-			undo._execute( batch1 );
-			// After this execution, undo algorithm should update both `attrDelta1` and `attrDelta2` with new
-			// versions, that have incremented offsets.
-			expect( getCaseText( root ) ).to.equal( 'aBCdEf' );
-
-			undo._execute( batch2 );
-			// This execution checks whether undo algorithm correctly updated deltas in previous execution
-			// and also whether it correctly "reads" both deltas from history.
-			expect( getCaseText( root ) ).to.equal( 'abcdef' );
-		} );
-
-		it( 'merges touching ranges when restoring selection', () => {
-			root.appendChildren( new Text( 'abcdef' ) );
-			expect( getCaseText( root ) ).to.equal( 'abcdef' );
-
-			editor.document.selection.setRanges( [ r( 1, 4 ) ] );
-			let batch0 = doc.batch();
-			undo.addBatch( batch0 );
-			batch0.setAttribute( r( 1, 4 ), 'uppercase', true );
-			expect( getCaseText( root ) ).to.equal( 'aBCDef' );
-
-			editor.document.selection.setRanges( [ r( 3, 4 ) ] );
-			let batch1 = doc.batch();
-			undo.addBatch( batch1 );
-			batch1.move( r( 3, 4 ), p( 1 ) );
-			expect( getCaseText( root ) ).to.equal( 'aDBCef' );
-
-			undo._execute( batch0 );
+	afterEach( () => {
+		undo.destroy();
+	} );
 
-			// After undo-attr: acdbef <--- "cdb" should be selected, it would look weird if only "cd" or "b" is selected
-			// but the whole unbroken part "cdb" changed attribute.
-			expect( getCaseText( root ) ).to.equal( 'adbcef' );
-			expect( editor.document.selection.getFirstRange().isEqual( r( 1, 4 ) ) ).to.be.true;
+	describe( 'UndoCommand', () => {
+		const p = pos => new Position( root, [].concat( pos ) );
+		const r = ( a, b ) => new Range( p( a ), p( b ) );
+
+		describe( '_execute', () => {
+			let batch0, batch1, batch2, batch3;
+
+			beforeEach( () => {
+				/*
+				 [root]
+				 - {}
+				 */
+				editor.document.selection.setRanges( [ r( 0, 0 ) ] );
+				batch0 = doc.batch();
+				undo.addBatch( batch0 );
+				batch0.insert( p( 0 ), 'foobar' );
+				/*
+				 [root]
+				 - f
+				 - o
+				 - o
+				 - b
+				 - a
+				 - r{}
+				 */
+				// Let's make things spicy and this time, make a backward selection.
+				editor.document.selection.setRanges( [ r( 2, 4 ) ], true );
+				batch1 = doc.batch();
+				undo.addBatch( batch1 );
+				batch1.setAttribute( r( 2, 4 ), 'key', 'value' );
+				/*
+				 [root]
+				 - f
+				 - o
+				 - {o (key: value)
+				 - b} (key: value)
+				 - a
+				 - r
+				 */
+				editor.document.selection.setRanges( [ r( 1, 3 ) ] );
+				batch2 = doc.batch();
+				undo.addBatch( batch2 );
+				batch2.move( r( 1, 3 ), p( 6 ) );
+				/*
+				 [root]
+				 - f
+				 - b (key: value)
+				 - a
+				 - r
+				 - {o
+				 - o} (key: value)
+				 */
+				editor.document.selection.setRanges( [ r( 1, 4 ) ] );
+				batch3 = doc.batch();
+				undo.addBatch( batch3 );
+				batch3.wrap( r( 1, 4 ), 'p' );
+				/*
+				 [root]
+				 - f
+				 - [p]
+				 	- {b (key: value)
+				 	- a
+				 	- r}
+				 - o
+				 - o (key: value)
+				 */
+				editor.document.selection.setRanges( [ r( 0, 1 ) ] );
+				batch2.move( r( 0, 1 ), p( 3 ) );
+				/*
+				 [root]
+				 - [p]
+				 	- b (key: value)
+				 	- a
+				 	- r
+				 - o
+				 - f
+				 - o{} (key: value)
+				 */
+				editor.document.selection.setRanges( [ r( 4, 4 ) ] );
+			} );
+
+			it( 'should revert changes done by deltas from the batch that was most recently added to the command stack', () => {
+				undo._execute();
+
+				// Selection is restored. Wrap is removed:
+				/*
+				 [root]
+				 - {b (key: value)
+				 - a
+				 - r}
+				 - o
+				 - f
+				 - o (key: value)
+				 */
+
+				expect( getText( root ) ).to.equal( 'barofo' );
+				expect( itemAt( root, 0 ).getAttribute( 'key' ) ).to.equal( 'value' );
+				expect( itemAt( root, 5 ).getAttribute( 'key' ) ).to.equal( 'value' );
+
+				expect( editor.document.selection.getFirstRange().isEqual( r( 0, 3 ) ) ).to.be.true;
+				expect( editor.document.selection.isBackward ).to.be.false;
+
+				undo._execute();
+
+				// Two moves are removed:
+				/*
+				 [root]
+				 - f
+				 - {o
+				 - o} (key: value)
+				 - b (key: value)
+				 - a
+				 - r
+				 */
+
+				expect( getText( root ) ).to.equal( 'foobar' );
+				expect( itemAt( root, 2 ).getAttribute( 'key' ) ).to.equal( 'value' );
+				expect( itemAt( root, 3 ).getAttribute( 'key' ) ).to.equal( 'value' );
+
+				expect( editor.document.selection.getFirstRange().isEqual( r( 1, 3 ) ) ).to.be.true;
+				expect( editor.document.selection.isBackward ).to.be.false;
+
+				undo._execute();
+
+				// Set attribute is undone:
+				/*
+				 [root]
+				 - f
+				 - o
+				 - {o
+				 - b}
+				 - a
+				 - r
+				 */
+
+				expect( getText( root ) ).to.equal( 'foobar' );
+				expect( itemAt( root, 2 ).hasAttribute( 'key' ) ).to.be.false;
+				expect( itemAt( root, 3 ).hasAttribute( 'key' ) ).to.be.false;
+
+				expect( editor.document.selection.getFirstRange().isEqual( r( 2, 4 ) ) ).to.be.true;
+				expect( editor.document.selection.isBackward ).to.be.true;
+
+				undo._execute();
+
+				// Insert is undone:
+				/*
+				 [root]
+				 */
+
+				expect( root.childCount ).to.equal( 0 );
+				expect( editor.document.selection.getFirstRange().isEqual( r( 0, 0 ) ) ).to.be.true;
+			} );
+
+			it( 'should revert changes done by deltas from given batch, if parameter was passed (test: revert set attribute)', () => {
+				undo._execute( batch1 );
+				// Remove attribute:
+				/*
+				 [root]
+				 - [p]
+				 	- b
+				 	- a
+				 	- r
+				 - o
+				 - f
+				 - o
+				 */
+
+				expect( itemAt( root, 0 ).name ).to.equal( 'p' );
+				expect( getText( root.getChild( 0 ) ) ).to.equal( 'bar' );
+				expect( root.getChild( 1 ).data ).to.equal( 'ofo' );
+
+				expect( itemAt( root.getChild( 0 ), 0 ).hasAttribute( 'key' ) ).to.be.false;
+				expect( itemAt( root, 2 ).hasAttribute( 'key' ) ).to.be.false;
+				expect( itemAt( root, 3 ).hasAttribute( 'key' ) ).to.be.false;
+
+				// Selection is only partially restored because the range got broken.
+				// The selection would have to best on letter "b" and letter "o", but it is set only on letter "b".
+				expect( editor.document.selection.getFirstRange().isEqual( r( [ 0, 0 ], [ 0, 1 ] ) ) ).to.be.true;
+				expect( editor.document.selection.isBackward ).to.be.true;
+			} );
+
+			it( 'should revert changes done by deltas from given batch, if parameter was passed (test: revert insert foobar)', () => {
+				undo._execute( batch0 );
+				// Remove foobar:
+				/*
+				 [root]
+				 - [p]
+				 */
+
+				// The `P` element wasn't removed because it wasn`t added by undone batch.
+				// It would be perfect if the `P` got removed aswell because wrapping was on removed nodes.
+				// But this would need a lot of logic / hardcoded ifs or a post-fixer.
+				expect( root.childCount ).to.equal( 1 );
+				expect( itemAt( root, 0 ).name ).to.equal( 'p' );
+
+				expect( editor.document.selection.getFirstRange().isEqual( r( 0, 0 ) ) ).to.be.true;
+				expect( editor.document.selection.isBackward ).to.be.false;
+
+				undo._execute( batch1 );
+				// Remove attributes.
+				// This does nothing in the `root` because attributes were set on nodes that already got removed.
+				// But those nodes should change in the graveyard and we can check them there.
+
+				expect( root.childCount ).to.equal( 1 );
+				expect( itemAt( root, 0 ).name ).to.equal( 'p' );
+
+				// Operations for undoing that batch were working on graveyard so document selection should not change.
+				expect( editor.document.selection.getFirstRange().isEqual( r( 0, 0 ) ) ).to.be.true;
+				expect( editor.document.selection.isBackward ).to.be.false;
+
+				expect( doc.graveyard.getChild( 0 ).maxOffset ).to.equal( 6 );
+
+				for ( let char of doc.graveyard._children ) {
+					expect( char.hasAttribute( 'key' ) ).to.be.false;
+				}
+
+				// Let's undo wrapping. This should leave us with empty root.
+				undo._execute( batch3 );
+				expect( root.maxOffset ).to.equal( 0 );
+
+				// Once again transformed range ends up in the graveyard.
+				expect( editor.document.selection.getFirstRange().isEqual( r( 0, 0 ) ) ).to.be.true;
+				expect( editor.document.selection.isBackward ).to.be.false;
+			} );
 		} );
 
-		it( 'does nothing (and not crashes) if delta to undo is no longer in history', () => {
-			// Also an edgy situation but it may come up if other plugins use `CompressedHistory` API.
-			root.appendChildren( new Text( 'abcdef' ) );
-			expect( getCaseText( root ) ).to.equal( 'abcdef' );
+		// Some tests to ensure 100% CC and proper behavior in edge cases.
+		describe( 'edge cases', () => {
+			function getCaseText( root ) {
+				let text = '';
 
-			editor.document.selection.setRanges( [ r( 0, 1 ) ] );
-			let batch0 = doc.batch();
-			undo.addBatch( batch0 );
-			batch0.setAttribute( r( 0, 1 ), 'uppercase', true );
-			expect( getCaseText( root ) ).to.equal( 'Abcdef' );
+				for ( let i = 0; i < root.childCount; i++ ) {
+					let node = root.getChild( i );
+					text += node.getAttribute( 'uppercase' ) ? node.data.toUpperCase() : node.data;
+				}
 
-			doc.history.removeDelta( 0 );
-			root.getChild( 0 ).removeAttribute( 'uppercase' );
-			expect( getCaseText( root ) ).to.equal( 'abcdef' );
-
-			undo._execute();
+				return text;
+			}
 
-			// Nothing happened. We are still alive.
-			expect( getCaseText( root ) ).to.equal( 'abcdef' );
+			it( 'correctly handles deltas in compressed history that were earlier updated into multiple deltas (or split when undoing)', () => {
+				// In this case we assume that one of the deltas in compressed history was updated to two deltas.
+				// This is a tricky edge case because it is almost impossible to come up with convincing scenario that produces it.
+				// At the moment of writing this test and comment, only Undo feature uses `CompressedHistory#updateDelta`.
+				// Because deltas that "stays" in history are transformed with `isStrong` flag set to `false`, `MoveOperation`
+				// won't get split and `AttributeDelta` can hold multiple `AttributeOperation` in it. So using most common deltas
+				// (`InsertDelta`, `RemoveDelta`, `MoveDelta`, `AttributeDelta`) and undo it's impossible to get to this edge case.
+				// Still there might be some weird scenarios connected with OT / Undo / Collaborative Editing / other deltas /
+				// fancy 3rd party plugin where it may come up, so it's better to be safe than sorry.
+
+				root.appendChildren( new Text( 'abcdef' ) );
+				expect( getCaseText( root ) ).to.equal( 'abcdef' );
+
+				editor.document.selection.setRanges( [ r( 1, 4 ) ] );
+				let batch0 = doc.batch();
+				undo.addBatch( batch0 );
+				batch0.move( r( 1, 4 ), p( 5 ) );
+				expect( getCaseText( root ) ).to.equal( 'aebcdf' );
+
+				editor.document.selection.setRanges( [ r( 1, 1 ) ]  );
+				let batch1 = doc.batch();
+				undo.addBatch( batch1 );
+				batch1.remove( r( 0, 1 ) );
+				expect( getCaseText( root ) ).to.equal( 'ebcdf' );
+
+				editor.document.selection.setRanges( [ r( 0, 3 ) ] );
+				let batch2 = doc.batch();
+				undo.addBatch( batch2 );
+				batch2.setAttribute( r( 0, 3 ), 'uppercase', true );
+				expect( getCaseText( root ) ).to.equal( 'EBCdf' );
+
+				undo._execute( batch0 );
+				expect( getCaseText( root ) ).to.equal( 'BCdEf' );
+
+				// Let's simulate splitting the delta by updating the history by hand.
+				let attrHistoryDelta = doc.history.getDelta( 2 )[ 0 ];
+				let attrDelta1 = new AttributeDelta();
+				attrDelta1.addOperation( attrHistoryDelta.operations[ 0 ] );
+				let attrDelta2 = new AttributeDelta();
+				attrDelta2.addOperation( attrHistoryDelta.operations[ 1 ] );
+				doc.history.updateDelta( 2, [ attrDelta1, attrDelta2 ] );
+
+				undo._execute( batch1 );
+				// After this execution, undo algorithm should update both `attrDelta1` and `attrDelta2` with new
+				// versions, that have incremented offsets.
+				expect( getCaseText( root ) ).to.equal( 'aBCdEf' );
+
+				undo._execute( batch2 );
+				// This execution checks whether undo algorithm correctly updated deltas in previous execution
+				// and also whether it correctly "reads" both deltas from history.
+				expect( getCaseText( root ) ).to.equal( 'abcdef' );
+			} );
+
+			it( 'merges touching ranges when restoring selection', () => {
+				root.appendChildren( new Text( 'abcdef' ) );
+				expect( getCaseText( root ) ).to.equal( 'abcdef' );
+
+				editor.document.selection.setRanges( [ r( 1, 4 ) ] );
+				let batch0 = doc.batch();
+				undo.addBatch( batch0 );
+				batch0.setAttribute( r( 1, 4 ), 'uppercase', true );
+				expect( getCaseText( root ) ).to.equal( 'aBCDef' );
+
+				editor.document.selection.setRanges( [ r( 3, 4 ) ] );
+				let batch1 = doc.batch();
+				undo.addBatch( batch1 );
+				batch1.move( r( 3, 4 ), p( 1 ) );
+				expect( getCaseText( root ) ).to.equal( 'aDBCef' );
+
+				undo._execute( batch0 );
+
+				// After undo-attr: acdbef <--- "cdb" should be selected, it would look weird if only "cd" or "b" is selected
+				// but the whole unbroken part "cdb" changed attribute.
+				expect( getCaseText( root ) ).to.equal( 'adbcef' );
+				expect( editor.document.selection.getFirstRange().isEqual( r( 1, 4 ) ) ).to.be.true;
+			} );
+
+			it( 'does nothing (and not crashes) if delta to undo is no longer in history', () => {
+				// Also an edgy situation but it may come up if other plugins use `CompressedHistory` API.
+				root.appendChildren( new Text( 'abcdef' ) );
+				expect( getCaseText( root ) ).to.equal( 'abcdef' );
+
+				editor.document.selection.setRanges( [ r( 0, 1 ) ] );
+				let batch0 = doc.batch();
+				undo.addBatch( batch0 );
+				batch0.setAttribute( r( 0, 1 ), 'uppercase', true );
+				expect( getCaseText( root ) ).to.equal( 'Abcdef' );
+
+				doc.history.removeDelta( 0 );
+				root.getChild( 0 ).removeAttribute( 'uppercase' );
+				expect( getCaseText( root ) ).to.equal( 'abcdef' );
+
+				undo._execute();
+
+				// Nothing happened. We are still alive.
+				expect( getCaseText( root ) ).to.equal( 'abcdef' );
+			} );
 		} );
 	} );
 } );

+ 212 - 210
packages/ckeditor5-undo/tests/undoengine-integration.js

@@ -10,303 +10,305 @@ import UndoEngine from 'ckeditor5/undo/undoengine.js';
 
 import { setData, getData } from 'ckeditor5/engine/dev-utils/model.js';
 
-let editor, doc, root;
-
-beforeEach( () => {
-	return ModelTestEditor.create( {
-			plugins: [ UndoEngine ]
-		} )
-		.then( newEditor => {
-			editor = newEditor;
-			doc = editor.document;
-			doc.schema.registerItem( 'p', '$block' );
-			root = doc.getRoot();
-		} );
-} );
+describe( 'UndoEngine integration', () => {
+	let editor, doc, root;
+
+	beforeEach( () => {
+		return ModelTestEditor.create( {
+				plugins: [ UndoEngine ]
+			} )
+			.then( newEditor => {
+				editor = newEditor;
+				doc = editor.document;
+				doc.schema.registerItem( 'p', '$block' );
+				root = doc.getRoot();
+			} );
+	} );
 
-function setSelection( pathA, pathB ) {
-	doc.selection.setRanges( [ new Range( new Position( root, pathA ), new Position( root, pathB ) ) ] );
-}
+	function setSelection( pathA, pathB ) {
+		doc.selection.setRanges( [ new Range( new Position( root, pathA ), new Position( root, pathB ) ) ] );
+	}
 
-function input( input ) {
-	setData( doc, input );
-}
+	function input( input ) {
+		setData( doc, input );
+	}
 
-function output( output ) {
-	expect( getData( doc ) ).to.equal( output );
-}
+	function output( output ) {
+		expect( getData( doc ) ).to.equal( output );
+	}
 
-function undoDisabled() {
-	expect( editor.commands.get( 'undo' ).isEnabled ).to.be.false;
-}
+	function undoDisabled() {
+		expect( editor.commands.get( 'undo' ).isEnabled ).to.be.false;
+	}
 
-describe( 'UndoEngine integration', () => {
-	describe( 'adding and removing content', () => {
-		it( 'add and undo', () => {
-			input( '<p>fo[]o</p><p>bar</p>' );
+	describe( 'UndoEngine integration', () => {
+		describe( 'adding and removing content', () => {
+			it( 'add and undo', () => {
+				input( '<p>fo[]o</p><p>bar</p>' );
 
-			doc.batch().insert( doc.selection.getFirstPosition(), 'zzz' );
-			output( '<p>fozzz[]o</p><p>bar</p>' );
+				doc.batch().insert( doc.selection.getFirstPosition(), 'zzz' );
+				output( '<p>fozzz[]o</p><p>bar</p>' );
 
-			editor.execute( 'undo' );
-			output( '<p>fo[]o</p><p>bar</p>' );
+				editor.execute( 'undo' );
+				output( '<p>fo[]o</p><p>bar</p>' );
 
-			undoDisabled();
-		} );
+				undoDisabled();
+			} );
 
-		it( 'multiple adding and undo', () => {
-			input( '<p>fo[]o</p><p>bar</p>' );
+			it( 'multiple adding and undo', () => {
+				input( '<p>fo[]o</p><p>bar</p>' );
 
-			doc.batch()
-				.insert( doc.selection.getFirstPosition(), 'zzz' )
-				.insert( new Position( root, [ 1, 0 ] ), 'xxx' );
-			output( '<p>fozzz[]o</p><p>xxxbar</p>' );
+				doc.batch()
+					.insert( doc.selection.getFirstPosition(), 'zzz' )
+					.insert( new Position( root, [ 1, 0 ] ), 'xxx' );
+				output( '<p>fozzz[]o</p><p>xxxbar</p>' );
 
-			setSelection( [ 1, 0 ], [ 1, 0 ] );
-			doc.batch().insert( doc.selection.getFirstPosition(), 'yyy' );
-			output( '<p>fozzzo</p><p>yyy[]xxxbar</p>' );
+				setSelection( [ 1, 0 ], [ 1, 0 ] );
+				doc.batch().insert( doc.selection.getFirstPosition(), 'yyy' );
+				output( '<p>fozzzo</p><p>yyy[]xxxbar</p>' );
 
-			editor.execute( 'undo' );
-			output( '<p>fozzzo</p><p>[]xxxbar</p>' );
+				editor.execute( 'undo' );
+				output( '<p>fozzzo</p><p>[]xxxbar</p>' );
 
-			editor.execute( 'undo' );
-			output( '<p>fo[]o</p><p>bar</p>' );
+				editor.execute( 'undo' );
+				output( '<p>fo[]o</p><p>bar</p>' );
 
-			undoDisabled();
-		} );
+				undoDisabled();
+			} );
 
-		it( 'multiple adding mixed with undo', () => {
-			input( '<p>fo[]o</p><p>bar</p>' );
+			it( 'multiple adding mixed with undo', () => {
+				input( '<p>fo[]o</p><p>bar</p>' );
 
-			doc.batch().insert( doc.selection.getFirstPosition(), 'zzz' );
-			output( '<p>fozzz[]o</p><p>bar</p>' );
+				doc.batch().insert( doc.selection.getFirstPosition(), 'zzz' );
+				output( '<p>fozzz[]o</p><p>bar</p>' );
 
-			setSelection( [ 1, 0 ], [ 1, 0 ] );
-			doc.batch().insert( doc.selection.getFirstPosition(), 'yyy' );
-			output( '<p>fozzzo</p><p>yyy[]bar</p>' );
+				setSelection( [ 1, 0 ], [ 1, 0 ] );
+				doc.batch().insert( doc.selection.getFirstPosition(), 'yyy' );
+				output( '<p>fozzzo</p><p>yyy[]bar</p>' );
 
-			editor.execute( 'undo' );
-			output( '<p>fozzzo</p><p>[]bar</p>' );
+				editor.execute( 'undo' );
+				output( '<p>fozzzo</p><p>[]bar</p>' );
 
-			setSelection( [ 0, 0 ], [ 0, 0 ] );
-			doc.batch().insert( doc.selection.getFirstPosition(), 'xxx' );
-			output( '<p>xxx[]fozzzo</p><p>bar</p>' );
+				setSelection( [ 0, 0 ], [ 0, 0 ] );
+				doc.batch().insert( doc.selection.getFirstPosition(), 'xxx' );
+				output( '<p>xxx[]fozzzo</p><p>bar</p>' );
 
-			editor.execute( 'undo' );
-			output( '<p>[]fozzzo</p><p>bar</p>' );
+				editor.execute( 'undo' );
+				output( '<p>[]fozzzo</p><p>bar</p>' );
 
-			editor.execute( 'undo' );
-			output( '<p>fo[]o</p><p>bar</p>' );
+				editor.execute( 'undo' );
+				output( '<p>fo[]o</p><p>bar</p>' );
 
-			undoDisabled();
-		} );
+				undoDisabled();
+			} );
 
-		it( 'multiple remove and undo', () => {
-			input( '<p>[]foo</p><p>bar</p>' );
+			it( 'multiple remove and undo', () => {
+				input( '<p>[]foo</p><p>bar</p>' );
 
-			doc.batch().remove( Range.createFromPositionAndShift( doc.selection.getFirstPosition(), 2 ) );
-			output( '<p>[]o</p><p>bar</p>' );
+				doc.batch().remove( Range.createFromPositionAndShift( doc.selection.getFirstPosition(), 2 ) );
+				output( '<p>[]o</p><p>bar</p>' );
 
-			setSelection( [ 1, 1 ], [ 1, 1 ] );
-			doc.batch().remove( Range.createFromPositionAndShift( doc.selection.getFirstPosition(), 2 ) );
-			output( '<p>o</p><p>b[]</p>' );
+				setSelection( [ 1, 1 ], [ 1, 1 ] );
+				doc.batch().remove( Range.createFromPositionAndShift( doc.selection.getFirstPosition(), 2 ) );
+				output( '<p>o</p><p>b[]</p>' );
 
-			editor.execute( 'undo' );
-			// Here is an edge case that selection could be before or after `ar`.
-			output( '<p>o</p><p>b[]ar</p>' );
+				editor.execute( 'undo' );
+				// Here is an edge case that selection could be before or after `ar`.
+				output( '<p>o</p><p>b[]ar</p>' );
 
-			editor.execute( 'undo' );
-			// As above.
-			output( '<p>[]foo</p><p>bar</p>' );
+				editor.execute( 'undo' );
+				// As above.
+				output( '<p>[]foo</p><p>bar</p>' );
 
-			undoDisabled();
-		} );
+				undoDisabled();
+			} );
 
-		it( 'add and remove different parts and undo', () => {
-			input( '<p>fo[]o</p><p>bar</p>' );
+			it( 'add and remove different parts and undo', () => {
+				input( '<p>fo[]o</p><p>bar</p>' );
 
-			doc.batch().insert( doc.selection.getFirstPosition(), 'zzz' );
-			output( '<p>fozzz[]o</p><p>bar</p>' );
+				doc.batch().insert( doc.selection.getFirstPosition(), 'zzz' );
+				output( '<p>fozzz[]o</p><p>bar</p>' );
 
-			setSelection( [ 1, 2 ], [ 1, 2 ] );
-			doc.batch().remove( Range.createFromPositionAndShift( new Position( root, [ 1, 1 ] ) , 1 ) );
-			output( '<p>fozzzo</p><p>b[]r</p>' );
+				setSelection( [ 1, 2 ], [ 1, 2 ] );
+				doc.batch().remove( Range.createFromPositionAndShift( new Position( root, [ 1, 1 ] ) , 1 ) );
+				output( '<p>fozzzo</p><p>b[]r</p>' );
 
-			editor.execute( 'undo' );
-			output( '<p>fozzzo</p><p>ba[]r</p>' );
+				editor.execute( 'undo' );
+				output( '<p>fozzzo</p><p>ba[]r</p>' );
 
-			editor.execute( 'undo' );
-			output( '<p>fo[]o</p><p>bar</p>' );
+				editor.execute( 'undo' );
+				output( '<p>fo[]o</p><p>bar</p>' );
 
-			undoDisabled();
-		} );
+				undoDisabled();
+			} );
 
-		it( 'add and remove same part and undo', () => {
-			input( '<p>fo[]o</p><p>bar</p>' );
+			it( 'add and remove same part and undo', () => {
+				input( '<p>fo[]o</p><p>bar</p>' );
 
-			doc.batch().insert( doc.selection.getFirstPosition(), 'zzz' );
-			output( '<p>fozzz[]o</p><p>bar</p>' );
+				doc.batch().insert( doc.selection.getFirstPosition(), 'zzz' );
+				output( '<p>fozzz[]o</p><p>bar</p>' );
 
-			doc.batch().remove( Range.createFromPositionAndShift( new Position( root, [ 0, 2 ] ) , 3 ) );
-			output( '<p>fo[]o</p><p>bar</p>' );
+				doc.batch().remove( Range.createFromPositionAndShift( new Position( root, [ 0, 2 ] ) , 3 ) );
+				output( '<p>fo[]o</p><p>bar</p>' );
 
-			editor.execute( 'undo' );
-			output( '<p>fozzz[]o</p><p>bar</p>' );
+				editor.execute( 'undo' );
+				output( '<p>fozzz[]o</p><p>bar</p>' );
 
-			editor.execute( 'undo' );
-			output( '<p>fo[]o</p><p>bar</p>' );
+				editor.execute( 'undo' );
+				output( '<p>fo[]o</p><p>bar</p>' );
 
-			undoDisabled();
+				undoDisabled();
+			} );
 		} );
-	} );
 
-	describe( 'moving', () => {
-		it( 'move same content twice then undo', () => {
-			input( '<p>f[o]z</p><p>bar</p>' );
+		describe( 'moving', () => {
+			it( 'move same content twice then undo', () => {
+				input( '<p>f[o]z</p><p>bar</p>' );
 
-			doc.batch().move( doc.selection.getFirstRange(), new Position( root, [ 1, 0 ] ) );
-			output( '<p>fz</p><p>[o]bar</p>' );
+				doc.batch().move( doc.selection.getFirstRange(), new Position( root, [ 1, 0 ] ) );
+				output( '<p>fz</p><p>[o]bar</p>' );
 
-			doc.batch().move( doc.selection.getFirstRange(), new Position( root, [ 0, 2 ] ) );
-			output( '<p>fz[o]</p><p>bar</p>' );
+				doc.batch().move( doc.selection.getFirstRange(), new Position( root, [ 0, 2 ] ) );
+				output( '<p>fz[o]</p><p>bar</p>' );
 
-			editor.execute( 'undo' );
-			output( '<p>fz</p><p>[o]bar</p>' );
+				editor.execute( 'undo' );
+				output( '<p>fz</p><p>[o]bar</p>' );
 
-			editor.execute( 'undo' );
-			output( '<p>f[o]z</p><p>bar</p>' );
+				editor.execute( 'undo' );
+				output( '<p>f[o]z</p><p>bar</p>' );
 
-			undoDisabled();
-		} );
+				undoDisabled();
+			} );
 
-		it( 'move content and new parent then undo', () => {
-			input( '<p>f[o]z</p><p>bar</p>' );
+			it( 'move content and new parent then undo', () => {
+				input( '<p>f[o]z</p><p>bar</p>' );
 
-			doc.batch().move( doc.selection.getFirstRange(), new Position( root, [ 1, 0 ] ) );
-			output( '<p>fz</p><p>[o]bar</p>' );
+				doc.batch().move( doc.selection.getFirstRange(), new Position( root, [ 1, 0 ] ) );
+				output( '<p>fz</p><p>[o]bar</p>' );
 
-			setSelection( [ 1 ], [ 2 ] );
-			doc.batch().move( doc.selection.getFirstRange(), new Position( root, [ 0 ] ) );
-			output( '[<p>obar</p>]<p>fz</p>' );
+				setSelection( [ 1 ], [ 2 ] );
+				doc.batch().move( doc.selection.getFirstRange(), new Position( root, [ 0 ] ) );
+				output( '[<p>obar</p>]<p>fz</p>' );
 
-			editor.execute( 'undo' );
-			output( '<p>fz</p>[<p>obar</p>]' );
+				editor.execute( 'undo' );
+				output( '<p>fz</p>[<p>obar</p>]' );
 
-			editor.execute( 'undo' );
-			output( '<p>f[o]z</p><p>bar</p>' );
+				editor.execute( 'undo' );
+				output( '<p>f[o]z</p><p>bar</p>' );
 
-			undoDisabled();
+				undoDisabled();
+			} );
 		} );
-	} );
 
-	describe( 'attributes with other', () => {
-		it( 'attributes then insert inside then undo', () => {
-			input( '<p>fo[ob]ar</p>' );
+		describe( 'attributes with other', () => {
+			it( 'attributes then insert inside then undo', () => {
+				input( '<p>fo[ob]ar</p>' );
 
-			doc.batch().setAttribute( doc.selection.getFirstRange(), 'bold', true );
-			output( '<p>fo[<$text bold="true">ob</$text>]ar</p>' );
+				doc.batch().setAttribute( doc.selection.getFirstRange(), 'bold', true );
+				output( '<p>fo[<$text bold="true">ob</$text>]ar</p>' );
 
-			setSelection( [ 0, 3 ], [ 0, 3 ] );
-			doc.batch().insert( doc.selection.getFirstPosition(), 'zzz' );
-			output( '<p>fo<$text bold="true">o</$text>zzz<$text bold="true">[]b</$text>ar</p>' );
-			expect( doc.selection.getAttribute( 'bold' ) ).to.true;
+				setSelection( [ 0, 3 ], [ 0, 3 ] );
+				doc.batch().insert( doc.selection.getFirstPosition(), 'zzz' );
+				output( '<p>fo<$text bold="true">o</$text>zzz<$text bold="true">[]b</$text>ar</p>' );
+				expect( doc.selection.getAttribute( 'bold' ) ).to.true;
 
-			editor.execute( 'undo' );
-			output( '<p>fo<$text bold="true">o[]b</$text>ar</p>' );
-			expect( doc.selection.getAttribute( 'bold' ) ).to.true;
+				editor.execute( 'undo' );
+				output( '<p>fo<$text bold="true">o[]b</$text>ar</p>' );
+				expect( doc.selection.getAttribute( 'bold' ) ).to.true;
 
-			editor.execute( 'undo' );
-			output( '<p>fo[ob]ar</p>' );
+				editor.execute( 'undo' );
+				output( '<p>fo[ob]ar</p>' );
 
-			undoDisabled();
+				undoDisabled();
+			} );
 		} );
-	} );
 
-	describe( 'wrapping, unwrapping, merging, splitting', () => {
-		it( 'wrap and undo', () => {
-			doc.schema.allow( { name: '$text', inside: '$root' } );
-			input( 'fo[zb]ar' );
+		describe( 'wrapping, unwrapping, merging, splitting', () => {
+			it( 'wrap and undo', () => {
+				doc.schema.allow( { name: '$text', inside: '$root' } );
+				input( 'fo[zb]ar' );
 
-			doc.batch().wrap( doc.selection.getFirstRange(), 'p' );
-			output( 'fo[<p>zb</p>]ar' );
+				doc.batch().wrap( doc.selection.getFirstRange(), 'p' );
+				output( 'fo[<p>zb</p>]ar' );
 
-			editor.execute( 'undo' );
-			output( 'fo[zb]ar' );
+				editor.execute( 'undo' );
+				output( 'fo[zb]ar' );
 
-			undoDisabled();
-		} );
+				undoDisabled();
+			} );
 
-		it( 'wrap, move and undo', () => {
-			doc.schema.allow( { name: '$text', inside: '$root' } );
-			input( 'fo[zb]ar' );
+			it( 'wrap, move and undo', () => {
+				doc.schema.allow( { name: '$text', inside: '$root' } );
+				input( 'fo[zb]ar' );
 
-			doc.batch().wrap( doc.selection.getFirstRange(), 'p' );
-			// Would be better if selection was inside P.
-			output( 'fo[<p>zb</p>]ar' );
+				doc.batch().wrap( doc.selection.getFirstRange(), 'p' );
+				// Would be better if selection was inside P.
+				output( 'fo[<p>zb</p>]ar' );
 
-			setSelection( [ 2, 0 ], [ 2, 1 ] );
-			doc.batch().move( doc.selection.getFirstRange(), new Position( root, [ 0 ] ) );
-			output( '[z]fo<p>b</p>ar' );
+				setSelection( [ 2, 0 ], [ 2, 1 ] );
+				doc.batch().move( doc.selection.getFirstRange(), new Position( root, [ 0 ] ) );
+				output( '[z]fo<p>b</p>ar' );
 
-			editor.execute( 'undo' );
-			output( 'fo<p>[z]b</p>ar' );
+				editor.execute( 'undo' );
+				output( 'fo<p>[z]b</p>ar' );
 
-			editor.execute( 'undo' );
-			output( 'fo[zb]ar' );
+				editor.execute( 'undo' );
+				output( 'fo[zb]ar' );
 
-			undoDisabled();
-		} );
+				undoDisabled();
+			} );
 
-		it( 'unwrap and undo', () => {
-			input( '<p>foo[]bar</p>' );
+			it( 'unwrap and undo', () => {
+				input( '<p>foo[]bar</p>' );
 
-			doc.batch().unwrap( doc.selection.getFirstPosition().parent );
-			output( 'foo[]bar' );
+				doc.batch().unwrap( doc.selection.getFirstPosition().parent );
+				output( 'foo[]bar' );
 
-			editor.execute( 'undo' );
-			output( '<p>foo[]bar</p>' );
+				editor.execute( 'undo' );
+				output( '<p>foo[]bar</p>' );
 
-			undoDisabled();
-		} );
+				undoDisabled();
+			} );
 
-		it( 'merge and undo', () => {
-			input( '<p>foo</p><p>[]bar</p>' );
+			it( 'merge and undo', () => {
+				input( '<p>foo</p><p>[]bar</p>' );
 
-			doc.batch().merge( new Position( root, [ 1 ] ) );
-			// Because selection is stuck with <p> it ends up in graveyard. We have to manually move it to correct node.
-			setSelection( [ 0, 3 ], [ 0, 3 ] );
-			output( '<p>foo[]bar</p>' );
+				doc.batch().merge( new Position( root, [ 1 ] ) );
+				// Because selection is stuck with <p> it ends up in graveyard. We have to manually move it to correct node.
+				setSelection( [ 0, 3 ], [ 0, 3 ] );
+				output( '<p>foo[]bar</p>' );
 
-			editor.execute( 'undo' );
-			output( '<p>foo</p><p>[]bar</p>' );
+				editor.execute( 'undo' );
+				output( '<p>foo</p><p>[]bar</p>' );
 
-			undoDisabled();
-		} );
+				undoDisabled();
+			} );
 
-		it( 'split and undo', () => {
-			input( '<p>foo[]bar</p>' );
+			it( 'split and undo', () => {
+				input( '<p>foo[]bar</p>' );
 
-			doc.batch().split( doc.selection.getFirstPosition() );
-			// Because selection is stuck with <p> it ends up in wrong node. We have to manually move it to correct node.
-			setSelection( [ 1, 0 ], [ 1, 0 ] );
-			output( '<p>foo</p><p>[]bar</p>' );
+				doc.batch().split( doc.selection.getFirstPosition() );
+				// Because selection is stuck with <p> it ends up in wrong node. We have to manually move it to correct node.
+				setSelection( [ 1, 0 ], [ 1, 0 ] );
+				output( '<p>foo</p><p>[]bar</p>' );
 
-			editor.execute( 'undo' );
-			output( '<p>foo[]bar</p>' );
+				editor.execute( 'undo' );
+				output( '<p>foo[]bar</p>' );
 
-			undoDisabled();
+				undoDisabled();
+			} );
 		} );
-	} );
 
-	describe( 'other edge cases', () => {
-		it( 'deleteContent between two nodes', () => {
-			input( '<p>fo[o</p><p>b]ar</p>' );
+		describe( 'other edge cases', () => {
+			it( 'deleteContent between two nodes', () => {
+				input( '<p>fo[o</p><p>b]ar</p>' );
 
-			editor.data.deleteContent( doc.selection, doc.batch(), { merge: true } );
-			output( '<p>fo[]ar</p>' );
+				editor.data.deleteContent( doc.selection, doc.batch(), { merge: true } );
+				output( '<p>fo[]ar</p>' );
 
-			editor.execute( 'undo' );
-			output( '<p>fo[o</p><p>b]ar</p>' );
+				editor.execute( 'undo' );
+				output( '<p>fo[o</p><p>b]ar</p>' );
+			} );
 		} );
 	} );
 } );

+ 47 - 45
packages/ckeditor5-undo/tests/undoengine.js

@@ -7,69 +7,71 @@ import ModelTestEditor from 'tests/core/_utils/modeltesteditor.js';
 import Position from 'ckeditor5/engine/model/position.js';
 import UndoEngine from 'ckeditor5/undo/undoengine.js';
 
-let editor, undo, batch, doc, root;
-
-beforeEach( () => {
-	editor = new ModelTestEditor();
+describe( 'UndoEngine', () => {
+	let editor, undo, batch, doc, root;
 
-	doc = editor.document;
-	batch = doc.batch();
-	root = doc.getRoot();
+	beforeEach( () => {
+		editor = new ModelTestEditor();
 
-	undo = new UndoEngine( editor );
-	undo.init();
-} );
+		doc = editor.document;
+		batch = doc.batch();
+		root = doc.getRoot();
 
-afterEach( () => {
-	undo.destroy();
-} );
+		undo = new UndoEngine( editor );
+		undo.init();
+	} );
 
-describe( 'UndoEngine', () => {
-	it( 'should register undo command and redo command', () => {
-		expect( editor.commands.get( 'undo' ) ).to.equal( undo._undoCommand );
-		expect( editor.commands.get( 'redo' ) ).to.equal( undo._redoCommand );
+	afterEach( () => {
+		undo.destroy();
 	} );
 
-	it( 'should add a batch to undo command and clear redo stack, if it\'s type is "default"', () => {
-		sinon.spy( undo._undoCommand, 'addBatch' );
-		sinon.spy( undo._redoCommand, 'clearStack' );
+	describe( 'UndoEngine', () => {
+		it( 'should register undo command and redo command', () => {
+			expect( editor.commands.get( 'undo' ) ).to.equal( undo._undoCommand );
+			expect( editor.commands.get( 'redo' ) ).to.equal( undo._redoCommand );
+		} );
 
-		expect( undo._undoCommand.addBatch.called ).to.be.false;
-		expect( undo._redoCommand.clearStack.called ).to.be.false;
+		it( 'should add a batch to undo command and clear redo stack, if it\'s type is "default"', () => {
+			sinon.spy( undo._undoCommand, 'addBatch' );
+			sinon.spy( undo._redoCommand, 'clearStack' );
 
-		batch.insert( new Position( root, [ 0 ] ), 'foobar' );
+			expect( undo._undoCommand.addBatch.called ).to.be.false;
+			expect( undo._redoCommand.clearStack.called ).to.be.false;
 
-		expect( undo._undoCommand.addBatch.calledOnce ).to.be.true;
-		expect( undo._redoCommand.clearStack.calledOnce ).to.be.true;
-	} );
+			batch.insert( new Position( root, [ 0 ] ), 'foobar' );
 
-	it( 'should add each batch only once', () => {
-		sinon.spy( undo._undoCommand, 'addBatch' );
+			expect( undo._undoCommand.addBatch.calledOnce ).to.be.true;
+			expect( undo._redoCommand.clearStack.calledOnce ).to.be.true;
+		} );
 
-		batch.insert( new Position( root, [ 0 ] ), 'foobar' ).insert( new Position( root, [ 0 ] ), 'foobar' );
+		it( 'should add each batch only once', () => {
+			sinon.spy( undo._undoCommand, 'addBatch' );
 
-		expect( undo._undoCommand.addBatch.calledOnce ).to.be.true;
-	} );
+			batch.insert( new Position( root, [ 0 ] ), 'foobar' ).insert( new Position( root, [ 0 ] ), 'foobar' );
 
-	it( 'should add a batch to undo command, if it\'s type is undo and it comes from redo command', () => {
-		sinon.spy( undo._undoCommand, 'addBatch' );
-		sinon.spy( undo._redoCommand, 'clearStack' );
+			expect( undo._undoCommand.addBatch.calledOnce ).to.be.true;
+		} );
 
-		undo._redoCommand._createdBatches.add( batch );
+		it( 'should add a batch to undo command, if it\'s type is undo and it comes from redo command', () => {
+			sinon.spy( undo._undoCommand, 'addBatch' );
+			sinon.spy( undo._redoCommand, 'clearStack' );
 
-		batch.insert( new Position( root, [ 0 ] ), 'foobar' );
+			undo._redoCommand._createdBatches.add( batch );
 
-		expect( undo._undoCommand.addBatch.calledOnce ).to.be.true;
-		expect( undo._redoCommand.clearStack.called ).to.be.false;
-	} );
+			batch.insert( new Position( root, [ 0 ] ), 'foobar' );
+
+			expect( undo._undoCommand.addBatch.calledOnce ).to.be.true;
+			expect( undo._redoCommand.clearStack.called ).to.be.false;
+		} );
 
-	it( 'should add a batch to redo command on undo revert event', () => {
-		sinon.spy( undo._redoCommand, 'addBatch' );
-		sinon.spy( undo._redoCommand, 'clearStack' );
+		it( 'should add a batch to redo command on undo revert event', () => {
+			sinon.spy( undo._redoCommand, 'addBatch' );
+			sinon.spy( undo._redoCommand, 'clearStack' );
 
-		undo._undoCommand.fire( 'revert', null, batch );
+			undo._undoCommand.fire( 'revert', null, batch );
 
-		expect( undo._redoCommand.addBatch.calledOnce ).to.be.true;
-		expect( undo._redoCommand.clearStack.called ).to.be.false;
+			expect( undo._redoCommand.addBatch.calledOnce ).to.be.true;
+			expect( undo._redoCommand.clearStack.called ).to.be.false;
+		} );
 	} );
 } );