Browse Source

Added: engine.model.Selection added missing methods (compared to view.Selection).

Szymon Cofalik 9 years ago
parent
commit
8156e5d9c8

+ 75 - 1
packages/ckeditor5-engine/src/model/selection.js

@@ -180,6 +180,27 @@ export default class Selection {
 	}
 
 	/**
+	 * Returns a copy of the last range in the selection. Last range is the one which {@link engine.model.Range#end end} position
+	 * {@link engine.model.Position#isAfter is after} end position of all other ranges (not to confuse with the range most
+	 * recently added to the selection).
+	 *
+	 * Returns `null` if there are no ranges in selection.
+	 *
+	 * @returns {engine.model.Range|null}
+	 */
+	getLastRange() {
+		let last = null;
+
+		for ( let range of this._ranges ) {
+			if ( !last || range.end.isAfter( last.end ) ) {
+				last = range;
+			}
+		}
+
+		return last ? Range.createFromRange( last ) : null;
+	}
+
+	/**
 	 * Returns the first position in the selection. First position is the position that {@link engine.model.Position#isBefore is before}
 	 * any other position in the selection.
 	 *
@@ -194,6 +215,20 @@ export default class Selection {
 	}
 
 	/**
+	 * Returns the last position in the selection. Last position is the position that {@link engine.model.Position#isAfter is after}
+	 * any other position in the selection.
+	 *
+	 * Returns `null` if there are no ranges in selection.
+	 *
+	 * @returns {engine.model.Position|null}
+	 */
+	getLastPosition() {
+		const lastRange = this.getLastRange();
+
+		return lastRange ? Position.createFromPosition( lastRange.end ) : null;
+	}
+
+	/**
 	 * Adds a range to this selection. Added range is copied. This means that passed range is not saved in `Selection`
 	 * instance and operating on it will not change `Selection` state.
 	 *
@@ -253,6 +288,15 @@ export default class Selection {
 	}
 
 	/**
+	 * Sets this selection's ranges and direction to the ranges and direction of the given selection.
+	 *
+	 * @param {engine.model.Selection} otherSelection
+	 */
+	setTo( otherSelection ) {
+		this.setRanges( otherSelection.getRanges(), otherSelection.isBackward );
+	}
+
+	/**
 	 * Sets collapsed selection in the specified location.
 	 *
 	 * The location can be specified in the same form as {@link engine.model.Position.createAt} parameters.
@@ -270,6 +314,36 @@ export default class Selection {
 	}
 
 	/**
+	 * Collapses selection to the selection's {@link engine.model.Selection#getFirstPosition first position}.
+	 * All ranges, besides the collapsed one, will be removed. Nothing will change if there are no ranges stored
+	 * inside selection.
+	 *
+	 * @fires engine.view.Selection#change
+	 */
+	collapseToStart() {
+		const startPosition = this.getFirstPosition();
+
+		if ( startPosition !== null ) {
+			this.setRanges( [ new Range( startPosition, startPosition ) ] );
+		}
+	}
+
+	/**
+	 * Collapses selection to the selection's {@link engine.model.Selection#getLastPosition last position}.
+	 * All ranges, besides the collapsed one, will be removed. Nothing will change if there are no ranges stored
+	 * inside selection.
+	 *
+	 * @fires engine.view.Selection#change
+	 */
+	collapseToEnd() {
+		const endPosition = this.getLastPosition();
+
+		if ( endPosition !== null ) {
+			this.setRanges( [ new Range( endPosition, endPosition ) ] );
+		}
+	}
+
+	/**
 	 * Gets an attribute value for given key or `undefined` if that attribute is not set on the selection.
 	 *
 	 * @param {String} key Key of attribute to look for.
@@ -406,7 +480,7 @@ export default class Selection {
 	 */
 	static createFromSelection( otherSelection ) {
 		const selection = new this();
-		selection.setRanges( otherSelection.getRanges(), otherSelection.isBackward );
+		selection.setTo( otherSelection );
 
 		return selection;
 	}

+ 123 - 0
packages/ckeditor5-engine/tests/model/selection.js

@@ -579,6 +579,129 @@ describe( 'Selection', () => {
 		} );
 	} );
 
+	describe( 'getLastRange', () => {
+		it( 'should return null if no ranges were added', () => {
+			expect( selection.getLastRange() ).to.be.null;
+		} );
+
+		it( 'should return a range which start position is before all other ranges\' start positions', () => {
+			selection.addRange( range3 );
+			selection.addRange( range1 );
+			selection.addRange( range2 );
+
+			let range = selection.getLastRange();
+
+			expect( range.start.path ).to.deep.equal( [ 6 ] );
+			expect( range.end.path ).to.deep.equal( [ 7 ] );
+		} );
+	} );
+
+	describe( 'getLastPosition', () => {
+		it( 'should return null if no ranges were added', () => {
+			expect( selection.getLastPosition() ).to.be.null;
+		} );
+
+		it( 'should return a position that is in selection and is before any other position from the selection', () => {
+			selection.addRange( range3 );
+			selection.addRange( range1 );
+			selection.addRange( range2 );
+
+			let position = selection.getLastPosition();
+
+			expect( position.path ).to.deep.equal( [ 7 ] );
+		} );
+	} );
+
+	describe( 'isEqual', () => {
+		it( 'should return true if selections equal', () => {
+			selection.addRange( range1 );
+			selection.addRange( range2 );
+
+			const otherSelection = new Selection();
+			otherSelection.addRange( range1 );
+			otherSelection.addRange( range2 );
+
+			expect( selection.isEqual( otherSelection ) ).to.be.true;
+		} );
+
+		it( 'should return true if backward selections equal', () => {
+			selection.addRange( range1, true );
+
+			const otherSelection = new Selection();
+			otherSelection.addRange( range1, true );
+
+			expect( selection.isEqual( otherSelection ) ).to.be.true;
+		} );
+
+		it( 'should return false if ranges count does not equal', () => {
+			selection.addRange( range1 );
+			selection.addRange( range2 );
+
+			const otherSelection = new Selection();
+			otherSelection.addRange( range1 );
+
+			expect( selection.isEqual( otherSelection ) ).to.be.false;
+		} );
+
+		it( 'should return false if ranges do not equal', () => {
+			selection.addRange( range1 );
+
+			const otherSelection = new Selection();
+			otherSelection.addRange( range2 );
+
+			expect( selection.isEqual( otherSelection ) ).to.be.false;
+		} );
+
+		it( 'should return false if directions do not equal', () => {
+			selection.addRange( range1 );
+
+			const otherSelection = new Selection();
+			otherSelection.addRange( range1, true );
+
+			expect( selection.isEqual( otherSelection ) ).to.be.false;
+		} );
+	} );
+
+	describe( 'collapseToStart', () => {
+		it( 'should collapse to start position and fire change event', () => {
+			selection.setRanges( [ range2, range1, range3 ] );
+			selection.collapseToStart();
+
+			expect( selection.rangeCount ).to.equal( 1 );
+			expect( selection.isCollapsed ).to.be.true;
+			expect( selection.getFirstPosition().isEqual( range1.start ) ).to.be.true;
+		} );
+
+		it( 'should do nothing if no ranges present', () => {
+			const spy = sinon.spy( selection, 'fire' );
+
+			selection.collapseToStart();
+
+			spy.restore();
+			expect( spy.notCalled ).to.be.true;
+		} );
+	} );
+
+	describe( 'collapseToEnd', () => {
+		it( 'should collapse to start position and fire change event', () => {
+			selection.setRanges( [ range2, range3, range1 ] );
+			selection.collapseToEnd();
+
+			expect( selection.rangeCount ).to.equal( 1 );
+			expect( selection.isCollapsed ).to.be.true;
+			expect( selection.getLastPosition().isEqual( range3.end ) ).to.be.true;
+		} );
+
+		it( 'should do nothing if no ranges present', () => {
+			const spy = sinon.spy( selection, 'fire' );
+
+			selection.collapseToEnd();
+
+			spy.restore();
+			expect( spy.notCalled ).to.be.true;
+		} );
+	} );
+
 	describe( 'createFromSelection', () => {
 		it( 'should return a Selection instance with same ranges and direction as given selection', () => {
 			selection.addRange( liveRange );