`
* instead of ``, `` instead of `` and `` instead of ``).
diff --git a/src/view/document.js b/src/view/document.js
index 2b7b49af7..370d5551a 100644
--- a/src/view/document.js
+++ b/src/view/document.js
@@ -7,14 +7,14 @@
* @module engine/view/document
*/
-import Selection from './selection';
+import DocumentSelection from './documentselection';
import Collection from '@ckeditor/ckeditor5-utils/src/collection';
import mix from '@ckeditor/ckeditor5-utils/src/mix';
import ObservableMixin from '@ckeditor/ckeditor5-utils/src/observablemixin';
/**
* Document class creates an abstract layer over the content editable area, contains a tree of view elements and
- * {@link module:engine/view/selection~Selection view selection} associated with this document.
+ * {@link module:engine/view/documentselection~DocumentSelection view selection} associated with this document.
*
* @mixes module:utils/observablemixin~ObservableMixin
*/
@@ -27,9 +27,9 @@ export default class Document {
* Selection done on this document.
*
* @readonly
- * @member {module:engine/view/selection~Selection} module:engine/view/document~Document#selection
+ * @member {module:engine/view/documentselection~DocumentSelection} module:engine/view/document~Document#selection
*/
- this.selection = new Selection();
+ this.selection = new DocumentSelection();
/**
* Roots of the view tree. Collection of the {module:engine/view/element~Element view elements}.
diff --git a/src/view/documentselection.js b/src/view/documentselection.js
new file mode 100644
index 000000000..9774cb977
--- /dev/null
+++ b/src/view/documentselection.js
@@ -0,0 +1,374 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module engine/view/documentselection
+ */
+
+import Selection from './selection';
+import mix from '@ckeditor/ckeditor5-utils/src/mix';
+import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';
+
+/**
+ * Class representing document selection in tree view. It's instance is stored at
+ * {@link module:engine/view/document~Document#selection}. It is similar to {@link module:engine/view/selection~Selection} but
+ * it has read-only API and can be modified only by writer obtained from {@link module:engine/view/view~View#change} method.
+ *
+ * Selection can consist of {@link module:engine/view/range~Range ranges}.
+ * Selection's ranges can be obtained via {@link module:engine/view/documentselection~DocumentSelection#getRanges getRanges},
+ * {@link module:engine/view/documentselection~DocumentSelection#getFirstRange getFirstRange}
+ * and {@link module:engine/view/documentselection~DocumentSelection#getLastRange getLastRange}
+ * methods, which return copies of ranges stored inside selection. Modifications made on these copies will not change
+ * selection's state. Similar situation occurs when getting {@link module:engine/view/documentselection~DocumentSelection#anchor anchor},
+ * {@link module:engine/view/documentselection~DocumentSelection#focus focus},
+ * {@link module:engine/view/documentselection~DocumentSelection#getFirstPosition first} and
+ * {@link module:engine/view/documentselection~DocumentSelection#getLastPosition last} positions - all will return
+ * copies of requested positions.
+ */
+export default class DocumentSelection {
+ /**
+ * Creates new DocumentSelection instance.
+ *
+ * // Creates empty selection without ranges.
+ * const selection = new DocumentSelection();
+ *
+ * // Creates selection at the given range.
+ * const range = new Range( start, end );
+ * const selection = new DocumentSelection( range );
+ *
+ * // Creates selection at the given ranges
+ * const ranges = [ new Range( start1, end2 ), new Range( star2, end2 ) ];
+ * const selection = new DocumentSelection( ranges );
+ *
+ * // Creates selection from the other selection.
+ * const otherSelection = new Selection();
+ * const selection = new DocumentSelection( otherSelection );
+ *
+ * // Creates selection at the given position.
+ * const position = new Position( root, path );
+ * const selection = new DocumentSelection( position );
+ *
+ * // Creates collapsed selection at the position of given item and offset.
+ * const paragraph = writer.createContainerElement( 'paragraph' );
+ * const selection = new DocumentSelection( 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 DocumentSelection( 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 DocumentSelection( paragraph, 'on' );
+ *
+ * `Selection`'s constructor allow passing additional options (`backward`, `fake` and `label`) as the last argument.
+ *
+ * // Creates backward selection.
+ * const selection = new DocumentSelection( 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 DocumentSelection( 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/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 = null, placeOrOffset, options ) {
+ /**
+ * Selection is used internally (`DocumentSelection` is a proxy to that selection).
+ *
+ * @private
+ * @member {module:engine/view/selection~Selection}
+ */
+ this._selection = new Selection();
+
+ // Delegate change event to be fired on DocumentSelection instance.
+ this._selection.delegate( 'change' ).to( this );
+
+ // Set selection data.
+ this._selection.setTo( selectable, placeOrOffset, options );
+ }
+
+ /**
+ * Returns true if selection instance is marked as `fake`.
+ *
+ * @see #_setTo
+ * @returns {Boolean}
+ */
+ get isFake() {
+ return this._selection.isFake;
+ }
+
+ /**
+ * Returns fake selection label.
+ *
+ * @see #_setTo
+ * @returns {String}
+ */
+ get fakeSelectionLabel() {
+ return this._selection.fakeSelectionLabel;
+ }
+
+ /**
+ * Selection anchor. Anchor may be described as a position where the selection starts. Together with
+ * {@link #focus focus} they define the direction of selection, which is important
+ * when expanding/shrinking selection. Anchor is always the start or end of the most recent added range.
+ * It may be a bit unintuitive when there are multiple ranges in selection.
+ *
+ * @see #focus
+ * @type {module:engine/view/position~Position}
+ */
+ get anchor() {
+ return this._selection.anchor;
+ }
+
+ /**
+ * Selection focus. Focus is a position where the selection ends.
+ *
+ * @see #anchor
+ * @type {module:engine/view/position~Position}
+ */
+ get focus() {
+ return this._selection.focus;
+ }
+
+ /**
+ * Returns whether the selection is collapsed. Selection is collapsed when there is exactly one range which is
+ * collapsed.
+ *
+ * @type {Boolean}
+ */
+ get isCollapsed() {
+ return this._selection.isCollapsed;
+ }
+
+ /**
+ * Returns number of ranges in selection.
+ *
+ * @type {Number}
+ */
+ get rangeCount() {
+ return this._selection.rangeCount;
+ }
+
+ /**
+ * Specifies whether the {@link #focus} precedes {@link #anchor}.
+ *
+ * @type {Boolean}
+ */
+ get isBackward() {
+ return this._selection.isBackward;
+ }
+
+ /**
+ * {@link module:engine/view/editableelement~EditableElement EditableElement} instance that contains this selection, or `null`
+ * if the selection is not inside an editable element.
+ *
+ * @type {module:engine/view/editableelement~EditableElement|null}
+ */
+ get editableElement() {
+ return this._selection.editableElement;
+ }
+
+ /**
+ * Used for the compatibility with the {@link module:engine/view/selection~Selection#isEqual} method.
+ *
+ * @protected
+ */
+ get _ranges() {
+ return this._selection._ranges;
+ }
+
+ /**
+ * Returns an iterable that contains copies of all ranges added to the selection.
+ *
+ * @returns {Iterable.}
+ */
+ * getRanges() {
+ yield* this._selection.getRanges();
+ }
+
+ /**
+ * Returns copy of the first range in the selection. First range is the one which
+ * {@link module:engine/view/range~Range#start start} position {@link module:engine/view/position~Position#isBefore is before} start
+ * position of all other ranges (not to confuse with the first range added to the selection).
+ * Returns `null` if no ranges are added to selection.
+ *
+ * @returns {module:engine/view/range~Range|null}
+ */
+ getFirstRange() {
+ return this._selection.getFirstRange();
+ }
+
+ /**
+ * Returns copy of the last range in the selection. Last range is the one which {@link module:engine/view/range~Range#end end}
+ * position {@link module:engine/view/position~Position#isAfter is after} end position of all other ranges (not to confuse
+ * with the last range added to the selection). Returns `null` if no ranges are added to selection.
+ *
+ * @returns {module:engine/view/range~Range|null}
+ */
+ getLastRange() {
+ return this._selection.getLastRange();
+ }
+
+ /**
+ * Returns copy of the first position in the selection. First position is the position that
+ * {@link module:engine/view/position~Position#isBefore is before} any other position in the selection ranges.
+ * Returns `null` if no ranges are added to selection.
+ *
+ * @returns {module:engine/view/position~Position|null}
+ */
+ getFirstPosition() {
+ return this._selection.getFirstPosition();
+ }
+
+ /**
+ * Returns copy of the last position in the selection. Last position is the position that
+ * {@link module:engine/view/position~Position#isAfter is after} any other position in the selection ranges.
+ * Returns `null` if no ranges are added to selection.
+ *
+ * @returns {module:engine/view/position~Position|null}
+ */
+ getLastPosition() {
+ return this._selection.getLastPosition();
+ }
+
+ /**
+ * Returns the selected element. {@link module:engine/view/element~Element Element} is considered as selected if there is only
+ * one range in the selection, and that range contains exactly one element.
+ * Returns `null` if there is no selected element.
+ *
+ * @returns {module:engine/view/element~Element|null}
+ */
+ getSelectedElement() {
+ return this._selection.getSelectedElement();
+ }
+
+ /**
+ * Checks whether, this selection is equal to given selection. Selections are equal if they have same directions,
+ * same number of ranges and all ranges from one selection equal to a range from other selection.
+ *
+ * @param {module:engine/view/selection~Selection|module:engine/view/documentselection~DocumentSelection} otherSelection
+ * Selection to compare with.
+ * @returns {Boolean} `true` if selections are equal, `false` otherwise.
+ */
+ isEqual( otherSelection ) {
+ return this._selection.isEqual( otherSelection );
+ }
+
+ /**
+ * Checks whether this selection is similar to given selection. Selections are similar if they have same directions, same
+ * number of ranges, and all {@link module:engine/view/range~Range#getTrimmed trimmed} ranges from one selection are
+ * equal to any trimmed range from other selection.
+ *
+ * @param {module:engine/view/selection~Selection|module:engine/view/documentselection~DocumentSelection} otherSelection
+ * Selection to compare with.
+ * @returns {Boolean} `true` if selections are similar, `false` otherwise.
+ */
+ isSimilar( otherSelection ) {
+ return this._selection.isSimilar( otherSelection );
+ }
+
+ /**
+ * Sets this selection's ranges and direction to the specified location based on the given
+ * {@link module:engine/view/documentselection~DocumentSelection document selection},
+ * {@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 selection to the given range.
+ * const range = new Range( start, end );
+ * documentSelection._setTo( range );
+ *
+ * // Sets selection to given ranges.
+ * const ranges = [ new Range( start1, end2 ), new Range( star2, end2 ) ];
+ * documentSelection._setTo( range );
+ *
+ * // Sets selection to the other selection.
+ * const otherSelection = new Selection();
+ * documentSelection._setTo( otherSelection );
+ *
+ * // Sets collapsed selection at the given position.
+ * const position = new Position( root, path );
+ * documentSelection._setTo( position );
+ *
+ * // Sets collapsed selection at the position of given item and offset.
+ * documentSelection._setTo( 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.
+ *
+ * documentSelection._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.
+ *
+ * documentSelection._setTo( paragraph, 'on' );
+ *
+ * // Clears selection. Removes all ranges.
+ * documentSelection._setTo( null );
+ *
+ * `Selection#_setTo()` method allow passing additional options (`backward`, `fake` and `label`) as the last argument.
+ *
+ * // Sets selection as backward.
+ * documentSelection._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 des cribe fake selection in DOM
+ * (and be properly handled by screen readers).
+ *
+ * // Creates fake selection with label.
+ * documentSelection._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/item~Item|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.
+ * @param {Boolean} [options.fake] Sets this selection instance to be marked as `fake`.
+ * @param {String} [options.label] Label for the fake selection.
+ */
+ _setTo( selectable, placeOrOffset, options ) {
+ this._selection.setTo( selectable, placeOrOffset, options );
+ }
+
+ /**
+ * Moves {@link #focus} to the specified location.
+ *
+ * The location can be specified in the same form as {@link module:engine/view/position~Position.createAt} parameters.
+ *
+ * @protected
+ * @fires change
+ * @param {module:engine/view/item~Item|module:engine/view/position~Position} itemOrPosition
+ * @param {Number|'end'|'before'|'after'} [offset=0] Offset or one of the flags. Used only when
+ * first parameter is a {@link module:engine/view/item~Item view item}.
+ */
+ _setFocus( itemOrPosition, offset ) {
+ this._selection.setFocus( itemOrPosition, offset );
+ }
+
+ /**
+ * Fired whenever selection ranges are changed through {@link ~DocumentSelection Selection API}.
+ *
+ * @event change
+ */
+}
+
+mix( DocumentSelection, EmitterMixin );
diff --git a/src/view/domconverter.js b/src/view/domconverter.js
index 559f87cc2..3a58ebee2 100644
--- a/src/view/domconverter.js
+++ b/src/view/domconverter.js
@@ -103,20 +103,20 @@ export default class DomConverter {
}
/**
- * Binds given DOM element that represents fake selection to {@link module:engine/view/selection~Selection view selection}.
- * View selection copy is stored and can be retrieved by {@link module:engine/view/domconverter~DomConverter#fakeSelectionToView}
- * method.
+ * Binds given DOM element that represents fake selection to {@link module:engine/view/documentselection~DocumentSelection
+ * document selection}. Document selection copy is stored and can be retrieved by
+ * {@link module:engine/view/domconverter~DomConverter#fakeSelectionToView} method.
*
* @param {HTMLElement} domElement
- * @param {module:engine/view/selection~Selection} viewSelection
+ * @param {module:engine/view/documentselection~DocumentSelection} viewDocumentSelection
*/
- bindFakeSelection( domElement, viewSelection ) {
- this._fakeSelectionMapping.set( domElement, new ViewSelection( viewSelection ) );
+ bindFakeSelection( domElement, viewDocumentSelection ) {
+ this._fakeSelectionMapping.set( domElement, new ViewSelection( viewDocumentSelection ) );
}
/**
- * Returns {@link module:engine/view/selection~Selection view selection} instance corresponding to given DOM element that represents
- * fake selection. Returns `undefined` if binding to given DOM element does not exists.
+ * Returns {@link module:engine/view/selection~Selection view selection} instance corresponding to
+ * given DOM element that represents fake selection. Returns `undefined` if binding to given DOM element does not exists.
*
* @param {HTMLElement} domElement
* @returns {module:engine/view/selection~Selection|undefined}
diff --git a/src/view/observer/fakeselectionobserver.js b/src/view/observer/fakeselectionobserver.js
index 6af205e8c..00e04db0a 100644
--- a/src/view/observer/fakeselectionobserver.js
+++ b/src/view/observer/fakeselectionobserver.js
@@ -86,12 +86,12 @@ export default class FakeSelectionObserver extends Observer {
// Left or up arrow pressed - move selection to start.
if ( keyCode == keyCodes.arrowleft || keyCode == keyCodes.arrowup ) {
- newSelection._setTo( newSelection.getFirstPosition() );
+ newSelection.setTo( newSelection.getFirstPosition() );
}
// Right or down arrow pressed - move selection to end.
if ( keyCode == keyCodes.arrowright || keyCode == keyCodes.arrowdown ) {
- newSelection._setTo( newSelection.getLastPosition() );
+ newSelection.setTo( newSelection.getLastPosition() );
}
const data = {
diff --git a/src/view/observer/mutationobserver.js b/src/view/observer/mutationobserver.js
index 620acc7a0..9e44280ae 100644
--- a/src/view/observer/mutationobserver.js
+++ b/src/view/observer/mutationobserver.js
@@ -240,7 +240,7 @@ export default class MutationObserver extends Observer {
// Anchor and focus has to be properly mapped to view.
if ( viewSelectionAnchor && viewSelectionFocus ) {
viewSelection = new ViewSelection( viewSelectionAnchor );
- viewSelection._setFocus( viewSelectionFocus );
+ viewSelection.setFocus( viewSelectionFocus );
}
}
diff --git a/src/view/observer/selectionobserver.js b/src/view/observer/selectionobserver.js
index 7947508d4..dd4a5c454 100644
--- a/src/view/observer/selectionobserver.js
+++ b/src/view/observer/selectionobserver.js
@@ -42,10 +42,12 @@ export default class SelectionObserver extends Observer {
this.mutationObserver = view.getObserver( MutationObserver );
/**
- * Reference to the view {@link module:engine/view/selection~Selection} object used to compare new selection with it.
+ * Reference to the view {@link module:engine/view/documentselection~DocumentSelection} object used to compare
+ * new selection with it.
*
* @readonly
- * @member {module:engine/view/selection~Selection} module:engine/view/observer/selectionobserver~SelectionObserver#selection
+ * @member {module:engine/view/documentselection~DocumentSelection}
+ * module:engine/view/observer/selectionobserver~SelectionObserver#selection
*/
this.selection = this.document.selection;
@@ -205,7 +207,7 @@ export default class SelectionObserver extends Observer {
* @see module:engine/view/observer/selectionobserver~SelectionObserver
* @event module:engine/view/document~Document#event:selectionChange
* @param {Object} data
- * @param {module:engine/view/selection~Selection} data.oldSelection Old View selection which is
+ * @param {module:engine/view/documentselection~DocumentSelection} data.oldSelection Old View selection which is
* {@link module:engine/view/document~Document#selection}.
* @param {module:engine/view/selection~Selection} data.newSelection New View selection which is converted DOM selection.
* @param {Selection} data.domSelection Native DOM selection.
@@ -222,7 +224,7 @@ export default class SelectionObserver extends Observer {
* @see module:engine/view/observer/selectionobserver~SelectionObserver
* @event module:engine/view/document~Document#event:selectionChangeDone
* @param {Object} data
- * @param {module:engine/view/selection~Selection} data.oldSelection Old View selection which is
+ * @param {module:engine/view/documentselection~DocumentSelection} data.oldSelection Old View selection which is
* {@link module:engine/view/document~Document#selection}.
* @param {module:engine/view/selection~Selection} data.newSelection New View selection which is converted DOM selection.
* @param {Selection} data.domSelection Native DOM selection.
diff --git a/src/view/renderer.js b/src/view/renderer.js
index 247c46ab3..48f637c92 100644
--- a/src/view/renderer.js
+++ b/src/view/renderer.js
@@ -37,7 +37,7 @@ export default class Renderer {
* Creates a renderer instance.
*
* @param {module:engine/view/domconverter~DomConverter} domConverter Converter instance.
- * @param {module:engine/view/selection~Selection} selection View selection.
+ * @param {module:engine/view/documentselection~DocumentSelection} selection View selection.
*/
constructor( domConverter, selection ) {
/**
@@ -84,7 +84,7 @@ export default class Renderer {
* View selection. Renderer updates DOM selection based on the view selection.
*
* @readonly
- * @member {module:engine/view/selection~Selection}
+ * @member {module:engine/view/documentselection~DocumentSelection}
*/
this.selection = selection;
diff --git a/src/view/selection.js b/src/view/selection.js
index 87eeb895f..bd132dac8 100644
--- a/src/view/selection.js
+++ b/src/view/selection.js
@@ -16,21 +16,23 @@ import Node from './node';
import Element from './element';
import count from '@ckeditor/ckeditor5-utils/src/count';
import isIterable from '@ckeditor/ckeditor5-utils/src/isiterable';
+import DocumentSelection from './documentselection';
/**
* Class representing selection in tree view.
*
* Selection can consist of {@link module:engine/view/range~Range ranges} that can be set using
- * {@link module:engine/view/selection~Selection#_setTo} method.
+ * {@link module:engine/view/selection~Selection#setTo setTo} method.
* That method create copies of provided ranges and store those copies internally. Further modifications to passed
* ranges will not change selection's state.
* Selection's ranges can be obtained via {@link module:engine/view/selection~Selection#getRanges getRanges},
- * {@link module:engine/view/selection~Selection#getFirstRange getFirstRange}
- * and {@link module:engine/view/selection~Selection#getLastRange getLastRange}
- * methods, which return copies of ranges stored inside selection. Modifications made on these copies will not change
- * selection's state. Similar situation occurs when getting {@link module:engine/view/selection~Selection#anchor anchor},
- * {@link module:engine/view/selection~Selection#focus focus}, {@link module:engine/view/selection~Selection#getFirstPosition first} and
- * {@link module:engine/view/selection~Selection#getLastPosition last} positions - all will return copies of requested positions.
+ * {@link module:engine/view/selection~Selection#getFirstRange getFirstRange} and
+ * {@link module:engine/view/selection~Selection#getLastRange getLastRange} methods, which return copies of ranges
+ * stored inside selection. Modifications made on these copies will not change selection's state. Similar situation
+ * occurs when getting {@link module:engine/view/selection~Selection#anchor anchor},
+ * {@link module:engine/view/selection~Selection#focus focus}, {@link module:engine/view/selection~Selection#getFirstPosition first}
+ * and {@link module:engine/view/selection~Selection#getLastPosition last} positions - all will return
+ * copies of requested positions.
*/
export default class Selection {
/**
@@ -51,12 +53,15 @@ export default class Selection {
* const otherSelection = new Selection();
* const selection = new Selection( otherSelection );
*
+ * // Creates selection from the document selection.
+ * const selection = new Selection( editor.editing.view.document.selection );
+ *
* // Creates selection at the given position.
* const position = new Position( root, path );
* const selection = new Selection( position );
*
* // Creates collapsed selection at the position of given item and offset.
- * const paragraph = writer.createElement( 'paragraph' );
+ * const paragraph = writer.createContainerElement( 'paragraph' );
* const selection = new Selection( paragraph, offset );
*
* // Creates a range inside an {@link module:engine/view/element~Element element} which starts before the
@@ -82,8 +87,9 @@ export default class Selection {
* // 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/item~Item|null} [selectable=null]
+ * @param {module:engine/view/selection~Selection|module:engine/view/documentselection~DocumentSelection|
+ * module:engine/view/position~Position|Iterable.|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.
@@ -123,13 +129,13 @@ export default class Selection {
*/
this._fakeSelectionLabel = '';
- this._setTo( selectable, placeOrOffset, options );
+ this.setTo( selectable, placeOrOffset, options );
}
/**
* Returns true if selection instance is marked as `fake`.
*
- * @see #_setTo
+ * @see #setTo
* @returns {Boolean}
*/
get isFake() {
@@ -139,7 +145,7 @@ export default class Selection {
/**
* Returns fake selection label.
*
- * @see #_setTo
+ * @see #setTo
* @returns {String}
*/
get fakeSelectionLabel() {
@@ -303,7 +309,8 @@ export default class Selection {
* Checks whether, this selection is equal to given selection. Selections are equal if they have same directions,
* same number of ranges and all ranges from one selection equal to a range from other selection.
*
- * @param {module:engine/view/selection~Selection} otherSelection Selection to compare with.
+ * @param {module:engine/view/selection~Selection|module:engine/view/documentselection~DocumentSelection} otherSelection
+ * Selection to compare with.
* @returns {Boolean} `true` if selections are equal, `false` otherwise.
*/
isEqual( otherSelection ) {
@@ -348,7 +355,8 @@ export default class Selection {
* number of ranges, and all {@link module:engine/view/range~Range#getTrimmed trimmed} ranges from one selection are
* equal to any trimmed range from other selection.
*
- * @param {module:engine/view/selection~Selection} otherSelection Selection to compare with.
+ * @param {module:engine/view/selection~Selection|module:engine/view/documentselection~DocumentSelection} otherSelection
+ * Selection to compare with.
* @returns {Boolean} `true` if selections are similar, `false` otherwise.
*/
isSimilar( otherSelection ) {
@@ -415,45 +423,49 @@ export default class Selection {
/**
* Sets this selection's ranges and direction to the specified location based on the given
+ * {@link module:engine/view/documentselection~DocumentSelection document selection},
* {@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 selection to the given range.
* const range = new Range( start, end );
- * selection._setTo( range );
+ * selection.setTo( range );
*
* // Sets selection to given ranges.
* const ranges = [ new Range( start1, end2 ), new Range( star2, end2 ) ];
- * selection._setTo( range );
+ * selection.setTo( range );
*
* // Sets selection to the other selection.
* const otherSelection = new Selection();
- * selection._setTo( otherSelection );
+ * selection.setTo( otherSelection );
+ *
+ * // Sets selection to contents of DocumentSelection.
+ * selection.setTo( editor.editing.view.document.selection );
*
* // Sets collapsed selection at the given position.
* const position = new Position( root, path );
- * selection._setTo( position );
+ * selection.setTo( position );
*
* // Sets collapsed selection at the position of given item and offset.
- * selection._setTo( paragraph, offset );
+ * selection.setTo( 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.
*
- * selection._setTo( paragraph, 'in' );
+ * 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' );
+ * selection.setTo( paragraph, 'on' );
*
* // Clears selection. Removes all ranges.
- * selection._setTo( null );
+ * selection.setTo( null );
*
- * `Selection#_setTo()` method allow passing additional options (`backward`, `fake` and `label`) as the last argument.
+ * `Selection#setTo()` method allow passing additional options (`backward`, `fake` and `label`) as the last argument.
*
* // Sets selection as backward.
- * selection._setTo( range, { backward: true } );
+ * 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
@@ -463,23 +475,23 @@ export default class Selection {
* (and be properly handled by screen readers).
*
* // Creates fake selection with label.
- * selection._setTo( range, { fake: true, label: 'foo' } );
+ * 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/item~Item|null} selectable
+ * @param {module:engine/view/selection~Selection|module:engine/view/documentselection~DocumentSelection|
+ * module:engine/view/position~Position|Iterable.|module:engine/view/range~Range|
+ * module:engine/view/item~Item|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.
* @param {Boolean} [options.fake] Sets this selection instance to be marked as `fake`.
* @param {String} [options.label] Label for the fake selection.
*/
- _setTo( selectable, placeOrOffset, options ) {
+ setTo( selectable, placeOrOffset, options ) {
if ( selectable === null ) {
this._setRanges( [] );
this._setFakeOptions( placeOrOffset );
- } else if ( selectable instanceof Selection ) {
+ } else if ( selectable instanceof Selection || selectable instanceof DocumentSelection ) {
this._setRanges( selectable.getRanges(), selectable.isBackward );
this._setFakeOptions( { fake: selectable.isFake, label: selectable.fakeSelectionLabel } );
} else if ( selectable instanceof Range ) {
@@ -529,42 +541,17 @@ export default class Selection {
this.fire( 'change' );
}
- /**
- * Replaces all ranges that were added to the selection with given array of ranges. Last range of the array
- * 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.
- *
- * @private
- * @param {Iterable.} newRanges Iterable object of ranges to set.
- * @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 = 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 ) {
- this._addRange( range );
- }
-
- this._lastRangeBackward = !!isLastBackward;
- }
-
/**
* Moves {@link #focus} to the specified location.
*
* The location can be specified in the same form as {@link module:engine/view/position~Position.createAt} parameters.
*
- * @protected
* @fires change
* @param {module:engine/view/item~Item|module:engine/view/position~Position} itemOrPosition
* @param {Number|'end'|'before'|'after'} [offset=0] Offset or one of the flags. Used only when
* first parameter is a {@link module:engine/view/item~Item view item}.
*/
- _setFocus( itemOrPosition, offset ) {
+ setFocus( itemOrPosition, offset ) {
if ( this.anchor === null ) {
/**
* Cannot set selection focus if there are no ranges in selection.
@@ -595,6 +582,30 @@ export default class Selection {
this.fire( 'change' );
}
+ /**
+ * Replaces all ranges that were added to the selection with given array of ranges. Last range of the array
+ * 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.
+ *
+ * @private
+ * @param {Iterable.} newRanges Iterable object of ranges to set.
+ * @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 = 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 ) {
+ this._addRange( range );
+ }
+
+ this._lastRangeBackward = !!isLastBackward;
+ }
+
/**
* Sets this selection instance 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
@@ -667,12 +678,12 @@ export default class Selection {
this._ranges.push( Range.createFromRange( range ) );
}
+
+ /**
+ * Fired whenever selection ranges are changed through {@link ~Selection Selection API}.
+ *
+ * @event change
+ */
}
mix( Selection, EmitterMixin );
-
-/**
- * Fired whenever selection ranges are changed through {@link ~Selection Selection API}.
- *
- * @event change
- */
diff --git a/src/view/writer.js b/src/view/writer.js
index 7ddd6c775..ce0a9df11 100644
--- a/src/view/writer.js
+++ b/src/view/writer.js
@@ -44,7 +44,8 @@ export default class Writer {
}
/**
- * Sets {@link module:engine/view/selection~Selection selection's} ranges and direction to the specified location based on the given
+ * Sets {@link module:engine/view/documentselection~DocumentSelection selection's} ranges and direction to the
+ * specified location based on the given {@link module:engine/view/documentselection~DocumentSelection document selection},
* {@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.
@@ -72,6 +73,7 @@ export default class Writer {
* writer.setSelection( position );
*
* // Sets collapsed selection at the position of given item and offset.
+ * const paragraph = writer.createContainerElement( 'paragraph' );
* writer.setSelection( paragraph, offset );
*
* Creates a range inside an {@link module:engine/view/element~Element element} which starts before the first child of
@@ -114,7 +116,7 @@ export default class Writer {
}
/**
- * Moves {@link module:engine/view/selection~Selection#focus selection's focus} to the specified location.
+ * Moves {@link module:engine/view/documentselection~DocumentSelection#focus selection's focus} to the specified location.
*
* The location can be specified in the same form as {@link module:engine/view/position~Position.createAt} parameters.
*
diff --git a/tests/conversion/upcast-selection-converters.js b/tests/conversion/upcast-selection-converters.js
index 8beb0bf52..c62b1fc3b 100644
--- a/tests/conversion/upcast-selection-converters.js
+++ b/tests/conversion/upcast-selection-converters.js
@@ -46,7 +46,7 @@ describe( 'convertSelectionChange', () => {
it( 'should convert collapsed selection', () => {
const viewSelection = new ViewSelection();
- viewSelection._setTo( ViewRange.createFromParentsAndOffsets(
+ viewSelection.setTo( ViewRange.createFromParentsAndOffsets(
viewRoot.getChild( 0 ).getChild( 0 ), 1, viewRoot.getChild( 0 ).getChild( 0 ), 1 ) );
convertSelection( null, { newSelection: viewSelection } );
diff --git a/tests/dev-utils/view.js b/tests/dev-utils/view.js
index 341f32998..ddd5394dd 100644
--- a/tests/dev-utils/view.js
+++ b/tests/dev-utils/view.js
@@ -14,7 +14,7 @@ import ContainerElement from '../../src/view/containerelement';
import EmptyElement from '../../src/view/emptyelement';
import UIElement from '../../src/view/uielement';
import Text from '../../src/view/text';
-import Selection from '../../src/view/selection';
+import DocumentSelection from '../../src/view/documentselection';
import Range from '../../src/view/range';
import View from '../../src/view/view';
import XmlDataProcessor from '../../src/dataprocessor/xmldataprocessor';
@@ -167,7 +167,7 @@ describe( 'view test utils', () => {
const b2 = new Element( 'b', null, text2 );
const p = new Element( 'p', null, [ b1, b2 ] );
const range = Range.createFromParentsAndOffsets( p, 1, p, 2 );
- const selection = new Selection( [ range ] );
+ const selection = new DocumentSelection( [ range ] );
expect( stringify( p, selection ) ).to.equal( 'foobar[bazqux]
' );
} );
@@ -176,7 +176,7 @@ describe( 'view test utils', () => {
const b = new Element( 'b', null, text );
const p = new Element( 'p', null, b );
const range = Range.createFromParentsAndOffsets( p, 0, text, 4 );
- const selection = new Selection( [ range ] );
+ const selection = new DocumentSelection( [ range ] );
expect( stringify( p, selection ) ).to.equal( '[நிலை}க்கு
' );
} );
@@ -185,7 +185,7 @@ describe( 'view test utils', () => {
const text = new Text( 'foobar' );
const p = new Element( 'p', null, text );
const range = Range.createFromParentsAndOffsets( p, 0, p, 0 );
- const selection = new Selection( [ range ] );
+ const selection = new DocumentSelection( [ range ] );
expect( stringify( p, selection ) ).to.equal( '[]foobar
' );
} );
@@ -196,7 +196,7 @@ describe( 'view test utils', () => {
const b2 = new Element( 'b', null, text2 );
const p = new Element( 'p', null, [ b1, b2 ] );
const range = Range.createFromParentsAndOffsets( text1, 1, text1, 5 );
- const selection = new Selection( [ range ] );
+ const selection = new DocumentSelection( [ range ] );
expect( stringify( p, selection ) ).to.equal( 'f{ooba}rbazqux
' );
} );
@@ -207,7 +207,7 @@ describe( 'view test utils', () => {
const b2 = new Element( 'b', null, text2 );
const p = new Element( 'p', null, [ b1, b2 ] );
const range = Range.createFromParentsAndOffsets( text1, 1, text1, 5 );
- const selection = new Selection( [ range ] );
+ const selection = new DocumentSelection( [ range ] );
expect( stringify( p, selection, { sameSelectionCharacters: true } ) )
.to.equal( 'f[ooba]rbazqux
' );
} );
@@ -216,7 +216,7 @@ describe( 'view test utils', () => {
const text = new Text( 'foobar' );
const p = new Element( 'p', null, text );
const range = Range.createFromParentsAndOffsets( text, 0, text, 0 );
- const selection = new Selection( [ range ] );
+ const selection = new DocumentSelection( [ range ] );
expect( stringify( p, selection ) ).to.equal( '{}foobar
' );
} );
@@ -227,7 +227,7 @@ describe( 'view test utils', () => {
const b2 = new Element( 'b', null, text2 );
const p = new Element( 'p', null, [ b1, b2 ] );
const range = Range.createFromParentsAndOffsets( p, 0, text2, 5 );
- const selection = new Selection( [ range ] );
+ const selection = new DocumentSelection( [ range ] );
expect( stringify( p, selection ) ).to.equal( '[foobarbazqu}x
' );
} );
@@ -317,7 +317,7 @@ describe( 'view test utils', () => {
const p = new Element( 'p', null, [ b1, b2 ] );
const range1 = Range.createFromParentsAndOffsets( p, 0, p, 1 );
const range2 = Range.createFromParentsAndOffsets( p, 1, p, 1 );
- const selection = new Selection( [ range2, range1 ] );
+ const selection = new DocumentSelection( [ range2, range1 ] );
expect( stringify( p, selection ) ).to.equal( '[foobar][]bazqux
' );
} );
@@ -331,7 +331,7 @@ describe( 'view test utils', () => {
const range2 = Range.createFromParentsAndOffsets( text2, 0, text2, 3 );
const range3 = Range.createFromParentsAndOffsets( text2, 3, text2, 4 );
const range4 = Range.createFromParentsAndOffsets( p, 1, p, 1 );
- const selection = new Selection( [ range1, range2, range3, range4 ] );
+ const selection = new DocumentSelection( [ range1, range2, range3, range4 ] );
expect( stringify( p, selection ) ).to.equal( '[foobar][]{baz}{q}ux
' );
} );
diff --git a/tests/view/_utils/createdocumentmock.js b/tests/view/_utils/createdocumentmock.js
index 61b6490ba..e545e38b7 100644
--- a/tests/view/_utils/createdocumentmock.js
+++ b/tests/view/_utils/createdocumentmock.js
@@ -4,7 +4,7 @@
*/
import ObservableMixin from '@ckeditor/ckeditor5-utils/src/observablemixin';
-import Selection from '../../../src/view/selection';
+import DocumentSelection from '../../../src/view/documentselection';
/**
* Creates {@link module:engine/view/document~Document view Document} mock.
@@ -15,7 +15,7 @@ export default function createDocumentMock() {
const doc = Object.create( ObservableMixin );
doc.set( 'isFocused', false );
doc.set( 'isReadOnly', false );
- doc.selection = new Selection();
+ doc.selection = new DocumentSelection();
return doc;
}
diff --git a/tests/view/documentselection.js b/tests/view/documentselection.js
new file mode 100644
index 000000000..4e93df8e9
--- /dev/null
+++ b/tests/view/documentselection.js
@@ -0,0 +1,1124 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import DocumentSelection from '../../src/view/documentselection';
+import Selection from '../../src/view/selection';
+import Range from '../../src/view/range';
+import Document from '../../src/view/document';
+import Element from '../../src/view/element';
+import Text from '../../src/view/text';
+import Position from '../../src/view/position';
+import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
+import count from '@ckeditor/ckeditor5-utils/src/count';
+import createViewRoot from './_utils/createroot';
+import { parse } from '../../src/dev-utils/view';
+
+describe( 'DocumentSelection', () => {
+ let documentSelection, el, range1, range2, range3;
+
+ beforeEach( () => {
+ const text = new Text( 'xxxxxxxxxxxxxxxxxxxx' );
+ el = new Element( 'p', null, text );
+
+ documentSelection = new DocumentSelection();
+
+ range1 = Range.createFromParentsAndOffsets( text, 5, text, 10 );
+ range2 = Range.createFromParentsAndOffsets( text, 1, text, 2 );
+ range3 = Range.createFromParentsAndOffsets( text, 12, text, 14 );
+ } );
+
+ describe( 'constructor()', () => {
+ it( 'should be able to create an empty selection', () => {
+ const selection = new DocumentSelection();
+
+ expect( Array.from( selection.getRanges() ) ).to.deep.equal( [] );
+ } );
+
+ it( 'should be able to create a selection from the given ranges', () => {
+ const ranges = [ range1, range2, range3 ];
+ const selection = new DocumentSelection( ranges );
+
+ expect( Array.from( selection.getRanges() ) ).to.deep.equal( ranges );
+ } );
+
+ it( 'should be able to create a selection from the given ranges and isLastBackward flag', () => {
+ const ranges = [ range1, range2, range3 ];
+ const selection = new DocumentSelection( 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 DocumentSelection( range1, { backward: true } );
+
+ expect( Array.from( selection.getRanges() ) ).to.deep.equal( [ range1 ] );
+ expect( selection.isBackward ).to.be.true;
+ } );
+
+ 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 DocumentSelection( ranges, { backward: false } );
+
+ expect( Array.from( selection.getRanges() ) ).to.deep.equal( [ range1, range2, range3 ] );
+ expect( selection.isBackward ).to.be.false;
+ } );
+
+ it( 'should be able to create a collapsed selection at the given position', () => {
+ const position = range1.start;
+ const selection = new DocumentSelection( position );
+
+ expect( Array.from( selection.getRanges() ).length ).to.equal( 1 );
+ expect( selection.getFirstRange().start ).to.deep.equal( position );
+ expect( selection.getFirstRange().end ).to.deep.equal( position );
+ expect( selection.isBackward ).to.be.false;
+ } );
+
+ it( 'should be able to create a collapsed selection at the given position', () => {
+ const position = range1.start;
+ const selection = new DocumentSelection( position );
+
+ expect( Array.from( selection.getRanges() ).length ).to.equal( 1 );
+ expect( selection.getFirstRange().start ).to.deep.equal( position );
+ expect( selection.getFirstRange().end ).to.deep.equal( position );
+ expect( selection.isBackward ).to.be.false;
+ } );
+
+ it( 'should be able to create a selection from the other document selection', () => {
+ const otherSelection = new DocumentSelection( [ range2, range3 ], { backward: true } );
+ const selection = new DocumentSelection( otherSelection );
+
+ expect( Array.from( selection.getRanges() ) ).to.deep.equal( [ range2, range3 ] );
+ expect( selection.isBackward ).to.be.true;
+ } );
+
+ it( 'should be able to create a selection from the other selection', () => {
+ const otherSelection = new Selection( [ range2, range3 ], { backward: true } );
+ const selection = new DocumentSelection( otherSelection );
+
+ expect( Array.from( selection.getRanges() ) ).to.deep.equal( [ range2, range3 ] );
+ expect( selection.isBackward ).to.be.true;
+ } );
+
+ it( 'should be able to create a fake selection from the other fake selection', () => {
+ const otherSelection = new DocumentSelection( [ range2, range3 ], { fake: true, label: 'foo bar baz' } );
+ const selection = new DocumentSelection( otherSelection );
+
+ expect( selection.isFake ).to.be.true;
+ expect( selection.fakeSelectionLabel ).to.equal( 'foo bar baz' );
+ } );
+
+ it( 'should throw an error when range is invalid', () => {
+ expect( () => {
+ // eslint-disable-next-line no-new
+ new DocumentSelection( [ { invalid: 'range' } ] );
+ } ).to.throw( CKEditorError, 'view-selection-invalid-range: Invalid Range.' );
+ } );
+
+ it( 'should throw an error when ranges intersects', () => {
+ const text = el.getChild( 0 );
+ const range2 = Range.createFromParentsAndOffsets( text, 7, text, 15 );
+
+ expect( () => {
+ // eslint-disable-next-line no-new
+ new DocumentSelection( [ range1, range2 ] );
+ } ).to.throw( CKEditorError, 'view-selection-range-intersects' );
+ } );
+
+ it( 'should throw an error when trying to set to not selectable', () => {
+ expect( () => {
+ // eslint-disable-next-line no-new
+ new DocumentSelection( {} );
+ } ).to.throw( /view-selection-setTo-not-selectable/ );
+ } );
+ } );
+
+ describe( 'anchor', () => {
+ it( 'should return null if no ranges in selection', () => {
+ expect( documentSelection.anchor ).to.be.null;
+ } );
+
+ it( 'should return start of single range in selection', () => {
+ documentSelection._setTo( range1 );
+ const anchor = documentSelection.anchor;
+
+ expect( anchor.isEqual( range1.start ) ).to.be.true;
+ expect( anchor ).to.not.equal( range1.start );
+ } );
+
+ it( 'should return end of single range in selection when added as backward', () => {
+ documentSelection._setTo( range1, { backward: true } );
+ const anchor = documentSelection.anchor;
+
+ expect( anchor.isEqual( range1.end ) ).to.be.true;
+ expect( anchor ).to.not.equal( range1.end );
+ } );
+
+ it( 'should get anchor from last inserted range', () => {
+ documentSelection._setTo( [ range1, range2 ] );
+
+ expect( documentSelection.anchor.isEqual( range2.start ) ).to.be.true;
+ } );
+ } );
+
+ describe( 'focus', () => {
+ it( 'should return null if no ranges in selection', () => {
+ expect( documentSelection.focus ).to.be.null;
+ } );
+
+ it( 'should return end of single range in selection', () => {
+ documentSelection._setTo( range1 );
+ const focus = documentSelection.focus;
+
+ expect( focus.isEqual( range1.end ) ).to.be.true;
+ } );
+
+ it( 'should return start of single range in selection when added as backward', () => {
+ documentSelection._setTo( range1, { backward: true } );
+ const focus = documentSelection.focus;
+
+ expect( focus.isEqual( range1.start ) ).to.be.true;
+ expect( focus ).to.not.equal( range1.start );
+ } );
+
+ it( 'should get focus from last inserted range', () => {
+ documentSelection._setTo( [ range1, range2 ] );
+
+ expect( documentSelection.focus.isEqual( range2.end ) ).to.be.true;
+ } );
+ } );
+
+ describe( '_setFocus()', () => {
+ it( 'keeps all existing ranges when no modifications needed', () => {
+ documentSelection._setTo( range1 );
+ documentSelection._setFocus( documentSelection.focus );
+
+ expect( count( documentSelection.getRanges() ) ).to.equal( 1 );
+ } );
+
+ it( 'throws if there are no ranges in selection', () => {
+ const endPos = Position.createAt( el, 'end' );
+
+ expect( () => {
+ documentSelection._setFocus( endPos );
+ } ).to.throw( CKEditorError, /view-selection-setFocus-no-ranges/ );
+ } );
+
+ it( 'modifies existing collapsed selection', () => {
+ const startPos = Position.createAt( el, 1 );
+ const endPos = Position.createAt( el, 2 );
+
+ documentSelection._setTo( startPos );
+
+ documentSelection._setFocus( endPos );
+
+ expect( documentSelection.anchor.compareWith( startPos ) ).to.equal( 'same' );
+ expect( documentSelection.focus.compareWith( endPos ) ).to.equal( 'same' );
+ } );
+
+ it( 'makes existing collapsed selection a backward selection', () => {
+ const startPos = Position.createAt( el, 1 );
+ const endPos = Position.createAt( el, 0 );
+
+ documentSelection._setTo( startPos );
+
+ documentSelection._setFocus( endPos );
+
+ expect( documentSelection.anchor.compareWith( startPos ) ).to.equal( 'same' );
+ expect( documentSelection.focus.compareWith( endPos ) ).to.equal( 'same' );
+ expect( documentSelection.isBackward ).to.be.true;
+ } );
+
+ it( 'modifies existing non-collapsed selection', () => {
+ const startPos = Position.createAt( el, 1 );
+ const endPos = Position.createAt( el, 2 );
+ const newEndPos = Position.createAt( el, 3 );
+
+ documentSelection._setTo( new Range( startPos, endPos ) );
+
+ documentSelection._setFocus( newEndPos );
+
+ expect( documentSelection.anchor.compareWith( startPos ) ).to.equal( 'same' );
+ expect( documentSelection.focus.compareWith( newEndPos ) ).to.equal( 'same' );
+ } );
+
+ it( 'makes existing non-collapsed selection a backward selection', () => {
+ const startPos = Position.createAt( el, 1 );
+ const endPos = Position.createAt( el, 2 );
+ const newEndPos = Position.createAt( el, 0 );
+
+ documentSelection._setTo( new Range( startPos, endPos ) );
+
+ documentSelection._setFocus( newEndPos );
+
+ expect( documentSelection.anchor.compareWith( startPos ) ).to.equal( 'same' );
+ expect( documentSelection.focus.compareWith( newEndPos ) ).to.equal( 'same' );
+ expect( documentSelection.isBackward ).to.be.true;
+ } );
+
+ it( 'makes existing backward selection a forward selection', () => {
+ const startPos = Position.createAt( el, 1 );
+ const endPos = Position.createAt( el, 2 );
+ const newEndPos = Position.createAt( el, 3 );
+
+ documentSelection._setTo( new Range( startPos, endPos ), { backward: true } );
+
+ documentSelection._setFocus( newEndPos );
+
+ expect( documentSelection.anchor.compareWith( endPos ) ).to.equal( 'same' );
+ expect( documentSelection.focus.compareWith( newEndPos ) ).to.equal( 'same' );
+ expect( documentSelection.isBackward ).to.be.false;
+ } );
+
+ it( 'modifies existing backward selection', () => {
+ const startPos = Position.createAt( el, 1 );
+ const endPos = Position.createAt( el, 2 );
+ const newEndPos = Position.createAt( el, 0 );
+
+ documentSelection._setTo( new Range( startPos, endPos ), { backward: true } );
+
+ documentSelection._setFocus( newEndPos );
+
+ expect( documentSelection.anchor.compareWith( endPos ) ).to.equal( 'same' );
+ expect( documentSelection.focus.compareWith( newEndPos ) ).to.equal( 'same' );
+ expect( documentSelection.isBackward ).to.be.true;
+ } );
+
+ it( 'modifies only the last range', () => {
+ // Offsets are chosen in this way that the order of adding ranges must count, not their document order.
+ const startPos1 = Position.createAt( el, 4 );
+ const endPos1 = Position.createAt( el, 5 );
+ const startPos2 = Position.createAt( el, 1 );
+ const endPos2 = Position.createAt( el, 2 );
+
+ const newEndPos = Position.createAt( el, 0 );
+
+ documentSelection._setTo( [
+ new Range( startPos1, endPos1 ),
+ new Range( startPos2, endPos2 )
+ ] );
+
+ documentSelection._setFocus( newEndPos );
+
+ const ranges = Array.from( documentSelection.getRanges() );
+
+ expect( ranges ).to.have.lengthOf( 2 );
+ expect( ranges[ 0 ].start.compareWith( startPos1 ) ).to.equal( 'same' );
+ expect( ranges[ 0 ].end.compareWith( endPos1 ) ).to.equal( 'same' );
+
+ expect( documentSelection.anchor.compareWith( startPos2 ) ).to.equal( 'same' );
+ expect( documentSelection.focus.compareWith( newEndPos ) ).to.equal( 'same' );
+ expect( documentSelection.isBackward ).to.be.true;
+ } );
+
+ it( 'collapses the selection when extending to the anchor', () => {
+ const startPos = Position.createAt( el, 1 );
+ const endPos = Position.createAt( el, 2 );
+
+ documentSelection._setTo( new Range( startPos, endPos ) );
+
+ documentSelection._setFocus( startPos );
+
+ expect( documentSelection.focus.compareWith( startPos ) ).to.equal( 'same' );
+ expect( documentSelection.isCollapsed ).to.be.true;
+ } );
+
+ it( 'uses Position.createAt', () => {
+ const startPos = Position.createAt( el, 1 );
+ const endPos = Position.createAt( el, 2 );
+ const newEndPos = Position.createAt( el, 4 );
+
+ const spy = sinon.stub( Position, 'createAt' ).returns( newEndPos );
+
+ documentSelection._setTo( new Range( startPos, endPos ) );
+ documentSelection._setFocus( el, 'end' );
+
+ expect( spy.calledOnce ).to.be.true;
+ expect( documentSelection.focus.compareWith( newEndPos ) ).to.equal( 'same' );
+
+ Position.createAt.restore();
+ } );
+ } );
+
+ describe( 'isCollapsed', () => {
+ it( 'should return true when there is single collapsed range', () => {
+ const range = Range.createFromParentsAndOffsets( el, 5, el, 5 );
+ documentSelection._setTo( range );
+
+ expect( documentSelection.isCollapsed ).to.be.true;
+ } );
+
+ it( 'should return false when there are multiple ranges', () => {
+ const range1 = Range.createFromParentsAndOffsets( el, 5, el, 5 );
+ const range2 = Range.createFromParentsAndOffsets( el, 15, el, 15 );
+ documentSelection._setTo( [ range1, range2 ] );
+
+ expect( documentSelection.isCollapsed ).to.be.false;
+ } );
+
+ it( 'should return false when there is not collapsed range', () => {
+ const range = Range.createFromParentsAndOffsets( el, 15, el, 16 );
+ documentSelection._setTo( range );
+
+ expect( documentSelection.isCollapsed ).to.be.false;
+ } );
+ } );
+
+ describe( 'rangeCount', () => {
+ it( 'should return proper range count', () => {
+ expect( documentSelection.rangeCount ).to.equal( 0 );
+
+ documentSelection._setTo( range1 );
+
+ expect( documentSelection.rangeCount ).to.equal( 1 );
+
+ documentSelection._setTo( [ range1, range2 ] );
+
+ expect( documentSelection.rangeCount ).to.equal( 2 );
+ } );
+ } );
+
+ describe( 'isBackward', () => {
+ it( 'is defined by the last added range', () => {
+ const range1 = Range.createFromParentsAndOffsets( el, 5, el, 10 );
+ const range2 = Range.createFromParentsAndOffsets( el, 15, el, 16 );
+
+ documentSelection._setTo( range1, { backward: true } );
+ expect( documentSelection ).to.have.property( 'isBackward', true );
+
+ documentSelection._setTo( [ range1, range2 ] );
+ expect( documentSelection ).to.have.property( 'isBackward', false );
+ } );
+
+ it( 'is false when last range is collapsed', () => {
+ const range = Range.createFromParentsAndOffsets( el, 5, el, 5 );
+
+ documentSelection._setTo( range, { backward: true } );
+
+ expect( documentSelection.isBackward ).to.be.false;
+ } );
+ } );
+
+ describe( 'getRanges', () => {
+ it( 'should return iterator with copies of all ranges', () => {
+ documentSelection._setTo( [ range1, range2 ] );
+
+ const iterable = documentSelection.getRanges();
+ const ranges = Array.from( iterable );
+
+ expect( ranges.length ).to.equal( 2 );
+ expect( ranges[ 0 ].isEqual( range1 ) ).to.be.true;
+ expect( ranges[ 0 ] ).to.not.equal( range1 );
+ expect( ranges[ 1 ].isEqual( range2 ) ).to.be.true;
+ expect( ranges[ 1 ] ).to.not.equal( range2 );
+ } );
+ } );
+
+ describe( 'getFirstRange', () => {
+ it( 'should return copy of range with first position', () => {
+ documentSelection._setTo( [ range1, range2, range3 ] );
+
+ const range = documentSelection.getFirstRange();
+
+ expect( range.isEqual( range2 ) ).to.be.true;
+ expect( range ).to.not.equal( range2 );
+ } );
+
+ it( 'should return null if no ranges are present', () => {
+ expect( documentSelection.getFirstRange() ).to.be.null;
+ } );
+ } );
+
+ describe( 'getLastRange', () => {
+ it( 'should return copy of range with last position', () => {
+ documentSelection._setTo( [ range1, range2, range3 ] );
+
+ const range = documentSelection.getLastRange();
+
+ expect( range.isEqual( range3 ) ).to.be.true;
+ expect( range ).to.not.equal( range3 );
+ } );
+
+ it( 'should return null if no ranges are present', () => {
+ expect( documentSelection.getLastRange() ).to.be.null;
+ } );
+ } );
+
+ describe( 'getFirstPosition', () => {
+ it( 'should return copy of first position', () => {
+ documentSelection._setTo( [ range1, range2, range3 ] );
+
+ const position = documentSelection.getFirstPosition();
+
+ expect( position.isEqual( range2.start ) ).to.be.true;
+ expect( position ).to.not.equal( range2.start );
+ } );
+
+ it( 'should return null if no ranges are present', () => {
+ expect( documentSelection.getFirstPosition() ).to.be.null;
+ } );
+ } );
+
+ describe( 'getLastPosition', () => {
+ it( 'should return copy of range with last position', () => {
+ documentSelection._setTo( [ range1, range2, range3 ] );
+
+ const position = documentSelection.getLastPosition();
+
+ expect( position.isEqual( range3.end ) ).to.be.true;
+ expect( position ).to.not.equal( range3.end );
+ } );
+
+ it( 'should return null if no ranges are present', () => {
+ expect( documentSelection.getLastPosition() ).to.be.null;
+ } );
+ } );
+
+ describe( 'isEqual', () => {
+ it( 'should return true if selections equal', () => {
+ documentSelection._setTo( [ range1, range2 ] );
+
+ const otherSelection = new DocumentSelection();
+ otherSelection._setTo( [ range1, range2 ] );
+
+ expect( documentSelection.isEqual( otherSelection ) ).to.be.true;
+ } );
+
+ it( 'should return true if selections equal - DocumentSelection and Selection', () => {
+ documentSelection._setTo( [ range1, range2 ] );
+
+ const otherSelection = new Selection();
+ otherSelection.setTo( [ range1, range2 ] );
+
+ expect( documentSelection.isEqual( otherSelection ) ).to.be.true;
+ } );
+
+ it( 'should return true if backward selections equal', () => {
+ documentSelection._setTo( range1, { backward: true } );
+
+ const otherSelection = new DocumentSelection( [ range1 ], { backward: true } );
+
+ expect( documentSelection.isEqual( otherSelection ) ).to.be.true;
+ } );
+
+ it( 'should return true if backward selections equal - DocumentSelection and Selection', () => {
+ documentSelection._setTo( range1, { backward: true } );
+
+ const otherSelection = new Selection( [ range1 ], { backward: true } );
+
+ expect( documentSelection.isEqual( otherSelection ) ).to.be.true;
+ } );
+
+ it( 'should return false if ranges count does not equal', () => {
+ documentSelection._setTo( [ range1, range2 ] );
+
+ const otherSelection = new DocumentSelection( [ range1 ] );
+
+ expect( documentSelection.isEqual( otherSelection ) ).to.be.false;
+ } );
+
+ it( 'should return false if ranges count does not equal - DocumentSelection and Selection', () => {
+ documentSelection._setTo( [ range1, range2 ] );
+
+ const otherSelection = new Selection( [ range1 ] );
+
+ expect( documentSelection.isEqual( otherSelection ) ).to.be.false;
+ } );
+
+ it( 'should return false if ranges (other than the last added one) do not equal', () => {
+ documentSelection._setTo( [ range1, range3 ] );
+
+ const otherSelection = new DocumentSelection( [ range2, range3 ] );
+
+ expect( documentSelection.isEqual( otherSelection ) ).to.be.false;
+ } );
+
+ it( 'should return false if ranges (other than the last added one) do not equal - DocumentSelection and Selection', () => {
+ documentSelection._setTo( [ range1, range3 ] );
+
+ const otherSelection = new Selection( [ range2, range3 ] );
+
+ expect( documentSelection.isEqual( otherSelection ) ).to.be.false;
+ } );
+
+ it( 'should return false if directions do not equal', () => {
+ documentSelection._setTo( range1 );
+
+ const otherSelection = new DocumentSelection( [ range1 ], { backward: true } );
+
+ expect( documentSelection.isEqual( otherSelection ) ).to.be.false;
+ } );
+
+ it( 'should return false if directions do not equal - DocumentSelection and Selection', () => {
+ documentSelection._setTo( range1 );
+
+ const otherSelection = new Selection( [ range1 ], { backward: true } );
+
+ expect( documentSelection.isEqual( otherSelection ) ).to.be.false;
+ } );
+
+ it( 'should return false if one selection is fake', () => {
+ const otherSelection = new DocumentSelection( null, { fake: true } );
+
+ expect( documentSelection.isEqual( otherSelection ) ).to.be.false;
+ } );
+
+ it( 'should return false if one selection is fake', () => {
+ const otherSelection = new DocumentSelection( null, { fake: true } );
+
+ expect( documentSelection.isEqual( otherSelection ) ).to.be.false;
+ } );
+
+ it( 'should return true if both selection are fake - DocumentSelection and Selection', () => {
+ const otherSelection = new Selection( range1, { fake: true } );
+ documentSelection._setTo( range1, { fake: true } );
+
+ expect( documentSelection.isEqual( otherSelection ) ).to.be.true;
+ } );
+
+ it( 'should return false if both selection are fake but have different label', () => {
+ const otherSelection = new DocumentSelection( [ range1 ], { fake: true, label: 'foo bar baz' } );
+ documentSelection._setTo( range1, { fake: true, label: 'foo' } );
+
+ expect( documentSelection.isEqual( otherSelection ) ).to.be.false;
+ } );
+
+ it( 'should return false if both selection are fake but have different label - DocumentSelection and Selection', () => {
+ const otherSelection = new Selection( [ range1 ], { fake: true, label: 'foo bar baz' } );
+ documentSelection._setTo( range1, { fake: true, label: 'foo' } );
+
+ expect( documentSelection.isEqual( otherSelection ) ).to.be.false;
+ } );
+
+ it( 'should return true if both selections are empty', () => {
+ const otherSelection = new DocumentSelection();
+
+ expect( documentSelection.isEqual( otherSelection ) ).to.be.true;
+ } );
+
+ it( 'should return true if both selections are empty - DocumentSelection and Selection', () => {
+ const otherSelection = new Selection();
+
+ expect( documentSelection.isEqual( otherSelection ) ).to.be.true;
+ } );
+ } );
+
+ describe( 'isSimilar', () => {
+ it( 'should return true if selections equal', () => {
+ documentSelection._setTo( [ range1, range2 ] );
+
+ const otherSelection = new DocumentSelection( [ range1, range2 ] );
+
+ expect( documentSelection.isSimilar( otherSelection ) ).to.be.true;
+ } );
+
+ it( 'should return true if selections equal - DocumentSelection and Selection', () => {
+ documentSelection._setTo( [ range1, range2 ] );
+
+ const otherSelection = new Selection( [ range1, range2 ] );
+
+ expect( documentSelection.isSimilar( otherSelection ) ).to.be.true;
+ } );
+
+ it( 'should return false if ranges count does not equal', () => {
+ documentSelection._setTo( [ range1, range2 ] );
+
+ const otherSelection = new DocumentSelection( [ range1 ] );
+
+ expect( documentSelection.isSimilar( otherSelection ) ).to.be.false;
+ } );
+
+ it( 'should return false if ranges count does not equal - DocumentSelection and Selection', () => {
+ documentSelection._setTo( [ range1, range2 ] );
+
+ const otherSelection = new Selection( [ range1 ] );
+
+ expect( documentSelection.isSimilar( otherSelection ) ).to.be.false;
+ } );
+
+ it( 'should return false if trimmed ranges (other than the last added one) are not equal', () => {
+ documentSelection._setTo( [ range1, range3 ] );
+
+ const otherSelection = new DocumentSelection( [ range2, range3 ] );
+
+ expect( documentSelection.isSimilar( otherSelection ) ).to.be.false;
+ } );
+
+ it( 'should return false if trimmed ranges (other than the last added one) are not equal - with Selection', () => {
+ documentSelection._setTo( [ range1, range3 ] );
+
+ const otherSelection = new Selection( [ range2, range3 ] );
+
+ expect( documentSelection.isSimilar( otherSelection ) ).to.be.false;
+ } );
+
+ it( 'should return false if directions are not equal', () => {
+ documentSelection._setTo( range1 );
+
+ const otherSelection = new DocumentSelection( [ range1 ], { backward: true } );
+
+ expect( documentSelection.isSimilar( otherSelection ) ).to.be.false;
+ } );
+
+ it( 'should return false if directions are not equal - DocumentSelection and Selection', () => {
+ documentSelection._setTo( range1 );
+
+ const otherSelection = new Selection( [ range1 ], { backward: true } );
+
+ expect( documentSelection.isSimilar( otherSelection ) ).to.be.false;
+ } );
+
+ it( 'should return true if both selections are empty', () => {
+ const otherSelection = new DocumentSelection();
+
+ expect( documentSelection.isSimilar( otherSelection ) ).to.be.true;
+ } );
+
+ it( 'should return true if both selections are empty - DocumentSelection and Selection', () => {
+ const otherSelection = new Selection();
+
+ expect( documentSelection.isSimilar( otherSelection ) ).to.be.true;
+ } );
+
+ it( 'should return true if all ranges trimmed from both selections are equal', () => {
+ const view = parse(
+ '' +
+ 'xx'
+ );
+
+ const p1 = view.getChild( 0 );
+ const p2 = view.getChild( 1 );
+ const span1 = p1.getChild( 0 );
+ const span2 = p2.getChild( 0 );
+
+ // [{]}
[{xx}]
+ const rangeA1 = Range.createFromParentsAndOffsets( p1, 0, span1, 0 );
+ const rangeB1 = Range.createFromParentsAndOffsets( span1, 0, p1, 1 );
+ const rangeA2 = Range.createFromParentsAndOffsets( p2, 0, p2, 1 );
+ const rangeB2 = Range.createFromParentsAndOffsets( span2, 0, span2, 1 );
+
+ documentSelection._setTo( [ rangeA1, rangeA2 ] );
+
+ const otherSelection = new DocumentSelection( [ rangeB2, rangeB1 ] );
+
+ expect( documentSelection.isSimilar( otherSelection ) ).to.be.true;
+ expect( otherSelection.isSimilar( documentSelection ) ).to.be.true;
+
+ expect( documentSelection.isEqual( otherSelection ) ).to.be.false;
+ expect( otherSelection.isEqual( documentSelection ) ).to.be.false;
+ } );
+
+ it( 'should return true if all ranges trimmed from both selections are equal - DocumentSelection and Selection', () => {
+ const view = parse(
+ '' +
+ 'xx'
+ );
+
+ const p1 = view.getChild( 0 );
+ const p2 = view.getChild( 1 );
+ const span1 = p1.getChild( 0 );
+ const span2 = p2.getChild( 0 );
+
+ // [{]}
[{xx}]
+ const rangeA1 = Range.createFromParentsAndOffsets( p1, 0, span1, 0 );
+ const rangeB1 = Range.createFromParentsAndOffsets( span1, 0, p1, 1 );
+ const rangeA2 = Range.createFromParentsAndOffsets( p2, 0, p2, 1 );
+ const rangeB2 = Range.createFromParentsAndOffsets( span2, 0, span2, 1 );
+
+ documentSelection._setTo( [ rangeA1, rangeA2 ] );
+
+ const otherSelection = new Selection( [ rangeB2, rangeB1 ] );
+
+ expect( documentSelection.isSimilar( otherSelection ) ).to.be.true;
+ expect( otherSelection.isSimilar( documentSelection ) ).to.be.true;
+
+ expect( documentSelection.isEqual( otherSelection ) ).to.be.false;
+ expect( otherSelection.isEqual( documentSelection ) ).to.be.false;
+ } );
+ } );
+
+ describe( '_setTo()', () => {
+ describe( 'simple scenarios', () => {
+ it( 'should set selection ranges from the given selection', () => {
+ documentSelection._setTo( range1 );
+
+ const otherSelection = new DocumentSelection( [ range2, range3 ], { backward: true } );
+
+ documentSelection._setTo( otherSelection );
+
+ expect( documentSelection.rangeCount ).to.equal( 2 );
+ expect( documentSelection._ranges[ 0 ].isEqual( range2 ) ).to.be.true;
+ expect( documentSelection._ranges[ 0 ] ).is.not.equal( range2 );
+ expect( documentSelection._ranges[ 1 ].isEqual( range3 ) ).to.be.true;
+ expect( documentSelection._ranges[ 1 ] ).is.not.equal( range3 );
+
+ expect( documentSelection.anchor.isEqual( range3.end ) ).to.be.true;
+ } );
+
+ it( 'should set selection on the given Range', () => {
+ documentSelection._setTo( range1 );
+
+ expect( Array.from( documentSelection.getRanges() ) ).to.deep.equal( [ range1 ] );
+ expect( documentSelection.isBackward ).to.be.false;
+ } );
+
+ it( 'should set selection on the given iterable of Ranges', () => {
+ documentSelection._setTo( new Set( [ range1, range2 ] ) );
+
+ expect( Array.from( documentSelection.getRanges() ) ).to.deep.equal( [ range1, range2 ] );
+ expect( documentSelection.isBackward ).to.be.false;
+ } );
+
+ it( 'should set collapsed selection on the given Position', () => {
+ documentSelection._setTo( range1.start );
+
+ expect( Array.from( documentSelection.getRanges() ).length ).to.equal( 1 );
+ expect( Array.from( documentSelection.getRanges() )[ 0 ].start ).to.deep.equal( range1.start );
+ expect( documentSelection.isBackward ).to.be.false;
+ expect( documentSelection.isCollapsed ).to.be.true;
+ } );
+
+ it( 'should fire change event', done => {
+ documentSelection.on( 'change', () => {
+ expect( documentSelection.rangeCount ).to.equal( 1 );
+ expect( documentSelection.getFirstRange().isEqual( range1 ) ).to.be.true;
+ done();
+ } );
+
+ const otherSelection = new DocumentSelection( [ range1 ] );
+
+ documentSelection._setTo( otherSelection );
+ } );
+
+ it( 'should set fake state and label', () => {
+ const label = 'foo bar baz';
+ const otherSelection = new DocumentSelection( null, { fake: true, label } );
+ documentSelection._setTo( otherSelection );
+
+ expect( documentSelection.isFake ).to.be.true;
+ expect( documentSelection.fakeSelectionLabel ).to.equal( label );
+ } );
+
+ it( 'should throw an error when trying to set to not selectable', () => {
+ const otherSelection = new DocumentSelection();
+
+ expect( () => {
+ otherSelection._setTo( {} );
+ } ).to.throw( /view-selection-setTo-not-selectable/ );
+ } );
+
+ it( 'should throw an error when trying to set to not selectable #2', () => {
+ const otherSelection = new DocumentSelection();
+
+ expect( () => {
+ otherSelection._setTo();
+ } ).to.throw( /view-selection-setTo-not-selectable/ );
+ } );
+ } );
+
+ describe( 'setting collapsed selection', () => {
+ beforeEach( () => {
+ documentSelection._setTo( [ range1, range2 ] );
+ } );
+
+ it( 'should collapse selection at position', () => {
+ const position = new Position( el, 4 );
+
+ documentSelection._setTo( position );
+ const range = documentSelection.getFirstRange();
+
+ expect( range.start.parent ).to.equal( el );
+ expect( range.start.offset ).to.equal( 4 );
+ expect( range.start.isEqual( range.end ) ).to.be.true;
+ } );
+
+ it( 'should collapse selection at node and offset', () => {
+ const foo = new Text( 'foo' );
+ const p = new Element( 'p', null, foo );
+
+ documentSelection._setTo( foo, 0 );
+ let range = documentSelection.getFirstRange();
+
+ expect( range.start.parent ).to.equal( foo );
+ expect( range.start.offset ).to.equal( 0 );
+ expect( range.start.isEqual( range.end ) ).to.be.true;
+
+ documentSelection._setTo( p, 1 );
+ range = documentSelection.getFirstRange();
+
+ expect( range.start.parent ).to.equal( p );
+ expect( range.start.offset ).to.equal( 1 );
+ 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( () => {
+ documentSelection._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 );
+
+ documentSelection._setTo( foo, 'end' );
+ let range = documentSelection.getFirstRange();
+
+ expect( range.start.parent ).to.equal( foo );
+ expect( range.start.offset ).to.equal( 3 );
+ expect( range.start.isEqual( range.end ) ).to.be.true;
+
+ documentSelection._setTo( foo, 'before' );
+ range = documentSelection.getFirstRange();
+
+ expect( range.start.parent ).to.equal( p );
+ expect( range.start.offset ).to.equal( 0 );
+ expect( range.start.isEqual( range.end ) ).to.be.true;
+
+ documentSelection._setTo( foo, 'after' );
+ range = documentSelection.getFirstRange();
+
+ expect( range.start.parent ).to.equal( p );
+ expect( range.start.offset ).to.equal( 1 );
+ expect( range.start.isEqual( range.end ) ).to.be.true;
+ } );
+ } );
+
+ describe( 'setting collapsed selection at start', () => {
+ it( 'should collapse to start position and fire change event', done => {
+ documentSelection._setTo( [ range1, range2, range3 ] );
+ documentSelection.once( 'change', () => {
+ expect( documentSelection.rangeCount ).to.equal( 1 );
+ expect( documentSelection.isCollapsed ).to.be.true;
+ expect( documentSelection._ranges[ 0 ].start.isEqual( range2.start ) ).to.be.true;
+ done();
+ } );
+
+ documentSelection._setTo( documentSelection.getFirstPosition() );
+ } );
+ } );
+
+ describe( 'setting collapsed selection to end', () => {
+ it( 'should collapse to end position and fire change event', done => {
+ documentSelection._setTo( [ range1, range2, range3 ] );
+ documentSelection.once( 'change', () => {
+ expect( documentSelection.rangeCount ).to.equal( 1 );
+ expect( documentSelection.isCollapsed ).to.be.true;
+ expect( documentSelection._ranges[ 0 ].end.isEqual( range3.end ) ).to.be.true;
+ done();
+ } );
+
+ documentSelection._setTo( documentSelection.getLastPosition() );
+ } );
+ } );
+
+ describe( 'removing all ranges', () => {
+ it( 'should remove all ranges and fire change event', done => {
+ documentSelection._setTo( [ range1, range2 ] );
+
+ documentSelection.once( 'change', () => {
+ expect( documentSelection.rangeCount ).to.equal( 0 );
+ done();
+ } );
+
+ documentSelection._setTo( null );
+ } );
+ } );
+
+ describe( 'setting fake selection', () => {
+ it( 'should allow to set selection to fake', () => {
+ documentSelection._setTo( range1, { fake: true } );
+
+ expect( documentSelection.isFake ).to.be.true;
+ } );
+
+ it( 'should allow to set fake selection label', () => {
+ const label = 'foo bar baz';
+ documentSelection._setTo( range1, { fake: true, label } );
+
+ expect( documentSelection.fakeSelectionLabel ).to.equal( label );
+ } );
+
+ it( 'should not set label when set to false', () => {
+ const label = 'foo bar baz';
+ documentSelection._setTo( range1, { fake: false, label } );
+
+ expect( documentSelection.fakeSelectionLabel ).to.equal( '' );
+ } );
+
+ it( 'should reset label when set to false', () => {
+ const label = 'foo bar baz';
+ documentSelection._setTo( range1, { fake: true, label } );
+ documentSelection._setTo( range1 );
+
+ expect( documentSelection.fakeSelectionLabel ).to.equal( '' );
+ } );
+
+ it( 'should fire change event', done => {
+ documentSelection.once( 'change', () => {
+ expect( documentSelection.isFake ).to.be.true;
+ expect( documentSelection.fakeSelectionLabel ).to.equal( 'foo bar baz' );
+
+ done();
+ } );
+
+ documentSelection._setTo( range1, { fake: true, label: 'foo bar baz' } );
+ } );
+
+ it( 'should be possible to create an empty fake selection', () => {
+ documentSelection._setTo( null, { fake: true, label: 'foo bar baz' } );
+
+ expect( documentSelection.fakeSelectionLabel ).to.equal( 'foo bar baz' );
+ expect( documentSelection.isFake ).to.be.true;
+ } );
+ } );
+
+ describe( 'setting selection to itself', () => {
+ it( 'should correctly set ranges when setting to the same selection', () => {
+ documentSelection._setTo( [ range1, range2 ] );
+ documentSelection._setTo( documentSelection );
+
+ const ranges = Array.from( documentSelection.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', () => {
+ documentSelection._setTo( [ range1, range2 ] );
+ documentSelection._setTo( documentSelection.getRanges() );
+
+ const ranges = Array.from( documentSelection.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( () => {
+ documentSelection._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( () => {
+ documentSelection._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 ] );
+
+ documentSelection._setTo( textNode2, 'on' );
+
+ const ranges = Array.from( documentSelection.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' ) ] );
+
+ documentSelection._setTo( element, 'in' );
+
+ const ranges = Array.from( documentSelection.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' ) ] );
+
+ documentSelection._setTo( element, 'in', { backward: true } );
+
+ const ranges = Array.from( documentSelection.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( documentSelection.isBackward ).to.be.true;
+ } );
+ } );
+
+ describe( 'getEditableElement()', () => {
+ it( 'should return null if no ranges in selection', () => {
+ expect( documentSelection.editableElement ).to.be.null;
+ } );
+
+ it( 'should return null if selection is placed in container that is not EditableElement', () => {
+ documentSelection._setTo( range1 );
+
+ expect( documentSelection.editableElement ).to.be.null;
+ } );
+
+ it( 'should return EditableElement when selection is placed inside', () => {
+ const viewDocument = new Document();
+ documentSelection._setTo( viewDocument.selection );
+ const root = createViewRoot( viewDocument, 'div', 'main' );
+ const element = new Element( 'p' );
+ root._appendChildren( element );
+
+ documentSelection._setTo( Range.createFromParentsAndOffsets( element, 0, element, 0 ) );
+
+ expect( documentSelection.editableElement ).to.equal( root );
+ } );
+ } );
+
+ describe( 'isFake', () => {
+ it( 'should be false for newly created instance', () => {
+ expect( documentSelection.isFake ).to.be.false;
+ } );
+ } );
+
+ describe( 'getSelectedElement()', () => {
+ it( 'should return selected element', () => {
+ const { selection: documentSelection, view } = parse( 'foo [bar] baz' );
+ const b = view.getChild( 1 );
+
+ expect( documentSelection.getSelectedElement() ).to.equal( b );
+ } );
+
+ it( 'should return null if there is more than one range', () => {
+ const { selection: documentSelection } = parse( 'foo [bar] [baz]' );
+
+ expect( documentSelection.getSelectedElement() ).to.be.null;
+ } );
+
+ it( 'should return null if there is no selection', () => {
+ expect( documentSelection.getSelectedElement() ).to.be.null;
+ } );
+
+ it( 'should return null if selection is not over single element #1', () => {
+ const { selection: documentSelection } = parse( 'foo [bar ba}z' );
+
+ expect( documentSelection.getSelectedElement() ).to.be.null;
+ } );
+
+ it( 'should return null if selection is not over single element #2', () => {
+ const { selection: documentSelection } = parse( 'foo {bar} baz' );
+
+ expect( documentSelection.getSelectedElement() ).to.be.null;
+ } );
+ } );
+} );
diff --git a/tests/view/domconverter/binding.js b/tests/view/domconverter/binding.js
index 568ac3cb8..289d37e2e 100644
--- a/tests/view/domconverter/binding.js
+++ b/tests/view/domconverter/binding.js
@@ -6,7 +6,7 @@
/* globals document */
import ViewElement from '../../../src/view/element';
-import ViewSelection from '../../../src/view/selection';
+import ViewDocumentSelection from '../../../src/view/documentselection';
import DomConverter from '../../../src/view/domconverter';
import ViewDocumentFragment from '../../../src/view/documentfragment';
import { INLINE_FILLER } from '../../../src/view/filler';
@@ -269,7 +269,7 @@ describe( 'DomConverter', () => {
beforeEach( () => {
viewElement = new ViewElement();
domEl = document.createElement( 'div' );
- selection = new ViewSelection( viewElement, 'in' );
+ selection = new ViewDocumentSelection( viewElement, 'in' );
converter.bindFakeSelection( domEl, selection );
} );
@@ -280,7 +280,7 @@ describe( 'DomConverter', () => {
} );
it( 'should keep a copy of selection', () => {
- const selectionCopy = new ViewSelection( selection );
+ const selectionCopy = new ViewDocumentSelection( selection );
selection._setTo( new ViewElement(), 'in', { backward: true } );
const bindSelection = converter.fakeSelectionToView( domEl );
diff --git a/tests/view/domconverter/dom-to-view.js b/tests/view/domconverter/dom-to-view.js
index 558cde1ca..d01ec1df9 100644
--- a/tests/view/domconverter/dom-to-view.js
+++ b/tests/view/domconverter/dom-to-view.js
@@ -6,7 +6,7 @@
/* globals document */
import ViewElement from '../../../src/view/element';
-import ViewSelection from '../../../src/view/selection';
+import ViewDocumentSelection from '../../../src/view/documentselection';
import DomConverter from '../../../src/view/domconverter';
import ViewDocumentFragment from '../../../src/view/documentfragment';
import { INLINE_FILLER, INLINE_FILLER_LENGTH, NBSP_FILLER } from '../../../src/view/filler';
@@ -882,7 +882,7 @@ describe( 'DomConverter', () => {
domContainer.innerHTML = 'fake selection container';
document.body.appendChild( domContainer );
- const viewSelection = new ViewSelection( new ViewElement(), 'in' );
+ const viewSelection = new ViewDocumentSelection( new ViewElement(), 'in' );
converter.bindFakeSelection( domContainer, viewSelection );
const domRange = document.createRange();
@@ -903,7 +903,7 @@ describe( 'DomConverter', () => {
domContainer.innerHTML = 'fake selection container';
document.body.appendChild( domContainer );
- const viewSelection = new ViewSelection( new ViewElement(), 'in' );
+ const viewSelection = new ViewDocumentSelection( new ViewElement(), 'in' );
converter.bindFakeSelection( domContainer, viewSelection );
const domRange = document.createRange();
diff --git a/tests/view/observer/selectionobserver.js b/tests/view/observer/selectionobserver.js
index 27bdf25d2..3e2ba711f 100644
--- a/tests/view/observer/selectionobserver.js
+++ b/tests/view/observer/selectionobserver.js
@@ -7,6 +7,7 @@
import ViewRange from '../../../src/view/range';
import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
+import DocumentSelection from '../../../src/view/documentselection';
import ViewSelection from '../../../src/view/selection';
import View from '../../../src/view/view';
import SelectionObserver from '../../../src/view/observer/selectionobserver';
@@ -64,7 +65,7 @@ describe( 'SelectionObserver', () => {
viewDocument.on( 'selectionChange', ( evt, data ) => {
expect( data ).to.have.property( 'domSelection' ).that.equals( domDocument.getSelection() );
- expect( data ).to.have.property( 'oldSelection' ).that.is.instanceof( ViewSelection );
+ expect( data ).to.have.property( 'oldSelection' ).that.is.instanceof( DocumentSelection );
expect( data.oldSelection.rangeCount ).to.equal( 0 );
expect( data ).to.have.property( 'newSelection' ).that.is.instanceof( ViewSelection );
@@ -265,7 +266,7 @@ describe( 'SelectionObserver', () => {
expect( spy.calledOnce ).to.true;
expect( data ).to.have.property( 'domSelection' ).to.equal( domDocument.getSelection() );
- expect( data ).to.have.property( 'oldSelection' ).to.instanceof( ViewSelection );
+ expect( data ).to.have.property( 'oldSelection' ).to.instanceof( DocumentSelection );
expect( data.oldSelection.rangeCount ).to.equal( 0 );
expect( data ).to.have.property( 'newSelection' ).to.instanceof( ViewSelection );
diff --git a/tests/view/renderer.js b/tests/view/renderer.js
index 419c4decb..bd34cb614 100644
--- a/tests/view/renderer.js
+++ b/tests/view/renderer.js
@@ -12,7 +12,7 @@ import ViewAttributeElement from '../../src/view/attributeelement';
import ViewText from '../../src/view/text';
import ViewRange from '../../src/view/range';
import ViewPosition from '../../src/view/position';
-import Selection from '../../src/view/selection';
+import DocumentSelection from '../../src/view/documentselection';
import DomConverter from '../../src/view/domconverter';
import Renderer from '../../src/view/renderer';
import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
@@ -29,7 +29,7 @@ describe( 'Renderer', () => {
let selection, domConverter, renderer;
beforeEach( () => {
- selection = new Selection();
+ selection = new DocumentSelection();
domConverter = new DomConverter();
renderer = new Renderer( domConverter, selection );
renderer.domDocuments.add( document );
diff --git a/tests/view/selection.js b/tests/view/selection.js
index f92b5f1a7..5b000d399 100644
--- a/tests/view/selection.js
+++ b/tests/view/selection.js
@@ -4,6 +4,7 @@
*/
import Selection from '../../src/view/selection';
+import DocumentSelection from '../../src/view/documentselection';
import Range from '../../src/view/range';
import Document from '../../src/view/document';
import Element from '../../src/view/element';
@@ -92,6 +93,14 @@ describe( 'Selection', () => {
expect( selection.isBackward ).to.be.true;
} );
+ it( 'should be able to create a selection from the other document selection', () => {
+ const otherSelection = new DocumentSelection( [ range2, range3 ], { backward: true } );
+ const selection = new Selection( otherSelection );
+
+ expect( Array.from( selection.getRanges() ) ).to.deep.equal( [ range2, range3 ] );
+ expect( selection.isBackward ).to.be.true;
+ } );
+
it( 'should be able to create a fake selection from the other fake selection', () => {
const otherSelection = new Selection( [ range2, range3 ], { fake: true, label: 'foo bar baz' } );
const selection = new Selection( otherSelection );
@@ -131,7 +140,7 @@ describe( 'Selection', () => {
} );
it( 'should return start of single range in selection', () => {
- selection._setTo( range1 );
+ selection.setTo( range1 );
const anchor = selection.anchor;
expect( anchor.isEqual( range1.start ) ).to.be.true;
@@ -139,7 +148,7 @@ describe( 'Selection', () => {
} );
it( 'should return end of single range in selection when added as backward', () => {
- selection._setTo( range1, { backward: true } );
+ selection.setTo( range1, { backward: true } );
const anchor = selection.anchor;
expect( anchor.isEqual( range1.end ) ).to.be.true;
@@ -147,7 +156,7 @@ describe( 'Selection', () => {
} );
it( 'should get anchor from last inserted range', () => {
- selection._setTo( [ range1, range2 ] );
+ selection.setTo( [ range1, range2 ] );
expect( selection.anchor.isEqual( range2.start ) ).to.be.true;
} );
@@ -159,14 +168,14 @@ describe( 'Selection', () => {
} );
it( 'should return end of single range in selection', () => {
- selection._setTo( range1 );
+ selection.setTo( range1 );
const focus = selection.focus;
expect( focus.isEqual( range1.end ) ).to.be.true;
} );
it( 'should return start of single range in selection when added as backward', () => {
- selection._setTo( range1, { backward: true } );
+ selection.setTo( range1, { backward: true } );
const focus = selection.focus;
expect( focus.isEqual( range1.start ) ).to.be.true;
@@ -174,16 +183,16 @@ describe( 'Selection', () => {
} );
it( 'should get focus from last inserted range', () => {
- selection._setTo( [ range1, range2 ] );
+ selection.setTo( [ range1, range2 ] );
expect( selection.focus.isEqual( range2.end ) ).to.be.true;
} );
} );
- describe( '_setFocus()', () => {
+ describe( 'setFocus()', () => {
it( 'keeps all existing ranges when no modifications needed', () => {
- selection._setTo( range1 );
- selection._setFocus( selection.focus );
+ selection.setTo( range1 );
+ selection.setFocus( selection.focus );
expect( count( selection.getRanges() ) ).to.equal( 1 );
} );
@@ -192,7 +201,7 @@ describe( 'Selection', () => {
const endPos = Position.createAt( el, 'end' );
expect( () => {
- selection._setFocus( endPos );
+ selection.setFocus( endPos );
} ).to.throw( CKEditorError, /view-selection-setFocus-no-ranges/ );
} );
@@ -200,9 +209,9 @@ describe( 'Selection', () => {
const startPos = Position.createAt( el, 1 );
const endPos = Position.createAt( el, 2 );
- selection._setTo( startPos );
+ selection.setTo( startPos );
- selection._setFocus( endPos );
+ selection.setFocus( endPos );
expect( selection.anchor.compareWith( startPos ) ).to.equal( 'same' );
expect( selection.focus.compareWith( endPos ) ).to.equal( 'same' );
@@ -212,9 +221,9 @@ describe( 'Selection', () => {
const startPos = Position.createAt( el, 1 );
const endPos = Position.createAt( el, 0 );
- selection._setTo( startPos );
+ selection.setTo( startPos );
- selection._setFocus( endPos );
+ selection.setFocus( endPos );
expect( selection.anchor.compareWith( startPos ) ).to.equal( 'same' );
expect( selection.focus.compareWith( endPos ) ).to.equal( 'same' );
@@ -226,9 +235,9 @@ describe( 'Selection', () => {
const endPos = Position.createAt( el, 2 );
const newEndPos = Position.createAt( el, 3 );
- selection._setTo( new Range( startPos, endPos ) );
+ selection.setTo( new Range( startPos, endPos ) );
- selection._setFocus( newEndPos );
+ selection.setFocus( newEndPos );
expect( selection.anchor.compareWith( startPos ) ).to.equal( 'same' );
expect( selection.focus.compareWith( newEndPos ) ).to.equal( 'same' );
@@ -239,9 +248,9 @@ describe( 'Selection', () => {
const endPos = Position.createAt( el, 2 );
const newEndPos = Position.createAt( el, 0 );
- selection._setTo( new Range( startPos, endPos ) );
+ selection.setTo( new Range( startPos, endPos ) );
- selection._setFocus( newEndPos );
+ selection.setFocus( newEndPos );
expect( selection.anchor.compareWith( startPos ) ).to.equal( 'same' );
expect( selection.focus.compareWith( newEndPos ) ).to.equal( 'same' );
@@ -253,9 +262,9 @@ describe( 'Selection', () => {
const endPos = Position.createAt( el, 2 );
const newEndPos = Position.createAt( el, 3 );
- selection._setTo( new Range( startPos, endPos ), { backward: true } );
+ selection.setTo( new Range( startPos, endPos ), { backward: true } );
- selection._setFocus( newEndPos );
+ selection.setFocus( newEndPos );
expect( selection.anchor.compareWith( endPos ) ).to.equal( 'same' );
expect( selection.focus.compareWith( newEndPos ) ).to.equal( 'same' );
@@ -267,9 +276,9 @@ describe( 'Selection', () => {
const endPos = Position.createAt( el, 2 );
const newEndPos = Position.createAt( el, 0 );
- selection._setTo( new Range( startPos, endPos ), { backward: true } );
+ selection.setTo( new Range( startPos, endPos ), { backward: true } );
- selection._setFocus( newEndPos );
+ selection.setFocus( newEndPos );
expect( selection.anchor.compareWith( endPos ) ).to.equal( 'same' );
expect( selection.focus.compareWith( newEndPos ) ).to.equal( 'same' );
@@ -285,12 +294,12 @@ describe( 'Selection', () => {
const newEndPos = Position.createAt( el, 0 );
- selection._setTo( [
+ selection.setTo( [
new Range( startPos1, endPos1 ),
new Range( startPos2, endPos2 )
] );
- selection._setFocus( newEndPos );
+ selection.setFocus( newEndPos );
const ranges = Array.from( selection.getRanges() );
@@ -307,9 +316,9 @@ describe( 'Selection', () => {
const startPos = Position.createAt( el, 1 );
const endPos = Position.createAt( el, 2 );
- selection._setTo( new Range( startPos, endPos ) );
+ selection.setTo( new Range( startPos, endPos ) );
- selection._setFocus( startPos );
+ selection.setFocus( startPos );
expect( selection.focus.compareWith( startPos ) ).to.equal( 'same' );
expect( selection.isCollapsed ).to.be.true;
@@ -322,8 +331,8 @@ describe( 'Selection', () => {
const spy = sinon.stub( Position, 'createAt' ).returns( newEndPos );
- selection._setTo( new Range( startPos, endPos ) );
- selection._setFocus( el, 'end' );
+ selection.setTo( new Range( startPos, endPos ) );
+ selection.setFocus( el, 'end' );
expect( spy.calledOnce ).to.be.true;
expect( selection.focus.compareWith( newEndPos ) ).to.equal( 'same' );
@@ -335,7 +344,7 @@ describe( 'Selection', () => {
describe( 'isCollapsed', () => {
it( 'should return true when there is single collapsed range', () => {
const range = Range.createFromParentsAndOffsets( el, 5, el, 5 );
- selection._setTo( range );
+ selection.setTo( range );
expect( selection.isCollapsed ).to.be.true;
} );
@@ -343,14 +352,14 @@ describe( 'Selection', () => {
it( 'should return false when there are multiple ranges', () => {
const range1 = Range.createFromParentsAndOffsets( el, 5, el, 5 );
const range2 = Range.createFromParentsAndOffsets( el, 15, el, 15 );
- selection._setTo( [ range1, range2 ] );
+ selection.setTo( [ range1, range2 ] );
expect( selection.isCollapsed ).to.be.false;
} );
it( 'should return false when there is not collapsed range', () => {
const range = Range.createFromParentsAndOffsets( el, 15, el, 16 );
- selection._setTo( range );
+ selection.setTo( range );
expect( selection.isCollapsed ).to.be.false;
} );
@@ -360,11 +369,11 @@ describe( 'Selection', () => {
it( 'should return proper range count', () => {
expect( selection.rangeCount ).to.equal( 0 );
- selection._setTo( range1 );
+ selection.setTo( range1 );
expect( selection.rangeCount ).to.equal( 1 );
- selection._setTo( [ range1, range2 ] );
+ selection.setTo( [ range1, range2 ] );
expect( selection.rangeCount ).to.equal( 2 );
} );
@@ -375,17 +384,17 @@ describe( 'Selection', () => {
const range1 = Range.createFromParentsAndOffsets( el, 5, el, 10 );
const range2 = Range.createFromParentsAndOffsets( el, 15, el, 16 );
- selection._setTo( range1, { backward: true } );
+ selection.setTo( range1, { backward: true } );
expect( selection ).to.have.property( 'isBackward', true );
- selection._setTo( [ range1, range2 ] );
+ selection.setTo( [ range1, range2 ] );
expect( selection ).to.have.property( 'isBackward', false );
} );
it( 'is false when last range is collapsed', () => {
const range = Range.createFromParentsAndOffsets( el, 5, el, 5 );
- selection._setTo( range, { backward: true } );
+ selection.setTo( range, { backward: true } );
expect( selection.isBackward ).to.be.false;
} );
@@ -393,7 +402,7 @@ describe( 'Selection', () => {
describe( 'getRanges', () => {
it( 'should return iterator with copies of all ranges', () => {
- selection._setTo( [ range1, range2 ] );
+ selection.setTo( [ range1, range2 ] );
const iterable = selection.getRanges();
const ranges = Array.from( iterable );
@@ -408,7 +417,7 @@ describe( 'Selection', () => {
describe( 'getFirstRange', () => {
it( 'should return copy of range with first position', () => {
- selection._setTo( [ range1, range2, range3 ] );
+ selection.setTo( [ range1, range2, range3 ] );
const range = selection.getFirstRange();
@@ -423,7 +432,7 @@ describe( 'Selection', () => {
describe( 'getLastRange', () => {
it( 'should return copy of range with last position', () => {
- selection._setTo( [ range1, range2, range3 ] );
+ selection.setTo( [ range1, range2, range3 ] );
const range = selection.getLastRange();
@@ -438,7 +447,7 @@ describe( 'Selection', () => {
describe( 'getFirstPosition', () => {
it( 'should return copy of first position', () => {
- selection._setTo( [ range1, range2, range3 ] );
+ selection.setTo( [ range1, range2, range3 ] );
const position = selection.getFirstPosition();
@@ -453,7 +462,7 @@ describe( 'Selection', () => {
describe( 'getLastPosition', () => {
it( 'should return copy of range with last position', () => {
- selection._setTo( [ range1, range2, range3 ] );
+ selection.setTo( [ range1, range2, range3 ] );
const position = selection.getLastPosition();
@@ -468,16 +477,16 @@ describe( 'Selection', () => {
describe( 'isEqual', () => {
it( 'should return true if selections equal', () => {
- selection._setTo( [ range1, range2 ] );
+ selection.setTo( [ range1, range2 ] );
const otherSelection = new Selection();
- otherSelection._setTo( [ range1, range2 ] );
+ otherSelection.setTo( [ range1, range2 ] );
expect( selection.isEqual( otherSelection ) ).to.be.true;
} );
it( 'should return true if backward selections equal', () => {
- selection._setTo( range1, { backward: true } );
+ selection.setTo( range1, { backward: true } );
const otherSelection = new Selection( [ range1 ], { backward: true } );
@@ -485,7 +494,7 @@ describe( 'Selection', () => {
} );
it( 'should return false if ranges count does not equal', () => {
- selection._setTo( [ range1, range2 ] );
+ selection.setTo( [ range1, range2 ] );
const otherSelection = new Selection( [ range1 ] );
@@ -493,7 +502,7 @@ describe( 'Selection', () => {
} );
it( 'should return false if ranges (other than the last added one) do not equal', () => {
- selection._setTo( [ range1, range3 ] );
+ selection.setTo( [ range1, range3 ] );
const otherSelection = new Selection( [ range2, range3 ] );
@@ -501,7 +510,7 @@ describe( 'Selection', () => {
} );
it( 'should return false if directions do not equal', () => {
- selection._setTo( range1 );
+ selection.setTo( range1 );
const otherSelection = new Selection( [ range1 ], { backward: true } );
@@ -516,14 +525,14 @@ describe( 'Selection', () => {
it( 'should return true if both selection are fake', () => {
const otherSelection = new Selection( range1, { fake: true } );
- selection._setTo( 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 ], { fake: true, label: 'foo bar baz' } );
- selection._setTo( range1, { fake: true, label: 'foo' } );
+ selection.setTo( range1, { fake: true, label: 'foo' } );
expect( selection.isEqual( otherSelection ) ).to.be.false;
} );
@@ -537,7 +546,7 @@ describe( 'Selection', () => {
describe( 'isSimilar', () => {
it( 'should return true if selections equal', () => {
- selection._setTo( [ range1, range2 ] );
+ selection.setTo( [ range1, range2 ] );
const otherSelection = new Selection( [ range1, range2 ] );
@@ -545,7 +554,7 @@ describe( 'Selection', () => {
} );
it( 'should return false if ranges count does not equal', () => {
- selection._setTo( [ range1, range2 ] );
+ selection.setTo( [ range1, range2 ] );
const otherSelection = new Selection( [ range1 ] );
@@ -553,7 +562,7 @@ describe( 'Selection', () => {
} );
it( 'should return false if trimmed ranges (other than the last added one) are not equal', () => {
- selection._setTo( [ range1, range3 ] );
+ selection.setTo( [ range1, range3 ] );
const otherSelection = new Selection( [ range2, range3 ] );
@@ -561,7 +570,7 @@ describe( 'Selection', () => {
} );
it( 'should return false if directions are not equal', () => {
- selection._setTo( range1 );
+ selection.setTo( range1 );
const otherSelection = new Selection( [ range1 ], { backward: true } );
@@ -591,7 +600,7 @@ describe( 'Selection', () => {
const rangeA2 = Range.createFromParentsAndOffsets( p2, 0, p2, 1 );
const rangeB2 = Range.createFromParentsAndOffsets( span2, 0, span2, 1 );
- selection._setTo( [ rangeA1, rangeA2 ] );
+ selection.setTo( [ rangeA1, rangeA2 ] );
const otherSelection = new Selection( [ rangeB2, rangeB1 ] );
@@ -603,14 +612,14 @@ describe( 'Selection', () => {
} );
} );
- describe( '_setTo()', () => {
+ describe( 'setTo()', () => {
describe( 'simple scenarios', () => {
it( 'should set selection ranges from the given selection', () => {
- selection._setTo( range1 );
+ selection.setTo( range1 );
const otherSelection = new Selection( [ range2, range3 ], { backward: true } );
- selection._setTo( otherSelection );
+ selection.setTo( otherSelection );
expect( selection.rangeCount ).to.equal( 2 );
expect( selection._ranges[ 0 ].isEqual( range2 ) ).to.be.true;
@@ -622,21 +631,21 @@ describe( 'Selection', () => {
} );
it( 'should set selection on the given Range', () => {
- selection._setTo( range1 );
+ selection.setTo( range1 );
expect( Array.from( selection.getRanges() ) ).to.deep.equal( [ range1 ] );
expect( selection.isBackward ).to.be.false;
} );
it( 'should set selection on the given iterable of Ranges', () => {
- selection._setTo( new Set( [ range1, range2 ] ) );
+ selection.setTo( new Set( [ range1, range2 ] ) );
expect( Array.from( selection.getRanges() ) ).to.deep.equal( [ range1, range2 ] );
expect( selection.isBackward ).to.be.false;
} );
it( 'should set collapsed selection on the given Position', () => {
- selection._setTo( range1.start );
+ 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 );
@@ -653,13 +662,13 @@ describe( 'Selection', () => {
const otherSelection = new Selection( [ range1 ] );
- selection._setTo( otherSelection );
+ selection.setTo( otherSelection );
} );
it( 'should set fake state and label', () => {
const label = 'foo bar baz';
const otherSelection = new Selection( null, { fake: true, label } );
- selection._setTo( otherSelection );
+ selection.setTo( otherSelection );
expect( selection.isFake ).to.be.true;
expect( selection.fakeSelectionLabel ).to.equal( label );
@@ -669,7 +678,7 @@ describe( 'Selection', () => {
const otherSelection = new Selection();
expect( () => {
- otherSelection._setTo( {} );
+ otherSelection.setTo( {} );
} ).to.throw( /view-selection-setTo-not-selectable/ );
} );
@@ -677,20 +686,20 @@ describe( 'Selection', () => {
const otherSelection = new Selection();
expect( () => {
- otherSelection._setTo();
+ otherSelection.setTo();
} ).to.throw( /view-selection-setTo-not-selectable/ );
} );
} );
describe( 'setting collapsed selection', () => {
beforeEach( () => {
- selection._setTo( [ range1, range2 ] );
+ selection.setTo( [ range1, range2 ] );
} );
it( 'should collapse selection at position', () => {
const position = new Position( el, 4 );
- selection._setTo( position );
+ selection.setTo( position );
const range = selection.getFirstRange();
expect( range.start.parent ).to.equal( el );
@@ -702,14 +711,14 @@ describe( 'Selection', () => {
const foo = new Text( 'foo' );
const p = new Element( 'p', null, foo );
- selection._setTo( foo, 0 );
+ selection.setTo( foo, 0 );
let range = selection.getFirstRange();
expect( range.start.parent ).to.equal( foo );
expect( range.start.offset ).to.equal( 0 );
expect( range.start.isEqual( range.end ) ).to.be.true;
- selection._setTo( p, 1 );
+ selection.setTo( p, 1 );
range = selection.getFirstRange();
expect( range.start.parent ).to.equal( p );
@@ -721,7 +730,7 @@ describe( 'Selection', () => {
const foo = new Text( 'foo' );
expect( () => {
- selection._setTo( foo );
+ selection.setTo( foo );
} ).to.throw( CKEditorError, /view-selection-setTo-required-second-parameter/ );
} );
@@ -729,21 +738,21 @@ describe( 'Selection', () => {
const foo = new Text( 'foo' );
const p = new Element( 'p', null, foo );
- selection._setTo( foo, 'end' );
+ selection.setTo( foo, 'end' );
let range = selection.getFirstRange();
expect( range.start.parent ).to.equal( foo );
expect( range.start.offset ).to.equal( 3 );
expect( range.start.isEqual( range.end ) ).to.be.true;
- selection._setTo( foo, 'before' );
+ selection.setTo( foo, 'before' );
range = selection.getFirstRange();
expect( range.start.parent ).to.equal( p );
expect( range.start.offset ).to.equal( 0 );
expect( range.start.isEqual( range.end ) ).to.be.true;
- selection._setTo( foo, 'after' );
+ selection.setTo( foo, 'after' );
range = selection.getFirstRange();
expect( range.start.parent ).to.equal( p );
@@ -754,7 +763,7 @@ describe( 'Selection', () => {
describe( 'setting collapsed selection at start', () => {
it( 'should collapse to start position and fire change event', done => {
- selection._setTo( [ range1, range2, range3 ] );
+ selection.setTo( [ range1, range2, range3 ] );
selection.once( 'change', () => {
expect( selection.rangeCount ).to.equal( 1 );
expect( selection.isCollapsed ).to.be.true;
@@ -762,13 +771,13 @@ describe( 'Selection', () => {
done();
} );
- selection._setTo( selection.getFirstPosition() );
+ selection.setTo( selection.getFirstPosition() );
} );
} );
describe( 'setting collapsed selection to end', () => {
it( 'should collapse to end position and fire change event', done => {
- selection._setTo( [ range1, range2, range3 ] );
+ selection.setTo( [ range1, range2, range3 ] );
selection.once( 'change', () => {
expect( selection.rangeCount ).to.equal( 1 );
expect( selection.isCollapsed ).to.be.true;
@@ -776,48 +785,48 @@ describe( 'Selection', () => {
done();
} );
- selection._setTo( selection.getLastPosition() );
+ selection.setTo( selection.getLastPosition() );
} );
} );
describe( 'removing all ranges', () => {
it( 'should remove all ranges and fire change event', done => {
- selection._setTo( [ range1, range2 ] );
+ selection.setTo( [ range1, range2 ] );
selection.once( 'change', () => {
expect( selection.rangeCount ).to.equal( 0 );
done();
} );
- selection._setTo( null );
+ selection.setTo( null );
} );
} );
describe( 'setting fake selection', () => {
it( 'should allow to set selection to fake', () => {
- selection._setTo( range1, { fake: true } );
+ 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 } );
+ 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 } );
+ 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 );
+ selection.setTo( range1, { fake: true, label } );
+ selection.setTo( range1 );
expect( selection.fakeSelectionLabel ).to.equal( '' );
} );
@@ -830,11 +839,11 @@ describe( 'Selection', () => {
done();
} );
- selection._setTo( range1, { fake: true, label: 'foo bar baz' } );
+ 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' } );
+ selection.setTo( null, { fake: true, label: 'foo bar baz' } );
expect( selection.fakeSelectionLabel ).to.equal( 'foo bar baz' );
expect( selection.isFake ).to.be.true;
@@ -843,8 +852,8 @@ describe( 'Selection', () => {
describe( 'setting selection to itself', () => {
it( 'should correctly set ranges when setting to the same selection', () => {
- selection._setTo( [ range1, range2 ] );
- selection._setTo( selection );
+ selection.setTo( [ range1, range2 ] );
+ selection.setTo( selection );
const ranges = Array.from( selection.getRanges() );
expect( ranges.length ).to.equal( 2 );
@@ -854,8 +863,8 @@ describe( 'Selection', () => {
} );
it( 'should correctly set ranges when setting to the same selection\'s ranges', () => {
- selection._setTo( [ range1, range2 ] );
- selection._setTo( selection.getRanges() );
+ selection.setTo( [ range1, range2 ] );
+ selection.setTo( selection.getRanges() );
const ranges = Array.from( selection.getRanges() );
expect( ranges.length ).to.equal( 2 );
@@ -868,7 +877,7 @@ describe( 'Selection', () => {
describe( 'throwing errors', () => {
it( 'should throw an error when range is invalid', () => {
expect( () => {
- selection._setTo( [ { invalid: 'range' } ] );
+ selection.setTo( [ { invalid: 'range' } ] );
} ).to.throw( CKEditorError, 'view-selection-invalid-range: Invalid Range.' );
} );
@@ -877,7 +886,7 @@ describe( 'Selection', () => {
const range2 = Range.createFromParentsAndOffsets( text, 7, text, 15 );
expect( () => {
- selection._setTo( [ range1, range2 ] );
+ selection.setTo( [ range1, range2 ] );
} ).to.throw( CKEditorError, 'view-selection-range-intersects' );
} );
} );
@@ -888,7 +897,7 @@ describe( 'Selection', () => {
const textNode3 = new Text( 'baz' );
const element = new Element( 'p', null, [ textNode1, textNode2, textNode3 ] );
- selection._setTo( textNode2, 'on' );
+ selection.setTo( textNode2, 'on' );
const ranges = Array.from( selection.getRanges() );
expect( ranges.length ).to.equal( 1 );
@@ -901,7 +910,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( element, 'in' );
+ selection.setTo( element, 'in' );
const ranges = Array.from( selection.getRanges() );
expect( ranges.length ).to.equal( 1 );
@@ -914,7 +923,7 @@ describe( 'Selection', () => {
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 } );
+ selection.setTo( element, 'in', { backward: true } );
const ranges = Array.from( selection.getRanges() );
expect( ranges.length ).to.equal( 1 );
@@ -932,19 +941,19 @@ describe( 'Selection', () => {
} );
it( 'should return null if selection is placed in container that is not EditableElement', () => {
- selection._setTo( range1 );
+ selection.setTo( range1 );
expect( selection.editableElement ).to.be.null;
} );
it( 'should return EditableElement when selection is placed inside', () => {
const viewDocument = new Document();
- const selection = viewDocument.selection;
+ selection.setTo( viewDocument.selection );
const root = createViewRoot( viewDocument, 'div', 'main' );
const element = new Element( 'p' );
root._appendChildren( element );
- selection._setTo( Range.createFromParentsAndOffsets( element, 0, element, 0 ) );
+ selection.setTo( Range.createFromParentsAndOffsets( element, 0, element, 0 ) );
expect( selection.editableElement ).to.equal( root );
} );
@@ -958,14 +967,16 @@ describe( 'Selection', () => {
describe( 'getSelectedElement()', () => {
it( 'should return selected element', () => {
- const { selection, view } = parse( 'foo [bar] baz' );
+ const { selection: docSelection, view } = parse( 'foo [bar] baz' );
const b = view.getChild( 1 );
+ const selection = new Selection( docSelection );
expect( selection.getSelectedElement() ).to.equal( b );
} );
it( 'should return null if there is more than one range', () => {
- const { selection } = parse( 'foo [bar] [baz]' );
+ const { selection: docSelection } = parse( 'foo [bar] [baz]' );
+ const selection = new Selection( docSelection );
expect( selection.getSelectedElement() ).to.be.null;
} );
@@ -975,13 +986,15 @@ describe( 'Selection', () => {
} );
it( 'should return null if selection is not over single element #1', () => {
- const { selection } = parse( 'foo [bar ba}z' );
+ const { selection: docSelection } = parse( 'foo [bar ba}z' );
+ const selection = new Selection( docSelection );
expect( selection.getSelectedElement() ).to.be.null;
} );
it( 'should return null if selection is not over single element #2', () => {
- const { selection } = parse( 'foo {bar} baz' );
+ const { selection: docSelection } = parse( 'foo {bar} baz' );
+ const selection = new Selection( docSelection );
expect( selection.getSelectedElement() ).to.be.null;
} );