瀏覽代碼

Moved OT algorithms from operations classes to separate module named transformOperation.

Szymon Cofalik 10 年之前
父節點
當前提交
ec733ad5cc

+ 1 - 3
packages/ckeditor5-utils/src/document/element.js

@@ -7,9 +7,7 @@
 
 CKEDITOR.define( [
 	'document/node',
-	'document/nodelist',
-	'document/range',
-	'document/position'
+	'document/nodelist'
 ], ( Node, NodeList ) => {
 	/**
 	 * Tree data model element.

+ 4 - 161
packages/ckeditor5-utils/src/document/operation/changeoperation.js

@@ -7,12 +7,8 @@
 
 CKEDITOR.define( [
 	'document/operation/operation',
-	'document/range',
-	'document/operation/nooperation',
-	'ckeditorerror',
-	'document/operation/moveoperation',
-	'document/operation/insertoperation'
-], ( Operation, Range, NoOperation, CKEditorError ) => {
+	'ckeditorerror'
+], ( Operation, CKEditorError ) => {
 	/**
 	 * Operation to change nodes' attribute. Using this class you can add, remove or change value of the attribute.
 	 *
@@ -102,28 +98,8 @@ CKEDITOR.define( [
 			return new ChangeOperation( this.range, this.newAttr, this.oldAttr, this.baseVersion + 1 );
 		}
 
-		getTransformedBy( operation, isStrong ) {
-			// Circular dependency re-require.
-			const InsertOperation = CKEDITOR.require( 'document/operation/insertoperation' );
-			const MoveOperation = CKEDITOR.require( 'document/operation/moveoperation' );
-
-			if ( operation instanceof InsertOperation ) {
-				return getTransformedByInsertOperation.call( this, operation, !!isStrong );
-			} else if ( operation instanceof MoveOperation ) {
-				return getTransformedByMoveOperation.call( this, operation, !!isStrong );
-			} else if ( operation instanceof ChangeOperation ) {
-				return getTransformedByChangeOperation.call( this, operation, !!isStrong );
-			}
-
-			return [ this.clone( this.baseVersion + 1 ) ];
-		}
-
-		clone( baseVersion ) {
-			if ( !baseVersion ) {
-				baseVersion = this.baseVersion;
-			}
-
-			return new ChangeOperation( this.range.clone(), this.oldAttr, this.newAttr, baseVersion );
+		clone() {
+			return new ChangeOperation( this.range.clone(), this.oldAttr, this.newAttr, this.baseVersion );
 		}
 
 		/**
@@ -152,138 +128,5 @@ CKEDITOR.define( [
 		}
 	}
 
-	/**
-	 * Returns an array containing the result of transforming this operation by given {document.operation.InsertOperation}.
-	 *
-	 * @method getTransformedByInsertOperation
-	 * @param {document.operation.InsertOperation} insert Operation to transform by.
-	 * @returns {Array.<document.operation.ChangeOperation>} Result of the transformation.
-	 * @private
-	 */
-	function getTransformedByInsertOperation( insert ) {
-		/*jshint validthis:true */
-
-		// Transform this operation's range.
-		const ranges = this.range.getTransformedByInsertion( insert.position, insert.nodeList.length );
-
-		// Map transformed range(s) to operations and return them.
-		return ranges.map( ( range, i ) => {
-			return new ChangeOperation(
-				range,
-				this.oldAttr,
-				this.newAttr,
-				this.baseVersion + i + 1
-			);
-		} );
-	}
-
-	/**
-	 * Returns an array containing the result of transforming this operation by given {document.operation.MoveOperation}.
-	 *
-	 * @method getTransformedByRangeMove
-	 * @param {document.operation.MoveOperation} move Operation to transform by.
-	 * @returns {Array.<document.operation.ChangeOperation>} Result of the transformation.
-	 * @private
-	 */
-	function getTransformedByMoveOperation( move ) {
-		/*jshint validthis:true */
-
-		// Convert MoveOperation properties into a range.
-		const moveRange = Range.createFromPositionAndOffset( move.sourcePosition, move.howMany );
-
-		// Get a target position from a state "after" nodes from moveRange are "detached".
-		const newTargetPosition = move.targetPosition.getTransformedByDeletion( move.sourcePosition, move.howMany );
-
-		// This will aggregate transformed ranges.
-		let ranges = [];
-
-		const differenceSet = this.range.getDifference( moveRange );
-		const common = this.range.getCommon( moveRange );
-
-		// Difference is the range(s) that are modified by this operation but are not affected by MoveOperation.
-		if ( differenceSet.length > 0 ) {
-			const difference = differenceSet[ 0 ];
-
-			// If two ranges were returned it means that moveRange was inside this operation range.
-			// We will cover that moveRange later. Right now we simply join the ranges and transform them as one.
-			if ( differenceSet.length == 2 ) {
-				difference.end = differenceSet[ 1 ].end.clone();
-			}
-
-			// MoveOperation removes nodes from their original position. We acknowledge this by proper transformation.
-			// Take the start and the end of the range and transform them by deletion of moved nodes.
-			// Note that if moveRange was inside ChangeOperation range, only difference.end will be transformed.
-			// This nicely covers the joining simplification we did in the previous step.
-			difference.start = difference.start.getTransformedByDeletion( move.sourcePosition, move.howMany );
-			difference.end = difference.end.getTransformedByDeletion( move.sourcePosition, move.howMany );
-
-			// MoveOperation pastes nodes into target position. We acknowledge this by proper transformation.
-			// Note that since we operate on transformed difference range, we should transform by
-			// previously transformed target position.
-			ranges = difference.getTransformedByInsertion( newTargetPosition, move.howMany, false );
-		}
-
-		// Common is a range of nodes that is affected by MoveOperation. So it got moved to other place.
-		if ( common !== null ) {
-			// We substitute original position by the combination of target position and original position.
-			// This reflects that those nodes were moved to another place by MoveOperation.
-			common.start = common.start.getCombined( move.sourcePosition, newTargetPosition );
-			common.end = common.end.getCombined( move.sourcePosition, newTargetPosition );
-
-			ranges.push( common );
-		}
-
-		// Map transformed range(s) to operations and return them.
-		return ranges.map( ( range, i ) => {
-			return new ChangeOperation(
-				range,
-				this.oldAttr,
-				this.newAttr,
-				this.baseVersion + i + 1
-			);
-		} );
-	}
-
-	/**
-	 * Returns an array containing the result of transforming this operation by given {document.operation.ChangeOperation}.
-	 *
-	 * @method getTransformedByChangeOperation
-	 * @param {document.operation.ChangeOperation} change Operation to transform by.
-	 * @param {Boolean} isStrong Flag indicating whether this operation should be treated as more important
-	 * when resolving conflicts.
-	 * @returns {Array.<document.operation.ChangeOperation>} Result of the transformation.
-	 * @private
-	 */
-	function getTransformedByChangeOperation( change, isStrong ) {
-		/*jshint validthis:true */
-
-		if ( !isStrong && this.conflictsAttributesWith( change ) ) {
-			// If operations' attributes are in conflict and this operation is less important
-			// we have to check if operations' ranges intersect and manage them properly.
-
-			// We get the range(s) which are only affected by this operation.
-			const ranges = this.range.getDifference( change.range );
-
-			if ( ranges.length === 0 ) {
-				// If there are no such ranges, this operation should not do anything (as it is less important).
-				return [ new NoOperation( this.baseVersion + 1 ) ];
-			} else {
-				// If there are such ranges, map them to operations and then return.
-				return ranges.map( ( range, i ) => {
-					return new ChangeOperation(
-						range,
-						this.oldAttr,
-						this.newAttr,
-						this.baseVersion + i + 1
-					);
-				} );
-			}
-		} else {
-			// If operations don't conflict or this operation is more important
-			// simply, return an array containing just a clone of this operation.
-			return [ this.clone( this.baseVersion + 1 ) ];
-		}
-	}
-
 	return ChangeOperation;
 } );

+ 3 - 77
packages/ckeditor5-utils/src/document/operation/insertoperation.js

@@ -8,8 +8,7 @@
 CKEDITOR.define( [
 	'document/operation/operation',
 	'document/nodelist',
-	'document/operation/removeoperation',
-	'document/operation/moveoperation'
+	'document/operation/removeoperation'
 ], ( Operation, NodeList ) => {
 	/**
 	 * Operation to insert list of nodes on the given position in the tree data model.
@@ -57,82 +56,9 @@ CKEDITOR.define( [
 			return new RemoveOperation( this.position, this.nodeList.length, this.baseVersion + 1 );
 		}
 
-		getTransformedBy( operation, isStrong ) {
-			// Circular dependency re-require.
-			const MoveOperation = CKEDITOR.require( 'document/operation/moveoperation' );
-
-			if ( operation instanceof InsertOperation ) {
-				return getTransformedByInsertOperation.call( this, operation, !!isStrong );
-			} else if ( operation instanceof MoveOperation ) {
-				return getTransformedByMoveOperation.call( this, operation, !!isStrong );
-			}
-
-			return [ this.clone( this.baseVersion + 1 ) ];
+		clone() {
+			return new InsertOperation( this.position, this.nodeList, this.baseVersion );
 		}
-
-		clone( baseVersion ) {
-			if ( !baseVersion ) {
-				baseVersion = this.baseVersion;
-			}
-
-			return new InsertOperation( this.position, this.nodeList, baseVersion );
-		}
-	}
-
-	/**
-	 * Returns an array containing the result of transforming this operation by given {document.operation.InsertOperation}.
-	 *
-	 * @method getTransformedByInsertOperation
-	 * @param {document.operation.InsertOperation} insert Operation to transform by.
-	 * @param {Boolean} isStrong Flag indicating whether this operation should be treated as more important
-	 * when resolving conflicts.
-	 * @returns {Array.<document.operation.ChangeOperation>} Result of the transformation.
-	 * @private
-	 */
-	function getTransformedByInsertOperation( insert, isStrong ) {
-		/*jshint validthis:true */
-
-		// Transformed operations are always new instances, not references to the original operations.
-		const transformed = this.clone( this.baseVersion + 1 );
-
-		// Transform this operation's position by given operation's position.
-		transformed.position = transformed.position.getTransformedByInsertion( insert.position, insert.nodeList.length, !isStrong );
-
-		return [ transformed ];
-	}
-
-	/**
-	 * Returns an array containing the result of transforming this operation by given {document.operation.MoveOperation}.
-	 *
-	 * @method getTransformedByMoveOperation
-	 * @param {document.operation.MoveOperation} move Operation to transform by.
-	 * @param {Boolean} isStrong Flag indicating whether this operation should be treated as more important
-	 * when resolving conflicts.
-	 * @returns {Array.<document.operation.InsertOperation>} Result of the transformation.
-	 * @private
-	 */
-	function getTransformedByMoveOperation( move, isStrong ) {
-		/*jshint validthis:true */
-
-		// Transformed operations are always new instances, not references to the original operations.
-		const transformed = this.clone( this.baseVersion + 1 );
-
-		// MoveOperation removes nodes from their original position. We acknowledge this by proper transformation.
-		const newPosition = this.position.getTransformedByDeletion( move.sourcePosition, move.howMany );
-
-		if ( newPosition === null ) {
-			// This operation's position was inside a node moved by MoveOperation. We substitute that position by
-			// the combination of move target position and insert position. This reflects changes done by MoveOperation.
-
-			transformed.position = transformed.position.getCombined( move.sourcePosition, move.targetPosition );
-		} else {
-			// Here we have the insert position after some nodes has been removed by MoveOperation.
-			// Next step is to reflect pasting nodes by MoveOperation, which might further affect the position.
-
-			transformed.position = newPosition.getTransformedByInsertion( move.targetPosition, move.howMany, !isStrong );
-		}
-
-		return [ transformed ];
 	}
 
 	return InsertOperation;

+ 4 - 180
packages/ckeditor5-utils/src/document/operation/moveoperation.js

@@ -8,11 +8,8 @@
 CKEDITOR.define( [
 	'document/operation/operation',
 	'ckeditorerror',
-	'utils',
-	'document/operation/nooperation',
-	'document/range',
-	//'document/operation/insertoperation'
-], ( Operation, CKEditorError, utils, NoOperation, Range ) => {
+	'utils'
+], ( Operation, CKEditorError, utils ) => {
 	/**
 	 * Operation to move list of subsequent nodes from one position in the document to another.
 	 *
@@ -125,182 +122,9 @@ CKEDITOR.define( [
 			return new MoveOperation( this.targetPosition.clone(), this.sourcePosition.clone(), this.howMany, this.baseVersion + 1 );
 		}
 
-		getTransformedBy( operation, isStrong ) {
-			// Circular dependency re-require.
-			const InsertOperation = CKEDITOR.require( 'document/operation/insertoperation' );
-
-			if ( operation instanceof InsertOperation ) {
-				return getTransformedByInsertOperation.call( this, operation, !!isStrong );
-			} else if ( operation instanceof MoveOperation ) {
-				return getTransformedByMoveOperation.call( this, operation, !!isStrong );
-			}
-
-			return [ this.clone( this.baseVersion + 1 ) ];
-		}
-
-		clone( baseVersion ) {
-			if ( !baseVersion ) {
-				baseVersion = this.baseVersion;
-			}
-
-			return new MoveOperation( this.sourcePosition.clone(), this.targetPosition.clone(), this.howMany, baseVersion );
-		}
-	}
-
-	/**
-	 * Returns an array containing the result of transforming this operation by given {document.operation.InsertOperation}.
-	 *
-	 * @method getTransformedByInsertOperation
-	 * @param {document.operation.InsertOperation} insert Operation to transform by.
-	 * @param {Boolean} isStrong Flag indicating whether this operation should be treated as more important
-	 * when resolving conflicts.
-	 * @returns {Array.<document.operation.MoveOperation>} Result of the transformation.
-	 * @private
-	 */
-	function getTransformedByInsertOperation( insert, isStrong ) {
-		/*jshint validthis:true */
-
-		// Get a target position from a state "after" nodes are inserted by InsertOperation.
-		const newTargetPosition = this.targetPosition.getTransformedByInsertion( insert.position, insert.nodeList.length, !isStrong );
-
-		// Create a range from MoveOperation properties and transform it by insertion as well.
-		const moveRange = Range.createFromPositionAndOffset( this.sourcePosition, this.howMany );
-		const ranges = moveRange.getTransformedByInsertion( insert.position, insert.nodeList.length, true );
-
-		// Map transformed range(s) to operations and return them.
-		return ranges.map( ( range, i ) => {
-			return new MoveOperation(
-				range.start,
-				newTargetPosition.clone(),
-				range.end.offset - range.start.offset,
-				this.baseVersion + i + 1
-			);
-		} );
-	}
-
-	/**
-	 * Returns an array containing the result of transforming this operation by given {document.operation.MoveOperation}.
-	 *
-	 * @method getTransformedByMoveOperation
-	 * @param {document.operation.MoveOperation} move Operation to transform by.
-	 * @param {Boolean} isStrong Flag indicating whether this operation should be treated as more important
-	 * when resolving conflicts.
-	 * @returns {Array.<document.operation.MoveOperation>} Result of the transformation.
-	 * @private
-	 */
-	function getTransformedByMoveOperation( move, isStrong ) {
-		/*jshint validthis:true */
-
-		// There is a special case when both move operations' target positions are inside nodes that are
-		// being moved by the other move operation. So in other words, we move ranges into inside of each other.
-		// This case can't be solved reasonably (on the other hand, it should not happen often).
-		if ( _targetsIntoMoveOperation.call( this, move ) && _targetsIntoMoveOperation.call( move, this ) ) {
-			// Instead of transforming this operation, we return a reverse of the operation that we transform by.
-			// So when the results of this "transformation" will be applied, given MoveOperation will get reversed.
-			return [ move.getReversed() ];
-		}
-
-		// Get a target position from a state "after" nodes from moveRange are "detached".
-		const moveTargetPosition = move.targetPosition.getTransformedByDeletion( move.sourcePosition, move.howMany );
-
-		// This will aggregate transformed ranges.
-		let ranges = [];
-
-		// Create ranges from MoveOperations properties.
-		const thisRange = Range.createFromPositionAndOffset( this.sourcePosition, this.howMany );
-		const moveRange = Range.createFromPositionAndOffset( move.sourcePosition, move.howMany );
-
-		const differenceSet = thisRange.getDifference( moveRange );
-
-		// MoveOperations ranges may intersect.
-		// First, we take care of that part of the range that is only modified by this operation.
-		if ( differenceSet.length > 0 ) {
-			const difference = differenceSet[ 0 ];
-
-			// If two ranges were returned it means that moveRange was inside thisRange.
-			// We will cover that moveRange later. Right now we simply join the ranges and transform them as one.
-			if ( differenceSet.length == 2 ) {
-				difference.end = differenceSet[ 1 ].end.clone();
-			}
-
-			// MoveOperation removes nodes from their original position. We acknowledge this by proper transformation.
-			// Take the start and the end of the range and transform them by deletion of moved nodes.
-			// Note that if moveRange was inside thisRange, only difference.end will be transformed.
-			// This nicely covers the joining simplification we did in the previous step.
-			difference.start = difference.start.getTransformedByDeletion( move.sourcePosition, move.howMany );
-			difference.end = difference.end.getTransformedByDeletion( move.sourcePosition, move.howMany );
-
-			// MoveOperation pastes nodes into target position. We acknowledge this by proper transformation.
-			// Note that since we operate on transformed difference range, we should transform by
-			// previously transformed target position.
-			ranges = difference.getTransformedByInsertion( moveTargetPosition, move.howMany, true );
+		clone() {
+			return new MoveOperation( this.sourcePosition.clone(), this.targetPosition.clone(), this.howMany, this.baseVersion );
 		}
-
-		// Then, we have to manage the common part of both move ranges.
-		// If MoveOperations has common range it can be one of two:
-		// * on the same tree level - it means that we move the same nodes into different places
-		// * on deeper tree level - it means that we move nodes that are inside moved nodes
-		// The operations are conflicting only if they try to explicitly move same nodes, so only in the first case.
-		// So, we will handle common range if it is deeper or if this operation is more important.
-		if ( utils.compareArrays( move.sourcePosition.parentPath, this.sourcePosition.parentPath ) == utils.compareArrays.PREFIX || isStrong ) {
-			const common = thisRange.getCommon( moveRange );
-
-			if ( common !== null ) {
-				// We substitute original position by the combination of target position and original position.
-				// This reflects that those nodes were moved to another place by MoveOperation.
-				common.start = common.start.getCombined( move.sourcePosition, moveTargetPosition );
-				common.end = common.end.getCombined( move.sourcePosition, moveTargetPosition );
-
-				ranges.push( common );
-			}
-		}
-
-		// At this point we transformed this operation's source range. Now, we need to transform target position.
-
-		// First, transform target position by deletion of the other operation's range.
-		let newTargetPosition = this.targetPosition.getTransformedByDeletion( move.sourcePosition, move.howMany );
-
-		if ( newTargetPosition === null ) {
-			// This operation's target position was inside a node moved by the other MoveOperation.
-			// We substitute that position by the combination of the other move target position and this target position.
-			// This reflects changes done by the other MoveOperation.
-
-			newTargetPosition = this.targetPosition.getCombined( move.sourcePosition, moveTargetPosition );
-		} else {
-			// Here we have the target position after some nodes has been removed by MoveOperation.
-			// Next step is to reflect pasting nodes by MoveOperation, which might further affect the position.
-
-			newTargetPosition = newTargetPosition.getTransformedByInsertion( moveTargetPosition, move.howMany, true );
-		}
-
-		// If we haven't got any ranges this far it means that both operations tried to move the same nodes and
-		// this operation is less important. We return NoOperation in this case.
-		if ( ranges.length === 0 ) {
-			return [ new NoOperation( this.baseVersion + 1 ) ];
-		}
-
-		// At this point we might have multiple ranges. All those ranges will be moved to newTargetPosition.
-		// To keep them in the right order, we need to move them starting from the last one.
-		// [|ABC||DEF||GHI|] ==> [^], [|ABC||DEF|] ==> [^GHI], [|ABC|] ==> [^DEFGHI], [] ==> [ABCDEFGHI]
-		// To achieve this, we sort ranges by their starting position in descending order.
-		ranges.sort( ( a, b ) => b.start.compareWith( a.start ) );
-
-		// Map transformed range(s) to operations and return them.
-		return ranges.map( ( range, i ) => {
-			return new MoveOperation(
-				range.start,
-				newTargetPosition,
-				range.end.offset - range.start.offset,
-				this.baseVersion + i + 1
-			);
-		} );
-	}
-
-	// Checks whether this MoveOperation targetPosition is inside a node from the source range of given MoveOperation.
-	function _targetsIntoMoveOperation( operation ) {
-		/*jshint validthis:true */
-
-		return this.targetPosition.getTransformedByDeletion( operation.sourcePosition, operation.howMany ) === null;
 	}
 
 	return MoveOperation;

+ 2 - 11
packages/ckeditor5-utils/src/document/operation/nooperation.js

@@ -24,22 +24,13 @@ CKEDITOR.define( [
 			// Do nothing.
 		}
 
-		clone( baseVersion ) {
-			/* istanbul ignore else */
-			if ( !baseVersion ) {
-				baseVersion = this.baseVersion;
-			}
-
-			return new NoOperation( baseVersion );
+		clone() {
+			return new NoOperation( this.baseVersion );
 		}
 
 		getReversed() {
 			return new NoOperation( this.baseVersion + 1 );
 		}
-
-		getTransformedBy() {
-			return this.clone();
-		}
 	}
 
 	return NoOperation;

+ 0 - 30
packages/ckeditor5-utils/src/document/operation/operation.js

@@ -60,39 +60,9 @@ CKEDITOR.define( [], () => {
 			 */
 
 			/**
-			 * Creates and returns an array containing one or more operations, that are the result of
-			 * transforming this operation by the given operation. When operation is transformed its parameters
-			 * may change accordingly to the operation by which it is transformed. If the given operation
-			 * applied any modifications to the tree model that affects ranges/positions/nodes connected with this
-			 * operation, those changes will be reflected in the parameters of the returned operation(s).
-			 *
-			 * In some cases, when given operation apply changes to the same nodes as this operation, there is a need
-			 * to create two operations as a result. It would be impossible to create just one operation that handles
-			 * modifications needed to be applied to the tree. This is why array is returned instead of single object.
-			 * In those cases, returned Array will contain two or more objects. All of those operations has to be
-			 * applied or further transformed.
-			 *
-			 * Whenever the {@link document.Document document} has different {@link document.Document#baseVersion}
-			 * than an operation you want to {@link document.Document#applyOperation apply}, you need to transform that
-			 * operation by all the operations that were executed on the {@link document.Document document} since it has
-			 * {@link document.Document#baseVersion} same as the operation. Transform them in the same order as those
-			 * operations were applied. This way all modifications done to the tree model will be reflected
-			 * in the operation parameters and the operation will "operate" on "up-to-date" version of the tree model.
-			 * This is mostly the case with Operational Transformation but it might be needed in features code.
-			 *
-			 * @method getTransformedBy
-			 * @param {document.operation.Operation} operation Operation by which this operation will be transformed.
-			 * @param {Boolean} isStrong Flag indicating whether this operation should be treated as more important
-			 * when resolving conflicts.
-			 * @returns {Array.<document.operation.Operation>} Result of the transformation.
-			 */
-
-			/**
 			 * Creates and returns an operation that has the same parameters as this operation.
 			 *
 			 * @method clone
-			 * @param {Number|null} baseVersion New baseVersion for cloned operation. Will copy this operation's
-			 * baseVersion if omitted.
 			 * @returns {document.operation.Operation} Clone of this operation.
 			 */
 		}

+ 434 - 0
packages/ckeditor5-utils/src/document/transformoperation.js

@@ -0,0 +1,434 @@
+/**
+ * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+/**
+ * Transforms given {document.operation.Operation} by another {document.operation.Operation} and returns the result of
+ * that transformation as an Array containing one or more {document.operation.Operation} elements.
+ *
+ * Operations operate on specified positions, passed to them when they are created. Whenever {@link document.Document document}
+ * changes, we have to reflect those modifications by updating, or "transforming", operations (which are not yet applied).
+ * When operation is transformed, its parameters may change basing on the operation by which it is transformed.
+ * If the transform-by operation applied any modifications to the Tree Model that affects positions or nodes
+ * connected with transformed operation, those changes will be reflected in the parameters of the returned operation(s).
+ *
+ * Whenever the {@link document.Document document} has different {@link document.Document#baseVersion}
+ * than an operation you want to {@link document.Document#applyOperation apply}, you need to transform that
+ * operation by all the operations that were applied to the {@link document.Document document} since it has
+ * {@link document.Document#baseVersion} same as the operation. Transform them in the same order as those
+ * operations were applied. This way all modifications done to the Tree Model will be reflected
+ * in the operation parameters and the operation will "operate" on "up-to-date" version of the Tree Model.
+ * This is mostly the case with Operational Transformation but it might be needed in particular features.
+ *
+ * In some cases, when given operation apply changes to the same nodes as this operation, there is a need
+ * to create two operations as a result. It would be impossible to create just one operation that handles
+ * modifications needed to be applied to the tree. This is why Array is returned instead of single object.
+ * All returned operations has to be applied (or further transformed) to get an effect that was intended in
+ * pre-transformed operation.
+ *
+ * @function transformOperation
+ * @param {document.operation.Operation} a Operation that will be transformed.
+ * @param {document.operation.Operation} b Operation to transform by.
+ * @param {Boolean} isStrong Flag indicating whether this operation should be treated as more important
+ * when resolving conflicts.
+ * @returns {Array.<document.operation.Operation>} Result of the transformation.
+ */
+
+CKEDITOR.define( [
+	'document/operation/insertoperation',
+	'document/operation/changeoperation',
+	'document/operation/moveoperation',
+	'document/operation/nooperation',
+	'document/range',
+	'utils'
+], ( InsertOperation, ChangeOperation, MoveOperation, NoOperation, Range, utils ) => {
+
+	// When we don't want to update an operation, we create and return a clone of it.
+	// Returns the operation in "unified format" - wrapped in an Array.
+	function doNotUpdate( operation ) {
+		return [ operation.clone() ];
+	}
+
+	// Takes an Array of operations and sets consecutive base versions for them, starting from given base version.
+	// Returns the passed array.
+	function updateBaseVersions( baseVersion, operations ) {
+		for ( let i = 0; i < operations.length; i++ ) {
+			operations[ i ].baseVersion = baseVersion + i + 1;
+		}
+
+		return operations;
+	}
+
+	// Checks whether MoveOperation targetPosition is inside a node from the moved range of the other MoveOperation.
+	function moveTargetIntoMovedRange( a, b ) {
+		return a.targetPosition.getTransformedByDeletion( b.sourcePosition, b.howMany ) === null;
+	}
+
+	const ot = {
+		InsertOperation: {
+			/**
+			 * Returns an array containing the result of transforming
+			 * {document.operation.InsertOperation} by {document.operation.InsertOperation}.
+			 *
+			 * @param {document.operation.InsertOperation} a Operation that will be transformed.
+			 * @param {document.operation.InsertOperation} b Operation to transform by.
+			 * @param {Boolean} isStrong Flag indicating whether this operation should be treated as more important
+			 * when resolving conflicts.
+			 * @returns {Array.<document.operation.InsertOperation>} Result of the transformation.
+			 */
+			InsertOperation( a, b, isStrong ) {
+				// Transformed operations are always new instances, not references to the original operations.
+				const transformed = a.clone();
+
+				// Transform operation position by the other operation position.
+				transformed.position = transformed.position.getTransformedByInsertion( b.position, b.nodeList.length, !isStrong );
+
+				return [ transformed ];
+			},
+
+			ChangeOperation: doNotUpdate,
+
+			/**
+			 * Returns an array containing the result of transforming
+			 * {document.operation.InsertOperation} by {document.operation.MoveOperation}.
+			 *
+			 * @param {document.operation.InsertOperation} a Operation that will be transformed.
+			 * @param {document.operation.MoveOperation} b Operation to transform by.
+			 * @param {Boolean} isStrong Flag indicating whether this operation should be treated as more important
+			 * when resolving conflicts.
+			 * @returns {Array.<document.operation.InsertOperation>} Result of the transformation.
+			 */
+			MoveOperation( a, b, isStrong ) {
+				const transformed = a.clone();
+
+				// MoveOperation removes nodes from their original position. We acknowledge this by proper transformation.
+				const newPosition = a.position.getTransformedByDeletion( b.sourcePosition, b.howMany );
+
+				if ( newPosition === null ) {
+					// This operation's position was inside a node moved by MoveOperation. We substitute that position by
+					// the combination of move target position and insert position. This reflects changes done by MoveOperation.
+
+					transformed.position = transformed.position.getCombined( b.sourcePosition, b.targetPosition );
+				} else {
+					// Here we have the insert position after some nodes has been removed by MoveOperation.
+					// Next step is to reflect pasting nodes by MoveOperation, which might further affect the position.
+
+					transformed.position = newPosition.getTransformedByInsertion( b.targetPosition, b.howMany, !isStrong );
+				}
+
+				return [ transformed ];
+			}
+		},
+		ChangeOperation: {
+			/**
+			 * Returns an array containing the result of transforming
+			 * {document.operation.ChangeOperation} by {document.operation.InsertOperation}.
+			 *
+			 * @param {document.operation.ChangeOperation} a Operation that will be transformed.
+			 * @param {document.operation.InsertOperation} b Operation to transform by.
+			 * @returns {Array.<document.operation.ChangeOperation>} Result of the transformation.
+			 */
+			InsertOperation( a, b ) {
+				// Transform this operation's range.
+				const ranges = a.range.getTransformedByInsertion( b.position, b.nodeList.length );
+
+				// Map transformed range(s) to operations and return them.
+				return ranges.map( ( range ) => {
+					return new ChangeOperation(
+						range,
+						a.oldAttr,
+						a.newAttr,
+						a.baseVersion
+					);
+				} );
+			},
+
+			/**
+			 * Returns an array containing the result of transforming
+			 * {document.operation.ChangeOperation} by {document.operation.ChangeOperation}.
+			 *
+			 * @param {document.operation.ChangeOperation} a Operation that will be transformed.
+			 * @param {document.operation.ChangeOperation} b Operation to transform by.
+			 * @param {Boolean} isStrong Flag indicating whether this operation should be treated as more important
+			 * when resolving conflicts.
+			 * @returns {Array.<document.operation.ChangeOperation>} Result of the transformation.
+			 */
+			ChangeOperation( a, b, isStrong ) {
+				if ( !isStrong && a.conflictsAttributesWith( b ) ) {
+					// If operations' attributes are in conflict and this operation is less important
+					// we have to check if operations' ranges intersect and manage them properly.
+
+					// We get the range(s) which are only affected by this operation.
+					const ranges = a.range.getDifference( b.range );
+
+					if ( ranges.length === 0 ) {
+						// If there are no such ranges, this operation should not do anything (as it is less important).
+						return [ new NoOperation( a.baseVersion ) ];
+					} else {
+						// If there are such ranges, map them to operations and then return.
+						return ranges.map( ( range ) => {
+							return new ChangeOperation(
+								range,
+								a.oldAttr,
+								a.newAttr,
+								a.baseVersion
+							);
+						} );
+					}
+				} else {
+					// If operations don't conflict or this operation is more important
+					// simply, return an array containing just a clone of this operation.
+					return [ a.clone() ];
+				}
+			},
+
+			/**
+			 * Returns an array containing the result of transforming
+			 * {document.operation.ChangeOperation} by {document.operation.MoveOperation}.
+			 *
+			 * @param {document.operation.ChangeOperation} a Operation that will be transformed.
+			 * @param {document.operation.MoveOperation} b Operation to transform by.
+			 * @returns {Array.<document.operation.ChangeOperation>} Result of the transformation.
+			 */
+			MoveOperation( a, b ) {
+				// Convert MoveOperation properties into a range.
+				const rangeB = Range.createFromPositionAndOffset( b.sourcePosition, b.howMany );
+
+				// Get target position from the state "after" nodes specified by MoveOperation are "detached".
+				const newTargetPosition = b.targetPosition.getTransformedByDeletion( b.sourcePosition, b.howMany );
+
+				// This will aggregate transformed ranges.
+				let ranges = [];
+
+				const differenceSet = a.range.getDifference( rangeB );
+				const common = a.range.getCommon( rangeB );
+
+				// Difference is a part of changed range that is modified by ChangeOperation but are not affected
+				// by MoveOperation. This can be zero, one or two ranges (if moved range is inside changed range).
+				if ( differenceSet.length > 0 ) {
+					const difference = differenceSet[ 0 ];
+
+					// If two ranges were returned it means that rangeB was inside rangeA. We will cover rangeB later.
+					// Right now we will make a simplification and join difference ranges and transform them as one.
+					if ( differenceSet.length == 2 ) {
+						difference.end = differenceSet[ 1 ].end.clone();
+					}
+
+					// MoveOperation removes nodes from their original position. We acknowledge this by proper transformation.
+					// Take the start and the end of the range and transform them by deletion of moved nodes.
+					// Note that if rangeB was inside ChangeOperation range, only difference.end will be transformed.
+					// This nicely covers the joining simplification we did in the previous step.
+					difference.start = difference.start.getTransformedByDeletion( b.sourcePosition, b.howMany );
+					difference.end = difference.end.getTransformedByDeletion( b.sourcePosition, b.howMany );
+
+					// MoveOperation pastes nodes into target position. We acknowledge this by proper transformation.
+					// Note that since we operate on transformed difference range, we should transform by
+					// previously transformed target position.
+					ranges = difference.getTransformedByInsertion( newTargetPosition, b.howMany, false );
+				}
+
+				// Common is a range of nodes that is affected by MoveOperation. So it got moved to other place.
+				if ( common !== null ) {
+					// We substitute original position by the combination of target position and original position.
+					// This reflects that those nodes were moved to another place by MoveOperation.
+					common.start = common.start.getCombined( b.sourcePosition, newTargetPosition );
+					common.end = common.end.getCombined( b.sourcePosition, newTargetPosition );
+
+					ranges.push( common );
+				}
+
+				// Map transformed range(s) to operations and return them.
+				return ranges.map( ( range ) => {
+					return new ChangeOperation(
+						range,
+						a.oldAttr,
+						a.newAttr,
+						a.baseVersion
+					);
+				} );
+			}
+		},
+		MoveOperation: {
+			/**
+			 * Returns an array containing the result of transforming
+			 * {document.operation.MoveOperation} by {document.operation.InsertOperation}.
+			 *
+			 * @param {document.operation.MoveOperation} a Operation that will be transformed.
+			 * @param {document.operation.InsertOperation} b Operation to transform by.
+			 * @param {Boolean} isStrong Flag indicating whether this operation should be treated as more important
+			 * when resolving conflicts.
+			 * @returns {Array.<document.operation.MoveOperation>} Result of the transformation.
+			 */
+			InsertOperation( a, b, isStrong ) {
+				// Get target position from the state "after" nodes are inserted by InsertOperation.
+				const newTargetPosition = a.targetPosition.getTransformedByInsertion( b.position, b.nodeList.length, !isStrong );
+
+				// Create range from MoveOperation properties and transform it by insertion as well.
+				const rangeB = Range.createFromPositionAndOffset( a.sourcePosition, a.howMany );
+				const ranges = rangeB.getTransformedByInsertion( b.position, b.nodeList.length, true );
+
+				// Map transformed range(s) to operations and return them.
+				return ranges.map( ( range ) => {
+					return new MoveOperation(
+						range.start,
+						newTargetPosition.clone(),
+						range.end.offset - range.start.offset,
+						a.baseVersion
+					);
+				} );
+			},
+
+			ChangeOperation: doNotUpdate,
+
+			/**
+			 * Returns an array containing the result of transforming
+			 * {document.operation.MoveOperation} by {document.operation.MoveOperation}.
+			 *
+			 * @param {document.operation.MoveOperation} a Operation that will be transformed.
+			 * @param {document.operation.MoveOperation} b Operation to transform by.
+			 * @param {Boolean} isStrong Flag indicating whether this operation should be treated as more important
+			 * when resolving conflicts.
+			 * @returns {Array.<document.operation.MoveOperation>} Result of the transformation.
+			 */
+			MoveOperation( a, b, isStrong ) {
+				// There is a special case when both move operations' target positions are inside nodes that are
+				// being moved by the other move operation. So in other words, we move ranges into inside of each other.
+				// This case can't be solved reasonably (on the other hand, it should not happen often).
+				if ( moveTargetIntoMovedRange( a, b ) && moveTargetIntoMovedRange( b, a ) ) {
+					// Instead of transforming operation, we return a reverse of the operation that we transform by.
+					// So when the results of this "transformation" will be applied, `b` MoveOperation will get reversed.
+					return [ b.getReversed() ];
+				}
+
+				// Get target position from the state "after" nodes specified by other MoveOperation are "detached".
+				const moveTargetPosition = b.targetPosition.getTransformedByDeletion( b.sourcePosition, b.howMany );
+
+				// This will aggregate transformed ranges.
+				let ranges = [];
+
+				// Create ranges from MoveOperations properties.
+				const rangeA = Range.createFromPositionAndOffset( a.sourcePosition, a.howMany );
+				const rangeB = Range.createFromPositionAndOffset( b.sourcePosition, b.howMany );
+
+				const differenceSet = rangeA.getDifference( rangeB );
+
+				// MoveOperations ranges may intersect.
+				// First, we take care of that part of the range that is only modified by transformed operation.
+				if ( differenceSet.length > 0 ) {
+					const difference = differenceSet[ 0 ];
+
+					// If two ranges were returned it means that rangeB was inside rangeA. We will cover rangeB later.
+					// Right now we will make a simplification and join difference ranges and transform them as one.
+					if ( differenceSet.length == 2 ) {
+						difference.end = differenceSet[ 1 ].end.clone();
+					}
+
+					// MoveOperation removes nodes from their original position. We acknowledge this by proper transformation.
+					// Take the start and the end of the range and transform them by deletion of moved nodes.
+					// Note that if rangeB was inside rangeA, only difference.end will be transformed.
+					// This nicely covers the joining simplification we did in the previous step.
+					difference.start = difference.start.getTransformedByDeletion( b.sourcePosition, b.howMany );
+					difference.end = difference.end.getTransformedByDeletion( b.sourcePosition, b.howMany );
+
+					// MoveOperation pastes nodes into target position. We acknowledge this by proper transformation.
+					// Note that since we operate on transformed difference range, we should transform by
+					// previously transformed target position.
+					ranges = difference.getTransformedByInsertion( moveTargetPosition, b.howMany, true );
+				}
+
+				// Then, we have to manage the common part of both move ranges.
+				// If MoveOperations has common range it can be one of two:
+				// * on the same tree level - it means that we move the same nodes into different places
+				// * on deeper tree level - it means that we move nodes that are inside moved nodes
+				// The operations are conflicting only if they try to move exactly same nodes, so only in the first case.
+				// So, we will handle common range if it is "deeper" or if transformed operation is more important.
+				if ( utils.compareArrays( b.sourcePosition.parentPath, a.sourcePosition.parentPath ) == utils.compareArrays.PREFIX || isStrong ) {
+					const common = rangeA.getCommon( rangeB );
+
+					if ( common !== null ) {
+						// We substitute original position by the combination of target position and original position.
+						// This reflects that those nodes were moved to another place by MoveOperation.
+						common.start = common.start.getCombined( b.sourcePosition, moveTargetPosition );
+						common.end = common.end.getCombined( b.sourcePosition, moveTargetPosition );
+
+						ranges.push( common );
+					}
+				}
+
+				// At this point we transformed this operation's source range. Now, we need to transform target position.
+
+				// First, transform target position by deletion of the other operation's range.
+				let newTargetPosition = a.targetPosition.getTransformedByDeletion( b.sourcePosition, b.howMany );
+
+				if ( newTargetPosition === null ) {
+					// Transformed operation target position was inside a node moved by the other MoveOperation.
+					// We substitute that position by the combination of the other move target position and
+					// transformed operation target position. This reflects changes done by the other MoveOperation.
+
+					newTargetPosition = a.targetPosition.getCombined( b.sourcePosition, moveTargetPosition );
+				} else {
+					// Here we have transformed operation target position after some nodes has been removed by MoveOperation.
+					// Next step is to reflect pasting nodes by MoveOperation, which might further affect the position.
+
+					newTargetPosition = newTargetPosition.getTransformedByInsertion( moveTargetPosition, b.howMany, true );
+				}
+
+				// If we haven't got any ranges this far it means that both operations tried to move the same nodes and
+				// transformed operation is less important. We return NoOperation in this case.
+				if ( ranges.length === 0 ) {
+					return [ new NoOperation( a.baseVersion ) ];
+				}
+
+				// At this point we might have multiple ranges. All ranges will be moved to the same, newTargetPosition.
+				// To keep them in the right order, we need to move them starting from the last one.
+				// [|ABC||DEF||GHI|] ==> [^], [|ABC||DEF|] ==> [^GHI], [|ABC|] ==> [^DEFGHI], [] ==> [ABCDEFGHI]
+				// To achieve this, we sort ranges by their starting position in descending order.
+				ranges.sort( ( a, b ) => b.start.compareWith( a.start ) );
+
+				// Map transformed range(s) to operations and return them.
+				return ranges.map( ( range ) => {
+					return new MoveOperation(
+						range.start,
+						newTargetPosition,
+						range.end.offset - range.start.offset,
+						a.baseVersion
+					);
+				} );
+			}
+		}
+	};
+
+	return ( a, b, isStrong ) => {
+		let group;
+		let algorithm;
+
+		if ( a instanceof InsertOperation ) {
+			group = ot.InsertOperation;
+		} else if ( a instanceof ChangeOperation ) {
+			group = ot.ChangeOperation;
+		} else if ( a instanceof MoveOperation ) {
+			group = ot.MoveOperation
+		} else {
+			algorithm = doNotUpdate;
+		}
+
+		if ( group ) {
+			if ( b instanceof InsertOperation ) {
+				algorithm = group.InsertOperation;
+			} else if ( b instanceof ChangeOperation ) {
+				algorithm = group.ChangeOperation;
+			} else if ( b instanceof MoveOperation ) {
+				algorithm = group.MoveOperation
+			} else {
+				algorithm = doNotUpdate;
+			}
+		}
+
+		let transformed = algorithm( a, b, isStrong );
+
+		return updateBaseVersions( a.baseVersion, transformed );
+	};
+} );

+ 1 - 749
packages/ckeditor5-utils/tests/document/operation/changeoperation.js

@@ -4,7 +4,6 @@
  */
 
 /* bender-tags: document */
-/* bender-include: ../../_tools/tools.js */
 /* global describe, before, beforeEach, it, expect */
 
 /* bender-include: ../../_tools/tools.js */
@@ -15,36 +14,25 @@ const getIteratorCount = bender.tools.core.getIteratorCount;
 
 const modules = bender.amd.require(
 	'document/document',
-	'document/node',
 	'document/operation/changeoperation',
-	'document/operation/insertoperation',
-	'document/operation/moveoperation',
-	'document/operation/nooperation',
 	'document/position',
 	'document/range',
 	'document/character',
 	'document/attribute',
-	'document/nodelist',
 	'document/text',
 	'ckeditorerror'
 );
 
 describe( 'ChangeOperation', () => {
-	let Document, Node, ChangeOperation, InsertOperation, MoveOperation, NoOperation, Position, Range,
-		Character, Attribute, NodeList, Text, CKEditorError;
+	let Document, ChangeOperation, Position, Range, Character, Attribute, Text, CKEditorError;
 
 	before( () => {
 		Document = modules[ 'document/document' ];
-		Node = modules[ 'document/node' ];
 		ChangeOperation = modules[ 'document/operation/changeoperation' ];
-		InsertOperation = modules[ 'document/operation/insertoperation' ];
-		MoveOperation = modules[ 'document/operation/moveoperation' ];
-		NoOperation = modules[ 'document/operation/nooperation' ];
 		Position = modules[ 'document/position' ];
 		Range = modules[ 'document/range' ];
 		Character = modules[ 'document/character' ];
 		Attribute = modules[ 'document/attribute' ];
-		NodeList = modules[ 'document/nodelist' ];
 		Text = modules[ 'document/text' ];
 		CKEditorError = modules.ckeditorerror;
 	} );
@@ -390,740 +378,4 @@ describe( 'ChangeOperation', () => {
 			expect( conflicts ).to.be.true;
 		} );
 	} );
-
-	describe( 'getTransformedBy', () => {
-		let nodeA, nodeB, start, end, range, oldAttr, newAttr, op, baseVersion, expected;
-		let expectOperation;
-
-		beforeEach( () => {
-			nodeA = new Node();
-			nodeB = new Node();
-
-			baseVersion = doc.version;
-
-			oldAttr = new Attribute( 'foo', 'abc' );
-			newAttr = new Attribute( 'foo', 'bar' );
-
-			expected = {
-				type: ChangeOperation,
-				oldAttr: oldAttr,
-				newAttr: newAttr,
-				baseVersion: baseVersion + 1
-			};
-
-			expectOperation = bender.tools.operations.expectOperation( Position, Range );
-		} );
-
-		describe( 'on multi-level range', () => {
-			beforeEach( () => {
-				start = new Position( [ 1, 2 ], root );
-				end = new Position( [ 2, 2, 4 ], root );
-
-				range = new Range( start, end );
-
-				op = new ChangeOperation( range, oldAttr, newAttr, baseVersion );
-
-				expected.range = new Range( start.clone(), end.clone() );
-			} );
-
-			describe( 'InsertOperation', () => {
-				it( 'target at different position: no operation update', () => {
-					let transformBy = new InsertOperation(
-						new Position( [ 3, 3, 2 ], root ),
-						[ nodeA, nodeB ],
-						baseVersion
-					);
-
-					let transOp = op.getTransformedBy( transformBy );
-
-					expectOperation( transOp[ 0 ], expected );
-				} );
-
-				it( 'target at offset before: increment offset', () => {
-					let transformBy = new InsertOperation(
-						new Position( [ 1, 1 ], root ),
-						[ nodeA, nodeB ],
-						baseVersion
-					);
-
-					let transOp = op.getTransformedBy( transformBy );
-
-					expected.range.start.offset += 2;
-
-					expectOperation( transOp[ 0 ], expected );
-				} );
-
-				it( 'target at same offset: increment offset', () => {
-					let transformBy = new InsertOperation(
-						new Position( [ 1, 2 ], root ),
-						[ nodeA, nodeB ],
-						baseVersion
-					);
-
-					let transOp = op.getTransformedBy( transformBy );
-
-					expected.range.start.offset += 2;
-
-					expectOperation( transOp[ 0 ], expected );
-				} );
-
-				it( 'target at offset after: no operation update', () => {
-					let transformBy = new InsertOperation(
-						new Position( [ 3, 2 ], root ),
-						[ nodeA, nodeB ],
-						baseVersion
-					);
-
-					let transOp = op.getTransformedBy( transformBy );
-
-					expectOperation( transOp[ 0 ], expected );
-				} );
-
-				it( 'target before node from path: increment index on path', () => {
-					let transformBy = new InsertOperation(
-						new Position( [ 0 ], root ),
-						[ nodeA, nodeB ],
-						baseVersion
-					);
-
-					let transOp = op.getTransformedBy( transformBy );
-
-					expected.range.start.path[ 0 ] += 2;
-					expected.range.end.path[ 0 ] += 2;
-
-					expectOperation( transOp[ 0 ], expected );
-				} );
-
-				it( 'target after node from path: no position change', () => {
-					let transformBy = new InsertOperation(
-						new Position( [ 2, 6 ], root ),
-						[ nodeA, nodeB ],
-						baseVersion
-					);
-
-					let transOp = op.getTransformedBy( transformBy );
-
-					expectOperation( transOp[ 0 ], expected );
-				} );
-
-				it( 'target inside change range: split into two operations', () => {
-					let transformBy = new InsertOperation(
-						new Position( [ 1, 3, 1 ], root ),
-						[ nodeA, nodeB ],
-						baseVersion
-					);
-
-					let transOp = op.getTransformedBy( transformBy );
-
-					expect( transOp ).to.be.instanceof( Array );
-					expect( transOp.length ).to.equal( 2 );
-
-					expected.range.start.path = [ 1, 3, 3 ];
-
-					expectOperation( transOp[ 0 ], expected );
-
-					expected.range.start = op.range.start;
-					expected.range.end.path = [ 1, 3, 1 ];
-					expected.baseVersion++;
-
-					expectOperation( transOp[ 1 ], expected );
-				} );
-			} );
-
-			describe( 'ChangeOperation', () => {
-				it( 'attributes not conflicting: no operation update', () => {
-					let transformBy = new ChangeOperation(
-						range.clone(),
-						new Attribute( 'abc', true ),
-						new Attribute( 'abc', false ),
-						baseVersion
-					);
-
-					let transOp = op.getTransformedBy( transformBy );
-
-					expectOperation( transOp[ 0 ], expected );
-				} );
-
-				it( 'is less important: no operation update', () => {
-					let transformBy = new ChangeOperation(
-						new Range( new Position( [ 1, 1, 4 ], root ), new Position( [ 3 ], root ) ),
-						oldAttr,
-						null,
-						baseVersion
-					);
-
-					let transOp = op.getTransformedBy( transformBy, true );
-
-					expectOperation( transOp[ 0 ], expected );
-				} );
-
-				it( 'range contains original range: convert to NoOperation', () => {
-					let transformBy = new ChangeOperation(
-						new Range( new Position( [ 1, 1, 4 ], root ), new Position( [ 3 ], root ) ),
-						oldAttr,
-						null,
-						baseVersion
-					);
-
-					let transOp = op.getTransformedBy( transformBy );
-
-					expectOperation( transOp[ 0 ], {
-						type: NoOperation,
-						baseVersion: baseVersion + 1
-					} );
-				} );
-
-				// [ original range   <   ]   range transformed by >
-				it( 'range intersects on left: shrink original range', () => {
-					let transformBy = new ChangeOperation(
-						new Range( new Position( [ 1, 4, 2 ], root ), new Position( [ 3 ], root ) ),
-						oldAttr,
-						null,
-						baseVersion
-					);
-
-					let transOp = op.getTransformedBy( transformBy );
-
-					expected.range.end = new Position( [ 1, 4, 2 ], root );
-
-					expectOperation( transOp[ 0 ], expected );
-				} );
-
-				// [  range transformed by  <   ]  original range  >
-				it( 'range intersects on right: shrink original range', () => {
-					let transformBy = new ChangeOperation(
-						new Range( new Position( [ 1 ], root ), new Position( [ 2, 1 ], root ) ),
-						oldAttr,
-						null,
-						baseVersion
-					);
-
-					let transOp = op.getTransformedBy( transformBy );
-
-					expected.range.start = new Position( [ 2, 1 ], root );
-
-					expectOperation( transOp[ 0 ], expected );
-				} );
-
-				it( 'range is inside original range: split original range', () => {
-					let transformBy = new ChangeOperation(
-						new Range( new Position( [ 1, 4, 1 ], root ), new Position( [ 2, 1 ], root ) ),
-						oldAttr,
-						null,
-						baseVersion
-					);
-
-					let transOp = op.getTransformedBy( transformBy );
-
-					expect( transOp ).to.be.instanceof( Array );
-					expect( transOp.length ).to.equal( 2 );
-
-					expected.range.end.path = [ 1, 4, 1 ];
-
-					expectOperation( transOp[ 0 ], expected );
-
-					expected.range.start.path = [ 2, 1 ];
-					expected.range.end = op.range.end;
-					expected.baseVersion++;
-
-					expectOperation( transOp[ 1 ], expected );
-				} );
-			} );
-
-			describe( 'MoveOperation', () => {
-				it( 'range and target are different than change range: no operation update', () => {
-					let transformBy = new MoveOperation(
-						new Position( [ 1, 1, 2 ], root ),
-						new Position( [ 3, 4 ], root ),
-						2,
-						baseVersion
-					);
-
-					let transOp = op.getTransformedBy( transformBy );
-
-					expectOperation( transOp[ 0 ], expected );
-				} );
-
-				it( 'range offset is before change range start offset: decrement offset', () => {
-					let transformBy = new MoveOperation(
-						new Position( [ 1, 0 ], root ),
-						new Position( [ 3, 4, 1 ], root ),
-						2,
-						baseVersion
-					);
-
-					let transOp = op.getTransformedBy( transformBy );
-
-					expected.range.start.offset -= 2;
-
-					expectOperation( transOp[ 0 ], expected );
-				} );
-
-				it( 'target offset is before change range start offset: increment offset', () => {
-					let transformBy = new MoveOperation(
-						new Position( [ 3, 4, 1 ], root ),
-						new Position( [ 1, 0 ], root ),
-						2,
-						baseVersion
-					);
-
-					let transOp = op.getTransformedBy( transformBy );
-
-					expected.range.start.offset += 2;
-
-					expectOperation( transOp[ 0 ], expected );
-				} );
-
-				it( 'range is before node from path to change range: decrement index on path', () => {
-					let transformBy = new MoveOperation(
-						new Position( [ 0 ], root ),
-						new Position( [ 2, 4, 1 ], root ),
-						1,
-						baseVersion
-					);
-
-					let transOp = op.getTransformedBy( transformBy );
-
-					expected.range.start.path[ 0 ]--;
-					expected.range.end.path[ 0 ]--;
-
-					expectOperation( transOp[ 0 ], expected );
-				} );
-
-				it( 'range is after node from path to change range: no position change', () => {
-					let transformBy = new MoveOperation(
-						new Position( [ 3 ], root ),
-						new Position( [ 0, 1 ], root ),
-						2,
-						baseVersion
-					);
-
-					let transOp = op.getTransformedBy( transformBy );
-
-					expectOperation( transOp[ 0 ], expected );
-				} );
-
-				it( 'target before node from path to change range: increment index on path', () => {
-					let transformBy = new MoveOperation(
-						new Position( [ 3, 4, 1 ], root ),
-						new Position( [ 1, 0 ], root ),
-						2,
-						baseVersion
-					);
-
-					let transOp = op.getTransformedBy( transformBy );
-
-					expected.range.start.path[ 1 ] += 2;
-
-					expectOperation( transOp[ 0 ], expected );
-				} );
-
-				it( 'target after node from path to change range: no position change', () => {
-					let transformBy = new MoveOperation(
-						new Position( [ 3, 4, 1 ], root ),
-						new Position( [ 3 ], root ),
-						2,
-						baseVersion
-					);
-
-					let transOp = op.getTransformedBy( transformBy );
-
-					expectOperation( transOp[ 0 ], expected );
-				} );
-
-				it( 'range intersects on left with change range: split into two operations', () => {
-					let transformBy = new MoveOperation(
-						new Position( [ 2, 1 ], root ),
-						new Position( [ 4 ], root ),
-						3,
-						baseVersion
-					);
-
-					let transOp = op.getTransformedBy( transformBy );
-
-					expect( transOp ).to.be.instanceof( Array );
-					expect( transOp.length ).to.equal( 2 );
-
-					expected.range.end.path = [ 2, 1 ];
-
-					expectOperation( transOp[ 0 ], expected );
-
-					expected.range.start.path = [ 4 ];
-					expected.range.end.path = [ 5, 4 ];
-					expected.baseVersion++;
-
-					expectOperation( transOp[ 1 ], expected );
-				} );
-
-				it( 'range intersects on right with change range: split into two operation', () => {
-					let transformBy = new MoveOperation(
-						new Position( [ 1, 1 ], root ),
-						new Position( [ 0, 0 ], root ),
-						3,
-						baseVersion
-					);
-
-					let transOp = op.getTransformedBy( transformBy );
-
-					expect( transOp ).to.be.instanceof( Array );
-					expect( transOp.length ).to.equal( 2 );
-
-					expected.range.start.path = [ 1, 1 ];
-
-					expectOperation( transOp[ 0 ], expected );
-
-					expected.range.start.path = [ 0, 1 ];
-					expected.range.end.path = [ 0, 3 ];
-					expected.baseVersion++;
-
-					expectOperation( transOp[ 1 ], expected );
-				} );
-
-				it( 'range contains change range: update change range', () => {
-					let transformBy = new MoveOperation(
-						new Position( [ 1 ], root ),
-						new Position( [ 3, 4, 1 ], root ),
-						2,
-						baseVersion
-					);
-
-					let transOp = op.getTransformedBy( transformBy );
-
-					expected.range.start.path = [ 1, 4, 1, 2 ];
-					expected.range.end.path = [ 1, 4, 2, 2, 4 ];
-
-					expectOperation( transOp[ 0 ], expected );
-				} );
-
-				it( 'range is inside change range: split into two operations', () => {
-					let transformBy = new MoveOperation(
-						new Position( [ 1, 4 ], root ),
-						new Position( [ 3, 2 ], root ),
-						2,
-						baseVersion
-					);
-
-					let transOp = op.getTransformedBy( transformBy );
-
-					expect( transOp ).to.be.instanceof( Array );
-					expect( transOp.length ).to.equal( 2 );
-
-					expectOperation( transOp[ 0 ], expected );
-
-					expected.range.start.path = [ 3, 2 ];
-					expected.range.end.path = [ 3, 4 ];
-					expected.baseVersion++;
-
-					expectOperation( transOp[ 1 ], expected );
-				} );
-
-				it( 'target inside change range: split into two operations', () => {
-					let transformBy = new MoveOperation(
-						new Position( [ 3, 4, 1 ], root ),
-						new Position( [ 1, 4 ], root ),
-						2,
-						baseVersion
-					);
-
-					let transOp = op.getTransformedBy( transformBy );
-
-					expect( transOp ).to.be.instanceof( Array );
-					expect( transOp.length ).to.equal( 2 );
-
-					expected.range.start.path = [ 1, 6 ];
-
-					expectOperation( transOp[ 0 ], expected );
-
-					expected.range.start = op.range.start;
-					expected.range.end.path = [ 1, 4 ];
-					expected.baseVersion++;
-
-					expectOperation( transOp[ 1 ], expected );
-				} );
-
-				it( 'range intersects change range and target inside change range: split into three operations', () => {
-					let transformBy = new MoveOperation(
-						new Position( [ 1, 1 ], root ),
-						new Position( [ 2 ], root ),
-						3,
-						baseVersion
-					);
-
-					let transOp = op.getTransformedBy( transformBy );
-
-					expect( transOp ).to.be.instanceof( Array );
-					expect( transOp.length ).to.equal( 3 );
-
-					expected.range.start.path = [ 5 ];
-					expected.range.end.path = [ 5, 2, 4 ];
-
-					expectOperation( transOp[ 0 ], expected );
-
-					expected.range.start.path = [ 1, 1 ];
-					expected.range.end.path = [ 2 ];
-					expected.baseVersion++;
-
-					expectOperation( transOp[ 1 ], expected );
-
-					expected.range.start.path = [ 3 ];
-					expected.range.end.path = [ 5 ];
-					expected.baseVersion++;
-
-					expectOperation( transOp[ 2 ], expected );
-				} );
-			} );
-
-			describe( 'NoOperation', () => {
-				it( 'no operation update', () => {
-					let transformBy = new NoOperation( baseVersion );
-
-					let transOp = op.getTransformedBy( transformBy );
-
-					expectOperation( transOp[ 0 ], expected );
-				} );
-			} );
-		} );
-
-		// Some extra cases for a ChangeOperation that operates on single tree level range.
-		// This means that the change range start and end differs only on offset value.
-		describe( 'on single-level range', () => {
-			beforeEach( () => {
-				start = new Position( [ 0, 2, 1 ], root );
-				end = new Position( [ 0, 2, 4 ], root );
-
-				range = new Range( start, end );
-
-				op = new ChangeOperation( range, oldAttr, newAttr, baseVersion );
-
-				expected.range = new Range( start.clone(), end.clone() );
-			} );
-
-			describe( 'InsertOperation', () => {
-				it( 'target at offset before: increment offset', () => {
-					let transformBy = new InsertOperation(
-						new Position( [ 0, 2, 0 ], root ),
-						[ nodeA, nodeB ],
-						baseVersion
-					);
-
-					let transOp = op.getTransformedBy( transformBy );
-
-					expected.range.start.offset += 2;
-					expected.range.end.offset += 2;
-
-					expectOperation( transOp[ 0 ], expected );
-				} );
-
-				it( 'target at same offset: increment offset', () => {
-					let transformBy = new InsertOperation(
-						new Position( [ 0, 2, 1 ], root ),
-						[ nodeA, nodeB ],
-						baseVersion
-					);
-
-					let transOp = op.getTransformedBy( transformBy );
-
-					expected.range.start.offset += 2;
-					expected.range.end.offset += 2;
-
-					expectOperation( transOp[ 0 ], expected );
-				} );
-			} );
-
-			describe( 'MoveOperation', () => {
-				it( 'range offset is before change range start offset: decrement offset', () => {
-					let transformBy = new MoveOperation(
-						new Position( [ 0, 2, 0 ], root ),
-						new Position( [ 2, 4, 1 ], root ),
-						1,
-						baseVersion
-					);
-
-					let transOp = op.getTransformedBy( transformBy );
-
-					expected.range.start.offset--;
-					expected.range.end.offset--;
-
-					expectOperation( transOp[ 0 ], expected );
-				} );
-
-				it( 'target offset is before change range start offset: increment offset', () => {
-					let transformBy = new MoveOperation(
-						new Position( [ 2, 4, 1 ], root ),
-						new Position( [ 0, 2, 0 ], root ),
-						2,
-						baseVersion
-					);
-
-					let transOp = op.getTransformedBy( transformBy );
-
-					expected.range.start.offset += 2;
-					expected.range.end.offset += 2;
-
-					expectOperation( transOp[ 0 ], expected );
-				} );
-
-				it( 'range intersects on left with change range: split into two operations', () => {
-					let transformBy = new MoveOperation(
-						new Position( [ 0, 2, 2 ], root ),
-						new Position( [ 2, 4, 1 ], root ),
-						4,
-						baseVersion
-					);
-
-					let transOp = op.getTransformedBy( transformBy );
-
-					expect( transOp ).to.be.instanceof( Array );
-					expect( transOp.length ).to.equal( 2 );
-
-					expected.range.end.offset -= 2;
-
-					expectOperation( transOp[ 0 ], expected );
-
-					expected.range.start.path = [ 2, 4, 1 ];
-					expected.range.end.path = [ 2, 4, 3 ];
-					expected.baseVersion++;
-
-					expectOperation( transOp[ 1 ], expected );
-				} );
-
-				it( 'range intersects on right with change range: split into two operation', () => {
-					let transformBy = new MoveOperation(
-						new Position( [ 0, 2, 0 ], root ),
-						new Position( [ 2, 4, 1 ], root ),
-						2,
-						baseVersion
-					);
-
-					let transOp = op.getTransformedBy( transformBy );
-
-					expect( transOp ).to.be.instanceof( Array );
-					expect( transOp.length ).to.equal( 2 );
-
-					expected.range.start.offset -= 1;
-					expected.range.end.offset -= 2;
-
-					expectOperation( transOp[ 0 ], expected );
-
-					expected.range.start.path = [ 2, 4, 2 ];
-					expected.range.end.path = [ 2, 4, 3 ];
-					expected.baseVersion++;
-
-					expectOperation( transOp[ 1 ], expected );
-				} );
-
-				it( 'range contains change range: update change range', () => {
-					let transformBy = new MoveOperation(
-						new Position( [ 0, 1 ], root ),
-						new Position( [ 2, 4, 1 ], root ),
-						3,
-						baseVersion
-					);
-
-					let transOp = op.getTransformedBy( transformBy );
-
-					expected.range.start.path = [ 2, 4, 2, 1 ];
-					expected.range.end.path = [ 2, 4, 2, 4 ];
-
-					expectOperation( transOp[ 0 ], expected );
-				} );
-
-				it( 'range is inside change range: split into two operations', () => {
-					let transformBy = new MoveOperation(
-						new Position( [ 0, 2, 2 ], root ),
-						new Position( [ 2, 4, 1 ], root ),
-						1,
-						baseVersion
-					);
-
-					let transOp = op.getTransformedBy( transformBy );
-
-					expect( transOp ).to.be.instanceof( Array );
-					expect( transOp.length ).to.equal( 2 );
-
-					expected.range.end.offset--;
-
-					expectOperation( transOp[ 0 ], expected );
-
-					expected.range.start.path = [ 2, 4, 1 ];
-					expected.range.end.path = [ 2, 4, 2 ];
-					expected.baseVersion++;
-
-					expectOperation( transOp[ 1 ], expected );
-				} );
-
-				it( 'range is same as change range: update change range', () => {
-					let transformBy = new MoveOperation(
-						new Position( [ 0, 2, 1 ], root ),
-						new Position( [ 2, 4, 1 ], root ),
-						3,
-						baseVersion
-					);
-
-					let transOp = op.getTransformedBy( transformBy );
-
-					expected.range.start = new Position( [ 2, 4, 1 ], root );
-					expected.range.end = new Position( [ 2, 4, 4 ], root );
-
-					expectOperation( transOp[ 0 ], expected );
-				} );
-
-				it( 'target inside change range: split into two operations', () => {
-					let transformBy = new MoveOperation(
-						new Position( [ 2, 4, 1 ], root ),
-						new Position( [ 0, 2, 2 ], root ),
-						2,
-						baseVersion
-					);
-
-					let transOp = op.getTransformedBy( transformBy );
-
-					expect( transOp ).to.be.instanceof( Array );
-					expect( transOp.length ).to.equal( 2 );
-
-					expected.range.start.offset = 4;
-					expected.range.end.offset = 6;
-
-					expectOperation( transOp[ 0 ], expected );
-
-					expected.range.start.offset = op.range.start.offset;
-					expected.range.end.offset = 2;
-					expected.baseVersion++;
-
-					expectOperation( transOp[ 1 ], expected );
-				} );
-
-				it( 'range intersects change range and target inside change range: split into three operations', () => {
-					let transformBy = new MoveOperation(
-						new Position( [ 0, 2, 0 ], root ),
-						new Position( [ 0, 2, 3 ], root ),
-						2,
-						baseVersion
-					);
-
-					let transOp = op.getTransformedBy( transformBy );
-
-					expect( transOp ).to.be.instanceof( Array );
-					expect( transOp.length ).to.equal( 3 );
-
-					expected.range.start.offset = 3;
-					expected.range.end.offset = 4;
-
-					expectOperation( transOp[ 0 ], expected );
-
-					expected.range.start.offset = 0;
-					expected.range.end.offset = 1;
-					expected.baseVersion++;
-
-					expectOperation( transOp[ 1 ], expected );
-
-					expected.range.start.offset = 2;
-					expected.range.end.offset = 3;
-					expected.baseVersion++;
-
-					expectOperation( transOp[ 2 ], expected );
-				} );
-			} );
-		} );
-	} );
 } );

+ 2 - 323
packages/ckeditor5-utils/tests/document/operation/insertoperation.js

@@ -4,7 +4,6 @@
  */
 
 /* bender-tags: document */
-/* bender-include: ../../_tools/tools.js */
 /* global describe, before, beforeEach, it, expect */
 
 'use strict';
@@ -15,19 +14,13 @@ const modules = bender.amd.require(
 	'document/nodelist',
 	'document/operation/insertoperation',
 	'document/operation/removeoperation',
-	'document/operation/changeoperation',
-	'document/operation/moveoperation',
-	'document/operation/nooperation',
 	'document/position',
-	'document/range',
 	'document/character',
-	'document/nodelist',
-	'document/attribute'
+	'document/nodelist'
 );
 
 describe( 'InsertOperation', () => {
-	let Document, Node, NodeList, InsertOperation, RemoveOperation, ChangeOperation,
-		MoveOperation, NoOperation, Position, Range, Character, Attribute;
+	let Document, Node, NodeList, InsertOperation, RemoveOperation, Position, Character;
 
 	before( () => {
 		Document = modules[ 'document/document' ];
@@ -35,13 +28,8 @@ describe( 'InsertOperation', () => {
 		NodeList = modules[ 'document/nodelist' ];
 		InsertOperation = modules[ 'document/operation/insertoperation' ];
 		RemoveOperation = modules[ 'document/operation/removeoperation' ];
-		ChangeOperation = modules[ 'document/operation/changeoperation' ];
-		MoveOperation = modules[ 'document/operation/moveoperation' ];
-		NoOperation = modules[ 'document/operation/nooperation' ];
 		Position = modules[ 'document/position' ];
-		Range = modules[ 'document/range' ];
 		Character = modules[ 'document/character' ];
-		Attribute = modules[ 'document/attribute' ];
 	} );
 
 	let doc, root;
@@ -196,313 +184,4 @@ describe( 'InsertOperation', () => {
 		expect( clone.nodeList.length ).to.equal( 2 );
 		expect( clone.baseVersion ).to.equal( baseVersion );
 	} );
-
-	describe( 'getTransformedBy', () => {
-		let nodeA, nodeB, nodeC, nodeD, position, op, baseVersion, expected;
-		let expectOperation;
-
-		beforeEach( () => {
-			nodeA = new Node();
-			nodeB = new Node();
-			nodeC = new Node();
-			nodeD = new Node();
-
-			baseVersion = doc.version;
-
-			position = new Position( [ 0, 2, 1 ], root );
-
-			op = new InsertOperation( position, [ nodeA, nodeB ], baseVersion );
-
-			expected = {
-				type: InsertOperation,
-				position: position.clone(),
-				baseVersion: baseVersion + 1
-			};
-
-			expectOperation = bender.tools.operations.expectOperation( Position, Range );
-		} );
-
-		describe( 'InsertOperation', () => {
-			it( 'target at different position: no position update', () => {
-				let transformBy = new InsertOperation(
-					new Position( [ 1, 3, 2 ], root ),
-					[ nodeC, nodeD ],
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'target at offset before: increment offset', () => {
-				let transformBy = new InsertOperation(
-					new Position( [ 0, 2, 0 ], root ),
-					[ nodeC, nodeD ],
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-				expected.position.offset += 2;
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'target at same offset and is important: increment offset', () => {
-				let transformBy = new InsertOperation(
-					new Position( [ 0, 2, 1 ], root ),
-					[ nodeC, nodeD ],
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-				expected.position.offset += 2;
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'target at same offset and is less important: no position update', () => {
-				let transformBy = new InsertOperation(
-					new Position( [ 0, 2, 1 ], root ),
-					[ nodeC, nodeD ],
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy, true );
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'target at offset after: no position update', () => {
-				let transformBy = new InsertOperation(
-					new Position( [ 0, 2, 2 ], root ),
-					[ nodeC, nodeD ],
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'target before node from path: increment index on path', () => {
-				let transformBy = new InsertOperation(
-					new Position( [ 0, 1 ], root ),
-					[ nodeC, nodeD ],
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-				expected.position.path[ 1 ] += 2;
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'target after node from path: no position update', () => {
-				let transformBy = new InsertOperation(
-					new Position( [ 0, 6 ], root ),
-					[ nodeC, nodeD ],
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-		} );
-
-		describe( 'ChangeOperation', () => {
-			it( 'no position update', () => {
-				let rangeStart = position.clone();
-				let rangeEnd = position.clone();
-				rangeEnd.offset += 2;
-
-				let transformBy = new ChangeOperation(
-					new Range( rangeStart, rangeEnd ),
-					null,
-					new Attribute( 'foo', 'bar' ),
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-		} );
-
-		describe( 'MoveOperation', () => {
-			it( 'range and target are different than insert position: no position update', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 1, 3, 2 ], root ),
-					new Position( [ 2, 1 ], root ),
-					2
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'range offset is before insert position offset: decrement offset', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 0, 2, 0 ], root ),
-					new Position( [ 1, 1 ], root ),
-					1
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-				expected.position.offset--;
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'range offset is after insert position offset: no position update', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 0, 2, 4 ], root ),
-					new Position( [ 1, 1 ], root ),
-					1
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'target offset before insert position offset: increment offset', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 1, 1 ], root ),
-					new Position( [ 0, 2, 0 ], root ),
-					2
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-				expected.position.offset += 2;
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'target offset after insert position offset: no position update', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 1, 1 ], root ),
-					new Position( [ 0, 2, 4 ], root ),
-					2
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'target offset same as insert position offset and is important: increment offset', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 1, 1 ], root ),
-					new Position( [ 0, 2, 1 ], root ),
-					2
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-				expected.position.offset += 2;
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'target offset same as insert position offset and is less important: no position update', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 1, 1 ], root ),
-					new Position( [ 0, 2, 1 ], root ),
-					2
-				);
-
-				let transOp = op.getTransformedBy( transformBy, true );
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'range is before node from insert position path: decrement index on path', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 0, 0 ], root ),
-					new Position( [ 1, 0 ], root ),
-					2
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-				expected.position.path[ 1 ] -= 2;
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'range is after node from insert position path: no position update', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 0, 4 ], root ),
-					new Position( [ 1, 0 ], root ),
-					2
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'target before node from insert position path: increment index on path', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 1, 0 ], root ),
-					new Position( [ 0, 0 ], root ),
-					2
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-				expected.position.path[ 1 ] += 2;
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'target after node from insert position path: no position update', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 1, 0 ], root ),
-					new Position( [ 0, 4 ], root ),
-					2
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'range has node that contains insert position: update position', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 0, 1 ], root ),
-					new Position( [ 1, 1 ], root ),
-					2
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-				expected.position.path = [ 1, 2, 1 ];
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'range contains insert position (on same level): set position offset to range start', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 0, 2, 0 ], root ),
-					new Position( [ 1, 0 ], root ),
-					3
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-				expected.position.offset = 0;
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-		} );
-
-		describe( 'NoOperation', () => {
-			it( 'no operation update', () => {
-				let transformBy = new NoOperation( baseVersion );
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-		} );
-	} );
 } );

+ 1 - 817
packages/ckeditor5-utils/tests/document/operation/moveoperation.js

@@ -4,7 +4,6 @@
  */
 
 /* bender-tags: document */
-/* bender-include: ../../_tools/tools.js */
 /* global describe, before, beforeEach, it, expect */
 
 'use strict';
@@ -12,33 +11,20 @@
 const modules = bender.amd.require(
 	'document/document',
 	'document/operation/moveoperation',
-	'document/operation/insertoperation',
-	'document/operation/changeoperation',
-	'document/operation/nooperation',
-	'document/attribute',
 	'document/position',
-	'document/range',
 	'document/element',
-	'document/node',
 	'document/nodelist',
 	'ckeditorerror'
 );
 
 describe( 'MoveOperation', () => {
-	let Document, MoveOperation, InsertOperation, ChangeOperation, NoOperation,
-		Attribute, Position, Range, Element, Node, NodeList, CKEditorError;
+	let Document, MoveOperation, Position, Element, NodeList, CKEditorError;
 
 	before( () => {
 		Document = modules[ 'document/document' ];
 		MoveOperation = modules[ 'document/operation/moveoperation' ];
-		InsertOperation = modules[ 'document/operation/insertoperation' ];
-		ChangeOperation = modules[ 'document/operation/changeoperation' ];
-		NoOperation = modules[ 'document/operation/nooperation' ];
-		Attribute = modules[ 'document/attribute' ];
 		Position = modules[ 'document/position' ];
-		Range = modules[ 'document/range' ];
 		Element = modules[ 'document/element' ];
-		Node = modules[ 'document/node' ];
 		NodeList = modules[ 'document/nodelist' ];
 		CKEditorError = modules.ckeditorerror;
 	} );
@@ -261,806 +247,4 @@ describe( 'MoveOperation', () => {
 		expect( clone.howMany ).to.equal( howMany );
 		expect( clone.baseVersion ).to.equal( baseVersion );
 	} );
-
-	describe( 'getTransformedBy', () => {
-		let nodeA, nodeB, op, baseVersion, sourcePosition, targetPosition, rangeEnd, howMany, expected;
-		let expectOperation;
-
-		beforeEach( () => {
-			nodeA = new Node();
-			nodeB = new Node();
-
-			baseVersion = doc.version;
-
-			sourcePosition = new Position( [ 2, 2, 4 ], root );
-			targetPosition = new Position( [ 3, 3, 3 ], root );
-			howMany = 2;
-
-			rangeEnd = sourcePosition.clone();
-			rangeEnd.offset += howMany;
-
-			op = new MoveOperation( sourcePosition, targetPosition, howMany, baseVersion );
-
-			expected = {
-				type: MoveOperation,
-				sourcePosition: sourcePosition.clone(),
-				targetPosition: targetPosition.clone(),
-				howMany: howMany,
-				baseVersion: baseVersion + 1
-			};
-
-			expectOperation = bender.tools.operations.expectOperation( Position, Range );
-		} );
-
-		describe( 'InsertOperation', () => {
-			it( 'target at different position than move range and target: no operation update', () => {
-				let transformBy = new InsertOperation(
-					new Position( [ 1, 3, 2 ], root ),
-					[ nodeA, nodeB ],
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'target inside node from move range: no operation update', () => {
-				let transformBy = new InsertOperation(
-					new Position( [ 2, 2, 3, 1 ], root ),
-					[ nodeA, nodeB ],
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'target at offset before range offset: increment range offset', () => {
-				let transformBy = new InsertOperation(
-					new Position( [ 2, 2, 0 ], root ),
-					[ nodeA, nodeB ],
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expected.sourcePosition.offset += 2;
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'target at offset after range offset: no operation update', () => {
-				let transformBy = new InsertOperation(
-					new Position( [ 2, 2, 6 ], root ),
-					[ nodeA, nodeB ],
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'target before node from range start path: increment index on range start path', () => {
-				let transformBy = new InsertOperation(
-					new Position( [ 2, 0 ], root ),
-					[ nodeA, nodeB ],
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expected.sourcePosition.path[ 1 ] += 2;
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'target after node from range start path: no operation update', () => {
-				let transformBy = new InsertOperation(
-					new Position( [ 2, 3 ], root ),
-					[ nodeA, nodeB ],
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'target offset before move target offset: increment target offset', () => {
-				let transformBy = new InsertOperation(
-					new Position( [ 3, 3, 2 ], root ),
-					[ nodeA, nodeB ],
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expected.targetPosition.offset += 2;
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'target offset after move target offset: no operation update', () => {
-				let transformBy = new InsertOperation(
-					new Position( [ 3, 3, 4 ], root ),
-					[ nodeA, nodeB ],
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'target before node from move target position path: increment index on move target position path', () => {
-				let transformBy = new InsertOperation(
-					new Position( [ 3, 0 ], root ),
-					[ nodeA, nodeB ],
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expected.targetPosition.path[ 1 ] += 2;
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'target after node from move target position path: no operation update', () => {
-				let transformBy = new InsertOperation(
-					new Position( [ 3, 6 ], root ),
-					[ nodeA, nodeB ],
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'target offset same as move target offset and is important: increment target offset', () => {
-				let transformBy = new InsertOperation(
-					new Position( [ 3, 3, 3 ], root ),
-					[ nodeA, nodeB ],
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expected.targetPosition.offset += 2;
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'target offset same as move target offset and is less important: no operation update', () => {
-				let transformBy = new InsertOperation(
-					new Position( [ 3, 3, 3 ], root ),
-					[ nodeA, nodeB ],
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy, true );
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'target offset same as move target offset and is important: increment target offset', () => {
-				let transformBy = new InsertOperation(
-					new Position( [ 3, 3 ], root ),
-					[ nodeA, nodeB ],
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expected.targetPosition.path[ 1 ] += 2;
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'target inside move range: split into two operations', () => {
-				let transformBy = new InsertOperation(
-					new Position( [ 2, 2, 5 ], root ),
-					[ nodeA, nodeB ],
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expect( transOp ).to.be.instanceof( Array );
-				expect( transOp.length ).to.equal( 2 );
-
-				expected.sourcePosition.path = [ 2, 2, 7 ];
-				expected.howMany = 1;
-
-				expectOperation( transOp[ 0 ], expected );
-
-				expected.sourcePosition.path = [ 2, 2, 4 ];
-				expected.howMany = 1;
-				expected.baseVersion++;
-
-				expectOperation( transOp[ 1 ], expected );
-			} );
-		} );
-
-		describe( 'ChangeOperation', () => {
-			it( 'no operation update', () => {
-				let transformBy = new ChangeOperation(
-					new Range( sourcePosition, rangeEnd ),
-					new Attribute( 'abc', true ),
-					new Attribute( 'abc', false ),
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-		} );
-
-		describe( 'MoveOperation', () => {
-			it( 'range and target different than transforming range and target: no operation update', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 1, 2 ], root ),
-					new Position( [ 4, 1, 0 ], root ),
-					3,
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'target offset before transforming range start offset: increment range offset', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 4, 1, 0 ], root ),
-					new Position( [ 2, 2, 0 ], root ),
-					2,
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expected.sourcePosition.offset += 2;
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'target offset after transforming range start offset: no operation update', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 4, 1, 0 ], root ),
-					new Position( [ 2, 2, 7 ], root ),
-					2,
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'range start offset before transforming range start offset: decrement range offset', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 2, 2, 0 ], root ),
-					new Position( [ 4, 1, 0 ], root ),
-					2,
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expected.sourcePosition.offset -= 2;
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'range start offset after transforming range start offset: no operation update', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 2, 2, 9 ], root ),
-					new Position( [ 4, 1, 0 ], root ),
-					2,
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy, true );
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'target before node from transforming range start path: increment index on range start path', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 4, 1, 0 ], root ),
-					new Position( [ 2, 1 ], root ),
-					2,
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expected.sourcePosition.path[ 1 ] += 2;
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'target after node from transforming range start path: no operation update', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 4, 1, 0 ], root ),
-					new Position( [ 2, 5 ], root ),
-					2,
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'range before node from transforming range start path: decrement index on range start path', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 2, 0 ], root ),
-					new Position( [ 4, 1, 0 ], root ),
-					2,
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expected.sourcePosition.path[ 1 ] -= 2;
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'range after node from transforming range start path: no operation update', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 2, 3 ], root ),
-					new Position( [ 4, 1, 0 ], root ),
-					2,
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'target offset before transforming target offset: increment target offset', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 4, 1, 0 ], root ),
-					new Position( [ 3, 3, 0 ], root ),
-					2,
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expected.targetPosition.offset += 2;
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'target offset after transforming target offset: no operation update', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 4, 1, 0 ], root ),
-					new Position( [ 3, 3, 5 ], root ),
-					2,
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'range offset before transforming target offset: decrement target offset', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 3, 3, 0 ], root ),
-					new Position( [ 4, 1, 0 ], root ),
-					2,
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expected.targetPosition.offset -= 2;
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'range offset after transforming target offset: no operation update', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 3, 3, 5 ], root ),
-					new Position( [ 4, 1, 0 ], root ),
-					2,
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'target before node from transforming target path: increment index on target path', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 4, 1, 0 ], root ),
-					new Position( [ 3, 1 ], root ),
-					2,
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expected.targetPosition.path[ 1 ] += 2;
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'target after node from transforming target path: no operation update', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 4, 1, 0 ], root ),
-					new Position( [ 3, 5 ], root ),
-					2,
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'range before node from transforming target path: decrement index on target path', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 3, 0 ], root ),
-					new Position( [ 4, 1, 0 ], root ),
-					2,
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expected.targetPosition.path[ 1 ] -= 2;
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'range after node from transforming target path: no operation update', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 3, 5 ], root ),
-					new Position( [ 4, 1, 0 ], root ),
-					2,
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'target inside transforming move range: split into two operations', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 4, 1, 0 ], root ),
-					new Position( [ 2, 2, 5 ], root ),
-					2,
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expect( transOp ).to.be.instanceof( Array );
-				expect( transOp.length ).to.equal( 2 );
-
-				expected.howMany = 1;
-				expected.sourcePosition.offset = 7;
-
-				expectOperation( transOp[ 0 ], expected );
-
-				expected.sourcePosition.offset = 4;
-				expected.baseVersion++;
-
-				expectOperation( transOp[ 1 ], expected );
-			} );
-
-			it( 'target inside a node from transforming range: no operation update', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 4, 1, 0 ], root ),
-					new Position( [ 2, 2, 5, 1 ], root ),
-					2,
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'range has node that contains transforming range: update range path', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 2, 1 ], root ),
-					new Position( [ 4, 2 ], root ),
-					3,
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expected.sourcePosition.path = [ 4, 3, 4 ];
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'range has node that contains transforming target: update target path', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 3, 2 ], root ),
-					new Position( [ 0, 1 ], root ),
-					3,
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expected.targetPosition.path = [ 0, 2, 3 ];
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'target inside a node from transforming range and vice versa: reverse transform-by operation', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 3, 2 ], root ),
-					new Position( [ 2, 2, 5, 0 ], root ),
-					3,
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-				let reversed = transformBy.getReversed();
-
-				expected.sourcePosition = reversed.sourcePosition;
-				expected.targetPosition = reversed.targetPosition;
-				expected.howMany = reversed.howMany;
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'range is same as transforming range and is important: convert to NoOperation', () => {
-				let transformBy = new MoveOperation(
-					op.sourcePosition.clone(),
-					new Position( [ 4, 1, 0 ], root ),
-					op.howMany,
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expectOperation( transOp[ 0 ], {
-					type: NoOperation,
-					baseVersion: baseVersion + 1
-				} );
-			} );
-
-			it( 'range is same as transforming range and is less important: update range path', () => {
-				let transformBy = new MoveOperation(
-					op.sourcePosition.clone(),
-					new Position( [ 4, 1, 0 ], root ),
-					op.howMany,
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy, true );
-
-				expected.sourcePosition.path = [ 4, 1, 0 ];
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'range contains transforming range and is important: convert to NoOperation', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 2, 2, 3 ], root ),
-					new Position( [ 4, 1, 0 ], root ),
-					4,
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expectOperation( transOp[ 0 ], {
-					type: NoOperation,
-					baseVersion: baseVersion + 1
-				} );
-			} );
-
-			it( 'range contains transforming range and is less important: update range path', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 2, 2, 3 ], root ),
-					new Position( [ 4, 1, 0 ], root ),
-					4,
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy, true );
-
-				expected.sourcePosition.path = [ 4, 1, 1 ];
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'range intersects on left with transforming range and is important: shrink range', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 2, 2, 3 ], root ),
-					new Position( [ 4, 1, 0 ], root ),
-					2,
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expected.sourcePosition.path = [ 2, 2, 3 ];
-				expected.howMany = 1;
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'range intersects on left with transforming range and is less important: split into two operations', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 2, 2, 3 ], root ),
-					new Position( [ 4, 1, 0 ], root ),
-					2,
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy, true );
-
-				expect( transOp ).to.be.instanceof( Array );
-				expect( transOp.length ).to.equal( 2 );
-
-				expected.sourcePosition.path = [ 2, 2, 3 ];
-
-				expectOperation( transOp[ 0 ], {
-					type: MoveOperation,
-					sourcePosition: new Position( [ 4, 1, 1 ], root ),
-					targetPosition: expected.targetPosition,
-					howMany: 1,
-					baseVersion: expected.baseVersion
-				} );
-
-				expected.howMany = 1;
-				expected.baseVersion++;
-
-				expectOperation( transOp[ 1 ], expected );
-			} );
-
-			it( 'range intersects on right with transforming range and is important: shrink range', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 2, 2, 5 ], root ),
-					new Position( [ 4, 1, 0 ], root ),
-					2,
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expected.howMany = 1;
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'range intersects on right with transforming range and is less important: split into two operations', () => {
-				let transformBy = new MoveOperation(
-					new Position( [ 2, 2, 5 ], root ),
-					new Position( [ 4, 1, 0 ], root ),
-					2,
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy, true );
-
-				expect( transOp ).to.be.instanceof( Array );
-				expect( transOp.length ).to.equal( 2 );
-
-				expectOperation( transOp[ 0 ], {
-					type: MoveOperation,
-					sourcePosition: new Position( [ 4, 1, 0 ], root ),
-					targetPosition: expected.targetPosition,
-					howMany: 1,
-					baseVersion: expected.baseVersion
-				} );
-
-				expected.howMany = 1;
-				expected.baseVersion++;
-
-				expectOperation( transOp[ 1 ], expected );
-			} );
-
-			it( 'range inside transforming range and is important: shrink range', () => {
-				op.howMany = 4;
-
-				let transformBy = new MoveOperation(
-					new Position( [ 2, 2, 5 ], root ),
-					new Position( [ 4, 1, 0 ], root ),
-					2,
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expected.howMany = 2;
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-
-			it( 'range inside transforming range and is less important: split into two operations', () => {
-				op.howMany = 4;
-
-				let transformBy = new MoveOperation(
-					new Position( [ 2, 2, 5 ], root ),
-					new Position( [ 4, 1, 0 ], root ),
-					2,
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy, true );
-
-				expect( transOp ).to.be.instanceof( Array );
-				expect( transOp.length ).to.equal( 2 );
-
-				expectOperation( transOp[ 0 ], {
-					type: MoveOperation,
-					sourcePosition: new Position( [ 4, 1, 0 ], root ),
-					targetPosition: expected.targetPosition,
-					howMany: 2,
-					baseVersion: expected.baseVersion
-				} );
-
-				expected.howMany = 2;
-				expected.baseVersion++;
-
-				expectOperation( transOp[ 1 ], expected );
-			} );
-
-			it( 'range and target inside transforming range and is less important: split into three operations', () => {
-				op.howMany = 6;
-
-				let transformBy = new MoveOperation(
-					new Position( [ 2, 2, 5 ], root ),
-					new Position( [ 2, 2, 9 ], root ),
-					2,
-					baseVersion
-				);
-
-				let transOp = op.getTransformedBy( transformBy, true );
-
-				expect( transOp ).to.be.instanceof( Array );
-				expect( transOp.length ).to.equal( 3 );
-
-				expected.sourcePosition.path = [ 2, 2, 9 ];
-				expected.howMany = 1;
-
-				expectOperation( transOp[ 0 ], expected );
-
-				expected.sourcePosition.path = [ 2, 2, 7 ];
-				expected.howMany = 2;
-				expected.baseVersion++;
-
-				expectOperation( transOp[ 1 ], expected );
-
-				expected.sourcePosition.path = [ 2, 2, 4 ];
-				expected.howMany = 3;
-				expected.baseVersion++;
-
-				expectOperation( transOp[ 2 ], expected );
-			} );
-		} );
-
-		describe( 'NoOperation', () => {
-			it( 'no operation update', () => {
-				let transformBy = new NoOperation( baseVersion );
-
-				let transOp = op.getTransformedBy( transformBy );
-
-				expectOperation( transOp[ 0 ], expected );
-			} );
-		} );
-	} );
 } );

+ 2 - 54
packages/ckeditor5-utils/tests/document/operation/nooperation.js

@@ -9,27 +9,15 @@
 
 const modules = bender.amd.require(
 	'document/document',
-	'document/position',
-	'document/range',
-	'document/attribute',
-	'document/operation/nooperation',
-	'document/operation/insertoperation',
-	'document/operation/changeoperation',
-	'document/operation/moveoperation'
+	'document/operation/nooperation'
 );
 
 describe( 'NoOperation', () => {
-	let Document, Position, Range, Attribute, NoOperation, InsertOperation, ChangeOperation, MoveOperation;
+	let Document, NoOperation;
 
 	before( function() {
 		Document = modules[ 'document/document' ];
-		Position = modules[ 'document/position' ];
-		Range = modules[ 'document/range' ];
-		Attribute = modules[ 'document/attribute' ];
 		NoOperation = modules[ 'document/operation/nooperation' ];
-		InsertOperation = modules[ 'document/operation/insertoperation' ];
-		ChangeOperation = modules[ 'document/operation/changeoperation' ];
-		MoveOperation = modules[ 'document/operation/moveoperation' ];
 	} );
 
 	let noop, doc, root;
@@ -40,13 +28,6 @@ describe( 'NoOperation', () => {
 		root = doc.createRoot( 'root' );
 	} );
 
-	function expectNoTransformation( noop, transformBy ) {
-		const transOp = noop.getTransformedBy( transformBy );
-
-		expect( transOp ).to.be.instanceof( NoOperation );
-		expect( transOp.baseVersion ).to.equal( 0 );
-	}
-
 	it( 'should not throw an error when applied', () => {
 		expect( () => doc.applyOperation( noop ) ).to.not.throw( Error );
 	} );
@@ -64,37 +45,4 @@ describe( 'NoOperation', () => {
 		expect( clone ).to.be.an.instanceof( NoOperation );
 		expect( clone.baseVersion ).to.equal( 0 );
 	} );
-
-	it( 'should not change when transformed by InsertOperation', () => {
-		const transformBy = new InsertOperation( new Position( [ 0 ], root ), 'abc', 0 );
-
-		expectNoTransformation( noop, transformBy );
-	} );
-
-	it( 'should not change when transformed by ChangeOperation', () => {
-		const transformBy = new ChangeOperation(
-			new Range( new Position( [ 0 ], root ), new Position( [ 1 ], root ) ),
-			null,
-			new Attribute( 'foo', 'bar' ),
-			0
-		);
-
-		expectNoTransformation( noop, transformBy );
-	} );
-
-	it( 'should not change when transformed by MoveOperation', () => {
-		const transformBy = new MoveOperation(
-			new Position( [ 0 ], root ),
-			new Position( [ 1 ], root ),
-			4,
-			0
-		);
-
-		expectNoTransformation( noop, transformBy );
-	} );
-
-	it( 'should not change when transformed by NoOperation', () => {
-		const transformBy = new NoOperation( 0 );
-		expectNoTransformation( noop, transformBy );
-	} );
 } );

文件差異過大導致無法顯示
+ 1951 - 0
packages/ckeditor5-utils/tests/document/transformoperation.js