Browse Source

Added tests to engine.treeView.Selection.

Szymon Kupś 9 years ago
parent
commit
c9c88ece72

+ 42 - 15
packages/ckeditor5-engine/src/treeview/selection.js

@@ -12,7 +12,9 @@ import utils from '../../utils/utils.js';
 import EmitterMixin from '../../utils/emittermixin.js';
 
 /**
- * @memberOf engine.treeModel
+ * Class representing selection in tree view.
+ *
+ * @memberOf engine.treeView
  */
 export default class Selection {
 	constructor() {
@@ -104,6 +106,7 @@ export default class Selection {
 	 * Throws {@link utils.CKEditorError CKEditorError} `view-selection-range-intersects` if added range intersects
 	 * with ranges already stored in Selection instance.
 	 *
+	 * @fires {@link engine.treeView.Selection#change:range change:range}
 	 * @param {engine.treeView.Range} range
 	 */
 	addRange( range, isBackward ) {
@@ -202,6 +205,7 @@ export default class Selection {
 	/**
 	 * Removes range at given index.
 	 *
+	 * @fires {@link engine.treeView.Selection#change:range change:range}
 	 * @param {Number} index
 	 * @returns {engine.treeView.Range|null} Returns removed range or null if there is no range under given index.
 	 */
@@ -219,10 +223,14 @@ export default class Selection {
 
 	/**
 	 * Removes all ranges that were added to the selection.
+	 *
+	 * @fires {@link engine.treeView.Selection#change:range change:range}
 	 */
 	removeAllRanges() {
-		this._ranges = [];
-		this.fire( 'change:range' );
+		if ( this._ranges.length ) {
+			this._ranges = [];
+			this.fire( 'change:range' );
+		}
 	}
 
 	/**
@@ -230,12 +238,12 @@ export default class Selection {
 	 * is treated like the last added range and is used to set {@link #anchor} and {@link #focus}. Accepts a flag
 	 * describing in which way the selection is made (see {@link #addRange}).
 	 *
+	 * @fires {@link engine.treeView.Selection#change:range change:range}
 	 * @param {Array.<engine.treeView.Range>} newRanges Array of ranges to set.
 	 * @param {Boolean} [isLastBackward] 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 ) {
-		this.destroy();
 		this._ranges = [];
 
 		for ( let range of newRanges ) {
@@ -247,33 +255,46 @@ export default class Selection {
 	}
 
 	/**
-	 * Collapses selection to the {@link engine.treeView.Selection#getLastPosition last position} in stored ranges.
+	 * Collapses selection to the {@link engine.treeView.Selection#getFirstPosition first position} in stored ranges.
 	 * All ranges will be removed beside one collapsed range. Nothing will be changed if there are no ranges stored
 	 * inside selection.
+	 *
+	 * @fires {@link engine.treeView.Selection#change:range change:range}
 	 */
-	collapseToEnd() {
-		const endPosition = this.getLastPosition();
+	collapseToStart() {
+		const startPosition = this.getFirstPosition();
 
-		if ( endPosition !== null ) {
-			this.setRanges( [ new Range( endPosition, endPosition ) ] );
+		if ( startPosition !== null ) {
+			this.setRanges( [ new Range( startPosition, startPosition ) ] );
 			this.fire( 'change:range' );
 		}
 	}
 
 	/**
-	 * Collapses selection to the {@link engine.treeView.Selection#getFirstPosition first position} in stored ranges.
+	 * Collapses selection to the {@link engine.treeView.Selection#getLastPosition last position} in stored ranges.
 	 * All ranges will be removed beside one collapsed range. Nothing will be changed if there are no ranges stored
 	 * inside selection.
+	 *
+	 * @fires {@link engine.treeView.Selection#change:range change:range}
 	 */
-	collapseToStart() {
-		const startPosition = this.getFirstPosition();
+	collapseToEnd() {
+		const endPosition = this.getLastPosition();
 
-		if ( startPosition !== null ) {
-			this.setRanges( [ new Range( startPosition, startPosition ) ] );
+		if ( endPosition !== null ) {
+			this.setRanges( [ new Range( endPosition, endPosition ) ] );
 			this.fire( 'change:range' );
 		}
 	}
 
+	/**
+	 * Adds range to selection - creates copy of given range so it can be safely used and modified.
+	 *
+	 * Throws {@link utils.CKEditorError CKEditorError} `view-selection-range-intersects` if added range intersects
+	 * with ranges already stored in Selection instance.
+	 *
+	 * @private
+	 * @param {engine.treeView.Range} rang
+	 */
 	_pushRange( range ) {
 		for ( let storedRange of this._ranges ) {
 			if ( range.isIntersecting( storedRange ) ) {
@@ -295,4 +316,10 @@ export default class Selection {
 	}
 }
 
-utils.mix( Selection, EmitterMixin );
+utils.mix( Selection, EmitterMixin );
+
+/**
+ * Fired whenever selection ranges are changed through {@link engine.treeView.Selection Selection API}.
+ *
+ * @event engine.treeView.Selection#change:range
+ */

+ 234 - 42
packages/ckeditor5-engine/tests/treeview/selection.js

@@ -14,9 +14,15 @@ import CKEditorError from '/ckeditor5/utils/ckeditorerror.js';
 
 describe( 'Selection', () => {
 	let selection;
+	let el;
+	let range1, range2, range3;
 
 	beforeEach( () => {
 		selection = new Selection();
+		el = new Element( 'p' );
+		range1 = Range.createFromParentsAndOffsets( el, 5, el, 10 );
+		range2 = Range.createFromParentsAndOffsets( el, 1, el, 2 );
+		range3 = Range.createFromParentsAndOffsets( el, 12, el, 14 );
 	} );
 
 	describe( 'anchor', () => {
@@ -25,29 +31,22 @@ describe( 'Selection', () => {
 		} );
 
 		it( 'should return start of single range in selection', () => {
-			const el = new Element( 'p' );
-			const range = Range.createFromParentsAndOffsets( el, 5, el, 10 );
-			selection.addRange( range );
+			selection.addRange( range1 );
 			const anchor = selection.anchor;
 
-			expect( anchor.isEqual( range.start ) ).to.be.true;
-			expect( anchor ).to.not.equal( range.start );
+			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', () => {
-			const el = new Element( 'p' );
-			const range = Range.createFromParentsAndOffsets( el, 5, el, 10 );
-			selection.addRange( range, true );
+			selection.addRange( range1, true );
 			const anchor = selection.anchor;
 
-			expect( anchor.isEqual( range.end ) ).to.be.true;
-			expect( anchor ).to.not.equal( range.end );
+			expect( anchor.isEqual( range1.end ) ).to.be.true;
+			expect( anchor ).to.not.equal( range1.end );
 		} );
 
 		it( 'should get anchor from last inserted range', () => {
-			const el = new Element( 'p' );
-			const range1 = Range.createFromParentsAndOffsets( el, 5, el, 10 );
-			const range2 = Range.createFromParentsAndOffsets( el, 15, el, 20 );
 			selection.addRange( range1 );
 			selection.addRange( range2 );
 
@@ -61,28 +60,21 @@ describe( 'Selection', () => {
 		} );
 
 		it( 'should return end of single range in selection', () => {
-			const el = new Element( 'p' );
-			const range = Range.createFromParentsAndOffsets( el, 5, el, 10 );
-			selection.addRange( range );
+			selection.addRange( range1 );
 			const focus = selection.focus;
 
-			expect( focus.isEqual( range.end ) ).to.be.true;
+			expect( focus.isEqual( range1.end ) ).to.be.true;
 		} );
 
 		it( 'should return start of single range in selection when added as backward', () => {
-			const el = new Element( 'p' );
-			const range = Range.createFromParentsAndOffsets( el, 5, el, 10 );
-			selection.addRange( range, true );
+			selection.addRange( range1, true );
 			const focus = selection.focus;
 
-			expect( focus.isEqual( range.start ) ).to.be.true;
-			expect( focus ).to.not.equal( range.start );
+			expect( focus.isEqual( range1.start ) ).to.be.true;
+			expect( focus ).to.not.equal( range1.start );
 		} );
 
 		it( 'should get focus from last inserted range', () => {
-			const el = new Element( 'p' );
-			const range1 = Range.createFromParentsAndOffsets( el, 5, el, 10 );
-			const range2 = Range.createFromParentsAndOffsets( el, 15, el, 20 );
 			selection.addRange( range1 );
 			selection.addRange( range2 );
 
@@ -92,7 +84,6 @@ describe( 'Selection', () => {
 
 	describe( 'isCollapsed', () => {
 		it( 'should return true when all ranges are collapsed', () => {
-			const el = new Element( 'p' );
 			const range1 = Range.createFromParentsAndOffsets( el, 5, el, 5 );
 			const range2 = Range.createFromParentsAndOffsets( el, 15, el, 15 );
 			selection.addRange( range1 );
@@ -102,7 +93,6 @@ describe( 'Selection', () => {
 		} );
 
 		it( 'should return false when not all ranges are collapsed', () => {
-			const el = new Element( 'p' );
 			const range1 = Range.createFromParentsAndOffsets( el, 5, el, 5 );
 			const range2 = Range.createFromParentsAndOffsets( el, 15, el, 16 );
 			selection.addRange( range1 );
@@ -114,9 +104,6 @@ describe( 'Selection', () => {
 
 	describe( 'rangeCount', () => {
 		it( 'should return proper range count', () => {
-			const el = new Element( 'p' );
-			const range1 = Range.createFromParentsAndOffsets( el, 5, el, 5 );
-			const range2 = Range.createFromParentsAndOffsets( el, 15, el, 16 );
 			expect( selection.rangeCount ).to.equal( 0 );
 			selection.addRange( range1 );
 			expect( selection.rangeCount ).to.equal( 1 );
@@ -127,28 +114,20 @@ describe( 'Selection', () => {
 
 	describe( 'addRange', () => {
 		it( 'should add range to selection ranges', () => {
-			const el = new Element( 'p' );
-			const range = Range.createFromParentsAndOffsets( el, 0, el, 1 );
-
-			selection.addRange( range );
-			expect( selection.getRangeAt( 0 ).isEqual( range ) ).to.be.true;
+			selection.addRange( range1 );
+			expect( selection.getRangeAt( 0 ).isEqual( range1 ) ).to.be.true;
 		} );
 
 		it( 'should fire change event', ( done ) => {
-			const el = new Element( 'p' );
-			const range = Range.createFromParentsAndOffsets( el, 0, el, 1 );
-
 			selection.once( 'change:range', () => {
-				expect( selection.getRangeAt( 0 ).isEqual( range ) ).to.be.true;
+				expect( selection.getRangeAt( 0 ).isEqual( range1 ) ).to.be.true;
 				done();
 			} );
 
-			selection.addRange( range );
+			selection.addRange( range1 );
 		} );
 
 		it( 'should throw when range is intersecting with already added range', () => {
-			const el = new Element( 'p' );
-			const range1 = Range.createFromParentsAndOffsets( el, 5, el, 10 );
 			const range2 = Range.createFromParentsAndOffsets( el, 7, el, 15 );
 			selection.addRange( range1 );
 			expect( () => {
@@ -160,4 +139,217 @@ describe( 'Selection', () => {
 			} ).to.throw( CKEditorError, 'view-selection-range-intersects' );
 		} );
 	} );
+
+	describe( 'getRanges', () => {
+		it( 'should return copy array of all ranges', () => {
+			selection.addRange( range1 );
+			selection.addRange( range2 );
+
+			const ranges = selection.getRanges();
+			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( 'getRangeAt', () => {
+		it( 'should return copy of range at specified index', () => {
+			selection.addRange( range1 );
+			selection.addRange( range2 );
+			selection.addRange( range3 );
+
+			expect( selection.getRangeAt( 0 ).isEqual( range1 ) ).to.be.true;
+			expect( selection.getRangeAt( 1 ).isEqual( range2 ) ).to.be.true;
+			expect( selection.getRangeAt( 2 ).isEqual( range3 ) ).to.be.true;
+			expect( selection.getRangeAt( 0 ) ).to.not.equal( range1 );
+			expect( selection.getRangeAt( 1 ) ).to.not.equal( range2 );
+			expect( selection.getRangeAt( 2 ) ).to.not.equal( range3 );
+		} );
+
+		it( 'should return null if no range at given index', () => {
+			expect( selection.getRangeAt( 0 ) ).to.be.null;
+		} );
+	} );
+
+	describe( 'getFirstRange', () => {
+		it( 'should return copy of range with first position', () => {
+			selection.addRange( range1 );
+			selection.addRange( range2 );
+			selection.addRange( range3 );
+
+			const range = selection.getFirstRange();
+
+			expect( range.isEqual( range2 ) ).to.be.true;
+			expect( range ).to.not.equal( range2 );
+		} );
+
+		it( 'should return null if no ranges are present', () => {
+			expect( selection.getFirstRange() ).to.be.null;
+		} );
+	} );
+
+	describe( 'getLastRange', () => {
+		it( 'should return copy of range with last position', () => {
+			selection.addRange( range1 );
+			selection.addRange( range2 );
+			selection.addRange( range3 );
+
+			const range = selection.getLastRange();
+
+			expect( range.isEqual( range3 ) ).to.be.true;
+			expect( range ).to.not.equal( range3 );
+		} );
+
+		it( 'should return null if no ranges are present', () => {
+			expect( selection.getLastRange() ).to.be.null;
+		} );
+	} );
+
+	describe( 'getFirstPosition', () => {
+		it( 'should return copy of first position', () => {
+			selection.addRange( range1 );
+			selection.addRange( range2 );
+			selection.addRange( range3 );
+
+			const position = selection.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( selection.getFirstPosition() ).to.be.null;
+		} );
+	} );
+
+	describe( 'getLastPosition', () => {
+		it( 'should return copy of range with last position', () => {
+			selection.addRange( range1 );
+			selection.addRange( range2 );
+			selection.addRange( range3 );
+
+			const position = selection.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( selection.getLastPosition() ).to.be.null;
+		} );
+	} );
+
+	describe( 'removeRangeAt', () => {
+		it( 'should remove range at given index', () => {
+			selection.addRange( range1 );
+			selection.addRange( range2 );
+			selection.addRange( range3 );
+
+			const range = selection.removeRangeAt( 1 );
+			expect( range.isEqual( range2 ) ).to.be.true;
+			expect( selection.rangeCount ).to.equal( 2 );
+		} );
+
+		it( 'should return null if no range at specified index', () => {
+			expect( selection.removeRangeAt( 2 ) ).to.be.null;
+		} );
+
+		it( 'should fire change event', ( done ) => {
+			selection.addRange( range1 );
+
+			selection.once( 'change:range', () => {
+				expect( selection.rangeCount ).to.equal( 0 );
+				done();
+			} );
+
+			selection.removeRangeAt( 0 );
+		} );
+	} );
+
+	describe( 'removeAllRanges', () => {
+		it( 'should remove all ranges and fire change event', ( done ) => {
+			selection.addRange( range1 );
+			selection.addRange( range2 );
+
+			selection.once( 'change:range', () => {
+				expect( selection.rangeCount ).to.equal( 0 );
+				done();
+			} );
+
+			selection.removeAllRanges();
+		} );
+
+		it( 'should do nothing when no ranges are present', () => {
+			const fireSpy = sinon.spy( selection, 'fire' );
+			selection.removeAllRanges();
+
+			fireSpy.restore();
+			expect( fireSpy.notCalled ).to.be.true;
+		} );
+	} );
+
+	describe( 'setRanges', () => {
+		it( 'should add ranges and fire change event', ( done ) => {
+			selection.addRange( range1 );
+
+			selection.once( 'change:range', () => {
+				expect( selection.rangeCount ).to.equal( 2 );
+				expect( selection.getRangeAt( 0 ).isEqual( range2 ) ).to.be.true;
+				expect( selection.getRangeAt( 0 ) ).is.not.equal( range2 );
+				expect( selection.getRangeAt( 1 ).isEqual( range3 ) ).to.be.true;
+				expect( selection.getRangeAt( 1 ) ).is.not.equal( range3 );
+				done();
+			} );
+
+			selection.setRanges( [ range2, range3 ] );
+		} );
+	} );
+
+	describe( 'collapseToStart', () => {
+		it( 'should collapse to start position and fire change event', ( done ) => {
+			selection.setRanges( [ range1, range2, range3 ] );
+			selection.once( 'change:range', () => {
+				expect( selection.rangeCount ).to.equal( 1 );
+				expect( selection.isCollapsed ).to.be.true;
+				expect( selection.getRangeAt( 0 ).start.isEqual( range2.start ) ).to.be.true;
+				done();
+			} );
+
+			selection.collapseToStart();
+		} );
+
+		it( 'should do nothing if no ranges present', () => {
+			const fireSpy = sinon.spy( selection, 'fire' );
+
+			selection.collapseToStart();
+
+			fireSpy.restore();
+			expect( fireSpy.notCalled ).to.be.true;
+		} );
+	} );
+
+	describe( 'collapseToEnd', () => {
+		it( 'should collapse to end position and fire change event', ( done ) => {
+			selection.setRanges( [ range1, range2, range3 ] );
+			selection.once( 'change:range', () => {
+				expect( selection.rangeCount ).to.equal( 1 );
+				expect( selection.isCollapsed ).to.be.true;
+				expect( selection.getRangeAt( 0 ).end.isEqual( range3.end ) ).to.be.true;
+				done();
+			} );
+
+			selection.collapseToEnd();
+		} );
+
+		it( 'should do nothing if no ranges present', () => {
+			const fireSpy = sinon.spy( selection, 'fire' );
+
+			selection.collapseToEnd();
+
+			fireSpy.restore();
+			expect( fireSpy.notCalled ).to.be.true;
+		} );
+	} );
 } );