8
0
فهرست منبع

Refactored model utils in tests.

Oskar Wrobel 9 سال پیش
والد
کامیت
f7e4d536de

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

@@ -418,7 +418,9 @@ describe( 'model test utils', () => {
 			data: '<a foo="1" bar="true" car="x y"><b x="y"></b></a>',
 			data: '<a foo="1" bar="true" car="x y"><b x="y"></b></a>',
 			output: '<a bar="true" car="x y" foo="1"><b x="y"></b></a>',
 			output: '<a bar="true" car="x y" foo="1"><b x="y"></b></a>',
 			check( a ) {
 			check( a ) {
+				expect( a.getAttribute( 'bar' ) ).to.equal( 'true' );
 				expect( a.getAttribute( 'car' ) ).to.equal( 'x y' );
 				expect( a.getAttribute( 'car' ) ).to.equal( 'x y' );
+				expect( a.getAttribute( 'foo' ) ).to.equal( '1' );
 			}
 			}
 		} );
 		} );
 
 

+ 5 - 5
packages/ckeditor5-engine/tests/_utils/model.js

@@ -75,7 +75,7 @@ getData._stringify = stringify;
  * @param {Object} options
  * @param {Object} options
  * @param {String} [options.rootName='main'] Root name where parsed data will be stored. If not provided, default `main`
  * @param {String} [options.rootName='main'] Root name where parsed data will be stored. If not provided, default `main`
  * name will be used.
  * name will be used.
- * @param {Array<Object>} [options.selectionAttributes] List of attributes which will be passed to selection.
+ * @param {Array<Object>} [options.selectionAttributes] List of attributes which will be passed to the selection.
  * @param {Boolean} [options.lastRangeBackward=false] If set to true last range will be added as backward.
  * @param {Boolean} [options.lastRangeBackward=false] If set to true last range will be added as backward.
  * @param {String} [options.batchType='transparent'] Batch type used for inserting elements. See {@link engine.model.Batch#type}.
  * @param {String} [options.batchType='transparent'] Batch type used for inserting elements. See {@link engine.model.Batch#type}.
  */
  */
@@ -104,7 +104,7 @@ export function setData( document, data, options = {} ) {
 	document.enqueueChanges( () => {
 	document.enqueueChanges( () => {
 		// Replace existing model in document by new one.
 		// Replace existing model in document by new one.
 		document.batch( options.batchType || 'transparent' )
 		document.batch( options.batchType || 'transparent' )
-			.remove( Range.createFromElement( modelRoot ) )
+			.remove( Range.createIn( modelRoot ) )
 			.insert( Position.createAt( modelRoot, 0 ), modelDocumentFragment );
 			.insert( Position.createAt( modelRoot, 0 ), modelDocumentFragment );
 
 
 		// Clean up previous document selection.
 		// Clean up previous document selection.
@@ -165,12 +165,12 @@ export function stringify( node, selectionOrPositionOrRange = null ) {
 
 
 	// Create a range witch wraps passed node.
 	// Create a range witch wraps passed node.
 	if ( node instanceof RootElement || node instanceof ModelDocumentFragment ) {
 	if ( node instanceof RootElement || node instanceof ModelDocumentFragment ) {
-		range = Range.createFromElement( node );
+		range = Range.createIn( node );
 	} else {
 	} else {
 		// Node is detached - create new document fragment.
 		// Node is detached - create new document fragment.
 		if ( !node.parent ) {
 		if ( !node.parent ) {
 			const fragment = new ModelDocumentFragment( node );
 			const fragment = new ModelDocumentFragment( node );
-			range = Range.createFromElement( fragment );
+			range = Range.createIn( fragment );
 		} else {
 		} else {
 			range = new Range(
 			range = new Range(
 				Position.createBefore( node ),
 				Position.createBefore( node ),
@@ -227,8 +227,8 @@ export function stringify( node, selectionOrPositionOrRange = null ) {
  * @param {String} data HTML-like string to be parsed.
  * @param {String} data HTML-like string to be parsed.
  * @param {engine.model.schema} schema Schema instance uses by converters for element validation.
  * @param {engine.model.schema} schema Schema instance uses by converters for element validation.
  * @param {Object} options Additional configuration.
  * @param {Object} options Additional configuration.
+ * @param {Array<Object>} [options.selectionAttributes] List of attributes which will be passed to the selection.
  * @param {Boolean} [options.lastRangeBackward=false] If set to true last range will be added as backward.
  * @param {Boolean} [options.lastRangeBackward=false] If set to true last range will be added as backward.
- * @param {Array<Object>} [options.selectionAttributes] List of attributes which will be passed to selection.
  * @returns {engine.model.Element|engine.model.Text|engine.model.DocumentFragment|Object} Returns parsed model node or
  * @returns {engine.model.Element|engine.model.Text|engine.model.DocumentFragment|Object} Returns parsed model node or
  * object with two fields `model` and `selection` when selection ranges were included in data to parse.
  * object with two fields `model` and `selection` when selection ranges were included in data to parse.
  */
  */

+ 6 - 6
packages/ckeditor5-engine/tests/conversion/model-to-view-converters.js

@@ -92,7 +92,7 @@ describe( 'insertText', () => {
 		modelRoot.appendChildren( new ModelText( 'நிலைக்கு' ) );
 		modelRoot.appendChildren( new ModelText( 'நிலைக்கு' ) );
 		dispatcher.on( 'insert:$text', insertText() );
 		dispatcher.on( 'insert:$text', insertText() );
 
 
-		dispatcher.convertInsert( ModelRange.createFromElement( modelRoot ) );
+		dispatcher.convertInsert( ModelRange.createIn( modelRoot ) );
 
 
 		expect( viewToString( viewRoot ) ).to.equal( '<div>நிலைக்கு</div>' );
 		expect( viewToString( viewRoot ) ).to.equal( '<div>நிலைக்கு</div>' );
 	} );
 	} );
@@ -294,13 +294,13 @@ describe( 'wrap/unwrap', () => {
 		dispatcher.on( 'addAttribute:bold', wrap( viewB ) );
 		dispatcher.on( 'addAttribute:bold', wrap( viewB ) );
 		dispatcher.on( 'removeAttribute:bold', unwrap( viewB ) );
 		dispatcher.on( 'removeAttribute:bold', unwrap( viewB ) );
 
 
-		dispatcher.convertInsert( ModelRange.createFromElement( modelRoot ) );
+		dispatcher.convertInsert( ModelRange.createIn( modelRoot ) );
 
 
 		expect( viewToString( viewRoot ) ).to.equal( '<div><p>நி<b>லைக்</b>கு</p></div>' );
 		expect( viewToString( viewRoot ) ).to.equal( '<div><p>நி<b>லைக்</b>கு</p></div>' );
 
 
-		modelWriter.removeAttribute( ModelRange.createFromElement( modelElement ), 'bold' );
+		modelWriter.removeAttribute( ModelRange.createIn( modelElement ), 'bold' );
 
 
-		dispatcher.convertAttribute( 'removeAttribute', ModelRange.createFromElement( modelElement ), 'bold', true, null );
+		dispatcher.convertAttribute( 'removeAttribute', ModelRange.createIn( modelElement ), 'bold', true, null );
 
 
 		expect( viewToString( viewRoot ) ).to.equal( '<div><p>நிலைக்கு</p></div>' );
 		expect( viewToString( viewRoot ) ).to.equal( '<div><p>நிலைக்கு</p></div>' );
 	} );
 	} );
@@ -344,7 +344,7 @@ describe( 'move', () => {
 		dispatcher.on( 'insert:$text', insertText() );
 		dispatcher.on( 'insert:$text', insertText() );
 		dispatcher.on( 'move', move() );
 		dispatcher.on( 'move', move() );
 
 
-		dispatcher.convertInsert( ModelRange.createFromElement( modelRoot ) );
+		dispatcher.convertInsert( ModelRange.createIn( modelRoot ) );
 
 
 		modelWriter.move(
 		modelWriter.move(
 			ModelRange.createFromParentsAndOffsets( modelDivA, 2, modelDivA, 6 ),
 			ModelRange.createFromParentsAndOffsets( modelDivA, 2, modelDivA, 6 ),
@@ -395,7 +395,7 @@ describe( 'remove', () => {
 		dispatcher.on( 'insert:$text', insertText() );
 		dispatcher.on( 'insert:$text', insertText() );
 		dispatcher.on( 'remove', remove() );
 		dispatcher.on( 'remove', remove() );
 
 
-		dispatcher.convertInsert( ModelRange.createFromElement( modelRoot ) );
+		dispatcher.convertInsert( ModelRange.createIn( modelRoot ) );
 
 
 		modelWriter.move(
 		modelWriter.move(
 			ModelRange.createFromParentsAndOffsets( modelDiv, 0, modelDiv, 6 ),
 			ModelRange.createFromParentsAndOffsets( modelDiv, 0, modelDiv, 6 ),

+ 4 - 3
packages/ckeditor5-engine/tests/editingcontroller.js

@@ -128,7 +128,8 @@ describe( 'EditingController', () => {
 			const modelData = new ModelDocumentFragment( parse(
 			const modelData = new ModelDocumentFragment( parse(
 				'<paragraph>foo</paragraph>' +
 				'<paragraph>foo</paragraph>' +
 				'<paragraph></paragraph>' +
 				'<paragraph></paragraph>' +
-				'<paragraph>bar</paragraph>'
+				'<paragraph>bar</paragraph>',
+				model.schema
 			)._children );
 			)._children );
 
 
 			model.enqueueChanges( () => {
 			model.enqueueChanges( () => {
@@ -174,7 +175,7 @@ describe( 'EditingController', () => {
 					expect( getModelData( model ) ).to.equal(
 					expect( getModelData( model ) ).to.equal(
 						'<paragraph>foo</paragraph>' +
 						'<paragraph>foo</paragraph>' +
 						'<paragraph></paragraph>' +
 						'<paragraph></paragraph>' +
-						'<paragraph>b<selection>a</selection>r</paragraph>' );
+						'<paragraph>b[a]r</paragraph>' );
 					done();
 					done();
 				} );
 				} );
 			} );
 			} );
@@ -246,7 +247,7 @@ describe( 'EditingController', () => {
 			editing.destroy();
 			editing.destroy();
 
 
 			model.enqueueChanges( () => {
 			model.enqueueChanges( () => {
-				const modelData = parse( '<paragraph>foo</paragraph>' ).getChild( 0 );
+				const modelData = parse( '<paragraph>foo</paragraph>', model.schema ).getChild( 0 );
 				model.batch().insert( ModelPosition.createAt( model.getRoot(), 0 ), modelData );
 				model.batch().insert( ModelPosition.createAt( model.getRoot(), 0 ), modelData );
 			} );
 			} );
 
 

+ 54 - 31
packages/ckeditor5-engine/tests/model/composer/deletecontents.js

@@ -46,11 +46,13 @@ describe( 'Delete utils', () => {
 				'f[]o'
 				'f[]o'
 			);
 			);
 
 
-			test(
-				'xdeletes single character (backward selection)',
-				'f<selection backward>o]o',
-				'f[]o'
-			);
+			it( 'deletes single character (backward selection)' , () => {
+				setData( document, 'f[o]o', { lastRangeBackward: true } );
+
+				deleteContents( document.batch(), document.selection );
+
+				expect( getData( document ) ).to.equal( 'f[]o' );
+			} );
 
 
 			test(
 			test(
 				'deletes whole text',
 				'deletes whole text',
@@ -85,29 +87,49 @@ describe( 'Delete utils', () => {
 		} );
 		} );
 
 
 		describe( 'with text attributes', () => {
 		describe( 'with text attributes', () => {
-			test(
-				'deletes characters (first half has attrs)',
-				'<$text bold="true">fo<selection bold=true>o</$text>b]ar',
-				'<$text bold="true">fo</$text><selection bold=true />ar'
-			);
+			it( 'deletes characters (first half has attrs)', () => {
+				setData( document, '<$text bold="true">fo[o</$text>b]ar', { selectionAttributes: {
+					bold: true
+				} } );
 
 
-			test(
-				'deletes characters (2nd half has attrs)',
-				'fo<selection bold=true>o<$text bold="true">b]ar</$text>',
-				'fo[]<$text bold="true">ar</$text>'
-			);
+				deleteContents( document.batch(), document.selection );
 
 
-			test(
-				'clears selection attrs when emptied content',
-				'<p>x</p><p><selection bold=true><$text bold="true">foo</$text>]</p><p>y</p>',
-				'<p>x</p><p>[]</p><p>y</p>'
-			);
+				expect( getData( document ) ).to.equal( '<$text bold="true">fo</$text>[]ar' );
+				expect( document.selection.getAttribute( 'bold' ) ).to.equal( 'true' );
+			} );
 
 
-			test(
-				'leaves selection attributes when text contains them',
-				'<p>x<$text bold="true">a<selection bold=true>foo]b</$text>y</p>',
-				'<p>x<$text bold="true">a<selection bold=true />b</$text>y</p>'
-			);
+			it( 'deletes characters (2nd half has attrs)', () => {
+				setData( document, 'fo[o<$text bold="true">b]ar</$text>', { selectionAttributes: {
+					bold: true
+				} } );
+
+				deleteContents( document.batch(), document.selection );
+
+				expect( getData( document ) ).to.equal( 'fo[]<$text bold="true">ar</$text>' );
+				expect( document.selection.getAttribute( 'bold' ) ).to.undefined;
+			} );
+
+			it( 'clears selection attrs when emptied content', () => {
+				setData( document, '<p>x</p><p>[<$text bold="true">foo</$text>]</p><p>y</p>', { selectionAttributes: {
+					bold: true
+				} } );
+
+				deleteContents( document.batch(), document.selection );
+
+				expect( getData( document ) ).to.equal( '<p>x</p><p>[]</p><p>y</p>' );
+				expect( document.selection.getAttribute( 'bold' ) ).to.undefined;
+			} );
+
+			it( 'leaves selection attributes when text contains them', () => {
+				setData( document, '<p>x<$text bold="true">a[foo]b</$text>y</p>', { selectionAttributes: {
+					bold: true
+				} } );
+
+				deleteContents( document.batch(), document.selection );
+
+				expect( getData( document ) ).to.equal( '<p>x<$text bold="true">a[]b</$text>y</p>' );
+				expect( document.selection.getAttribute( 'bold' ) ).to.equal( 'true' );
+			} );
 		} );
 		} );
 
 
 		// Note: The algorithm does not care what kind of it's merging as it knows nothing useful about these elements.
 		// Note: The algorithm does not care what kind of it's merging as it knows nothing useful about these elements.
@@ -154,12 +176,13 @@ describe( 'Delete utils', () => {
 				{ merge: true }
 				{ merge: true }
 			);
 			);
 
 
-			test(
-				'merges second element into the first one (different name, backward selection)',
-				'<p>x</p><h1>fo<selection backward>o</h1><p>b]ar</p><p>y</p>',
-				'<p>x</p><h1>fo[]ar</h1><p>y</p>',
-				{ merge: true }
-			);
+			it( 'merges second element into the first one (different name, backward selection)', () => {
+				setData( document, '<p>x</p><h1>fo[o</h1><p>b]ar</p><p>y</p>', { lastRangeBackward: true } );
+
+				deleteContents( document.batch(), document.selection, { merge: true } );
+
+				expect( getData( document ) ).to.equal( '<p>x</p><h1>fo[]ar</h1><p>y</p>' );
+			} );
 
 
 			test(
 			test(
 				'merges second element into the first one (different attrs)',
 				'merges second element into the first one (different attrs)',

+ 153 - 113
packages/ckeditor5-engine/tests/model/composer/modifyselection.js

@@ -63,12 +63,14 @@ describe( 'Delete utils', () => {
 					'<p>f[o]o</p>'
 					'<p>f[o]o</p>'
 				);
 				);
 
 
-				test(
-					'extends one character backward',
-					'<p>fo[]o</p>',
-					'<p>f<selection backward>o]o</p>',
-					{ direction: 'backward' }
-				);
+				it( 'extends one character backward', () => {
+					setData( document, '<p>fo[]o</p>', { lastRangeBackward: true } );
+
+					modifySelection( document.selection, { direction: 'backward' } );
+
+					expect( stringify( document.getRoot(), document.selection ) ).to.equal( '<p>f[o]o</p>' );
+					expect( document.selection.isBackward ).to.true;
+				} );
 
 
 				test(
 				test(
 					'extends one character forward (non-collapsed)',
 					'extends one character forward (non-collapsed)',
@@ -76,12 +78,14 @@ describe( 'Delete utils', () => {
 					'<p>f[oo]bar</p>'
 					'<p>f[oo]bar</p>'
 				);
 				);
 
 
-				test(
-					'extends one character backward (non-collapsed)',
-					'<p>foob<selection backward>a]r</p>',
-					'<p>foo<selection backward>ba]r</p>',
-					{ direction: 'backward' }
-				);
+				it( 'extends one character backward (non-collapsed)', () => {
+					setData( document, '<p>foob[a]r</p>', { lastRangeBackward: true } );
+
+					modifySelection( document.selection, { direction: 'backward' } );
+
+					expect( stringify( document.getRoot(), document.selection ) ).to.equal( '<p>foo[ba]r</p>' );
+					expect( document.selection.isBackward ).to.true;
+				} );
 
 
 				test(
 				test(
 					'extends to element boundary',
 					'extends to element boundary',
@@ -89,12 +93,14 @@ describe( 'Delete utils', () => {
 					'<p>fo[o]</p>'
 					'<p>fo[o]</p>'
 				);
 				);
 
 
-				test(
-					'extends to element boundary (backward)',
-					'<p>f[]oo</p>',
-					'<p><selection backward>f]oo</p>',
-					{ direction: 'backward' }
-				);
+				it( 'extends to element boundary (backward)', () => {
+					setData( document, '<p>f[]oo</p>' );
+
+					modifySelection( document.selection, { direction: 'backward' } );
+
+					expect( stringify( document.getRoot(), document.selection ) ).to.equal( '<p>[f]oo</p>' );
+					expect( document.selection.isBackward ).to.true;
+				} );
 
 
 				test(
 				test(
 					'shrinks forward selection (to collapsed)',
 					'shrinks forward selection (to collapsed)',
@@ -103,11 +109,14 @@ describe( 'Delete utils', () => {
 					{ direction: 'backward' }
 					{ direction: 'backward' }
 				);
 				);
 
 
-				test(
-					'shrinks backward selection (to collapsed)',
-					'<p>foo<selection backward>b]ar</p>',
-					'<p>foob[]ar</p>'
-				);
+				it( 'shrinks backward selection (to collapsed)', () => {
+					setData( document, '<p>foo[b]ar</p>', { lastRangeBackward: true } );
+
+					modifySelection( document.selection );
+
+					expect( stringify( document.getRoot(), document.selection ) ).to.equal( '<p>foob[]ar</p>' );
+					expect( document.selection.isBackward ).to.false;
+				} );
 
 
 				test(
 				test(
 					'extends one element forward',
 					'extends one element forward',
@@ -121,12 +130,14 @@ describe( 'Delete utils', () => {
 					'<p>f[<img>x</img>]oo</p>'
 					'<p>f[<img>x</img>]oo</p>'
 				);
 				);
 
 
-				test(
-					'extends one element backward',
-					'<p>fo<img></img>[]o</p>',
-					'<p>fo<selection backward><img></img>]o</p>',
-					{ direction: 'backward' }
-				);
+				it( 'extends one element backward', () => {
+					setData( document, '<p>fo<img></img>[]o</p>' );
+
+					modifySelection( document.selection, { direction: 'backward' } );
+
+					expect( stringify( document.getRoot(), document.selection ) ).to.equal( '<p>fo[<img></img>]o</p>' );
+					expect( document.selection.isBackward ).to.true;
+				} );
 
 
 				test(
 				test(
 					'unicode support - combining mark forward',
 					'unicode support - combining mark forward',
@@ -134,12 +145,14 @@ describe( 'Delete utils', () => {
 					'<p>foo[b̂]ar</p>'
 					'<p>foo[b̂]ar</p>'
 				);
 				);
 
 
-				test(
-					'unicode support - combining mark backward',
-					'<p>foob̂[]ar</p>',
-					'<p>foo<selection backward>b̂]ar</p>',
-					{ direction: 'backward' }
-				);
+				it( 'unicode support - combining mark backward', () => {
+					setData( document, '<p>foob̂[]ar</p>' );
+
+					modifySelection( document.selection, { direction: 'backward' } );
+
+					expect( stringify( document.getRoot(), document.selection ) ).to.equal( '<p>foo[b̂]ar</p>' );
+					expect( document.selection.isBackward ).to.true;
+				} );
 
 
 				test(
 				test(
 					'unicode support - combining mark multiple',
 					'unicode support - combining mark multiple',
@@ -147,12 +160,14 @@ describe( 'Delete utils', () => {
 					'<p>fo[o̻̐ͩ]bar</p>'
 					'<p>fo[o̻̐ͩ]bar</p>'
 				);
 				);
 
 
-				test(
-					'unicode support - combining mark multiple backward',
-					'<p>foo̻̐ͩ[]bar</p>',
-					'<p>fo<selection backward>o̻̐ͩ]bar</p>',
-					{ direction: 'backward' }
-				);
+				it( 'unicode support - combining mark multiple backward', () => {
+					setData( document, '<p>foo̻̐ͩ[]bar</p>' );
+
+					modifySelection( document.selection, { direction: 'backward' } );
+
+					expect( stringify( document.getRoot(), document.selection ) ).to.equal( '<p>fo[o̻̐ͩ]bar</p>' );
+					expect( document.selection.isBackward ).to.true;
+				} );
 
 
 				test(
 				test(
 					'unicode support - combining mark to the end',
 					'unicode support - combining mark to the end',
@@ -166,12 +181,14 @@ describe( 'Delete utils', () => {
 					'<p>[\uD83D\uDCA9]</p>'
 					'<p>[\uD83D\uDCA9]</p>'
 				);
 				);
 
 
-				test(
-					'unicode support - surrogate pairs backward',
-					'<p>\uD83D\uDCA9[]</p>',
-					'<p><selection backward>\uD83D\uDCA9]</p>',
-					{ direction: 'backward' }
-				);
+				it( 'unicode support - surrogate pairs backward', () => {
+					setData( document, '<p>\uD83D\uDCA9[]</p>' );
+
+					modifySelection( document.selection, { direction: 'backward' } );
+
+					expect( stringify( document.getRoot(), document.selection ) ).to.equal( '<p>[\uD83D\uDCA9]</p>' );
+					expect( document.selection.isBackward ).to.true;
+				} );
 			} );
 			} );
 
 
 			describe( 'beyond element', () => {
 			describe( 'beyond element', () => {
@@ -181,12 +198,14 @@ describe( 'Delete utils', () => {
 					'<p>[</p><p>]</p><p></p>'
 					'<p>[</p><p>]</p><p></p>'
 				);
 				);
 
 
-				test(
-					'extends over boundary of empty elements (backward)',
-					'<p></p><p></p><p>[]</p>',
-					'<p></p><p><selection backward></p><p>]</p>',
-					{ direction: 'backward' }
-				);
+				it( 'extends over boundary of empty elements (backward)', () => {
+					setData( document, '<p></p><p></p><p>[]</p>' );
+
+					modifySelection( document.selection, { direction: 'backward' } );
+
+					expect( stringify( document.getRoot(), document.selection ) ).to.equal( '<p></p><p>[</p><p>]</p>' );
+					expect( document.selection.isBackward ).to.true;
+				} );
 
 
 				test(
 				test(
 					'extends over boundary of non-empty elements',
 					'extends over boundary of non-empty elements',
@@ -194,12 +213,14 @@ describe( 'Delete utils', () => {
 					'<p>a[</p><p>]bcd</p>'
 					'<p>a[</p><p>]bcd</p>'
 				);
 				);
 
 
-				test(
-					'extends over boundary of non-empty elements (backward)',
-					'<p>a</p><p>[]bcd</p>',
-					'<p>a<selection backward></p><p>]bcd</p>',
-					{ direction: 'backward' }
-				);
+				it( 'extends over boundary of non-empty elements (backward)', () => {
+					setData( document, '<p>a</p><p>[]bcd</p>' );
+
+					modifySelection( document.selection, { direction: 'backward' } );
+
+					expect( stringify( document.getRoot(), document.selection ) ).to.equal( '<p>a[</p><p>]bcd</p>' );
+					expect( document.selection.isBackward ).to.true;
+				} );
 
 
 				test(
 				test(
 					'extends over character after boundary',
 					'extends over character after boundary',
@@ -207,12 +228,14 @@ describe( 'Delete utils', () => {
 					'<p>a[</p><p>b]cd</p>'
 					'<p>a[</p><p>b]cd</p>'
 				);
 				);
 
 
-				test(
-					'extends over character after boundary (backward)',
-					'<p>abc<selection backward></p><p>]d</p>',
-					'<p>ab<selection backward>c</p><p>]d</p>',
-					{ direction: 'backward' }
-				);
+				it( 'extends over character after boundary (backward)', () => {
+					setData( document, '<p>abc[</p><p>]d</p>', { lastRangeBackward: true } );
+
+					modifySelection( document.selection, { direction: 'backward' } );
+
+					expect( stringify( document.getRoot(), document.selection ) ).to.equal( '<p>ab[c</p><p>]d</p>' );
+					expect( document.selection.isBackward ).to.true;
+				} );
 
 
 				test(
 				test(
 					'extends over boundary when next element has nested elements',
 					'extends over boundary when next element has nested elements',
@@ -232,31 +255,41 @@ describe( 'Delete utils', () => {
 					'<p>a[</p>]bc'
 					'<p>a[</p>]bc'
 				);
 				);
 
 
-				test(
-					'extends over element when next node is a text (backward)',
-					'ab<p>[]c</p>',
-					'ab<selection backward><p>]c</p>',
-					{ direction: 'backward' }
-				);
+				it( 'extends over element when next node is a text (backward)', () => {
+					setData( document, 'ab<p>[]c</p>' );
 
 
-				test(
-					'shrinks over boundary of empty elements',
-					'<p><selection backward></p><p>]</p>',
-					'<p></p><p>[]</p>'
-				);
+					modifySelection( document.selection, { direction: 'backward' } );
 
 
-				test(
-					'shrinks over boundary of empty elements (backward)',
-					'<p>[</p><p>]</p>',
-					'<p>[]</p><p></p>',
-					{ direction: 'backward' }
-				);
+					expect( stringify( document.getRoot(), document.selection ) ).to.equal( 'ab[<p>]c</p>' );
+					expect( document.selection.isBackward ).to.true;
+				} );
 
 
-				test(
-					'shrinks over boundary of non-empty elements',
-					'<p>a<selection backward></p><p>]b</p>',
-					'<p>a</p><p>[]b</p>'
-				);
+				it( 'shrinks over boundary of empty elements', () => {
+					setData( document, '<p>[</p><p>]</p>', { lastRangeBackward: true } );
+
+					modifySelection( document.selection );
+
+					expect( stringify( document.getRoot(), document.selection ) ).to.equal( '<p></p><p>[]</p>' );
+					expect( document.selection.isBackward ).to.false;
+				} );
+
+				it( 'shrinks over boundary of empty elements (backward)', () => {
+					setData( document, '<p>[</p><p>]</p>' );
+
+					modifySelection( document.selection, { direction: 'backward' } );
+
+					expect( stringify( document.getRoot(), document.selection ) ).to.equal( '<p>[]</p><p></p>' );
+					expect( document.selection.isBackward ).to.false;
+				} );
+
+				it( 'shrinks over boundary of non-empty elements', () => {
+					setData( document, '<p>a[</p><p>]b</p>', { lastRangeBackward: true } );
+
+					modifySelection( document.selection );
+
+					expect( stringify( document.getRoot(), document.selection ) ).to.equal( '<p>a</p><p>[]b</p>' );
+					expect( document.selection.isBackward ).to.false;
+				} );
 
 
 				test(
 				test(
 					'shrinks over boundary of non-empty elements (backward)',
 					'shrinks over boundary of non-empty elements (backward)',
@@ -265,12 +298,14 @@ describe( 'Delete utils', () => {
 					{ direction: 'backward' }
 					{ direction: 'backward' }
 				);
 				);
 
 
-				test(
-					'updates selection attributes',
-					'<p><$text bold="true">foo</$text>[b]</p>',
-					'<p><$text bold="true>"foo</$text><selection bold=true />b</p>',
-					{ direction: 'backward' }
-				);
+				it( 'updates selection attributes', () => {
+					setData( document, '<p><$text bold="true">foo</$text>[b]</p>' );
+
+					modifySelection( document.selection, { direction: 'backward' } );
+
+					expect( stringify( document.getRoot(), document.selection ) ).to.equal( '<p><$text bold="true">foo</$text>[]b</p>' );
+					expect( document.selection.getAttribute( 'bold' ) ).to.equal( 'true' );
+				} );
 			} );
 			} );
 		} );
 		} );
 
 
@@ -296,12 +331,14 @@ describe( 'Delete utils', () => {
 				{ unit: 'codePoint' }
 				{ unit: 'codePoint' }
 			);
 			);
 
 
-			test(
-				'extends one user-perceived character backward - latin letters',
-				'<p>fo[]o</p>',
-				'<p>f<selection backward>o]o</p>',
-				{ unit: 'codePoint', direction: 'backward' }
-			);
+			it( 'extends one user-perceived character backward - latin letters', () => {
+				setData( document, '<p>fo[]o</p>' );
+
+				modifySelection( document.selection, { unit: 'codePoint', direction: 'backward' } );
+
+				expect( stringify( document.getRoot(), document.selection ) ).to.equal( '<p>f[o]o</p>' );
+				expect( document.selection.isBackward ).to.true;
+			} );
 
 
 			test(
 			test(
 				'unicode support - combining mark forward',
 				'unicode support - combining mark forward',
@@ -310,12 +347,14 @@ describe( 'Delete utils', () => {
 				{ unit: 'codePoint' }
 				{ unit: 'codePoint' }
 			);
 			);
 
 
-			test(
-				'unicode support - combining mark backward',
-				'<p>foob̂[]ar</p>',
-				'<p>foob<selection backward>̂]ar</p>',
-				{ unit: 'codePoint', direction: 'backward' }
-			);
+			it.skip( 'unicode support - combining mark backward', () => {
+				setData( document, '<p>foob[]̂ar</p>' );
+
+				modifySelection( document.selection, { unit: 'codePoint', direction: 'backward' } );
+
+				expect( stringify( document.getRoot(), document.selection ) ).to.equal( '<p>foob[]̂̂ar</p>' );
+				expect( document.selection.isBackward ).to.true;
+			} );
 
 
 			test(
 			test(
 				'unicode support - combining mark multiple',
 				'unicode support - combining mark multiple',
@@ -331,16 +370,18 @@ describe( 'Delete utils', () => {
 				{ unit: 'codePoint' }
 				{ unit: 'codePoint' }
 			);
 			);
 
 
-			test(
-				'unicode support surrogate pairs backward',
-				'<p>\uD83D\uDCA9[]</p>',
-				'<p><selection backward>\uD83D\uDCA9]</p>',
-				{ unit: 'codePoint', direction: 'backward' }
-			);
+			it( 'unicode support surrogate pairs backward', () => {
+				setData( document, '<p>\uD83D\uDCA9[]</p>' );
+
+				modifySelection( document.selection, { unit: 'codePoint', direction: 'backward' } );
+
+				expect( stringify( document.getRoot(), document.selection ) ).to.equal( '<p>[\uD83D\uDCA9]</p>' );
+				expect( document.selection.isBackward ).to.true;
+			} );
 		} );
 		} );
 	} );
 	} );
 
 
-	function test( title, input, output, options, isBackward ) {
+	function test( title, input, output, options ) {
 		it( title, () => {
 		it( title, () => {
 			input = input.normalize();
 			input = input.normalize();
 			output = output.normalize();
 			output = output.normalize();
@@ -354,7 +395,6 @@ describe( 'Delete utils', () => {
 			modifySelection( testSelection, options );
 			modifySelection( testSelection, options );
 
 
 			expect( stringify( document.getRoot(), testSelection ) ).to.equal( output );
 			expect( stringify( document.getRoot(), testSelection ) ).to.equal( output );
-			expect( document.selection.isBackward ).to.equal( !!isBackward );
 		} );
 		} );
 	}
 	}
 } );
 } );