8
0
Просмотр исходного кода

Merge branch 'master' into t/1267

Szymon Kupś 7 лет назад
Родитель
Сommit
a85b2d3588
31 измененных файлов с 788 добавлено и 389 удалено
  1. 1 3
      packages/ckeditor5-engine/src/controller/editingcontroller.js
  2. 1 9
      packages/ckeditor5-engine/src/conversion/downcast-selection-converters.js
  3. 1 1
      packages/ckeditor5-engine/src/conversion/upcast-selection-converters.js
  4. 2 2
      packages/ckeditor5-engine/src/dev-utils/model.js
  5. 1 1
      packages/ckeditor5-engine/src/dev-utils/view.js
  6. 9 7
      packages/ckeditor5-engine/src/model/documentselection.js
  7. 77 26
      packages/ckeditor5-engine/src/model/selection.js
  8. 2 2
      packages/ckeditor5-engine/src/model/utils/deletecontent.js
  9. 1 0
      packages/ckeditor5-engine/src/model/utils/modifyselection.js
  10. 30 15
      packages/ckeditor5-engine/src/model/writer.js
  11. 12 5
      packages/ckeditor5-engine/src/view/domconverter.js
  12. 1 2
      packages/ckeditor5-engine/src/view/observer/fakeselectionobserver.js
  13. 1 2
      packages/ckeditor5-engine/src/view/observer/mutationobserver.js
  14. 124 58
      packages/ckeditor5-engine/src/view/selection.js
  15. 44 28
      packages/ckeditor5-engine/src/view/writer.js
  16. 4 6
      packages/ckeditor5-engine/tests/conversion/downcast-selection-converters.js
  17. 1 1
      packages/ckeditor5-engine/tests/conversion/upcast-selection-converters.js
  18. 3 3
      packages/ckeditor5-engine/tests/dev-utils/model.js
  19. 1 1
      packages/ckeditor5-engine/tests/model/documentselection.js
  20. 2 0
      packages/ckeditor5-engine/tests/model/model.js
  21. 42 26
      packages/ckeditor5-engine/tests/model/selection.js
  22. 4 4
      packages/ckeditor5-engine/tests/model/writer.js
  23. 87 0
      packages/ckeditor5-engine/tests/tickets/ckeditor5-692.js
  24. 2 3
      packages/ckeditor5-engine/tests/view/domconverter/binding.js
  25. 26 3
      packages/ckeditor5-engine/tests/view/domconverter/dom-to-view.js
  26. 1 2
      packages/ckeditor5-engine/tests/view/manual/fakeselection.js
  27. 6 3
      packages/ckeditor5-engine/tests/view/observer/fakeselectionobserver.js
  28. 107 1
      packages/ckeditor5-engine/tests/view/observer/mutationobserver.js
  29. 13 14
      packages/ckeditor5-engine/tests/view/renderer.js
  30. 163 141
      packages/ckeditor5-engine/tests/view/selection.js
  31. 19 20
      packages/ckeditor5-engine/tests/view/writer/writer.js

+ 1 - 3
packages/ckeditor5-engine/src/controller/editingcontroller.js

@@ -20,8 +20,7 @@ import { convertSelectionChange } from '../conversion/upcast-selection-converter
 import {
 	convertRangeSelection,
 	convertCollapsedSelection,
-	clearAttributes,
-	clearFakeSelection
+	clearAttributes
 } from '../conversion/downcast-selection-converters';
 
 import ObservableMixin from '@ckeditor/ckeditor5-utils/src/observablemixin';
@@ -95,7 +94,6 @@ export default class EditingController {
 
 		// Attach default model selection converters.
 		this.downcastDispatcher.on( 'selection', clearAttributes(), { priority: 'low' } );
-		this.downcastDispatcher.on( 'selection', clearFakeSelection(), { priority: 'low' } );
 		this.downcastDispatcher.on( 'selection', convertRangeSelection(), { priority: 'low' } );
 		this.downcastDispatcher.on( 'selection', convertCollapsedSelection(), { priority: 'low' } );
 

+ 1 - 9
packages/ckeditor5-engine/src/conversion/downcast-selection-converters.js

@@ -39,7 +39,7 @@ export function convertRangeSelection() {
 			viewRanges.push( viewRange );
 		}
 
-		conversionApi.writer.setSelection( viewRanges, selection.isBackward );
+		conversionApi.writer.setSelection( viewRanges, { backward: selection.isBackward } );
 	};
 }
 
@@ -127,11 +127,3 @@ export function clearAttributes() {
 		viewWriter.setSelection( null );
 	};
 }
-
-/**
- * Function factory, creates a converter that clears fake selection marking after the previous
- * {@link module:engine/model/selection~Selection model selection} conversion.
- */
-export function clearFakeSelection() {
-	return ( evt, data, conversionApi ) => conversionApi.writer.setFakeSelection( false );
-}

+ 1 - 1
packages/ckeditor5-engine/src/conversion/upcast-selection-converters.js

@@ -37,7 +37,7 @@ export function convertSelectionChange( model, mapper ) {
 			ranges.push( mapper.toModelRange( viewRange ) );
 		}
 
-		modelSelection.setTo( ranges, viewSelection.isBackward );
+		modelSelection.setTo( ranges, { backward: viewSelection.isBackward } );
 
 		if ( !modelSelection.isEqual( model.document.selection ) ) {
 			model.change( writer => {

+ 2 - 2
packages/ckeditor5-engine/src/dev-utils/model.js

@@ -131,7 +131,7 @@ export function setData( model, data, options = {} ) {
 				ranges.push( new ModelRange( start, end ) );
 			}
 
-			writer.setSelection( ranges, selection.isBackward );
+			writer.setSelection( ranges, { backward: selection.isBackward } );
 
 			if ( options.selectionAttributes ) {
 				writer.setSelectionAttribute( selection.getAttributes() );
@@ -326,7 +326,7 @@ export function parse( data, schema, options = {} ) {
 		}
 
 		// Create new selection.
-		selection = new ModelSelection( ranges, viewSelection.isBackward );
+		selection = new ModelSelection( ranges, { backward: viewSelection.isBackward } );
 
 		// Set attributes to selection if specified.
 		for ( const [ key, value ] of toMap( options.selectionAttributes || [] ) ) {

+ 1 - 1
packages/ckeditor5-engine/src/dev-utils/view.js

@@ -340,7 +340,7 @@ export function parse( data, options = {} ) {
 
 	// When ranges are present - return object containing view, and selection.
 	if ( ranges.length ) {
-		const selection = new Selection( ranges, !!options.lastRangeBackward );
+		const selection = new Selection( ranges, { backward: !!options.lastRangeBackward } );
 
 		return {
 			view,

+ 9 - 7
packages/ckeditor5-engine/src/model/documentselection.js

@@ -344,19 +344,21 @@ export default class DocumentSelection {
 	/**
 	 * Sets this selection's ranges and direction to the specified location based on the given
 	 * {@link module:engine/model/selection~Selection selection}, {@link module:engine/model/position~Position position},
-	 * {@link module:engine/model/element~Element element}, {@link module:engine/model/position~Position position},
+	 * {@link module:engine/model/node~Node node}, {@link module:engine/model/position~Position position},
 	 * {@link module:engine/model/range~Range range}, an iterable of {@link module:engine/model/range~Range ranges} or null.
 	 * Should be used only within the {@link module:engine/model/writer~Writer#setSelection} method.
 	 *
 	 * @see module:engine/model/writer~Writer#setSelection
 	 * @protected
 	 * @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection|
-	 * module:engine/model/position~Position|module:engine/model/element~Element|
+	 * module:engine/model/position~Position|module:engine/model/node~Node|
 	 * Iterable.<module:engine/model/range~Range>|module:engine/model/range~Range|null} selectable
-	 * @param {Boolean|Number|'before'|'end'|'after'} [backwardSelectionOrOffset]
+	 * @param {Number|'before'|'end'|'after'|'on'|'in'} [placeOrOffset] Sets place or offset of the selection.
+	 * @param {Object} [options]
+	 * @param {Boolean} [options.backward] Sets this selection instance to be backward.
 	 */
-	_setTo( selectable, backwardSelectionOrOffset ) {
-		this._selection.setTo( selectable, backwardSelectionOrOffset );
+	_setTo( selectable, placeOrOffset, options ) {
+		this._selection.setTo( selectable, placeOrOffset, options );
 	}
 
 	/**
@@ -611,8 +613,8 @@ class LiveSelection extends Selection {
 		return super.getLastRange() || this._document._getDefaultRange();
 	}
 
-	setTo( selectable, backwardSelectionOrOffset ) {
-		super.setTo( selectable, backwardSelectionOrOffset );
+	setTo( selectable, optionsOrPlaceOrOffset, options ) {
+		super.setTo( selectable, optionsOrPlaceOrOffset, options );
 		this._refreshAttributes();
 	}
 

+ 77 - 26
packages/ckeditor5-engine/src/model/selection.js

@@ -9,6 +9,7 @@
 
 import Position from './position';
 import Element from './element';
+import Node from './node';
 import Range from './range';
 import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
@@ -35,11 +36,11 @@ export default class Selection {
 	 *
 	 *		// Creates selection at the given range.
 	 *		const range = new Range( start, end );
-	 *		const selection = new Selection( range, isBackwardSelection );
+	 *		const selection = new Selection( range );
 	 *
 	 *		// Creates selection at the given ranges
 	 * 		const ranges = [ new Range( start1, end2 ), new Range( star2, end2 ) ];
-	 *		const selection = new Selection( ranges, isBackwardSelection );
+	 *		const selection = new Selection( ranges );
 	 *
 	 *		// Creates selection from the other selection.
 	 *		// Note: It doesn't copies selection attributes.
@@ -55,16 +56,31 @@ export default class Selection {
 	 *		const position = new Position( root, path );
 	 *		const selection = new Selection( position );
 	 *
-	 * 		// Creates selection at the start position of given element.
+	 * 		// Creates selection at the start position of the given element.
 	 *		const paragraph = writer.createElement( 'paragraph' );
 	 *		const selection = new Selection( paragraph, offset );
 	 *
+	 * 		// Creates a range inside an {@link module:engine/model/element~Element element} which starts before the
+	 * 		// first child of that element and ends after the last child of that element.
+	 *		const selection = new Selection( paragraph, 'in' );
+	 *
+	 * 		// Creates a range on an {@link module:engine/model/item~Item item} which starts before the item and ends
+	 * 		// just after the item.
+	 *		const selection = new Selection( paragraph, 'on' );
+	 *
+	 * `Selection`'s constructor allow passing additional options (`backward`) as the last argument.
+	 *
+	 * 		// Creates backward selection.
+	 *		const selection = new Selection( range, { backward: true } );
+	 *
 	 * @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection|
 	 * module:engine/model/position~Position|module:engine/model/element~Element|
-	 * Iterable.<module:engine/model/range~Range>|module:engine/model/range~Range} [selectable]
-	 * @param {Boolean|Number|'before'|'end'|'after'} [backwardSelectionOrOffset]
+	 * Iterable.<module:engine/model/range~Range>|module:engine/model/range~Range|null} selectable
+	 * @param {Number|'before'|'end'|'after'|'on'|'in'} [placeOrOffset] Sets place or offset of the selection.
+	 * @param {Object} [options]
+	 * @param {Boolean} [options.backward] Sets this selection instance to be backward.
 	 */
-	constructor( selectable, backwardSelectionOrOffset ) {
+	constructor( selectable, placeOrOffset, options ) {
 		/**
 		 * Specifies whether the last added range was added as a backward or forward range.
 		 *
@@ -90,7 +106,7 @@ export default class Selection {
 		this._attrs = new Map();
 
 		if ( selectable ) {
-			this.setTo( selectable, backwardSelectionOrOffset );
+			this.setTo( selectable, placeOrOffset, options );
 		}
 	}
 
@@ -300,58 +316,93 @@ export default class Selection {
 	 * {@link module:engine/model/element~Element element}, {@link module:engine/model/position~Position position},
 	 * {@link module:engine/model/range~Range range}, an iterable of {@link module:engine/model/range~Range ranges} or null.
 	 *
-	 *		// Sets ranges from the given range.
+	 * 		// Removes all selection's ranges.
+	 *		selection.setTo( null );
+	 *
+	 *		// Sets selection to the given range.
 	 *		const range = new Range( start, end );
-	 *		selection.setTo( range, isBackwardSelection );
+	 *		selection.setTo( range );
 	 *
-	 *		// Sets ranges from the iterable of ranges.
+	 *		// Sets selection to given ranges.
 	 * 		const ranges = [ new Range( start1, end2 ), new Range( star2, end2 ) ];
-	 *		selection.setTo( ranges, isBackwardSelection );
+	 *		selection.setTo( ranges );
 	 *
-	 *		// Sets ranges from the other selection.
+	 *		// Sets selection to other selection.
 	 *		// Note: It doesn't copies selection attributes.
 	 *		const otherSelection = new Selection();
 	 *		selection.setTo( otherSelection );
 	 *
-	 * 		// Sets ranges from the given document selection's ranges.
+	 * 		// Sets selection to the given document selection.
 	 *		// Note: It doesn't copies selection attributes.
 	 *		const documentSelection = new DocumentSelection( doc );
 	 *		selection.setTo( documentSelection );
 	 *
-	 * 		// Sets range at the given position.
+	 * 		// Sets collapsed selection at the given position.
 	 *		const position = new Position( root, path );
 	 *		selection.setTo( position );
 	 *
-	 * 		// Sets range at the position of given element and optional offset.
-	 *		const paragraph = writer.createElement( 'paragraph' );
+	 * 		// Sets collapsed selection at the position of the given node and an offset.
 	 *		selection.setTo( paragraph, offset );
 	 *
-	 * 		// Removes all ranges.
-	 *		selection.setTo( null );
+	 * Creates a range inside an {@link module:engine/model/element~Element element} which starts before the first child of
+ 	 * that element and ends after the last child of that element.
+	 *
+	 *		selection.setTo( paragraph, 'in' );
+	 *
+	 * Creates a range on an {@link module:engine/model/item~Item item} which starts before the item and ends just after the item.
+	 *
+	 *		selection.setTo( paragraph, 'on' );
+	 *
+	 * `Selection#setTo()`' method allow passing additional options (`backward`) as the last argument.
+	 *
+	 * 		// Sets backward selection.
+	 *		const selection = new Selection( range, { backward: true } );
 	 *
 	 * @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection|
-	 * module:engine/model/position~Position|module:engine/model/element~Element|
+	 * module:engine/model/position~Position|module:engine/model/node~Node|
 	 * Iterable.<module:engine/model/range~Range>|module:engine/model/range~Range|null} selectable
-	 * @param {Boolean|Number|'before'|'end'|'after'} [backwardSelectionOrOffset]
+	 * @param {Number|'before'|'end'|'after'|'on'|'in'} [placeOrOffset] Sets place or offset of the selection.
+	 * @param {Object} [options]
+	 * @param {Boolean} [options.backward] Sets this selection instance to be backward.
 	 */
-	setTo( selectable, backwardSelectionOrOffset ) {
+	setTo( selectable, placeOrOffset, options ) {
 		if ( selectable === null ) {
 			this._setRanges( [] );
 		} else if ( selectable instanceof Selection ) {
 			this._setRanges( selectable.getRanges(), selectable.isBackward );
-		} else if ( selectable && selectable._selection instanceof Selection ) {
+		} else if ( selectable && typeof selectable.getRanges == 'function' ) {
 			// We assume that the selectable is a DocumentSelection.
 			// It can't be imported here, because it would lead to circular imports.
 			this._setRanges( selectable.getRanges(), selectable.isBackward );
 		} else if ( selectable instanceof Range ) {
-			this._setRanges( [ selectable ], backwardSelectionOrOffset );
+			this._setRanges( [ selectable ], !!placeOrOffset && !!placeOrOffset.backward );
 		} else if ( selectable instanceof Position ) {
 			this._setRanges( [ new Range( selectable ) ] );
-		} else if ( selectable instanceof Element ) {
-			this._setRanges( [ Range.createCollapsedAt( selectable, backwardSelectionOrOffset ) ] );
+		} else if ( selectable instanceof Node ) {
+			const backward = !!options && !!options.backward;
+			let range;
+
+			if ( placeOrOffset == 'in' ) {
+				range = Range.createIn( selectable );
+			} else if ( placeOrOffset == 'on' ) {
+				range = Range.createOn( selectable );
+			} else if ( placeOrOffset !== undefined ) {
+				range = Range.createCollapsedAt( selectable, placeOrOffset );
+			} else {
+				/**
+				 * selection.setTo requires the second parameter when the first parameter is a node.
+				 *
+				 * @error model-selection-setTo-required-second-parameter
+				 */
+				throw new CKEditorError(
+					'model-selection-setTo-required-second-parameter: ' +
+					'selection.setTo requires the second parameter when the first parameter is a node.' );
+			}
+
+			this._setRanges( [ range ], backward );
 		} else if ( isIterable( selectable ) ) {
 			// We assume that the selectable is an iterable of ranges.
-			this._setRanges( selectable, backwardSelectionOrOffset );
+			this._setRanges( selectable, placeOrOffset && !!placeOrOffset.backward );
 		} else {
 			/**
 			 * Cannot set selection to given place.

+ 2 - 2
packages/ckeditor5-engine/src/model/utils/deletecontent.js

@@ -194,9 +194,9 @@ function insertParagraph( writer, position, selection ) {
 	writer.insert( paragraph, position );
 
 	if ( selection instanceof DocumentSelection ) {
-		writer.setSelection( paragraph );
+		writer.setSelection( paragraph, 0 );
 	} else {
-		selection.setTo( paragraph );
+		selection.setTo( paragraph, 0 );
 	}
 }
 

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

@@ -50,6 +50,7 @@ export default function modifySelection( model, selection, options = {} ) {
 	const unit = options.unit ? options.unit : 'character';
 
 	const focus = selection.focus;
+
 	const walker = new TreeWalker( {
 		boundaries: getSearchRange( focus, isForward ),
 		singleCharacters: true,

+ 30 - 15
packages/ckeditor5-engine/src/model/writer.js

@@ -911,47 +911,62 @@ export default class Writer {
 	/**
 	 * Sets this selection's ranges and direction to the specified location based on the given
 	 * {@link module:engine/model/selection~Selection selection}, {@link module:engine/model/position~Position position},
-	 * {@link module:engine/model/element~Element element}, {@link module:engine/model/position~Position position},
+	 * {@link module:engine/model/node~Node node}, {@link module:engine/model/position~Position position},
 	 * {@link module:engine/model/range~Range range}, an iterable of {@link module:engine/model/range~Range ranges} or null.
 	 *
-	 *		// Sets ranges from the given range.
+	 *		// Sets selection to the given range.
 	 *		const range = new Range( start, end );
-	 *		writer.setSelection( range, isBackwardSelection );
+	 *		writer.setSelection( range );
 	 *
-	 *		// Sets ranges from the iterable of ranges.
+	 *		// Sets selection to given ranges.
 	 * 		const ranges = [ new Range( start1, end2 ), new Range( star2, end2 ) ];
-	 *		writer.setSelection( range, isBackwardSelection );
+	 *		writer.setSelection( range );
 	 *
-	 *		// Sets ranges from the other selection.
+	 *		// Sets selection to other selection.
 	 *		const otherSelection = new Selection();
 	 *		writer.setSelection( otherSelection );
 	 *
-	 * 		// Sets ranges from the given document selection's ranges.
+	 * 		// Sets selection to the given document selection.
 	 *		const documentSelection = new DocumentSelection( doc );
 	 *		writer.setSelection( documentSelection );
 	 *
-	 * 		// Sets collapsed range at the given position.
+	 * 		// Sets collapsed selection at the given position.
 	 *		const position = new Position( root, path );
 	 *		writer.setSelection( position );
 	 *
-	 * 		// Sets collapsed range at the given offset in element.
-	 *		const paragraph = writer.createElement( 'paragraph' );
+	 * 		// Sets collapsed selection at the position of the given node and an offset.
 	 *		writer.setSelection( paragraph, offset );
 	 *
-	 * 		// Removes all ranges.
+	 * Creates a range inside an {@link module:engine/model/element~Element element} which starts before the first child of
+ 	 * that element and ends after the last child of that element.
+	 *
+	 *		writer.setSelection( paragraph, 'in' );
+	 *
+	 * Creates a range on an {@link module:engine/model/item~Item item} which starts before the item and ends just after the item.
+	 *
+	 *		writer.setSelection( paragraph, 'on' );
+	 *
+	 * 		// Removes all selection's ranges.
 	 *		writer.setSelection( null );
 	 *
+	 * `Writer#setSelection()` allow passing additional options (`backward`) as the last argument.
+	 *
+	 * 		// Sets selection as backward.
+	 *		writer.setSelection( range, { backward: true } );
+	 *
 	 * Throws `writer-incorrect-use` error when the writer is used outside the `change()` block.
 	 *
 	 * @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection|
-	 * module:engine/model/position~Position|module:engine/model/element~Element|
+	 * module:engine/model/position~Position|module:engine/model/node~Node|
 	 * Iterable.<module:engine/model/range~Range>|module:engine/model/range~Range|null} selectable
-	 * @param {Boolean|Number|'before'|'end'|'after'} [backwardSelectionOrOffset]
+	 * @param {Number|'before'|'end'|'after'|'on'|'in'} [placeOrOffset] Sets place or offset of the selection.
+	 * @param {Object} [options]
+	 * @param {Boolean} [options.backward] Sets this selection instance to be backward.
 	 */
-	setSelection( selectable, backwardSelectionOrOffset ) {
+	setSelection( selectable, placeOrOffset, options ) {
 		this._assertWriterUsedCorrectly();
 
-		this.model.document.selection._setTo( selectable, backwardSelectionOrOffset );
+		this.model.document.selection._setTo( selectable, placeOrOffset, options );
 	}
 
 	/**

+ 12 - 5
packages/ckeditor5-engine/src/view/domconverter.js

@@ -7,7 +7,7 @@
  * @module engine/view/domconverter
  */
 
-/* globals document, Node, NodeFilter */
+/* globals document, Node, NodeFilter, Text */
 
 import ViewText from './text';
 import ViewElement from './element';
@@ -486,7 +486,7 @@ export default class DomConverter {
 			}
 		}
 
-		return new ViewSelection( viewRanges, isBackward );
+		return new ViewSelection( viewRanges, { backward: isBackward } );
 	}
 
 	/**
@@ -956,10 +956,10 @@ export default class DomConverter {
 	 * @private
 	 */
 	_processDataFromDomText( node ) {
-		let data = getDataWithoutFiller( node );
+		let data = node.data;
 
 		if ( _hasDomParentOfType( node, this.preElements ) ) {
-			return data;
+			return getDataWithoutFiller( node );
 		}
 
 		// Change all consecutive whitespace characters (from the [ \n\t\r] set –
@@ -978,9 +978,16 @@ export default class DomConverter {
 		}
 
 		// If next text node does not exist remove space character from the end of this text node.
-		if ( !nextNode ) {
+		if ( !nextNode && !startsWithFiller( node ) ) {
 			data = data.replace( / $/, '' );
 		}
+
+		// At the beginning and end of a block element, Firefox inserts normal space + <br> instead of non-breaking space.
+		// This means that the text node starts/end with normal space instead of non-breaking space.
+		// This causes a problem because the normal space would be removed in `.replace` calls above. To prevent that,
+		// the inline filler is removed only after the data is initially processed (by the `.replace` above). See ckeditor5#692.
+		data = getDataWithoutFiller( new Text( data ) );
+
 		// At this point we should have removed all whitespaces from DOM text data.
 
 		// Now we have to change &nbsp; chars, that were in DOM text data because of rendering reasons, to spaces.

+ 1 - 2
packages/ckeditor5-engine/src/view/observer/fakeselectionobserver.js

@@ -82,8 +82,7 @@ export default class FakeSelectionObserver extends Observer {
 	 */
 	_handleSelectionMove( keyCode ) {
 		const selection = this.document.selection;
-		const newSelection = new ViewSelection( selection );
-		newSelection._setFake( false );
+		const newSelection = new ViewSelection( selection.getRanges(), { backward: selection.isBackward, fake: false } );
 
 		// Left or up arrow pressed - move selection to start.
 		if ( keyCode == keyCodes.arrowleft || keyCode == keyCodes.arrowup ) {

+ 1 - 2
packages/ckeditor5-engine/src/view/observer/mutationobserver.js

@@ -239,8 +239,7 @@ export default class MutationObserver extends Observer {
 
 			// Anchor and focus has to be properly mapped to view.
 			if ( viewSelectionAnchor && viewSelectionFocus ) {
-				viewSelection = new ViewSelection();
-				viewSelection._setTo( viewSelectionAnchor );
+				viewSelection = new ViewSelection( viewSelectionAnchor );
 				viewSelection._setFocus( viewSelectionFocus );
 			}
 		}

+ 124 - 58
packages/ckeditor5-engine/src/view/selection.js

@@ -12,8 +12,8 @@ import Range from './range';
 import Position from './position';
 import mix from '@ckeditor/ckeditor5-utils/src/mix';
 import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';
+import Node from './node';
 import Element from './element';
-import Text from './text';
 import count from '@ckeditor/ckeditor5-utils/src/count';
 import isIterable from '@ckeditor/ckeditor5-utils/src/isiterable';
 
@@ -41,11 +41,11 @@ export default class Selection {
 	 *
 	 *		// Creates selection at the given range.
 	 *		const range = new Range( start, end );
-	 *		const selection = new Selection( range, isBackwardSelection );
+	 *		const selection = new Selection( range );
 	 *
 	 *		// Creates selection at the given ranges
 	 * 		const ranges = [ new Range( start1, end2 ), new Range( star2, end2 ) ];
-	 *		const selection = new Selection( ranges, isBackwardSelection );
+	 *		const selection = new Selection( ranges );
 	 *
 	 *		// Creates selection from the other selection.
 	 *		const otherSelection = new Selection();
@@ -55,15 +55,42 @@ export default class Selection {
 	 *		const position = new Position( root, path );
 	 *		const selection = new Selection( position );
 	 *
-	 * 		// Creates selection at the start position of given element.
+	 *		// Creates collapsed selection at the position of given item and offset.
 	 *		const paragraph = writer.createElement( 'paragraph' );
 	 *		const selection = new Selection( paragraph, offset );
 	 *
+	 *		// Creates a range inside an {@link module:engine/view/element~Element element} which starts before the
+	 *		// first child of that element and ends after the last child of that element.
+	 *		const selection = new Selection( paragraph, 'in' );
+	 *
+	 *		// Creates a range on an {@link module:engine/view/item~Item item} which starts before the item and ends
+	 *		// just after the item.
+	 *		const selection = new Selection( paragraph, 'on' );
+	 *
+	 * `Selection`'s constructor allow passing additional options (`backward`, `fake` and `label`) as the last argument.
+	 *
+	 *		// Creates backward selection.
+	 *		const selection = new Selection( range, { backward: true } );
+	 *
+	 * Fake selection does not render as browser native selection over selected elements and is hidden to the user.
+	 * This way, no native selection UI artifacts are displayed to the user and selection over elements can be
+	 * represented in other way, for example by applying proper CSS class.
+	 *
+	 * Additionally fake's selection label can be provided. It will be used to describe fake selection in DOM
+	 * (and be  properly handled by screen readers).
+	 *
+	 *		// Creates fake selection with label.
+	 *		const selection = new Selection( range, { fake: true, label: 'foo' } );
+	 *
 	 * @param {module:engine/view/selection~Selection|module:engine/view/position~Position|
-	 * Iterable.<module:engine/view/range~Range>|module:engine/view/range~Range|module:engine/view/item~Item} [selectable]
-	 * @param {Boolean|Number|'before'|'end'|'after'} [backwardSelectionOrOffset]
+	 * Iterable.<module:engine/view/range~Range>|module:engine/view/range~Range|module:engine/view/item~Item|null} [selectable=null]
+	 * @param {Number|'before'|'end'|'after'|'on'|'in'} [placeOrOffset] Offset or place when selectable is an `Item`.
+	 * @param {Object} [options]
+	 * @param {Boolean} [options.backward] Sets this selection instance to be backward.
+	 * @param {Boolean} [options.fake] Sets this selection instance to be marked as `fake`.
+	 * @param {String} [options.label] Label for the fake selection.
 	 */
-	constructor( selectable, backwardSelectionOrOffset ) {
+	constructor( selectable = null, placeOrOffset, options ) {
 		/**
 		 * Stores all ranges that are selected.
 		 *
@@ -96,15 +123,13 @@ export default class Selection {
 		 */
 		this._fakeSelectionLabel = '';
 
-		if ( selectable ) {
-			this._setTo( selectable, backwardSelectionOrOffset );
-		}
+		this._setTo( selectable, placeOrOffset, options );
 	}
 
 	/**
 	 * Returns true if selection instance is marked as `fake`.
 	 *
-	 * @see #_setFake
+	 * @see #_setTo
 	 * @returns {Boolean}
 	 */
 	get isFake() {
@@ -114,7 +139,7 @@ export default class Selection {
 	/**
 	 * Returns fake selection label.
 	 *
-	 * @see #_setFake
+	 * @see #_setTo
 	 * @returns {String}
 	 */
 	get fakeSelectionLabel() {
@@ -388,70 +413,110 @@ export default class Selection {
 		return ( nodeAfterStart instanceof Element && nodeAfterStart == nodeBeforeEnd ) ? nodeAfterStart : null;
 	}
 
-	/**
-	 * Removes all ranges that were added to the selection.
-	 *
-	 * @fires change
-	 */
-	_removeAllRanges() {
-		if ( this._ranges.length ) {
-			this._ranges = [];
-			this.fire( 'change' );
-		}
-	}
-
 	/**
 	 * Sets this selection's ranges and direction to the specified location based on the given
 	 * {@link module:engine/view/selection~Selection selection}, {@link module:engine/view/position~Position position},
 	 * {@link module:engine/view/item~Item item}, {@link module:engine/view/range~Range range},
 	 * an iterable of {@link module:engine/view/range~Range ranges} or null.
 	 *
-	 *		// Sets ranges from the given range.
+	 *		// Sets selection to the given range.
 	 *		const range = new Range( start, end );
-	 *		selection.setTo( range, isBackwardSelection );
+	 *		selection.setTo( range );
 	 *
-	 *		// Sets ranges from the iterable of ranges.
+	 *		// Sets selection to given ranges.
 	 * 		const ranges = [ new Range( start1, end2 ), new Range( star2, end2 ) ];
-	 *		selection.setTo( range, isBackwardSelection );
+	 *		selection.setTo( range );
 	 *
-	 *		// Sets ranges from the other selection.
+	 *		// Sets selection to the other selection.
 	 *		const otherSelection = new Selection();
 	 *		selection.setTo( otherSelection );
 	 *
-	 * 		// Sets collapsed range at the given position.
+	 * 		// Sets collapsed selection at the given position.
 	 *		const position = new Position( root, path );
 	 *		selection.setTo( position );
 	 *
-	 * 		// Sets collapsed range on the given item.
-	 *		const paragraph = writer.createElement( 'paragraph' );
+	 * 		// Sets collapsed selection at the position of given item and offset.
 	 *		selection.setTo( paragraph, offset );
 	 *
-	 * 		// Removes all ranges.
+	 * Creates a range inside an {@link module:engine/view/element~Element element} which starts before the first child of
+ 	 * that element and ends after the last child of that element.
+	 *
+	 *		selection.setTo( paragraph, 'in' );
+	 *
+	 * Creates a range on an {@link module:engine/view/item~Item item} which starts before the item and ends just after the item.
+	 *
+	 *		selection.setTo( paragraph, 'on' );
+	 *
+	 * 		// Clears selection. Removes all ranges.
 	 *		selection.setTo( null );
 	 *
+	 * `Selection#setTo()` method allow passing additional options (`backward`, `fake` and `label`) as the last argument.
+	 *
+	 *		// Sets selection as backward.
+	 *		selection.setTo( range, { backward: true } );
+	 *
+	 * Fake selection does not render as browser native selection over selected elements and is hidden to the user.
+	 * This way, no native selection UI artifacts are displayed to the user and selection over elements can be
+	 * represented in other way, for example by applying proper CSS class.
+	 *
+	 * Additionally fake's selection label can be provided. It will be used to describe fake selection in DOM
+	 * (and be  properly handled by screen readers).
+	 *
+	 *		// Creates fake selection with label.
+	 *		selection.setTo( range, { fake: true, label: 'foo' } );
+	 *
 	 * @protected
+	 * @fires change
 	 * @param {module:engine/view/selection~Selection|module:engine/view/position~Position|
 	 * Iterable.<module:engine/view/range~Range>|module:engine/view/range~Range|module:engine/view/item~Item|null} selectable
-	 * @param {Boolean|Number|'before'|'end'|'after'} [backwardSelectionOrOffset]
+	 * @param {Number|'before'|'end'|'after'|'on'|'in'} [placeOrOffset] Sets place or offset of the selection.
+	 * @param {Object} [options]
+	 * @param {Boolean} [options.backward] Sets this selection instance to be backward.
+	 * @param {Boolean} [options.fake] Sets this selection instance to be marked as `fake`.
+	 * @param {String} [options.label] Label for the fake selection.
 	 */
-	_setTo( selectable, backwardSelectionOrOffset ) {
+	_setTo( selectable, placeOrOffset, options ) {
 		if ( selectable === null ) {
-			this._removeAllRanges();
+			this._setRanges( [] );
+			this._setFakeOptions( placeOrOffset );
 		} else if ( selectable instanceof Selection ) {
-			this._isFake = selectable.isFake;
-			this._fakeSelectionLabel = selectable.fakeSelectionLabel;
 			this._setRanges( selectable.getRanges(), selectable.isBackward );
+			this._setFakeOptions( { fake: selectable.isFake, label: selectable.fakeSelectionLabel } );
 		} else if ( selectable instanceof Range ) {
-			this._setRanges( [ selectable ], backwardSelectionOrOffset );
+			this._setRanges( [ selectable ], placeOrOffset && placeOrOffset.backward );
+			this._setFakeOptions( placeOrOffset );
 		} else if ( selectable instanceof Position ) {
 			this._setRanges( [ new Range( selectable ) ] );
-		} else if ( selectable instanceof Text ) {
-			this._setRanges( [ Range.createCollapsedAt( selectable, backwardSelectionOrOffset ) ] );
-		} else if ( selectable instanceof Element ) {
-			this._setRanges( [ Range.createCollapsedAt( selectable, backwardSelectionOrOffset ) ] );
+			this._setFakeOptions( placeOrOffset );
+		} else if ( selectable instanceof Node ) {
+			const backward = !!options && !!options.backward;
+			let range;
+
+			if ( placeOrOffset === undefined ) {
+				/**
+				 * selection.setTo requires the second parameter when the first parameter is a node.
+				 *
+				 * @error view-selection-setTo-required-second-parameter
+				 */
+				throw new CKEditorError(
+					'view-selection-setTo-required-second-parameter: ' +
+					'selection.setTo requires the second parameter when the first parameter is a node.'
+				);
+			} else if ( placeOrOffset == 'in' ) {
+				range = Range.createIn( selectable );
+			} else if ( placeOrOffset == 'on' ) {
+				range = Range.createOn( selectable );
+			} else {
+				range = Range.createCollapsedAt( selectable, placeOrOffset );
+			}
+
+			this._setRanges( [ range ], backward );
+			this._setFakeOptions( options );
 		} else if ( isIterable( selectable ) ) {
 			// We assume that the selectable is an iterable of ranges.
-			this._setRanges( selectable, backwardSelectionOrOffset );
+			// Array.from() is used to prevent setting ranges to the old iterable
+			this._setRanges( selectable, placeOrOffset && placeOrOffset.backward );
+			this._setFakeOptions( placeOrOffset );
 		} else {
 			/**
 			 * Cannot set selection to given place.
@@ -460,6 +525,8 @@ export default class Selection {
 			 */
 			throw new CKEditorError( 'view-selection-setTo-not-selectable: Cannot set selection to given place.' );
 		}
+
+		this.fire( 'change' );
 	}
 
 	/**
@@ -467,13 +534,16 @@ export default class Selection {
 	 * is treated like the last added range and is used to set {@link #anchor anchor} and {@link #focus focus}.
 	 * Accepts a flag describing in which way the selection is made.
 	 *
-	 * @protected
-	 * @fires change
+	 * @private
 	 * @param {Iterable.<module:engine/view/range~Range>} newRanges Iterable object of ranges to set.
-	 * @param {Boolean} [isLastBackward] Flag describing if last added range was selected forward - from start to end
+	 * @param {Boolean} [isLastBackward=false] Flag describing if last added range was selected forward - from start to end
 	 * (`false`) or backward - from end to start (`true`). Defaults to `false`.
 	 */
-	_setRanges( newRanges, isLastBackward ) {
+	_setRanges( newRanges, isLastBackward = false ) {
+		// New ranges should be copied to prevent removing them by setting them to `[]` first.
+		// Only applies to situations when selection is set to the same selection or same selection's ranges.
+		newRanges = Array.from( newRanges );
+
 		this._ranges = [];
 
 		for ( const range of newRanges ) {
@@ -481,7 +551,6 @@ export default class Selection {
 		}
 
 		this._lastRangeBackward = !!isLastBackward;
-		this.fire( 'change' );
 	}
 
 	/**
@@ -534,17 +603,14 @@ export default class Selection {
 	 * Additionally fake's selection label can be provided. It will be used to describe fake selection in DOM (and be
 	 * properly handled by screen readers).
 	 *
-	 * @protected
-	 * @fires change
-	 * @param {Boolean} [value=true] If set to true selection will be marked as `fake`.
-	 * @param {Object} [options] Additional options.
+	 * @private
+	 * @param {Object} [options] Options.
+	 * @param {Boolean} [options.fake] If set to true selection will be marked as `fake`.
 	 * @param {String} [options.label=''] Fake selection label.
 	 */
-	_setFake( value = true, options = {} ) {
-		this._isFake = value;
-		this._fakeSelectionLabel = value ? options.label || '' : '';
-
-		this.fire( 'change' );
+	_setFakeOptions( options = {} ) {
+		this._isFake = !!options.fake;
+		this._fakeSelectionLabel = options.fake ? options.label || '' : '';
 	}
 
 	/**

+ 44 - 28
packages/ckeditor5-engine/src/view/writer.js

@@ -36,35 +36,68 @@ export default class Writer {
 	 * {@link module:engine/view/item~Item item}, {@link module:engine/view/range~Range range},
 	 * an iterable of {@link module:engine/view/range~Range ranges} or null.
 	 *
-	 *		// Sets ranges from the given range.
+	 * ### Usage:
+	 *
+	 *		// Sets selection to the given range.
+	 *		const range = new Range( start, end );
+	 *		writer.setSelection( range );
+	 *
+	 *		// Sets backward selection to the given range.
 	 *		const range = new Range( start, end );
-	 *		writer.setSelection( range, isBackwardSelection );
+	 *		writer.setSelection( range );
 	 *
-	 *		// Sets ranges from the iterable of ranges.
+	 *		// Sets selection to given ranges.
 	 * 		const ranges = [ new Range( start1, end2 ), new Range( star2, end2 ) ];
-	 *		writer.setSelection( range, isBackwardSelection );
+	 *		writer.setSelection( range );
 	 *
-	 *		// Sets ranges from the other selection.
+	 *		// Sets selection to the other selection.
 	 *		const otherSelection = new Selection();
 	 *		writer.setSelection( otherSelection );
 	 *
-	 * 		// Sets collapsed range at the given position.
+	 * 		// Sets collapsed selection at the given position.
 	 *		const position = new Position( root, path );
 	 *		writer.setSelection( position );
 	 *
-	 * 		// Sets collapsed range on the given item.
-	 *		const paragraph = writer.createContainerElement( 'paragraph' );
+	 * 		// Sets collapsed selection at the position of given item and offset.
 	 *		writer.setSelection( paragraph, offset );
 	 *
+	 * Creates a range inside an {@link module:engine/view/element~Element element} which starts before the first child of
+ 	 * that element and ends after the last child of that element.
+	 *
+	 * 		writer.setSelection( paragraph, 'in' );
+	 *
+	 * Creates a range on the {@link module:engine/view/item~Item item} which starts before the item and ends just after the item.
+	 *
+	 *		writer.setSelection( paragraph, 'on' );
+	 *
 	 * 		// Removes all ranges.
 	 *		writer.setSelection( null );
 	 *
+	 * `Writer#setSelection()` allow passing additional options (`backward`, `fake` and `label`) as the last argument.
+	 *
+	 *		// Sets selection as backward.
+	 *		writer.setSelection( range, { backward: true } );
+	 *
+	 *		// Sets selection as fake.
+	 *		// Fake selection does not render as browser native selection over selected elements and is hidden to the user.
+	 * 		// This way, no native selection UI artifacts are displayed to the user and selection over elements can be
+	 * 		// represented in other way, for example by applying proper CSS class.
+	 *		writer.setSelection( range, { fake: true } );
+	 *
+	 * 		// Additionally fake's selection label can be provided. It will be used to describe fake selection in DOM
+	 * 		// (and be  properly handled by screen readers).
+	 *		writer.setSelection( range, { fake: true, label: 'foo' } );
+	 *
 	 * @param {module:engine/view/selection~Selection|module:engine/view/position~Position|
 	 * Iterable.<module:engine/view/range~Range>|module:engine/view/range~Range|module:engine/view/item~Item|null} selectable
-	 * @param {Boolean|Number|'before'|'end'|'after'} [backwardSelectionOrOffset]
+	 * @param {Number|'before'|'end'|'after'|'on'|'in'} [placeOrOffset] Sets place or offset of the selection.
+	 * @param {Object} [options]
+	 * @param {Boolean} [options.backward] Sets this selection instance to be backward.
+	 * @param {Boolean} [options.fake] Sets this selection instance to be marked as `fake`.
+	 * @param {String} [options.label] Label for the fake selection.
 	 */
-	setSelection( selectable, backwardSelectionOrOffset ) {
-		this.document.selection._setTo( selectable, backwardSelectionOrOffset );
+	setSelection( selectable, placeOrOffset, options ) {
+		this.document.selection._setTo( selectable, placeOrOffset, options );
 	}
 
 	/**
@@ -80,23 +113,6 @@ export default class Writer {
 		this.document.selection._setFocus( itemOrPosition, offset );
 	}
 
-	/**
-	 * Sets {@link module:engine/view/selection~Selection selection's} to be marked as `fake`. A fake selection does
-	 * not render as browser native selection over selected elements and is hidden to the user.
-	 * This way, no native selection UI artifacts are displayed to the user and selection over elements can be
-	 * represented in other way, for example by applying proper CSS class.
-	 *
-	 * Additionally fake's selection label can be provided. It will be used to describe fake selection in DOM (and be
-	 * properly handled by screen readers).
-	 *
-	 * @param {Boolean} [value=true] If set to true selection will be marked as `fake`.
-	 * @param {Object} [options] Additional options.
-	 * @param {String} [options.label=''] Fake selection label.
-	 */
-	setFakeSelection( value, options ) {
-		this.document.selection._setFake( value, options );
-	}
-
 	/**
 	 * Creates a new {@link module:engine/view/text~Text text node}.
 	 *

+ 4 - 6
packages/ckeditor5-engine/tests/conversion/downcast-selection-converters.js

@@ -17,7 +17,6 @@ import {
 	convertRangeSelection,
 	convertCollapsedSelection,
 	clearAttributes,
-	clearFakeSelection
 } from '../../src/conversion/downcast-selection-converters';
 
 import {
@@ -475,14 +474,13 @@ describe( 'downcast-selection-converters', () => {
 				const viewString = stringifyView( viewRoot, viewSelection, { showType: false } );
 				expect( viewString ).to.equal( '<div>f{}oobar</div>' );
 			} );
-		} );
 
-		describe( 'clearFakeSelection', () => {
 			it( 'should clear fake selection', () => {
-				dispatcher.on( 'selection', clearFakeSelection() );
+				const modelRange = ModelRange.createFromParentsAndOffsets( modelRoot, 1, modelRoot, 1 );
 
 				view.change( writer => {
-					writer.setFakeSelection( true );
+					writer.setSelection( modelRange, { fake: true } );
+
 					dispatcher.convertSelection( docSelection, model.markers, writer );
 				} );
 				expect( viewSelection.isFake ).to.be.false;
@@ -574,7 +572,7 @@ describe( 'downcast-selection-converters', () => {
 
 		const isBackward = selectionPaths[ 2 ] === 'backward';
 		model.change( writer => {
-			writer.setSelection( new ModelRange( startPos, endPos ), isBackward );
+			writer.setSelection( new ModelRange( startPos, endPos ), { backward: isBackward } );
 
 			// And add or remove passed attributes.
 			for ( const key in selectionAttributes ) {

+ 1 - 1
packages/ckeditor5-engine/tests/conversion/upcast-selection-converters.js

@@ -104,7 +104,7 @@ describe( 'convertSelectionChange', () => {
 				viewRoot.getChild( 0 ).getChild( 0 ), 1, viewRoot.getChild( 0 ).getChild( 0 ), 2 ),
 			ViewRange.createFromParentsAndOffsets(
 				viewRoot.getChild( 1 ).getChild( 0 ), 1, viewRoot.getChild( 1 ).getChild( 0 ), 2 )
-		], true );
+		], { backward: true } );
 
 		convertSelection( null, { newSelection: viewSelection } );
 

+ 3 - 3
packages/ckeditor5-engine/tests/dev-utils/model.js

@@ -283,7 +283,7 @@ describe( 'model test utils', () => {
 			it( 'writes selection in an empty root', () => {
 				const root = document.createRoot( '$root', 'empty' );
 				model.change( writer => {
-					writer.setSelection( root );
+					writer.setSelection( root, 0 );
 				} );
 
 				expect( stringify( root, selection ) ).to.equal(
@@ -293,7 +293,7 @@ describe( 'model test utils', () => {
 
 			it( 'writes selection collapsed in an element', () => {
 				model.change( writer => {
-					writer.setSelection( root );
+					writer.setSelection( root, 0 );
 				} );
 
 				expect( stringify( root, selection ) ).to.equal(
@@ -386,7 +386,7 @@ describe( 'model test utils', () => {
 
 			it( 'writes selection when is backward', () => {
 				model.change( writer => {
-					writer.setSelection( Range.createFromParentsAndOffsets( elA, 0, elB, 0 ), true );
+					writer.setSelection( Range.createFromParentsAndOffsets( elA, 0, elB, 0 ), { backward: true } );
 				} );
 
 				expect( stringify( root, selection ) ).to.equal(

+ 1 - 1
packages/ckeditor5-engine/tests/model/documentselection.js

@@ -250,7 +250,7 @@ describe( 'DocumentSelection', () => {
 			selection._setTo( [ range, liveRange ] );
 
 			const spy = testUtils.sinon.spy( LiveRange.prototype, 'detach' );
-			selection._setTo( root );
+			selection._setTo( root, 0 );
 
 			expect( spy.calledTwice ).to.be.true;
 		} );

+ 2 - 0
packages/ckeditor5-engine/tests/model/model.js

@@ -476,6 +476,8 @@ describe( 'Model', () => {
 
 			setData( model, '<paragraph>fo[ob]ar</paragraph>' );
 
+			expect( getData( model ) ).to.equal( '<paragraph>fo[ob]ar</paragraph>' );
+
 			model.modifySelection( model.document.selection, { direction: 'backward' } );
 
 			expect( getData( model ) ).to.equal( '<paragraph>fo[o]bar</paragraph>' );

+ 42 - 26
packages/ckeditor5-engine/tests/model/selection.js

@@ -64,7 +64,7 @@ describe( 'Selection', () => {
 		} );
 
 		it( 'should be able to create a selection from the given range and isLastBackward flag', () => {
-			const selection = new Selection( range1, true );
+			const selection = new Selection( range1, { backward: true } );
 
 			expect( selection.isBackward ).to.be.true;
 			expect( Array.from( selection.getRanges() ) ).to.deep.equal( [ range1 ] );
@@ -72,7 +72,7 @@ describe( 'Selection', () => {
 
 		it( 'should be able to create a selection from the given ranges and isLastBackward flag', () => {
 			const ranges = new Set( [ range1, range2, range3 ] );
-			const selection = new Selection( ranges, true );
+			const selection = new Selection( ranges, { backward: true } );
 
 			expect( selection.isBackward ).to.be.true;
 			expect( Array.from( selection.getRanges() ) ).to.deep.equal( [ range1, range2, range3 ] );
@@ -80,7 +80,7 @@ describe( 'Selection', () => {
 
 		it( 'should be able to create a selection from the other selection', () => {
 			const ranges = [ range1, range2, range3 ];
-			const otherSelection = new Selection( ranges, true );
+			const otherSelection = new Selection( ranges, { backward: true } );
 			const selection = new Selection( otherSelection );
 
 			expect( selection.isBackward ).to.be.true;
@@ -88,7 +88,7 @@ describe( 'Selection', () => {
 		} );
 
 		it( 'should be able to create a selection at the start position of an item', () => {
-			const selection = new Selection( root );
+			const selection = new Selection( root, 0 );
 			const focus = selection.focus;
 
 			expect( selection ).to.have.property( 'isCollapsed', true );
@@ -173,7 +173,7 @@ describe( 'Selection', () => {
 
 	describe( 'isBackward', () => {
 		it( 'is defined by the last added range', () => {
-			selection.setTo( [ range ], true );
+			selection.setTo( [ range ], { backward: true } );
 			expect( selection ).to.have.property( 'isBackward', true );
 
 			selection.setTo( liveRange );
@@ -183,7 +183,7 @@ describe( 'Selection', () => {
 		it( 'is false when last range is collapsed', () => {
 			const pos = Position.createAt( root, 0 );
 
-			selection.setTo( [ new Range( pos, pos ) ], true );
+			selection.setTo( pos );
 
 			expect( selection.isBackward ).to.be.false;
 		} );
@@ -206,7 +206,7 @@ describe( 'Selection', () => {
 		} );
 
 		it( 'should return correct focus when last added range is backward one', () => {
-			selection.setTo( [ r1, r2, r3 ], true );
+			selection.setTo( [ r1, r2, r3 ], { backward: true } );
 
 			expect( selection.focus.isEqual( r3.start ) ).to.be.true;
 		} );
@@ -221,7 +221,7 @@ describe( 'Selection', () => {
 		it( 'should set selection to be same as given selection, using _setRanges method', () => {
 			const spy = sinon.spy( selection, '_setRanges' );
 
-			const otherSelection = new Selection( [ range1, range2 ], true );
+			const otherSelection = new Selection( [ range1, range2 ], { backward: true } );
 
 			selection.setTo( otherSelection );
 
@@ -294,7 +294,7 @@ describe( 'Selection', () => {
 		it( 'should allow setting selection inside an element', () => {
 			const element = new Element( 'p', null, [ new Text( 'foo' ), new Text( 'bar' ) ] );
 
-			selection.setTo( Range.createIn( element ) );
+			selection.setTo( element, 'in' );
 
 			const ranges = Array.from( selection.getRanges() );
 			expect( ranges.length ).to.equal( 1 );
@@ -310,7 +310,7 @@ describe( 'Selection', () => {
 			const textNode3 = new Text( 'baz' );
 			const element = new Element( 'p', null, [ textNode1, textNode2, textNode3 ] );
 
-			selection.setTo( Range.createOn( textNode2 ) );
+			selection.setTo( textNode2, 'on' );
 
 			const ranges = Array.from( selection.getRanges() );
 			expect( ranges.length ).to.equal( 1 );
@@ -320,25 +320,41 @@ describe( 'Selection', () => {
 			expect( ranges[ 0 ].end.offset ).to.deep.equal( 6 );
 		} );
 
+		it( 'should allow setting backward selection on an item', () => {
+			const textNode1 = new Text( 'foo' );
+			const textNode2 = new Text( 'bar' );
+			const textNode3 = new Text( 'baz' );
+			const element = new Element( 'p', null, [ textNode1, textNode2, textNode3 ] );
+
+			selection.setTo( textNode2, 'on', { backward: true } );
+
+			const ranges = Array.from( selection.getRanges() );
+			expect( ranges.length ).to.equal( 1 );
+			expect( ranges[ 0 ].start.parent ).to.equal( element );
+			expect( ranges[ 0 ].start.offset ).to.deep.equal( 3 );
+			expect( ranges[ 0 ].end.parent ).to.equal( element );
+			expect( ranges[ 0 ].end.offset ).to.deep.equal( 6 );
+			expect( selection.isBackward ).to.be.true;
+		} );
+
+		// TODO - backward
+		// TODO - throwing
+
 		describe( 'setting selection to position or item', () => {
 			it( 'should fire change:range', () => {
 				const spy = sinon.spy();
 
 				selection.on( 'change:range', spy );
 
-				selection.setTo( root );
+				selection.setTo( root, 0 );
 
 				expect( spy.calledOnce ).to.be.true;
 			} );
 
-			it( 'should set selection at the 0 offset if second parameter not passed', () => {
-				selection.setTo( root );
-
-				expect( selection ).to.have.property( 'isCollapsed', true );
-
-				const focus = selection.focus;
-				expect( focus ).to.have.property( 'parent', root );
-				expect( focus ).to.have.property( 'offset', 0 );
+			it( 'should throw if second parameter is not passed', () => {
+				expect( () => {
+					selection.setTo( root );
+				} ).to.throw( CKEditorError, /model-selection-setTo-required-second-parameter/ );
 			} );
 
 			it( 'should set selection at given offset in given parent', () => {
@@ -484,7 +500,7 @@ describe( 'Selection', () => {
 			const endPos = Position.createAt( root, 2 );
 			const newEndPos = Position.createAt( root, 3 );
 
-			selection.setTo( new Range( startPos, endPos ), true );
+			selection.setTo( new Range( startPos, endPos ), { backward: true } );
 
 			selection.setFocus( newEndPos );
 
@@ -498,7 +514,7 @@ describe( 'Selection', () => {
 			const endPos = Position.createAt( root, 2 );
 			const newEndPos = Position.createAt( root, 0 );
 
-			selection.setTo( new Range( startPos, endPos ), true );
+			selection.setTo( new Range( startPos, endPos ), { backward: true } );
 
 			selection.setFocus( newEndPos );
 
@@ -634,7 +650,7 @@ describe( 'Selection', () => {
 		} );
 
 		it( 'should acknowledge backward flag when setting anchor and focus', () => {
-			selection._setRanges( newRanges, true );
+			selection._setRanges( newRanges, { backward: true } );
 			expect( selection.anchor.path ).to.deep.equal( [ 6, 0 ] );
 			expect( selection.focus.path ).to.deep.equal( [ 5, 0 ] );
 		} );
@@ -677,7 +693,7 @@ describe( 'Selection', () => {
 		} );
 
 		it( 'should set anchor and focus to the end and start of the most recently added range if backward flag was used', () => {
-			selection._setRanges( [ liveRange, range ], true );
+			selection._setRanges( [ liveRange, range ], { backward: true } );
 
 			expect( selection.anchor.path ).to.deep.equal( [ 2 ] );
 			expect( selection.focus.path ).to.deep.equal( [ 2, 2 ] );
@@ -766,9 +782,9 @@ describe( 'Selection', () => {
 		} );
 
 		it( 'should return true if backward selections equal', () => {
-			selection.setTo( [ range1 ], true );
+			selection.setTo( [ range1 ], { backward: true } );
 
-			const otherSelection = new Selection( [ range1 ], true );
+			const otherSelection = new Selection( [ range1 ], { backward: true } );
 
 			expect( selection.isEqual( otherSelection ) ).to.be.true;
 		} );
@@ -798,7 +814,7 @@ describe( 'Selection', () => {
 		it( 'should return false if directions do not equal', () => {
 			selection.setTo( range1 );
 
-			const otherSelection = new Selection( [ range1 ], true );
+			const otherSelection = new Selection( [ range1 ], { backward: true } );
 
 			expect( selection.isEqual( otherSelection ) ).to.be.false;
 		} );

+ 4 - 4
packages/ckeditor5-engine/tests/model/writer.js

@@ -2173,7 +2173,7 @@ describe( 'Writer', () => {
 			const firstParagraph = root.getNodeByPath( [ 1 ] );
 
 			const setToSpy = sinon.spy( DocumentSelection.prototype, '_setTo' );
-			setSelection( firstParagraph );
+			setSelection( firstParagraph, 0 );
 
 			expect( setToSpy.calledOnce ).to.be.true;
 			setToSpy.restore();
@@ -2182,7 +2182,7 @@ describe( 'Writer', () => {
 		it( 'should change document selection ranges', () => {
 			const range = new Range( new Position( root, [ 1 ] ), new Position( root, [ 2, 2 ] ) );
 
-			setSelection( range, true );
+			setSelection( range, { backward: true } );
 
 			expect( model.document.selection._ranges.length ).to.equal( 1 );
 			expect( model.document.selection._ranges[ 0 ].start.path ).to.deep.equal( [ 1 ] );
@@ -2544,9 +2544,9 @@ describe( 'Writer', () => {
 		} );
 	}
 
-	function setSelection( selectable, backwardSelectionOrOffset ) {
+	function setSelection( selectable, optionsOrPlaceOrOffset, options ) {
 		model.enqueueChange( batch, writer => {
-			writer.setSelection( selectable, backwardSelectionOrOffset );
+			writer.setSelection( selectable, optionsOrPlaceOrOffset, options );
 		} );
 	}
 

+ 87 - 0
packages/ckeditor5-engine/tests/tickets/ckeditor5-692.js

@@ -0,0 +1,87 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import MutationObserver from '../../src/view/observer/mutationobserver';
+import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor.js';
+import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
+import { getData as getModelData, setData as setModelData } from '../../src/dev-utils/model';
+import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold.js';
+import { getData as getViewData } from '../../src/dev-utils/view';
+import { isInlineFiller } from '../../src/view/filler';
+import Input from '@ckeditor/ckeditor5-typing/src/input';
+
+/* globals document */
+
+describe( 'Bug ckeditor5#692', () => {
+	let editorElement, editor, mutationObserver, view, domEditor;
+
+	beforeEach( () => {
+		editorElement = document.createElement( 'div' );
+		document.body.appendChild( editorElement );
+
+		return ClassicTestEditor
+			.create( editorElement, {
+				plugins: [ Paragraph, Bold, Input ]
+			} )
+			.then( newEditor => {
+				editor = newEditor;
+				view = editor.editing.view;
+				mutationObserver = view.getObserver( MutationObserver );
+				domEditor = editor.ui.view.editableElement;
+			} );
+	} );
+
+	afterEach( () => {
+		document.body.removeChild( editorElement );
+
+		return editor.destroy();
+	} );
+
+	describe( 'DomConverter', () => {
+		// https://github.com/ckeditor/ckeditor5/issues/692 Scenario 1.
+		it( 'should handle space after inline filler at the end of container', () => {
+			setModelData( editor.model, '<paragraph>foo[]</paragraph>' );
+
+			// Create Bold attribute at the end of paragraph.
+			editor.execute( 'bold' );
+
+			expect( getModelData( editor.model ) ).to.equal( '<paragraph>foo<$text bold="true">[]</$text></paragraph>' );
+
+			const domParagraph = domEditor.childNodes[ 0 ];
+			const textNode = domParagraph.childNodes[ 1 ].childNodes[ 0 ];
+
+			expect( isInlineFiller( textNode ) ).to.be.true;
+
+			// Add space inside the strong's text node.
+			textNode.data += ' ';
+			mutationObserver.flush();
+
+			expect( getModelData( editor.model ) ).to.equal( '<paragraph>foo<$text bold="true"> []</$text></paragraph>' );
+			expect( getViewData( editor.editing.view ) ).to.equal( '<p>foo<strong> {}</strong></p>' );
+		} );
+
+		// https://github.com/ckeditor/ckeditor5/issues/692 Scenario 2.
+		it( 'should handle space after inline filler at the end of container', () => {
+			setModelData( editor.model, '<paragraph>[]foo</paragraph>' );
+
+			// Create Bold attribute at the end of paragraph.
+			editor.execute( 'bold' );
+
+			expect( getModelData( editor.model ) ).to.equal( '<paragraph><$text bold="true">[]</$text>foo</paragraph>' );
+
+			const domParagraph = domEditor.childNodes[ 0 ];
+			const textNode = domParagraph.childNodes[ 0 ].childNodes[ 0 ];
+
+			expect( isInlineFiller( textNode ) ).to.be.true;
+
+			// Add space inside the strong's text node.
+			textNode.data += ' ';
+			mutationObserver.flush();
+
+			expect( getModelData( editor.model ) ).to.equal( '<paragraph><$text bold="true"> []</$text>foo</paragraph>' );
+			expect( getViewData( editor.editing.view ) ).to.equal( '<p><strong> {}</strong>foo</p>' );
+		} );
+	} );
+} );

+ 2 - 3
packages/ckeditor5-engine/tests/view/domconverter/binding.js

@@ -7,7 +7,6 @@
 
 import ViewElement from '../../../src/view/element';
 import ViewSelection from '../../../src/view/selection';
-import ViewRange from '../../../src/view/range';
 import DomConverter from '../../../src/view/domconverter';
 import ViewDocumentFragment from '../../../src/view/documentfragment';
 import { INLINE_FILLER } from '../../../src/view/filler';
@@ -270,7 +269,7 @@ describe( 'DomConverter', () => {
 		beforeEach( () => {
 			viewElement = new ViewElement();
 			domEl = document.createElement( 'div' );
-			selection = new ViewSelection( ViewRange.createIn( viewElement ) );
+			selection = new ViewSelection( viewElement, 'in' );
 			converter.bindFakeSelection( domEl, selection );
 		} );
 
@@ -283,7 +282,7 @@ describe( 'DomConverter', () => {
 		it( 'should keep a copy of selection', () => {
 			const selectionCopy = new ViewSelection( selection );
 
-			selection._setTo( ViewRange.createIn( new ViewElement() ), true );
+			selection._setTo( new ViewElement(), 'in', { backward: true } );
 			const bindSelection = converter.fakeSelectionToView( domEl );
 
 			expect( bindSelection ).to.not.equal( selection );

+ 26 - 3
packages/ckeditor5-engine/tests/view/domconverter/dom-to-view.js

@@ -6,7 +6,6 @@
 /* globals document */
 
 import ViewElement from '../../../src/view/element';
-import ViewRange from '../../../src/view/range';
 import ViewSelection from '../../../src/view/selection';
 import DomConverter from '../../../src/view/domconverter';
 import ViewDocumentFragment from '../../../src/view/documentfragment';
@@ -526,6 +525,30 @@ describe( 'DomConverter', () => {
 			// See also whitespace-handling-integration.js.
 			//
 		} );
+
+		describe( 'clearing auto filler', () => {
+			it( 'should remove inline filler when converting dom to view', () => {
+				const text = document.createTextNode( INLINE_FILLER + 'foo' );
+				const view = converter.domToView( text );
+
+				expect( view.data ).to.equal( 'foo' );
+			} );
+
+			// See https://github.com/ckeditor/ckeditor5/issues/692.
+			it( 'should not remove space after inline filler if previous node nor next node does not exist', () => {
+				const text = document.createTextNode( INLINE_FILLER + ' ' );
+				const view = converter.domToView( text );
+
+				expect( view.data ).to.equal( ' ' );
+			} );
+
+			it( 'should convert non breaking space to normal space after inline filler', () => {
+				const text = document.createTextNode( INLINE_FILLER + '\u00A0' );
+				const view = converter.domToView( text );
+
+				expect( view.data ).to.equal( ' ' );
+			} );
+		} );
 	} );
 
 	describe( 'domChildrenToView', () => {
@@ -859,7 +882,7 @@ describe( 'DomConverter', () => {
 			domContainer.innerHTML = 'fake selection container';
 			document.body.appendChild( domContainer );
 
-			const viewSelection = new ViewSelection( ViewRange.createIn( new ViewElement() ) );
+			const viewSelection = new ViewSelection( new ViewElement(), 'in' );
 			converter.bindFakeSelection( domContainer, viewSelection );
 
 			const domRange = document.createRange();
@@ -880,7 +903,7 @@ describe( 'DomConverter', () => {
 			domContainer.innerHTML = 'fake selection container';
 			document.body.appendChild( domContainer );
 
-			const viewSelection = new ViewSelection( ViewRange.createIn( new ViewElement() ) );
+			const viewSelection = new ViewSelection( new ViewElement(), 'in' );
 			converter.bindFakeSelection( domContainer, viewSelection );
 
 			const domRange = document.createRange();

+ 1 - 2
packages/ckeditor5-engine/tests/view/manual/fakeselection.js

@@ -41,8 +41,7 @@ viewDocument.on( 'mouseup', ( evt, data ) => {
 		console.log( 'Making selection around the <strong>.' );
 
 		view.change( writer => {
-			writer.setSelection( ViewRange.createOn( viewStrong ) );
-			writer.setFakeSelection( true, { label: 'fake selection over bar' } );
+			writer.setSelection( ViewRange.createOn( viewStrong ), { fake: true, label: 'fake selection over bar' } );
 		} );
 
 		data.preventDefault();

+ 6 - 3
packages/ckeditor5-engine/tests/view/observer/fakeselectionobserver.js

@@ -33,7 +33,7 @@ describe( 'FakeSelectionObserver', () => {
 		root = createViewRoot( viewDocument );
 		view.attachDomRoot( domRoot );
 		observer = view.getObserver( FakeSelectionObserver );
-		viewDocument.selection._setFake();
+		viewDocument.selection._setTo( null, { fake: true } );
 	} );
 
 	afterEach( () => {
@@ -41,7 +41,7 @@ describe( 'FakeSelectionObserver', () => {
 	} );
 
 	it( 'should do nothing if selection is not fake', () => {
-		viewDocument.selection._setFake( false );
+		viewDocument.selection._setTo( null, { fake: false } );
 
 		return checkEventPrevention( keyCodes.arrowleft, false );
 	} );
@@ -200,7 +200,10 @@ describe( 'FakeSelectionObserver', () => {
 	//
 	// @param {Number} keyCode
 	function changeFakeSelectionPressing( keyCode ) {
-		viewDocument.selection._setFake();
+		viewDocument.selection._setTo( viewDocument.selection.getRanges(), {
+			backward: viewDocument.selection.isBackward,
+			fake: true
+		} );
 
 		const data = {
 			keyCode,

+ 107 - 1
packages/ckeditor5-engine/tests/view/observer/mutationobserver.js

@@ -269,6 +269,112 @@ describe( 'MutationObserver', () => {
 		expect( lastMutations[ 0 ].newText ).to.equal( 'xy' );
 	} );
 
+	// https://github.com/ckeditor/ckeditor5/issues/692 Scenario 1.
+	it( 'should handle space after inline filler at the end of container', () => {
+		const { view: viewContainer, selection } = parse(
+			'<container:p>' +
+				'foo' +
+				'<attribute:b>[]</attribute:b>' +
+			'</container:p>'
+		);
+
+		view.change( writer => {
+			viewRoot.appendChildren( viewContainer );
+			writer.setSelection( selection );
+		} );
+
+		// Appended container is third in the tree.
+		const container = domEditor.childNodes[ 2 ];
+		const inlineFiller = container.childNodes[ 1 ].childNodes[ 0 ];
+
+		inlineFiller.data += ' ';
+
+		mutationObserver.flush();
+
+		expect( lastMutations.length ).to.equal( 1 );
+		expect( lastMutations[ 0 ].type ).to.equal( 'children' );
+		expect( lastMutations[ 0 ].oldChildren.length ).to.equal( 0 );
+		expect( lastMutations[ 0 ].newChildren.length ).to.equal( 1 );
+		expect( lastMutations[ 0 ].newChildren[ 0 ].is( 'text' ) ).to.be.true;
+		expect( lastMutations[ 0 ].newChildren[ 0 ].data ).to.equal( ' ' );
+		expect( lastMutations[ 0 ].node ).to.equal( selection.getFirstPosition().parent );
+	} );
+
+	// https://github.com/ckeditor/ckeditor5/issues/692 Scenario 3.
+	it( 'should handle space after inline filler at the end of container #2', () => {
+		const { view: viewContainer, selection } = parse(
+			'<container:p>' +
+				'foo' +
+				'<attribute:b>bar</attribute:b>' +
+				'[]' +
+			'</container:p>'
+		);
+
+		view.change( writer => {
+			viewRoot.appendChildren( viewContainer );
+			writer.setSelection( selection );
+		} );
+
+		// Appended container is third in the tree.
+		const container = domEditor.childNodes[ 2 ];
+		const inlineFiller = container.childNodes[ 2 ];
+
+		inlineFiller.data += ' ';
+
+		mutationObserver.flush();
+
+		expect( lastMutations.length ).to.equal( 1 );
+		expect( lastMutations[ 0 ].type ).to.equal( 'children' );
+		expect( lastMutations[ 0 ].oldChildren.length ).to.equal( 2 );
+		expect( lastMutations[ 0 ].newChildren.length ).to.equal( 3 );
+
+		// Foo and attribute is removed and reinserted.
+		expect( lastMutations[ 0 ].oldChildren[ 0 ].is( 'text' ) ).to.be.true;
+		expect( lastMutations[ 0 ].oldChildren[ 0 ].data ).to.equal( 'foo' );
+		expect( lastMutations[ 0 ].newChildren[ 0 ].is( 'text' ) ).to.be.true;
+		expect( lastMutations[ 0 ].newChildren[ 0 ].data ).to.equal( 'foo' );
+
+		expect( lastMutations[ 0 ].oldChildren[ 1 ].is( 'attributeElement' ) ).to.be.true;
+		expect( lastMutations[ 0 ].oldChildren[ 1 ].name ).to.equal( 'b' );
+		expect( lastMutations[ 0 ].newChildren[ 1 ].is( 'attributeElement' ) ).to.be.true;
+		expect( lastMutations[ 0 ].newChildren[ 1 ].name ).to.equal( 'b' );
+
+		expect( lastMutations[ 0 ].newChildren[ 2 ].is( 'text' ) ).to.be.true;
+		expect( lastMutations[ 0 ].newChildren[ 2 ].data ).to.equal( ' ' );
+		expect( lastMutations[ 0 ].node ).to.equal( selection.getFirstPosition().parent );
+	} );
+
+	// https://github.com/ckeditor/ckeditor5/issues/692 Scenario 2.
+	it( 'should handle space after inline filler at the beginning of container', () => {
+		const { view: viewContainer, selection } = parse(
+			'<container:p>' +
+				'<attribute:b>[]</attribute:b>' +
+				'foo' +
+			'</container:p>'
+		);
+
+		view.change( writer => {
+			viewRoot.appendChildren( viewContainer );
+			writer.setSelection( selection );
+		} );
+
+		// Appended container is third in the tree.
+		const container = domEditor.childNodes[ 2 ];
+		const inlineFiller = container.childNodes[ 0 ].childNodes[ 0 ];
+
+		inlineFiller.data += ' ';
+
+		mutationObserver.flush();
+
+		expect( lastMutations.length ).to.equal( 1 );
+		expect( lastMutations[ 0 ].type ).to.equal( 'children' );
+		expect( lastMutations[ 0 ].oldChildren.length ).to.equal( 0 );
+		expect( lastMutations[ 0 ].newChildren.length ).to.equal( 1 );
+		expect( lastMutations[ 0 ].newChildren[ 0 ].is( 'text' ) ).to.be.true;
+		expect( lastMutations[ 0 ].newChildren[ 0 ].data ).to.equal( ' ' );
+		expect( lastMutations[ 0 ].node ).to.equal( selection.getFirstPosition().parent );
+	} );
+
 	it( 'should have no block filler in mutation', () => {
 		viewRoot.appendChildren( parse( '<container:p></container:p>' ) );
 
@@ -359,7 +465,7 @@ describe( 'MutationObserver', () => {
 
 		mutationObserver.flush();
 
-		// There was onlu P2 change. P1 must be ignored.
+		// There was only P2 change. P1 must be ignored.
 		const viewP2 = viewRoot.getChild( 1 );
 		expect( lastMutations.length ).to.equal( 1 );
 		expect( lastMutations[ 0 ].node ).to.equal( viewP2 );

+ 13 - 14
packages/ckeditor5-engine/tests/view/renderer.js

@@ -122,7 +122,6 @@ describe( 'Renderer', () => {
 			renderer.markedChildren.clear();
 
 			selection._setTo( null );
-			selection._setFake( false );
 
 			selectionEditable = viewRoot;
 
@@ -1369,7 +1368,7 @@ describe( 'Renderer', () => {
 
 			it( 'should render fake selection', () => {
 				const label = 'fake selection label';
-				selection._setFake( true, { label } );
+				selection._setTo( selection.getRanges(), { fake: true, label } );
 				renderer.render();
 
 				expect( domRoot.childNodes.length ).to.equal( 2 );
@@ -1386,7 +1385,7 @@ describe( 'Renderer', () => {
 			} );
 
 			it( 'should render &nbsp; if no selection label is provided', () => {
-				selection._setFake( true );
+				selection._setTo( selection.getRanges(), { fake: true } );
 				renderer.render();
 
 				expect( domRoot.childNodes.length ).to.equal( 2 );
@@ -1402,10 +1401,10 @@ describe( 'Renderer', () => {
 			} );
 
 			it( 'should remove fake selection container when selection is no longer fake', () => {
-				selection._setFake( true );
+				selection._setTo( selection.getRanges(), { fake: true } );
 				renderer.render();
 
-				selection._setFake( false );
+				selection._setTo( selection.getRanges(), { fake: false } );
 				renderer.render();
 
 				expect( domRoot.childNodes.length ).to.equal( 1 );
@@ -1421,14 +1420,14 @@ describe( 'Renderer', () => {
 			it( 'should reuse fake selection container #1', () => {
 				const label = 'fake selection label';
 
-				selection._setFake( true, { label } );
+				selection._setTo( selection.getRanges(), { fake: true, label } );
 				renderer.render();
 
 				expect( domRoot.childNodes.length ).to.equal( 2 );
 
 				const container = domRoot.childNodes[ 1 ];
 
-				selection._setFake( true, { label } );
+				selection._setTo( selection.getRanges(), { fake: true, label } );
 				renderer.render();
 
 				expect( domRoot.childNodes.length ).to.equal( 2 );
@@ -1442,19 +1441,19 @@ describe( 'Renderer', () => {
 			} );
 
 			it( 'should reuse fake selection container #2', () => {
-				selection._setFake( true, { label: 'label 1' } );
+				selection._setTo( selection.getRanges(), { fake: true, label: 'label 1' } );
 				renderer.render();
 
 				expect( domRoot.childNodes.length ).to.equal( 2 );
 
 				const container = domRoot.childNodes[ 1 ];
 
-				selection._setFake( false );
+				selection._setTo( selection.getRanges(), { fake: false } );
 				renderer.render();
 
 				expect( domRoot.childNodes.length ).to.equal( 1 );
 
-				selection._setFake( true, { label: 'label 2' } );
+				selection._setTo( selection.getRanges(), { fake: true, label: 'label 2' } );
 				renderer.render();
 
 				expect( domRoot.childNodes.length ).to.equal( 2 );
@@ -1468,14 +1467,14 @@ describe( 'Renderer', () => {
 			} );
 
 			it( 'should reuse fake selection container #3', () => {
-				selection._setFake( true, { label: 'label 1' } );
+				selection._setTo( selection.getRanges(), { fake: true, label: 'label 1' } );
 				renderer.render();
 
 				expect( domRoot.childNodes.length ).to.equal( 2 );
 
 				const container = domRoot.childNodes[ 1 ];
 
-				selection._setFake( true, { label: 'label 2' } );
+				selection._setTo( selection.getRanges(), { fake: true, label: 'label 2' } );
 				renderer.render();
 
 				expect( domRoot.childNodes.length ).to.equal( 2 );
@@ -1489,7 +1488,7 @@ describe( 'Renderer', () => {
 			} );
 
 			it( 'should style fake selection container properly', () => {
-				selection._setFake( true, { label: 'fake selection' } );
+				selection._setTo( selection.getRanges(), { fake: true, label: 'fake selection' } );
 				renderer.render();
 
 				expect( domRoot.childNodes.length ).to.equal( 2 );
@@ -1502,7 +1501,7 @@ describe( 'Renderer', () => {
 			} );
 
 			it( 'should bind fake selection container to view selection', () => {
-				selection._setFake( true, { label: 'fake selection' } );
+				selection._setTo( selection.getRanges(), { fake: true, label: 'fake selection' } );
 				renderer.render();
 
 				expect( domRoot.childNodes.length ).to.equal( 2 );

+ 163 - 141
packages/ckeditor5-engine/tests/view/selection.js

@@ -44,13 +44,13 @@ describe( 'Selection', () => {
 
 		it( 'should be able to create a selection from the given ranges and isLastBackward flag', () => {
 			const ranges = [ range1, range2, range3 ];
-			const selection = new Selection( ranges, true );
+			const selection = new Selection( ranges, { backward: true } );
 
 			expect( selection.isBackward ).to.be.true;
 		} );
 
 		it( 'should be able to create a selection from the given range and isLastBackward flag', () => {
-			const selection = new Selection( range1, true );
+			const selection = new Selection( range1, { backward: true } );
 
 			expect( Array.from( selection.getRanges() ) ).to.deep.equal( [ range1 ] );
 			expect( selection.isBackward ).to.be.true;
@@ -58,7 +58,7 @@ describe( 'Selection', () => {
 
 		it( 'should be able to create a selection from the given iterable of ranges and isLastBackward flag', () => {
 			const ranges = new Set( [ range1, range2, range3 ] );
-			const selection = new Selection( ranges, false );
+			const selection = new Selection( ranges, { backward: false } );
 
 			expect( Array.from( selection.getRanges() ) ).to.deep.equal( [ range1, range2, range3 ] );
 			expect( selection.isBackward ).to.be.false;
@@ -85,7 +85,7 @@ describe( 'Selection', () => {
 		} );
 
 		it( 'should be able to create a selection from the other selection', () => {
-			const otherSelection = new Selection( [ range2, range3 ], true );
+			const otherSelection = new Selection( [ range2, range3 ], { backward: true } );
 			const selection = new Selection( otherSelection );
 
 			expect( Array.from( selection.getRanges() ) ).to.deep.equal( [ range2, range3 ] );
@@ -93,8 +93,7 @@ describe( 'Selection', () => {
 		} );
 
 		it( 'should be able to create a fake selection from the other fake selection', () => {
-			const otherSelection = new Selection( [ range2, range3 ], true );
-			otherSelection._setFake( true, { label: 'foo bar baz' } );
+			const otherSelection = new Selection( [ range2, range3 ], { fake: true, label: 'foo bar baz' } );
 			const selection = new Selection( otherSelection );
 
 			expect( selection.isFake ).to.be.true;
@@ -140,7 +139,7 @@ describe( 'Selection', () => {
 		} );
 
 		it( 'should return end of single range in selection when added as backward', () => {
-			selection._setTo( range1, true );
+			selection._setTo( range1, { backward: true } );
 			const anchor = selection.anchor;
 
 			expect( anchor.isEqual( range1.end ) ).to.be.true;
@@ -167,7 +166,7 @@ describe( 'Selection', () => {
 		} );
 
 		it( 'should return start of single range in selection when added as backward', () => {
-			selection._setTo( range1, true );
+			selection._setTo( range1, { backward: true } );
 			const focus = selection.focus;
 
 			expect( focus.isEqual( range1.start ) ).to.be.true;
@@ -254,7 +253,7 @@ describe( 'Selection', () => {
 			const endPos = Position.createAt( el, 2 );
 			const newEndPos = Position.createAt( el, 3 );
 
-			selection._setTo( new Range( startPos, endPos ), true );
+			selection._setTo( new Range( startPos, endPos ), { backward: true } );
 
 			selection._setFocus( newEndPos );
 
@@ -268,7 +267,7 @@ describe( 'Selection', () => {
 			const endPos = Position.createAt( el, 2 );
 			const newEndPos = Position.createAt( el, 0 );
 
-			selection._setTo( new Range( startPos, endPos ), true );
+			selection._setTo( new Range( startPos, endPos ), { backward: true } );
 
 			selection._setFocus( newEndPos );
 
@@ -376,7 +375,7 @@ describe( 'Selection', () => {
 			const range1 = Range.createFromParentsAndOffsets( el, 5, el, 10 );
 			const range2 = Range.createFromParentsAndOffsets( el, 15, el, 16 );
 
-			selection._setTo( range1, true );
+			selection._setTo( range1, { backward: true } );
 			expect( selection ).to.have.property( 'isBackward', true );
 
 			selection._setTo( [ range1, range2 ] );
@@ -386,7 +385,7 @@ describe( 'Selection', () => {
 		it( 'is false when last range is collapsed', () => {
 			const range = Range.createFromParentsAndOffsets( el, 5, el, 5 );
 
-			selection._setTo( range, true );
+			selection._setTo( range, { backward: true } );
 
 			expect( selection.isBackward ).to.be.false;
 		} );
@@ -478,9 +477,9 @@ describe( 'Selection', () => {
 		} );
 
 		it( 'should return true if backward selections equal', () => {
-			selection._setTo( range1, true );
+			selection._setTo( range1, { backward: true } );
 
-			const otherSelection = new Selection( [ range1 ], true );
+			const otherSelection = new Selection( [ range1 ], { backward: true } );
 
 			expect( selection.isEqual( otherSelection ) ).to.be.true;
 		} );
@@ -504,32 +503,27 @@ describe( 'Selection', () => {
 		it( 'should return false if directions do not equal', () => {
 			selection._setTo( range1 );
 
-			const otherSelection = new Selection( [ range1 ], true );
+			const otherSelection = new Selection( [ range1 ], { backward: true } );
 
 			expect( selection.isEqual( otherSelection ) ).to.be.false;
 		} );
 
 		it( 'should return false if one selection is fake', () => {
-			const otherSelection = new Selection();
-			otherSelection._setFake( true );
+			const otherSelection = new Selection( null, { fake: true } );
 
 			expect( selection.isEqual( otherSelection ) ).to.be.false;
 		} );
 
 		it( 'should return true if both selection are fake', () => {
-			const otherSelection = new Selection( [ range1 ] );
-			otherSelection._setFake( true );
-			selection._setFake( true );
-			selection._setTo( range1 );
+			const otherSelection = new Selection( range1, { fake: true } );
+			selection._setTo( range1, { fake: true } );
 
 			expect( selection.isEqual( otherSelection ) ).to.be.true;
 		} );
 
 		it( 'should return false if both selection are fake but have different label', () => {
-			const otherSelection = new Selection( [ range1 ] );
-			otherSelection._setFake( true, { label: 'foo bar baz' } );
-			selection._setFake( true );
-			selection._setTo( range1 );
+			const otherSelection = new Selection( [ range1 ], { fake: true, label: 'foo bar baz' } );
+			selection._setTo( range1, { fake: true, label: 'foo' } );
 
 			expect( selection.isEqual( otherSelection ) ).to.be.false;
 		} );
@@ -569,7 +563,7 @@ describe( 'Selection', () => {
 		it( 'should return false if directions are not equal', () => {
 			selection._setTo( range1 );
 
-			const otherSelection = new Selection( [ range1 ], true );
+			const otherSelection = new Selection( [ range1 ], { backward: true } );
 
 			expect( selection.isSimilar( otherSelection ) ).to.be.false;
 		} );
@@ -609,44 +603,12 @@ describe( 'Selection', () => {
 		} );
 	} );
 
-	describe( '_setRanges()', () => {
-		it( 'should throw an error when range is invalid', () => {
-			expect( () => {
-				selection._setRanges( [ { invalid: 'range' } ] );
-			} ).to.throw( CKEditorError, 'view-selection-invalid-range: Invalid Range.' );
-		} );
-
-		it( 'should add ranges and fire change event', done => {
-			selection._setTo( range1 );
-
-			selection.once( 'change', () => {
-				expect( selection.rangeCount ).to.equal( 2 );
-				expect( selection._ranges[ 0 ].isEqual( range2 ) ).to.be.true;
-				expect( selection._ranges[ 0 ] ).is.not.equal( range2 );
-				expect( selection._ranges[ 1 ].isEqual( range3 ) ).to.be.true;
-				expect( selection._ranges[ 1 ] ).is.not.equal( range3 );
-				done();
-			} );
-
-			selection._setRanges( [ range2, range3 ] );
-		} );
-
-		it( 'should throw when range is intersecting with already added range', () => {
-			const text = el.getChild( 0 );
-			const range2 = Range.createFromParentsAndOffsets( text, 7, text, 15 );
-
-			expect( () => {
-				selection._setRanges( [ range1, range2 ] );
-			} ).to.throw( CKEditorError, 'view-selection-range-intersects' );
-		} );
-	} );
-
 	describe( '_setTo()', () => {
 		describe( 'simple scenarios', () => {
 			it( 'should set selection ranges from the given selection', () => {
 				selection._setTo( range1 );
 
-				const otherSelection = new Selection( [ range2, range3 ], true );
+				const otherSelection = new Selection( [ range2, range3 ], { backward: true } );
 
 				selection._setTo( otherSelection );
 
@@ -659,39 +621,27 @@ describe( 'Selection', () => {
 				expect( selection.anchor.isEqual( range3.end ) ).to.be.true;
 			} );
 
-			it( 'should set selection on the given Range using _setRanges method', () => {
-				const spy = sinon.spy( selection, '_setRanges' );
-
+			it( 'should set selection on the given Range', () => {
 				selection._setTo( range1 );
 
 				expect( Array.from( selection.getRanges() ) ).to.deep.equal( [ range1 ] );
 				expect( selection.isBackward ).to.be.false;
-				expect( selection._setRanges.calledOnce ).to.be.true;
-				spy.restore();
 			} );
 
-			it( 'should set selection on the given iterable of Ranges using _setRanges method', () => {
-				const spy = sinon.spy( selection, '_setRanges' );
-
+			it( 'should set selection on the given iterable of Ranges', () => {
 				selection._setTo( new Set( [ range1, range2 ] ) );
 
 				expect( Array.from( selection.getRanges() ) ).to.deep.equal( [ range1, range2 ] );
 				expect( selection.isBackward ).to.be.false;
-				expect( selection._setRanges.calledOnce ).to.be.true;
-				spy.restore();
 			} );
 
-			it( 'should set collapsed selection on the given Position using _setRanges method', () => {
-				const spy = sinon.spy( selection, '_setRanges' );
-
+			it( 'should set collapsed selection on the given Position', () => {
 				selection._setTo( range1.start );
 
 				expect( Array.from( selection.getRanges() ).length ).to.equal( 1 );
 				expect( Array.from( selection.getRanges() )[ 0 ].start ).to.deep.equal( range1.start );
 				expect( selection.isBackward ).to.be.false;
 				expect( selection.isCollapsed ).to.be.true;
-				expect( selection._setRanges.calledOnce ).to.be.true;
-				spy.restore();
 			} );
 
 			it( 'should fire change event', done => {
@@ -707,9 +657,8 @@ describe( 'Selection', () => {
 			} );
 
 			it( 'should set fake state and label', () => {
-				const otherSelection = new Selection();
 				const label = 'foo bar baz';
-				otherSelection._setFake( true, { label } );
+				const otherSelection = new Selection( null, { fake: true, label } );
 				selection._setTo( otherSelection );
 
 				expect( selection.isFake ).to.be.true;
@@ -753,7 +702,7 @@ describe( 'Selection', () => {
 				const foo = new Text( 'foo' );
 				const p = new Element( 'p', null, foo );
 
-				selection._setTo( foo );
+				selection._setTo( foo, 0 );
 				let range = selection.getFirstRange();
 
 				expect( range.start.parent ).to.equal( foo );
@@ -768,6 +717,14 @@ describe( 'Selection', () => {
 				expect( range.start.isEqual( range.end ) ).to.be.true;
 			} );
 
+			it( 'should throw an error when the second parameter is not passed and first is an item', () => {
+				const foo = new Text( 'foo' );
+
+				expect( () => {
+					selection._setTo( foo );
+				} ).to.throw( CKEditorError, /view-selection-setTo-required-second-parameter/ );
+			} );
+
 			it( 'should collapse selection at node and flag', () => {
 				const foo = new Text( 'foo' );
 				const p = new Element( 'p', null, foo );
@@ -807,15 +764,6 @@ describe( 'Selection', () => {
 
 				selection._setTo( selection.getFirstPosition() );
 			} );
-
-			it( 'should do nothing if no ranges present', () => {
-				const fireSpy = sinon.spy( selection, 'fire' );
-
-				selection._setTo( selection.getFirstPosition() );
-
-				fireSpy.restore();
-				expect( fireSpy.notCalled ).to.be.true;
-			} );
 		} );
 
 		describe( 'setting collapsed selection to end', () => {
@@ -830,15 +778,6 @@ describe( 'Selection', () => {
 
 				selection._setTo( selection.getLastPosition() );
 			} );
-
-			it( 'should do nothing if no ranges present', () => {
-				const fireSpy = sinon.spy( selection, 'fire' );
-
-				selection._setTo( selection.getLastPosition() );
-
-				fireSpy.restore();
-				expect( fireSpy.notCalled ).to.be.true;
-			} );
 		} );
 
 		describe( 'removing all ranges', () => {
@@ -852,15 +791,139 @@ describe( 'Selection', () => {
 
 				selection._setTo( null );
 			} );
+		} );
 
-			it( 'should do nothing when no ranges are present', () => {
-				const fireSpy = sinon.spy( selection, 'fire' );
-				selection._setTo( null );
+		describe( 'setting fake selection', () => {
+			it( 'should allow to set selection to fake', () => {
+				selection._setTo( range1, { fake: true } );
+
+				expect( selection.isFake ).to.be.true;
+			} );
+
+			it( 'should allow to set fake selection label', () => {
+				const label = 'foo bar baz';
+				selection._setTo( range1, { fake: true, label } );
+
+				expect( selection.fakeSelectionLabel ).to.equal( label );
+			} );
+
+			it( 'should not set label when set to false', () => {
+				const label = 'foo bar baz';
+				selection._setTo( range1, { fake: false, label } );
+
+				expect( selection.fakeSelectionLabel ).to.equal( '' );
+			} );
+
+			it( 'should reset label when set to false', () => {
+				const label = 'foo bar baz';
+				selection._setTo( range1, { fake: true, label } );
+				selection._setTo( range1 );
+
+				expect( selection.fakeSelectionLabel ).to.equal( '' );
+			} );
 
-				fireSpy.restore();
-				expect( fireSpy.notCalled ).to.be.true;
+			it( 'should fire change event', done => {
+				selection.once( 'change', () => {
+					expect( selection.isFake ).to.be.true;
+					expect( selection.fakeSelectionLabel ).to.equal( 'foo bar baz' );
+
+					done();
+				} );
+
+				selection._setTo( range1, { fake: true, label: 'foo bar baz' } );
+			} );
+
+			it( 'should be possible to create an empty fake selection', () => {
+				selection._setTo( null, { fake: true, label: 'foo bar baz' } );
+
+				expect( selection.fakeSelectionLabel ).to.equal( 'foo bar baz' );
+				expect( selection.isFake ).to.be.true;
+			} );
+		} );
+
+		describe( 'setting selection to itself', () => {
+			it( 'should correctly set ranges when setting to the same selection', () => {
+				selection._setTo( [ range1, range2 ] );
+				selection._setTo( selection );
+
+				const ranges = Array.from( selection.getRanges() );
+				expect( ranges.length ).to.equal( 2 );
+
+				expect( ranges[ 0 ].isEqual( range1 ) ).to.be.true;
+				expect( ranges[ 1 ].isEqual( range2 ) ).to.be.true;
+			} );
+
+			it( 'should correctly set ranges when setting to the same selection\'s ranges', () => {
+				selection._setTo( [ range1, range2 ] );
+				selection._setTo( selection.getRanges() );
+
+				const ranges = Array.from( selection.getRanges() );
+				expect( ranges.length ).to.equal( 2 );
+
+				expect( ranges[ 0 ].isEqual( range1 ) ).to.be.true;
+				expect( ranges[ 1 ].isEqual( range2 ) ).to.be.true;
 			} );
 		} );
+
+		describe( 'throwing errors', () => {
+			it( 'should throw an error when range is invalid', () => {
+				expect( () => {
+					selection._setTo( [ { invalid: 'range' } ] );
+				} ).to.throw( CKEditorError, 'view-selection-invalid-range: Invalid Range.' );
+			} );
+
+			it( 'should throw when range is intersecting with already added range', () => {
+				const text = el.getChild( 0 );
+				const range2 = Range.createFromParentsAndOffsets( text, 7, text, 15 );
+
+				expect( () => {
+					selection._setTo( [ range1, range2 ] );
+				} ).to.throw( CKEditorError, 'view-selection-range-intersects' );
+			} );
+		} );
+
+		it( 'should allow setting selection on an item', () => {
+			const textNode1 = new Text( 'foo' );
+			const textNode2 = new Text( 'bar' );
+			const textNode3 = new Text( 'baz' );
+			const element = new Element( 'p', null, [ textNode1, textNode2, textNode3 ] );
+
+			selection._setTo( textNode2, 'on' );
+
+			const ranges = Array.from( selection.getRanges() );
+			expect( ranges.length ).to.equal( 1 );
+			expect( ranges[ 0 ].start.parent ).to.equal( element );
+			expect( ranges[ 0 ].start.offset ).to.deep.equal( 1 );
+			expect( ranges[ 0 ].end.parent ).to.equal( element );
+			expect( ranges[ 0 ].end.offset ).to.deep.equal( 2 );
+		} );
+
+		it( 'should allow setting selection inside an element', () => {
+			const element = new Element( 'p', null, [ new Text( 'foo' ), new Text( 'bar' ) ] );
+
+			selection._setTo( element, 'in' );
+
+			const ranges = Array.from( selection.getRanges() );
+			expect( ranges.length ).to.equal( 1 );
+			expect( ranges[ 0 ].start.parent ).to.equal( element );
+			expect( ranges[ 0 ].start.offset ).to.deep.equal( 0 );
+			expect( ranges[ 0 ].end.parent ).to.equal( element );
+			expect( ranges[ 0 ].end.offset ).to.deep.equal( 2 );
+		} );
+
+		it( 'should allow setting backward selection inside an element', () => {
+			const element = new Element( 'p', null, [ new Text( 'foo' ), new Text( 'bar' ) ] );
+
+			selection._setTo( element, 'in', { backward: true } );
+
+			const ranges = Array.from( selection.getRanges() );
+			expect( ranges.length ).to.equal( 1 );
+			expect( ranges[ 0 ].start.parent ).to.equal( element );
+			expect( ranges[ 0 ].start.offset ).to.deep.equal( 0 );
+			expect( ranges[ 0 ].end.parent ).to.equal( element );
+			expect( ranges[ 0 ].end.offset ).to.deep.equal( 2 );
+			expect( selection.isBackward ).to.be.true;
+		} );
 	} );
 
 	describe( 'getEditableElement()', () => {
@@ -893,47 +956,6 @@ describe( 'Selection', () => {
 		} );
 	} );
 
-	describe( '_setFake()', () => {
-		it( 'should allow to set selection to fake', () => {
-			selection._setFake( true );
-
-			expect( selection.isFake ).to.be.true;
-		} );
-
-		it( 'should allow to set fake selection label', () => {
-			const label = 'foo bar baz';
-			selection._setFake( true, { label } );
-
-			expect( selection.fakeSelectionLabel ).to.equal( label );
-		} );
-
-		it( 'should not set label when set to false', () => {
-			const label = 'foo bar baz';
-			selection._setFake( false, { label } );
-
-			expect( selection.fakeSelectionLabel ).to.equal( '' );
-		} );
-
-		it( 'should reset label when set to false', () => {
-			const label = 'foo bar baz';
-			selection._setFake( true, { label } );
-			selection._setFake( false );
-
-			expect( selection.fakeSelectionLabel ).to.equal( '' );
-		} );
-
-		it( 'should fire change event', done => {
-			selection.once( 'change', () => {
-				expect( selection.isFake ).to.be.true;
-				expect( selection.fakeSelectionLabel ).to.equal( 'foo bar baz' );
-
-				done();
-			} );
-
-			selection._setFake( true, { label: 'foo bar baz' } );
-		} );
-	} );
-
 	describe( 'getSelectedElement()', () => {
 		it( 'should return selected element', () => {
 			const { selection, view } = parse( 'foo [<b>bar</b>] baz' );

+ 19 - 20
packages/ckeditor5-engine/tests/view/writer/writer.js

@@ -10,23 +10,33 @@ import ViewPosition from '../../../src/view/position';
 import createViewRoot from '../_utils/createroot';
 
 describe( 'Writer', () => {
-	let writer, attributes, root;
+	let writer, attributes, root, doc;
 
 	before( () => {
 		attributes = { foo: 'bar', baz: 'quz' };
-		const document = new Document();
-		root = createViewRoot( document );
-		writer = new Writer( document );
+		doc = new Document();
+		root = createViewRoot( doc );
+		writer = new Writer( doc );
 	} );
 
 	describe( 'setSelection()', () => {
-		it( 'should use selection._setTo method internally', () => {
-			const spy = sinon.spy( writer.document.selection, '_setTo' );
+		it( 'should set document view selection', () => {
 			const position = ViewPosition.createAt( root );
-			writer.setSelection( position, true );
+			writer.setSelection( position );
 
-			sinon.assert.calledWithExactly( spy, position, true );
-			spy.restore();
+			const ranges = Array.from( doc.selection.getRanges() );
+
+			expect( ranges.length ).to.equal( 1 );
+			expect( ranges[ 0 ].start.compareWith( position ) ).to.equal( 'same' );
+			expect( ranges[ 0 ].end.compareWith( position ) ).to.equal( 'same' );
+		} );
+
+		it( 'should be able to set fake selection', () => {
+			const position = ViewPosition.createAt( root );
+			writer.setSelection( position, { fake: true, label: 'foo' } );
+
+			expect( doc.selection.isFake ).to.be.true;
+			expect( doc.selection.fakeSelectionLabel ).to.equal( 'foo' );
 		} );
 	} );
 
@@ -40,17 +50,6 @@ describe( 'Writer', () => {
 		} );
 	} );
 
-	describe( 'setFakeSelection()', () => {
-		it( 'should use selection._setFake method internally', () => {
-			const spy = sinon.spy( writer.document.selection, '_setFake' );
-			const options = {};
-			writer.setFakeSelection( true, options );
-
-			sinon.assert.calledWithExactly( spy, true, options );
-			spy.restore();
-		} );
-	} );
-
 	describe( 'createText()', () => {
 		it( 'should create Text instance', () => {
 			const text = writer.createText( 'foo bar' );