浏览代码

Add model/view Position.clone() method.

Maciej Gołaszewski 7 年之前
父节点
当前提交
4056ace1e7
共有 24 个文件被更改,包括 101 次插入60 次删除
  1. 2 2
      packages/ckeditor5-engine/src/conversion/upcastdispatcher.js
  2. 1 2
      packages/ckeditor5-engine/src/model/documentselection.js
  3. 8 1
      packages/ckeditor5-engine/src/model/liveposition.js
  4. 2 3
      packages/ckeditor5-engine/src/model/markercollection.js
  5. 1 2
      packages/ckeditor5-engine/src/model/operation/detachoperation.js
  6. 1 1
      packages/ckeditor5-engine/src/model/operation/insertoperation.js
  7. 3 3
      packages/ckeditor5-engine/src/model/operation/mergeoperation.js
  8. 2 2
      packages/ckeditor5-engine/src/model/operation/moveoperation.js
  9. 2 2
      packages/ckeditor5-engine/src/model/operation/renameoperation.js
  10. 2 2
      packages/ckeditor5-engine/src/model/operation/splitoperation.js
  11. 10 10
      packages/ckeditor5-engine/src/model/operation/transform.js
  12. 10 1
      packages/ckeditor5-engine/src/model/position.js
  13. 2 2
      packages/ckeditor5-engine/src/model/selection.js
  14. 3 3
      packages/ckeditor5-engine/src/model/treewalker.js
  15. 1 1
      packages/ckeditor5-engine/src/model/utils/insertcontent.js
  16. 4 4
      packages/ckeditor5-engine/src/view/downcastwriter.js
  17. 4 0
      packages/ckeditor5-engine/src/view/position.js
  18. 2 2
      packages/ckeditor5-engine/src/view/range.js
  19. 4 4
      packages/ckeditor5-engine/src/view/selection.js
  20. 2 2
      packages/ckeditor5-engine/src/view/treewalker.js
  21. 3 2
      packages/ckeditor5-engine/tests/model/liveposition.js
  22. 7 7
      packages/ckeditor5-engine/tests/model/operation/transform.js
  23. 2 2
      packages/ckeditor5-engine/tests/model/operation/transform/utils.js
  24. 23 0
      packages/ckeditor5-engine/tests/model/position.js

+ 2 - 2
packages/ckeditor5-engine/src/conversion/upcastdispatcher.js

@@ -395,10 +395,10 @@ function extractMarkersFromModelFragment( modelItem, writer ) {
 
 		// When marker of given name is not stored it means that we have found the beginning of the range.
 		if ( !markers.has( markerName ) ) {
-			markers.set( markerName, new ModelRange( ModelPosition._createAt( currentPosition ) ) );
+			markers.set( markerName, new ModelRange( currentPosition.clone() ) );
 		// Otherwise is means that we have found end of the marker range.
 		} else {
-			markers.get( markerName ).end = ModelPosition._createAt( currentPosition );
+			markers.get( markerName ).end = currentPosition.clone();
 		}
 
 		// Remove marker element from DocumentFragment.

+ 1 - 2
packages/ckeditor5-engine/src/model/documentselection.js

@@ -11,7 +11,6 @@ import mix from '@ckeditor/ckeditor5-utils/src/mix';
 import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';
 
 import Selection from './selection';
-import Position from './position';
 import LiveRange from './liverange';
 import Text from './text';
 import TextProxy from './textproxy';
@@ -1015,7 +1014,7 @@ class LiveSelection extends Selection {
 	_fixGraveyardSelection( liveRange, removedRangeStart ) {
 		// The start of the removed range is the closest position to the `liveRange` - the original selection range.
 		// This is a good candidate for a fixed selection range.
-		const positionCandidate = Position._createAt( removedRangeStart );
+		const positionCandidate = removedRangeStart.clone();
 
 		// Find a range that is a correct selection range and is closest to the start of removed range.
 		const selectionRange = this._model.schema.getNearestSelectionRange( positionCandidate );

+ 8 - 1
packages/ckeditor5-engine/src/model/liveposition.js

@@ -71,6 +71,13 @@ export default class LivePosition extends Position {
 		return new Position( this.root, this.path.slice(), this.stickiness );
 	}
 
+	/**
+	 * Creates a `LivePosition` instance that is equal to position.
+	 *
+	 * @param {module:engine/model/position~Position} position
+	 * @param {module:engine/model/position~PositionStickiness} [stickiness]
+	 * @returns {module:engine/model/position~Position}
+	 */
 	static fromPosition( position, stickiness ) {
 		return new this( position.root, position.path.slice(), stickiness ? stickiness : position.stickiness );
 	}
@@ -130,7 +137,7 @@ function transform( operation ) {
 	const result = this.getTransformedByOperation( operation );
 
 	if ( !this.isEqual( result ) ) {
-		const oldPosition = Position._createAt( this );
+		const oldPosition = this.toPosition();
 
 		this.path = result.path;
 		this.root = result.root;

+ 2 - 3
packages/ckeditor5-engine/src/model/markercollection.js

@@ -8,7 +8,6 @@
  */
 
 import LiveRange from './liverange';
-import Position from './position';
 import Range from './range';
 import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
@@ -396,7 +395,7 @@ class Marker {
 			throw new CKEditorError( 'marker-destroyed: Cannot use a destroyed marker instance.' );
 		}
 
-		return Position._createAt( this._liveRange.start );
+		return this._liveRange.start.clone();
 	}
 
 	/**
@@ -409,7 +408,7 @@ class Marker {
 			throw new CKEditorError( 'marker-destroyed: Cannot use a destroyed marker instance.' );
 		}
 
-		return Position._createAt( this._liveRange.end );
+		return this._liveRange.end.clone();
 	}
 
 	/**

+ 1 - 2
packages/ckeditor5-engine/src/model/operation/detachoperation.js

@@ -8,7 +8,6 @@
  */
 
 import Operation from './operation';
-import Position from '../position';
 import Range from '../range';
 import { _remove } from './utils';
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
@@ -36,7 +35,7 @@ export default class DetachOperation extends Operation {
 		 *
 		 * @member {module:engine/model/position~Position} #sourcePosition
 		 */
-		this.sourcePosition = Position._createAt( sourcePosition );
+		this.sourcePosition = sourcePosition.clone();
 
 		/**
 		 * Offset size of moved range.

+ 1 - 1
packages/ckeditor5-engine/src/model/operation/insertoperation.js

@@ -39,7 +39,7 @@ export default class InsertOperation extends Operation {
 		 * @readonly
 		 * @member {module:engine/model/position~Position} module:engine/model/operation/insertoperation~InsertOperation#position
 		 */
-		this.position = Position._createAt( position );
+		this.position = position.clone();
 		this.position.stickiness = 'toNone';
 
 		/**

+ 3 - 3
packages/ckeditor5-engine/src/model/operation/mergeoperation.js

@@ -45,7 +45,7 @@ export default class MergeOperation extends Operation {
 		 *
 		 * @member {module:engine/model/position~Position} module:engine/model/operation/mergeoperation~MergeOperation#sourcePosition
 		 */
-		this.sourcePosition = Position._createAt( sourcePosition );
+		this.sourcePosition = sourcePosition.clone();
 		// This is, and should always remain, the first position in its parent.
 		this.sourcePosition.stickiness = 'toPrevious';
 
@@ -61,7 +61,7 @@ export default class MergeOperation extends Operation {
 		 *
 		 * @member {module:engine/model/position~Position} module:engine/model/operation/mergeoperation~MergeOperation#targetPosition
 		 */
-		this.targetPosition = Position._createAt( targetPosition );
+		this.targetPosition = targetPosition.clone();
 		// Except of a rare scenario in `MergeOperation` x `MergeOperation` transformation,
 		// this is, and should always remain, the last position in its parent.
 		this.targetPosition.stickiness = 'toNext';
@@ -71,7 +71,7 @@ export default class MergeOperation extends Operation {
 		 *
 		 * @member {module:engine/model/position~Position} module:engine/model/operation/mergeoperation~MergeOperation#graveyardPosition
 		 */
-		this.graveyardPosition = Position._createAt( graveyardPosition );
+		this.graveyardPosition = graveyardPosition.clone();
 	}
 
 	/**

+ 2 - 2
packages/ckeditor5-engine/src/model/operation/moveoperation.js

@@ -40,7 +40,7 @@ export default class MoveOperation extends Operation {
 		 *
 		 * @member {module:engine/model/position~Position} module:engine/model/operation/moveoperation~MoveOperation#sourcePosition
 		 */
-		this.sourcePosition = Position._createAt( sourcePosition );
+		this.sourcePosition = sourcePosition.clone();
 		// `'toNext'` because `sourcePosition` is a bit like a start of the moved range.
 		this.sourcePosition.stickiness = 'toNext';
 
@@ -56,7 +56,7 @@ export default class MoveOperation extends Operation {
 		 *
 		 * @member {module:engine/model/position~Position} module:engine/model/operation/moveoperation~MoveOperation#targetPosition
 		 */
-		this.targetPosition = Position._createAt( targetPosition );
+		this.targetPosition = targetPosition.clone();
 		this.targetPosition.stickiness = 'toNone';
 	}
 

+ 2 - 2
packages/ckeditor5-engine/src/model/operation/renameoperation.js

@@ -69,7 +69,7 @@ export default class RenameOperation extends Operation {
 	 * @returns {module:engine/model/operation/renameoperation~RenameOperation} Clone of this operation.
 	 */
 	clone() {
-		return new RenameOperation( Position._createAt( this.position ), this.oldName, this.newName, this.baseVersion );
+		return new RenameOperation( this.position.clone(), this.oldName, this.newName, this.baseVersion );
 	}
 
 	/**
@@ -78,7 +78,7 @@ export default class RenameOperation extends Operation {
 	 * @returns {module:engine/model/operation/renameoperation~RenameOperation}
 	 */
 	getReversed() {
-		return new RenameOperation( Position._createAt( this.position ), this.newName, this.oldName, this.baseVersion + 1 );
+		return new RenameOperation( this.position.clone(), this.newName, this.oldName, this.baseVersion + 1 );
 	}
 
 	/**

+ 2 - 2
packages/ckeditor5-engine/src/model/operation/splitoperation.js

@@ -41,7 +41,7 @@ export default class SplitOperation extends Operation {
 		 *
 		 * @member {module:engine/model/position~Position} module:engine/model/operation/splitoperation~SplitOperation#splitPosition
 		 */
-		this.splitPosition = Position._createAt( splitPosition );
+		this.splitPosition = splitPosition.clone();
 		// Keep position sticking to the next node. This way any new content added at the place where the element is split
 		// will be left in the original element.
 		this.splitPosition.stickiness = 'toNext';
@@ -69,7 +69,7 @@ export default class SplitOperation extends Operation {
 		 *
 		 * @member {module:engine/model/position~Position|null} #graveyardPosition
 		 */
-		this.graveyardPosition = graveyardPosition ? Position._createAt( graveyardPosition ) : null;
+		this.graveyardPosition = graveyardPosition ? graveyardPosition.clone() : null;
 
 		if ( this.graveyardPosition ) {
 			this.graveyardPosition.stickiness = 'toNext';

+ 10 - 10
packages/ckeditor5-engine/src/model/operation/transform.js

@@ -943,11 +943,11 @@ setTransformation( AttributeOperation, SplitOperation, ( a, b ) => {
 		const secondPart = a.clone();
 
 		secondPart.range = new Range(
-			Position._createAt( b.moveTargetPosition ),
+			b.moveTargetPosition.clone(),
 			a.range.end._getCombined( b.splitPosition, b.moveTargetPosition )
 		);
 
-		a.range.end = Position._createAt( b.splitPosition );
+		a.range.end = b.splitPosition.clone();
 		a.range.end.stickiness = 'toPrevious';
 
 		return [ a, secondPart ];
@@ -1348,7 +1348,7 @@ setTransformation( MergeOperation, SplitOperation, ( a, b, context ) => {
 	// So now we are fixing situation which was skipped in `MergeOperation` x `MergeOperation` case.
 	//
 	if ( a.sourcePosition.isEqual( b.splitPosition ) && ( context.abRelation == 'mergeSameElement' || a.sourcePosition.offset > 0 ) ) {
-		a.sourcePosition = Position._createAt( b.moveTargetPosition );
+		a.sourcePosition = b.moveTargetPosition.clone();
 		a.targetPosition = a.targetPosition._getTransformedBySplitOperation( b );
 
 		return [ a ];
@@ -1574,7 +1574,7 @@ setTransformation( MoveOperation, MoveOperation, ( a, b, context ) => {
 } );
 
 setTransformation( MoveOperation, SplitOperation, ( a, b, context ) => {
-	let newTargetPosition = Position._createAt( a.targetPosition );
+	let newTargetPosition = a.targetPosition.clone();
 
 	// Do not transform if target position is same as split insertion position and this split comes from undo.
 	// This should be done on relations but it is too much work for now as it would require relations working in collaboration.
@@ -1703,8 +1703,8 @@ setTransformation( MoveOperation, MergeOperation, ( a, b, context ) => {
 			if ( !context.aWasUndone ) {
 				const results = [];
 
-				let gyMoveSource = Position._createAt( b.graveyardPosition );
-				let splitNodesMoveSource = Position._createAt( b.targetPosition );
+				let gyMoveSource = b.graveyardPosition.clone();
+				let splitNodesMoveSource = b.targetPosition.clone();
 
 				if ( a.howMany > 1 ) {
 					results.push( new MoveOperation( a.sourcePosition, a.howMany - 1, a.targetPosition, 0 ) );
@@ -1737,7 +1737,7 @@ setTransformation( MoveOperation, MergeOperation, ( a, b, context ) => {
 				if ( !context.bWasUndone ) {
 					return [ new NoOperation( 0 ) ];
 				} else {
-					a.sourcePosition = Position._createAt( b.graveyardPosition );
+					a.sourcePosition = b.graveyardPosition.clone();
 					a.targetPosition = a.targetPosition._getTransformedByMergeOperation( b );
 
 					return [ a ];
@@ -1772,7 +1772,7 @@ setTransformation( RenameOperation, MergeOperation, ( a, b ) => {
 	// Element to rename got merged, so it was moved to `b.graveyardPosition`.
 	//
 	if ( a.position.isEqual( b.deletionPosition ) ) {
-		a.position = Position._createAt( b.graveyardPosition );
+		a.position = b.graveyardPosition.clone();
 		a.position.stickiness = 'toNext';
 
 		return [ a ];
@@ -1925,7 +1925,7 @@ setTransformation( SplitOperation, MergeOperation, ( a, b, context ) => {
 
 		a.splitPosition = a.splitPosition._getTransformedByMergeOperation( b );
 		a.insertionPosition = SplitOperation.getInsertionPosition( a.splitPosition );
-		a.graveyardPosition = Position._createAt( additionalSplit.insertionPosition );
+		a.graveyardPosition = additionalSplit.insertionPosition.clone();
 		a.graveyardPosition.stickiness = 'toNext';
 
 		return [ additionalSplit, a ];
@@ -2002,7 +2002,7 @@ setTransformation( SplitOperation, MoveOperation, ( a, b, context ) => {
 			a.howMany += b.howMany;
 		}
 
-		a.splitPosition = Position._createAt( b.sourcePosition );
+		a.splitPosition = b.sourcePosition.clone();
 		a.insertionPosition = SplitOperation.getInsertionPosition( a.splitPosition );
 
 		return [ a ];

+ 10 - 1
packages/ckeditor5-engine/src/model/position.js

@@ -360,7 +360,7 @@ export default class Position {
 	 * @returns {module:engine/model/position~Position} Shifted position.
 	 */
 	getShiftedBy( shift ) {
-		const shifted = Position._createAt( this );
+		const shifted = this.clone();
 
 		const offset = shifted.offset + shift;
 		shifted.offset = offset < 0 ? 0 : offset;
@@ -801,6 +801,15 @@ export default class Position {
 	}
 
 	/**
+	 * Returns a new position that is equal to current position.
+	 *
+	 * @returns {module:engine/model/position~Position}
+	 */
+	clone() {
+		return new this.constructor( this.root, this.path, this.stickiness );
+	}
+
+	/**
 	 * Creates position at the given location. The location can be specified as:
 	 *
 	 * * a {@link module:engine/model/position~Position position},

+ 2 - 2
packages/ckeditor5-engine/src/model/selection.js

@@ -302,7 +302,7 @@ export default class Selection {
 	getFirstPosition() {
 		const first = this.getFirstRange();
 
-		return first ? Position._createAt( first.start ) : null;
+		return first ? first.start.clone() : null;
 	}
 
 	/**
@@ -317,7 +317,7 @@ export default class Selection {
 	getLastPosition() {
 		const lastRange = this.getLastRange();
 
-		return lastRange ? Position._createAt( lastRange.end ) : null;
+		return lastRange ? lastRange.end.clone() : null;
 	}
 
 	/**

+ 3 - 3
packages/ckeditor5-engine/src/model/treewalker.js

@@ -85,7 +85,7 @@ export default class TreeWalker {
 		 * @member {module:engine/model/position~Position} module:engine/model/treewalker~TreeWalker#position
 		 */
 		if ( options.startPosition ) {
-			this.position = Position._createAt( options.startPosition );
+			this.position = options.startPosition.clone();
 		} else {
 			this.position = Position._createAt( this.boundaries[ this.direction == 'backward' ? 'end' : 'start' ] );
 		}
@@ -208,7 +208,7 @@ export default class TreeWalker {
 	 */
 	_next() {
 		const previousPosition = this.position;
-		const position = Position._createAt( this.position );
+		const position = this.position.clone();
 		const parent = this._visitedParent;
 
 		// We are at the end of the root.
@@ -282,7 +282,7 @@ export default class TreeWalker {
 	 */
 	_previous() {
 		const previousPosition = this.position;
-		const position = Position._createAt( this.position );
+		const position = this.position.clone();
 		const parent = this._visitedParent;
 
 		// We are at the beginning of the root.

+ 1 - 1
packages/ckeditor5-engine/src/model/utils/insertcontent.js

@@ -316,7 +316,7 @@ class Insertion {
 
 			this.writer.merge( mergePosLeft );
 
-			this.position = Position._createAt( livePosition );
+			this.position = livePosition.toPosition();
 			livePosition.detach();
 		}
 

+ 4 - 4
packages/ckeditor5-engine/src/view/downcastwriter.js

@@ -684,7 +684,7 @@ export default class DowncastWriter {
 		// Merge after removing.
 		const mergePosition = this.mergeAttributes( breakStart );
 		range.start = mergePosition;
-		range.end = Position._createAt( mergePosition );
+		range.end = mergePosition.clone();
 
 		// Return removed nodes.
 		return new DocumentFragment( removed );
@@ -1298,7 +1298,7 @@ export default class DowncastWriter {
 	_wrapPosition( position, attribute ) {
 		// Return same position when trying to wrap with attribute similar to position parent.
 		if ( attribute.isSimilar( position.parent ) ) {
-			return movePositionToTextNode( Position._createAt( position ) );
+			return movePositionToTextNode( position.clone() );
 		}
 
 		// When position is inside text node - break it and place new position between two text nodes.
@@ -1542,12 +1542,12 @@ export default class DowncastWriter {
 
 		// There are no attributes to break and text nodes breaking is not forced.
 		if ( !forceSplitText && positionParent.is( 'text' ) && isContainerOrFragment( positionParent.parent ) ) {
-			return Position._createAt( position );
+			return position.clone();
 		}
 
 		// Position's parent is container, so no attributes to break.
 		if ( isContainerOrFragment( positionParent ) ) {
-			return Position._createAt( position );
+			return position.clone();
 		}
 
 		// Break text and start again in new position.

+ 4 - 0
packages/ckeditor5-engine/src/view/position.js

@@ -289,6 +289,10 @@ export default class Position {
 		return new TreeWalker( options );
 	}
 
+	clone() {
+		return new Position( this.parent, this.offset );
+	}
+
 	/**
 	 * Creates position at the given location. The location can be specified as:
 	 *

+ 2 - 2
packages/ckeditor5-engine/src/view/range.js

@@ -29,7 +29,7 @@ export default class Range {
 		 * @readonly
 		 * @member {module:engine/view/position~Position}
 		 */
-		this.start = Position._createAt( start );
+		this.start = start.clone();
 
 		/**
 		 * End position.
@@ -37,7 +37,7 @@ export default class Range {
 		 * @readonly
 		 * @member {module:engine/view/position~Position}
 		 */
-		this.end = end ? Position._createAt( end ) : Position._createAt( start );
+		this.end = end ? end.clone() : start.clone();
 	}
 
 	/**

+ 4 - 4
packages/ckeditor5-engine/src/view/selection.js

@@ -172,7 +172,7 @@ export default class Selection {
 		const range = this._ranges[ this._ranges.length - 1 ];
 		const anchor = this._lastRangeBackward ? range.end : range.start;
 
-		return Position._createAt( anchor );
+		return anchor.clone();
 	}
 
 	/**
@@ -188,7 +188,7 @@ export default class Selection {
 		const range = this._ranges[ this._ranges.length - 1 ];
 		const focus = this._lastRangeBackward ? range.start : range.end;
 
-		return Position._createAt( focus );
+		return focus.clone();
 	}
 
 	/**
@@ -293,7 +293,7 @@ export default class Selection {
 	getFirstPosition() {
 		const firstRange = this.getFirstRange();
 
-		return firstRange ? Position._createAt( firstRange.start ) : null;
+		return firstRange ? firstRange.start.clone() : null;
 	}
 
 	/**
@@ -306,7 +306,7 @@ export default class Selection {
 	getLastPosition() {
 		const lastRange = this.getLastRange();
 
-		return lastRange ? Position._createAt( lastRange.end ) : null;
+		return lastRange ? lastRange.end.clone() : null;
 	}
 
 	/**

+ 2 - 2
packages/ckeditor5-engine/src/view/treewalker.js

@@ -189,7 +189,7 @@ export default class TreeWalker {
 	 * @returns {module:engine/view/treewalker~TreeWalkerValue} return.value Information about taken step.
 	 */
 	_next() {
-		let position = Position._createAt( this.position );
+		let position = this.position.clone();
 		const previousPosition = this.position;
 		const parent = position.parent;
 
@@ -295,7 +295,7 @@ export default class TreeWalker {
 	 * @returns {module:engine/view/treewalker~TreeWalkerValue} return.value Information about taken step.
 	 */
 	_previous() {
-		let position = Position._createAt( this.position );
+		let position = this.position.clone();
 		const previousPosition = this.position;
 		const parent = position.parent;
 

+ 3 - 2
packages/ckeditor5-engine/tests/model/liveposition.js

@@ -12,7 +12,8 @@ import LivePosition from '../../src/model/liveposition';
 import Range from '../../src/model/range';
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 
-describe( 'LivePosition', () => {
+describe( 'LivePosition', () =>
+{
 	let model, doc, root, ul, p, li1, li2;
 
 	beforeEach( () => {
@@ -68,7 +69,7 @@ describe( 'LivePosition', () => {
 		LivePosition.prototype.stopListening.restore();
 	} );
 
-	describe( '_fromPosition()', () => {
+	describe( 'fromPosition()', () => {
 		it( 'should return LivePosition', () => {
 			const position = LivePosition.fromPosition( new Position( root, [ 0 ] ) );
 			expect( position ).to.be.instanceof( LivePosition );

+ 7 - 7
packages/ckeditor5-engine/tests/model/operation/transform.js

@@ -100,7 +100,7 @@ describe( 'transform', () => {
 
 			expected = {
 				type: InsertOperation,
-				position: Position._createAt( position )
+				position: position.clone()
 			};
 		} );
 
@@ -914,15 +914,15 @@ describe( 'transform', () => {
 			targetPosition = new Position( root, [ 3, 3, 3 ] );
 			howMany = 2;
 
-			rangeEnd = Position._createAt( sourcePosition );
+			rangeEnd = sourcePosition.clone();
 			rangeEnd.offset += howMany;
 
 			op = new MoveOperation( sourcePosition, howMany, targetPosition, 0 );
 
 			expected = {
 				type: MoveOperation,
-				sourcePosition: Position._createAt( sourcePosition ),
-				targetPosition: Position._createAt( targetPosition ),
+				sourcePosition: sourcePosition.clone(),
+				targetPosition: targetPosition.clone(),
 				howMany
 			};
 		} );
@@ -1957,7 +1957,7 @@ describe( 'transform', () => {
 
 			beforeEach( () => {
 				transformBy = new MoveOperation(
-					Position._createAt( op.sourcePosition ),
+					op.sourcePosition.clone(),
 					op.howMany,
 					new Position( doc.graveyard, [ 0 ] ),
 					0
@@ -2015,11 +2015,11 @@ describe( 'transform', () => {
 			it( 'should force removing content even if was less important', () => {
 				const op = new MoveOperation( new Position( root, [ 8 ] ), 2, new Position( doc.graveyard, [ 0 ] ), 0 );
 
-				const targetPosition = Position._createAt( op.targetPosition );
+				const targetPosition = op.targetPosition.clone();
 
 				const transformBy = new MoveOperation( new Position( root, [ 8 ] ), 2, new Position( root, [ 1 ] ), 0 );
 
-				const sourcePosition = Position._createAt( transformBy.targetPosition );
+				const sourcePosition = transformBy.targetPosition.clone();
 
 				const transOp = transform( op, transformBy );
 

+ 2 - 2
packages/ckeditor5-engine/tests/model/operation/transform/utils.js

@@ -220,9 +220,9 @@ export class Client {
 		switch ( type ) {
 			default:
 			case 'start':
-				return Position._createAt( selRange.start );
+				return selRange.start.clone();
 			case 'end':
-				return Position._createAt( selRange.end );
+				return selRange.end.clone();
 			case 'beforeParent':
 				return Position._createBefore( selRange.start.parent );
 		}

+ 23 - 0
packages/ckeditor5-engine/tests/model/position.js

@@ -19,6 +19,7 @@ import MergeOperation from '../../src/model/operation/mergeoperation';
 import SplitOperation from '../../src/model/operation/splitoperation';
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
+import LivePosition from '../../src/model/liveposition';
 
 describe( 'Position', () => {
 	let doc, model, root, otherRoot, p, ul, li1, li2, f, o, z, b, a, r, foz, bar;
@@ -142,6 +143,16 @@ describe( 'Position', () => {
 				expect( positionCopy ).to.not.equal( position );
 			} );
 
+			it( 'should create positions from LivePosition', () => {
+				const position = new LivePosition( root, [ 0, 0 ] );
+				const created = Position._createAt( position );
+
+				expect( created.isEqual( position ) ).to.be.true;
+				expect( created ).to.not.be.equal( position );
+				expect( created ).to.be.instanceof( Position );
+				expect( created ).to.not.be.instanceof( LivePosition );
+			} );
+
 			it( 'should create positions from node and offset', () => {
 				expect( Position._createAt( root, 0 ) ).to.have.property( 'path' ).that.deep.equals( [ 0 ] );
 				expect( Position._createAt( root, 1 ) ).to.have.property( 'path' ).that.deep.equals( [ 1 ] );
@@ -627,6 +638,18 @@ describe( 'Position', () => {
 		} );
 	} );
 
+	describe( 'clone()', () => {
+		it( 'should return new instance of position', () => {
+			const position = Position._createAt( ul, 0 );
+
+			const positionCopy = position.clone();
+
+			expect( positionCopy ).to.have.property( 'path' ).that.deep.equals( [ 1, 0 ] );
+			expect( positionCopy ).to.have.property( 'root' ).that.equals( position.root );
+			expect( positionCopy ).to.not.equal( position );
+		} );
+	} );
+
 	// Note: We don't create model element structure in these tests because this method
 	// is used by OT so it must not check the structure.
 	describe( 'getTransformedByOperation()', () => {