Ver Fonte

Changed: treeModel.PositionIterator and others to use new treeModel.NodeList behavior and treeModel.TextNode.

Szymon Cofalik há 10 anos atrás
pai
commit
44c9e3ba9c

+ 1 - 1
packages/ckeditor5-engine/src/treemodel/delta/weakinsertdelta.js

@@ -41,7 +41,7 @@ register( 'weakInsert', function( position, nodes ) {
 
 	nodes = new NodeList( nodes );
 
-	for ( let node of nodes ) {
+	for ( let node of nodes._nodes ) {
 		node.attrs.setTo( this.doc.selection.attrs );
 	}
 

+ 9 - 6
packages/ckeditor5-engine/src/treemodel/element.js

@@ -89,11 +89,13 @@ export default class Element extends Node {
 	 * The list of nodes can be of any type accepted by the {@link treeModel.NodeList} constructor.
 	 */
 	insertChildren( index, nodes ) {
-		this._children.insert( index, new NodeList( nodes ) );
+		let nodeList = new NodeList( nodes );
 
-		for ( let node of this._children ) {
+		for ( let node of nodeList._nodes ) {
 			node.parent = this;
 		}
+
+		this._children.insert( index, nodeList );
 	}
 
 	/**
@@ -106,12 +108,13 @@ export default class Element extends Node {
 	 * @param {Number} number Number of nodes to remove.
 	 * @returns {treeModel.NodeList} The list of removed nodes.
 	 */
-
 	removeChildren( index, number ) {
-		for ( let i = index; i < index + number; i++ ) {
-			this._children.get( i ).parent = null;
+		let nodeList = this._children.remove( index, number );
+
+		for ( let node of nodeList._nodes ) {
+			node.parent = null;
 		}
 
-		return this._children.remove( index, number );
+		return nodeList;
 	}
 }

+ 18 - 4
packages/ckeditor5-engine/src/treemodel/operation/attributeoperation.js

@@ -8,6 +8,7 @@
 import Operation from './operation.js';
 import Range from '../range.js';
 import CKEditorError from '../../ckeditorerror.js';
+import TextNode from '../textnode.js';
 
 /**
  * Operation to change nodes' attribute. Using this class you can add, remove or change value of the attribute.
@@ -91,9 +92,17 @@ export default class AttributeOperation extends Operation {
 			);
 		}
 
+		// Split text nodes if needed, then get the nodes in the range and convert it to node list. It will be easier to operate.
+		this.range.start.parent._children._splitNodeAt( this.range.start.offset );
+		this.range.end.parent._children._splitNodeAt( this.range.end.offset );
+
 		// Remove or change.
 		if ( oldAttr !== null ) {
-			for ( let node of this.range.getAllNodes() ) {
+			for ( let node of this.range.getAllNodes( true ) ) {
+				if ( node instanceof TextNode ) {
+					node = node._textItem;
+				}
+
 				if ( !node.attrs.has( oldAttr ) ) {
 					/**
 					 * The attribute which should be removed does not exists for the given node.
@@ -108,8 +117,6 @@ export default class AttributeOperation extends Operation {
 					);
 				}
 
-				// There is no use in removing attribute if we will overwrite it later.
-				// Still it is profitable to run through the loop to check if all nodes in the range has old attribute.
 				if ( newAttr === null ) {
 					node.attrs.delete( oldAttr.key );
 				}
@@ -118,7 +125,11 @@ export default class AttributeOperation extends Operation {
 
 		// Insert or change.
 		if ( newAttr !== null ) {
-			for ( let node of this.range.getAllNodes() ) {
+			for ( let node of this.range.getAllNodes( true ) ) {
+				if ( node instanceof TextNode ) {
+					node = node._textItem;
+				}
+
 				if ( oldAttr === null && node.attrs.has( newAttr.key ) ) {
 					/**
 					 * The attribute with given key already exists for the given node.
@@ -137,6 +148,9 @@ export default class AttributeOperation extends Operation {
 			}
 		}
 
+		this.range.start.parent._children._mergeNodeAt( this.range.start.offset );
+		this.range.end.parent._children._mergeNodeAt( this.range.end.offset );
+
 		return { range: this.range, oldAttr: oldAttr, newAttr: newAttr };
 	}
 }

+ 28 - 56
packages/ckeditor5-engine/src/treemodel/positioniterator.js

@@ -5,16 +5,14 @@
 
 'use strict';
 
-import Character from './character.js';
+import TextNode from './textnode.js';
 import Element from './element.js';
 import Position from './position.js';
-import Text from './text.js';
 import Range from './range.js';
 
 const ELEMENT_ENTER = 0;
 const ELEMENT_LEAVE = 1;
-const CHARACTER = 2;
-const TEXT = 3;
+const TEXT = 2;
 
 /**
  * Position iterator class. It allows to iterate forward and backward over the tree document.
@@ -28,14 +26,12 @@ export default class PositionIterator {
 	 *
 	 * @param {treeModel.Range} [boundaries] Range to define boundaries of the iterator.
 	 * @param {treeModel.Position} [iteratorPosition] Starting position.
-	 * @param {Boolean} [mergeCharacters] Whether {@link treeModel.Character} nodes with same attributes
+	 * @param {Boolean} [mergeCharacters] Flag indicating whether all consecutive characters with the same attributes
+	 * should be returned as one {@link treeModel.TextNode} (`true`) or one by one (`false`). Defaults to `false`.
 	 * should be passed one by one or as one {@link treeModel.Text} object.
 	 * @constructor
 	 */
 	constructor() {
-		const Range = CKEDITOR.require( 'treemodel/range' );
-		const Position = CKEDITOR.require( 'treemodel/position' );
-
 		const args = Array.from( arguments );
 
 		if ( args[ 0 ] instanceof Range ) {
@@ -72,10 +68,8 @@ export default class PositionIterator {
 		 */
 
 		/**
-		 * Flag indicating whether {@link treeModel.Character} nodes with same attributes should be passed one by one
-		 * or as one {@link treeModel.Text} object.
-		 *
-		 * In this mode, all consecutive characters that has the same attributes will be passed in one iterator step.
+		 * Flag indicating whether all consecutive characters with the same attributes should be
+		 * returned as one {@link treeModel.TextNode} (`true`) or one by one (`false`).
 		 *
 		 * @property {Boolean} mergeCharacters
 		 */
@@ -88,7 +82,7 @@ export default class PositionIterator {
 	 * @returns {Boolean} return.done True if iterator is done.
 	 * @returns {Object} return.value
 	 * @returns {Number} return.value.type Encountered value type, possible options: {@link PositionIterator#ELEMENT_ENTER},
-	 * {@link PositionIterator#ELEMENT_LEAVE}, {@link PositionIterator#CHARACTER} or {@link PositionIterator#TEXT}.
+	 * {@link PositionIterator#ELEMENT_LEAVE} or {@link PositionIterator#TEXT}.
 	 * @returns {treeModel.Node} return.value.node Encountered node.
 	 */
 	next() {
@@ -110,26 +104,19 @@ export default class PositionIterator {
 			this.position = Position.createFromParentAndOffset( nodeAfter, 0 );
 
 			return formatReturnValue( ELEMENT_ENTER, nodeAfter );
-		} else if ( nodeAfter instanceof Character ) {
-			if ( this.mergeCharacters ) {
-				let text = '';
-				let node = this.position.nodeAfter;
+		} else if ( nodeAfter instanceof TextNode ) {
+			let offset = this.mergeCharacters ? nodeAfter._textItem.text.length - nodeAfter._start : 1;
+			let nextPos = Position.createFromParentAndOffset( parent, position.offset + offset );
 
-				while ( !this.position.isEqual( this.boundaries.end ) && node instanceof Character && node.attrs.isEqual( nodeAfter.attrs ) ) {
-					text += this.position.nodeAfter.character;
-					this.position.offset++;
+			if ( this.boundaries && nextPos.isAfter( this.boundaries.end ) ) {
+				nextPos = Position.createFromPosition( this.boundaries.end );
+			}
 
-					node = this.position.nodeAfter;
-				}
-				// This is so this.position is always a new object after each iterator step.
-				this.position = Position.createFromPosition( position );
+			let textNode = nodeAfter._textItem.getTextNode( nodeAfter._start, nextPos.offset - position.offset );
 
-				return formatReturnValue( TEXT, new Text( text, this.position.nodeBefore.attrs ) );
-			} else {
-				this.position = Position.createFromParentAndOffset( parent, position.offset + 1 );
+			this.position = nextPos;
 
-				return formatReturnValue( CHARACTER, nodeAfter );
-			}
+			return formatReturnValue( TEXT, textNode );
 		} else {
 			this.position = Position.createFromParentAndOffset( parent.parent, parent.getIndex() + 1 );
 
@@ -144,7 +131,7 @@ export default class PositionIterator {
 	 * @returns {Boolean} return.done True if iterator is done.
 	 * @returns {Object} return.value
 	 * @returns {Number} return.value.type Encountered value type, possible options: {@link PositionIterator#ELEMENT_ENTER},
-	 * {@link PositionIterator#ELEMENT_LEAVE} or {@link PositionIterator#CHARACTER}.
+	 * {@link PositionIterator#ELEMENT_LEAVE} or {@link PositionIterator#TEXT}.
 	 * @returns {treeModel.Node} return.value.node Scanned node.
 	 */
 	previous() {
@@ -166,26 +153,20 @@ export default class PositionIterator {
 			this.position = Position.createFromParentAndOffset( nodeBefore, nodeBefore.getChildCount() );
 
 			return formatReturnValue( ELEMENT_LEAVE, nodeBefore );
-		} else if ( nodeBefore instanceof Character ) {
-			if ( this.mergeCharacters ) {
-				let text = '';
-				let node = this.position.nodeBefore;
+		} else if ( nodeBefore instanceof TextNode ) {
+			let offset = this.mergeCharacters ? nodeBefore._start + 1 : 1;
+			let nextPos = Position.createFromParentAndOffset( parent, position.offset - offset );
 
-				while ( !this.position.isEqual( this.boundaries.start ) && node instanceof Character && node.attrs.isEqual( nodeBefore.attrs ) ) {
-					text = this.position.nodeBefore.character + text;
-					this.position.offset--;
+			if ( this.boundaries && nextPos.isBefore( this.boundaries.start ) ) {
+				nextPos = Position.createFromPosition( this.boundaries.start );
+			}
 
-					node = this.position.nodeBefore;
-				}
-				// This is so this.position is always a new object after each iterator step.
-				this.position = Position.createFromPosition( position );
+			let start = nodeBefore._start - position.offset + nextPos.offset + 1;
+			let textNode = nodeBefore._textItem.getTextNode( start, nodeBefore._start - start + 1 );
 
-				return formatReturnValue( TEXT, new Text( text, this.position.nodeAfter.attrs ) );
-			} else {
-				this.position = Position.createFromParentAndOffset( parent, position.offset - 1 );
+			this.position = nextPos;
 
-				return formatReturnValue( CHARACTER, nodeBefore );
-			}
+			return formatReturnValue( TEXT, textNode );
 		} else {
 			this.position = Position.createFromParentAndOffset( parent.parent, parent.getIndex() );
 
@@ -204,15 +185,6 @@ function formatReturnValue( type, node ) {
 	};
 }
 
-/**
- * Flag for character.
- *
- * @static
- * @readonly
- * @property {Number}
- */
-PositionIterator.CHARACTER = CHARACTER;
-
 /**
  * Flag for entering element.
  *
@@ -232,7 +204,7 @@ PositionIterator.ELEMENT_ENTER = ELEMENT_ENTER;
 PositionIterator.ELEMENT_LEAVE = ELEMENT_LEAVE;
 
 /**
- * Flag for merged characters / text.
+ * Flag for character or text.
  *
  * @static
  * @readonly

+ 14 - 12
packages/ckeditor5-engine/tests/treemodel/delta/attributedelta.js

@@ -14,7 +14,6 @@ import Attribute from '/ckeditor5/core/treemodel/attribute.js';
 import Range from '/ckeditor5/core/treemodel/range.js';
 import Position from '/ckeditor5/core/treemodel/position.js';
 import Element from '/ckeditor5/core/treemodel/element.js';
-import Character from '/ckeditor5/core/treemodel/character.js';
 
 const getIteratorCount = coreTestUtils.getIteratorCount;
 
@@ -38,12 +37,15 @@ describe( 'Batch', () => {
 	}
 
 	describe( 'change attribute on node', () => {
-		let node, character;
+		let node, text, textNode;
 
 		beforeEach( () => {
 			node = new Element( 'p', [ new Attribute( 'a', 1 ) ] );
-			character = new Character( 'c', [ new Attribute( 'a', 1 ) ] );
-			root.insertChildren( 0, [ node, character ] );
+			text = new Text( 'c', [ new Attribute( 'a', 1 ) ] );
+
+			root.insertChildren( 0, [ node, text ] );
+
+			textNode = root.getChild( 1 );
 		} );
 
 		describe( 'setAttr', () => {
@@ -59,16 +61,16 @@ describe( 'Batch', () => {
 				expect( node.attrs.getValue( 'a' ) ).to.equal( 2 );
 			} );
 
-			it( 'should create the attribute on character', () => {
-				batch.setAttr( 'b', 2, character );
+			it( 'should create the attribute on text node', () => {
+				batch.setAttr( 'b', 2, textNode );
 				expect( getOperationsCount() ).to.equal( 1 );
-				expect( character.attrs.getValue( 'b' ) ).to.equal( 2 );
+				expect( root.getChild( 1 ).attrs.getValue( 'b' ) ).to.equal( 2 );
 			} );
 
-			it( 'should change the attribute of character', () => {
-				batch.setAttr( 'a', 2, character );
+			it( 'should change the attribute of text node', () => {
+				batch.setAttr( 'a', 2, textNode );
 				expect( getOperationsCount() ).to.equal( 1 );
-				expect( character.attrs.getValue( 'a' ) ).to.equal( 2 );
+				expect( root.getChild( 1 ).attrs.getValue( 'a' ) ).to.equal( 2 );
 			} );
 
 			it( 'should do nothing if the attribute value is the same', () => {
@@ -91,9 +93,9 @@ describe( 'Batch', () => {
 			} );
 
 			it( 'should remove the attribute from character', () => {
-				batch.removeAttr( 'a', character );
+				batch.removeAttr( 'a', textNode );
 				expect( getOperationsCount() ).to.equal( 1 );
-				expect( character.attrs.getValue( 'a' ) ).to.be.null;
+				expect( root.getChild( 1 ).attrs.getValue( 'a' ) ).to.be.null;
 			} );
 
 			it( 'should do nothing if the attribute is not set', () => {

+ 12 - 8
packages/ckeditor5-engine/tests/treemodel/element.js

@@ -90,14 +90,18 @@ describe( 'Element', () => {
 
 	describe( 'getChildIndex', () => {
 		it( 'should return child index', () => {
-			let element = new Element( 'elem', [], [ 'bar' ] );
-			let b = element.getChild( 0 );
-			let a = element.getChild( 1 );
-			let r = element.getChild( 2 );
-
-			expect( element.getChildIndex( b ) ).to.equal( 0 );
-			expect( element.getChildIndex( a ) ).to.equal( 1 );
-			expect( element.getChildIndex( r ) ).to.equal( 2 );
+			let element = new Element( 'elem', [], [ new Element( 'p' ), 'bar', new Element( 'h' ) ] );
+			let p = element.getChild( 0 );
+			let b = element.getChild( 1 );
+			let a = element.getChild( 2 );
+			let r = element.getChild( 3 );
+			let h = element.getChild( 4 );
+
+			expect( element.getChildIndex( p ) ).to.equal( 0 );
+			expect( element.getChildIndex( b ) ).to.equal( 1 );
+			expect( element.getChildIndex( a ) ).to.equal( 2 );
+			expect( element.getChildIndex( r ) ).to.equal( 3 );
+			expect( element.getChildIndex( h ) ).to.equal( 4 );
 		} );
 	} );
 

+ 19 - 0
packages/ckeditor5-engine/tests/treemodel/operation/attributeoperation.js

@@ -360,4 +360,23 @@ describe( 'AttributeOperation', () => {
 		expect( clone.newAttr.isEqual( newAttr ) ).to.be.true;
 		expect( clone.baseVersion ).to.equal( baseVersion );
 	} );
+
+	it( 'should merge characters in node list', () => {
+		let attrA = new Attribute( 'foo', 'a' );
+		let attrB = new Attribute( 'foo', 'b' );
+		root.insertChildren( 0, new Text( 'abc', [ attrA ] ) );
+		root.insertChildren( 3, new Text( 'xyz', [ attrB ] ) );
+
+		doc.applyOperation(
+			new AttributeOperation(
+				new Range( new Position( root, [ 1 ] ), new Position( root, [ 3 ] ) ),
+				attrA,
+				attrB,
+				doc.version
+			)
+		);
+
+		expect( root._children._nodes[ 0 ].text ).to.equal( 'a' );
+		expect( root._children._nodes[ 1 ].text ).to.equal( 'bcxyz' );
+	} );
 } );

+ 65 - 47
packages/ckeditor5-engine/tests/treemodel/positioniterator.js

@@ -10,7 +10,6 @@
 import Document from '/ckeditor5/core/treemodel/document.js';
 import Attribute from '/ckeditor5/core/treemodel/attribute.js';
 import Element from '/ckeditor5/core/treemodel/element.js';
-import Character from '/ckeditor5/core/treemodel/character.js';
 import Text from '/ckeditor5/core/treemodel/text.js';
 import PositionIterator from '/ckeditor5/core/treemodel/positioniterator.js';
 import Position from '/ckeditor5/core/treemodel/position.js';
@@ -24,7 +23,6 @@ describe( 'range iterator', () => {
 	before( () => {
 		ELEMENT_ENTER = PositionIterator.ELEMENT_ENTER;
 		ELEMENT_LEAVE = PositionIterator.ELEMENT_LEAVE;
-		CHARACTER = PositionIterator.CHARACTER;
 		TEXT = PositionIterator.TEXT;
 
 		doc = new Document();
@@ -43,11 +41,11 @@ describe( 'range iterator', () => {
 
 		let attrBoldTrue = new Attribute( 'bold', true );
 
-		b = new Character( 'b', [ attrBoldTrue ] );
-		a = new Character( 'a', [ attrBoldTrue ] );
-		r = new Character( 'r' );
+		b = new Text( 'b', [ attrBoldTrue ] );
+		a = new Text( 'a', [ attrBoldTrue ] );
+		r = new Text( 'r' );
 		img2 = new Element( 'img2' );
-		x = new Character( 'x' );
+		x = new Text( 'x' );
 
 		paragraph = new Element( 'p', [], [ b, a, r, img2, x ] );
 		img1 = new Element( 'img1' );
@@ -58,12 +56,12 @@ describe( 'range iterator', () => {
 			{ type: ELEMENT_ENTER, node: img1 },
 			{ type: ELEMENT_LEAVE, node: img1 },
 			{ type: ELEMENT_ENTER, node: paragraph },
-			{ type: CHARACTER, node: b },
-			{ type: CHARACTER, node: a },
-			{ type: CHARACTER, node: r },
+			{ type: TEXT, text: 'b', attrs: [ attrBoldTrue ] },
+			{ type: TEXT, text: 'a', attrs: [ attrBoldTrue ] },
+			{ type: TEXT, text: 'r', attrs: [] },
 			{ type: ELEMENT_ENTER, node: img2 },
 			{ type: ELEMENT_LEAVE, node: img2 },
-			{ type: CHARACTER, node: x },
+			{ type: TEXT, text: 'x', attrs: [] },
 			{ type: ELEMENT_LEAVE, node: paragraph }
 		];
 
@@ -71,21 +69,34 @@ describe( 'range iterator', () => {
 			{ type: ELEMENT_ENTER, node: img1 },
 			{ type: ELEMENT_LEAVE, node: img1 },
 			{ type: ELEMENT_ENTER, node: paragraph },
-			{ type: TEXT, node: new Text( 'ba', [ attrBoldTrue ] ) },
-			{ type: TEXT, node: new Text( 'r' ) },
+			{ type: TEXT, text: 'ba', attrs: [ attrBoldTrue ] },
+			{ type: TEXT, text: 'r', attrs: [] },
 			{ type: ELEMENT_ENTER, node: img2 },
 			{ type: ELEMENT_LEAVE, node: img2 },
-			{ type: TEXT, node: new Text( 'x' ) },
+			{ type: TEXT, text: 'x', attrs: [] },
 			{ type: ELEMENT_LEAVE, node: paragraph }
 		];
 	} );
 
+	function expectItem( item, expected ) {
+		expect( item.done ).to.be.false;
+
+		if ( item.value.type == TEXT ) {
+			let text = item.value.node.text;
+
+			expect( text ).to.equal( expected.text );
+			expect( Array.from( item.value.node.attrs ) ).to.deep.equal( expected.attrs );
+		} else {
+			expect( item.value ).to.deep.equal( expected );
+		}
+	}
+
 	it( 'should return next position', () => {
 		let iterator = new PositionIterator( new Position( root, [ 0 ] ) ); // beginning of root
 		let i, len;
 
 		for ( i = 0, len = expectedItems.length; i < len; i++ ) {
-			expect( iterator.next() ).to.deep.equal( { done: false, value: expectedItems[ i ] } );
+			expectItem( iterator.next(), expectedItems[ i ] );
 		}
 		expect( iterator.next() ).to.have.property( 'done' ).that.is.true;
 	} );
@@ -94,7 +105,7 @@ describe( 'range iterator', () => {
 		let iterator = new PositionIterator( new Position( root, [ 2 ] ) ); // ending of root
 
 		for ( let i = expectedItems.length - 1; i >= 0; i-- ) {
-			expect( iterator.previous() ).to.deep.equal( { done: false, value: expectedItems[ i ] } );
+			expectItem( iterator.previous(), expectedItems[ i ] );
 		}
 		expect( iterator.previous() ).to.have.property( 'done' ).that.is.true;
 	} );
@@ -108,7 +119,7 @@ describe( 'range iterator', () => {
 		let i, len;
 
 		for ( i = 3, len = expectedItems.length; i < 7; i++ ) {
-			expect( iterator.next() ).to.deep.equal( { done: false, value: expectedItems[ i ] } );
+			expectItem( iterator.next(), expectedItems[ i ] );
 		}
 		expect( iterator.next() ).to.have.property( 'done' ).that.is.true;
 	} );
@@ -122,58 +133,65 @@ describe( 'range iterator', () => {
 		let i, len;
 
 		for ( i = 6, len = expectedItems.length; i > 2; i-- ) {
-			expect( iterator.previous() ).to.deep.equal( { done: false, value: expectedItems[ i ] } );
+			expectItem( iterator.previous(), expectedItems[ i ] );
 		}
 		expect( iterator.previous() ).to.have.property( 'done' ).that.is.true;
 	} );
 
-	it( 'should return iterate over the range', () => {
-		let start = new Position( root, [ 0 ] ); // beginning of root
-		let end = new Position( root, [ 2 ] ); // ending of root
+	it( 'should merge characters when iterating over the range using next', () => {
+		let start = new Position( root, [ 1 ] );
+		let end = new Position( root, [ 1, 4 ] );
 		let range = new Range( start, end );
 
-		let i = 0;
+		let iterator = new PositionIterator( range, range.start, true );
+		let i;
 
-		for ( let value of range ) {
-			expect( value ).to.deep.equal( expectedItems[ i ] );
-			i++;
+		for ( i = 2; i <= 6; i++ ) {
+			expectItem( iterator.next(), expectedItemsMerged[ i ] );
 		}
-		expect( i ).to.equal( expectedItems.length );
+		expect( iterator.next() ).to.have.property( 'done' ).that.is.true;
 	} );
 
-	it( 'should merge characters when iterating over the range using next', () => {
-		let start = new Position( root, [ 0 ] ); // beginning of root
-		let end = new Position( root, [ 2 ] ); // ending of root
+	it( 'should merge characters when iterating over the range using previous', () => {
+		let start = new Position( root, [ 1 ] );
+		let end = new Position( root, [ 1, 4 ] );
 		let range = new Range( start, end );
 
-		let iterator = new PositionIterator( range, range.start, true );
-		let step = iterator.next();
-		let i = 0;
+		let iterator = new PositionIterator( range, range.end, true );
 
-		while ( !step.done ) {
-			expect( step.value ).to.deep.equal( expectedItemsMerged[ i ] );
-			step = iterator.next();
-			i++;
+		for ( let i = 6; i >= 2; i-- ) {
+			expectItem( iterator.previous(), expectedItemsMerged[ i ] );
 		}
+		expect( iterator.previous() ).to.have.property( 'done' ).that.is.true;
+	} );
+
+	it( 'should respect boundaries when iterating using next and merging characters', () => {
+		let start = new Position( root, [ 1, 0 ] );
+		let end = new Position( root, [ 1, 1 ] );
+		let range = new Range( start, end );
 
-		expect( i ).to.equal( expectedItemsMerged.length );
+		let iterator = new PositionIterator( range, range.start, true );
+		let val = iterator.next();
+
+		expect( val.done ).to.be.false;
+		expect( val.value.node.text ).to.equal( 'b' );
+
+		val = iterator.next();
+		expect( val.done ).to.be.true;
 	} );
 
-	it( 'should merge characters when iterating over the range using previous', () => {
-		let start = new Position( root, [ 0 ] ); // beginning of root
-		let end = new Position( root, [ 2 ] ); // ending of root
+	it( 'should respect boundaries when iterating using previous and merging characters', () => {
+		let start = new Position( root, [ 1, 1 ] );
+		let end = new Position( root, [ 1, 2 ] );
 		let range = new Range( start, end );
 
 		let iterator = new PositionIterator( range, range.end, true );
-		let step = iterator.previous();
-		let i = expectedItemsMerged.length;
+		let val = iterator.previous();
 
-		while ( !step.done ) {
-			i--;
-			expect( step.value ).to.deep.equal( expectedItemsMerged[ i ] );
-			step = iterator.previous();
-		}
+		expect( val.done ).to.be.false;
+		expect( val.value.node.text ).to.equal( 'a' );
 
-		expect( i ).to.equal( 0 );
+		val = iterator.previous();
+		expect( val.done ).to.be.true;
 	} );
 } );