8
0
Просмотр исходного кода

Merge branch 'master' into t/471

Oskar Wrobel 9 лет назад
Родитель
Сommit
4e336d793a
32 измененных файлов с 460 добавлено и 326 удалено
  1. 6 6
      packages/ckeditor5-engine/src/model/batch.js
  2. 2 2
      packages/ckeditor5-engine/src/model/delta/attributedelta.js
  3. 2 2
      packages/ckeditor5-engine/src/model/delta/insertdelta.js
  4. 4 4
      packages/ckeditor5-engine/src/model/delta/mergedelta.js
  5. 2 2
      packages/ckeditor5-engine/src/model/delta/movedelta.js
  6. 2 2
      packages/ckeditor5-engine/src/model/delta/removedelta.js
  7. 4 4
      packages/ckeditor5-engine/src/model/delta/renamedelta.js
  8. 4 4
      packages/ckeditor5-engine/src/model/delta/splitdelta.js
  9. 4 4
      packages/ckeditor5-engine/src/model/delta/unwrapdelta.js
  10. 3 3
      packages/ckeditor5-engine/src/model/delta/weakinsertdelta.js
  11. 4 4
      packages/ckeditor5-engine/src/model/delta/wrapdelta.js
  12. 9 80
      packages/ckeditor5-engine/src/model/liveselection.js
  13. 92 0
      packages/ckeditor5-engine/src/model/selection.js
  14. 3 1
      packages/ckeditor5-engine/src/view/document.js
  15. 1 1
      packages/ckeditor5-engine/src/view/node.js
  16. 58 4
      packages/ckeditor5-engine/tests/_utils-tests/model.js
  17. 33 15
      packages/ckeditor5-engine/tests/_utils-tests/view.js
  18. 34 23
      packages/ckeditor5-engine/tests/_utils/model.js
  19. 28 1
      packages/ckeditor5-engine/tests/_utils/view.js
  20. 1 1
      packages/ckeditor5-engine/tests/model/document/document.js
  21. 0 101
      packages/ckeditor5-engine/tests/model/liveselection.js
  22. 142 0
      packages/ckeditor5-engine/tests/model/selection.js
  23. 3 2
      packages/ckeditor5-engine/tests/view/document/document.js
  24. 11 0
      packages/ckeditor5-engine/tests/view/node.js
  25. 1 9
      packages/ckeditor5-engine/tests/view/writer/breakAt.js
  26. 1 7
      packages/ckeditor5-engine/tests/view/writer/breakrange.js
  27. 1 7
      packages/ckeditor5-engine/tests/view/writer/insert.js
  28. 0 11
      packages/ckeditor5-engine/tests/view/writer/move.js
  29. 0 5
      packages/ckeditor5-engine/tests/view/writer/remove.js
  30. 1 6
      packages/ckeditor5-engine/tests/view/writer/unwrap.js
  31. 4 9
      packages/ckeditor5-engine/tests/view/writer/wrap.js
  32. 0 6
      packages/ckeditor5-engine/tests/view/writer/wrapposition.js

+ 6 - 6
packages/ckeditor5-engine/src/model/batch.js

@@ -34,17 +34,17 @@ export default class Batch {
 	/**
 	 * Creates Batch instance. Not recommended to use directly, use {@link engine.model.Document#batch} instead.
 	 *
-	 * @param {engine.model.Document} doc Document which this Batch changes.
+	 * @param {engine.model.Document} document Document which this Batch changes.
 	 * @param {'transparent'|'default'} [type='default'] Type of the batch.
 	 */
-	constructor( doc, type = 'default' ) {
+	constructor( document, type = 'default' ) {
 		/**
 		 * Document which this batch changes.
 		 *
 		 * @readonly
-		 * @member {engine.model.Document} engine.model.Batch#doc
+		 * @member {engine.model.Document} engine.model.Batch#document
 		 */
-		this.doc = doc;
+		this.document = document;
 
 		/**
 		 * Array of deltas which compose this batch.
@@ -123,13 +123,13 @@ export default class Batch {
  *			this.addDelta( delta );
  *
  *			// Create operations which should be components of this delta.
- *			const operation = new InsertOperation( position, nodes, this.doc.version );
+ *			const operation = new InsertOperation( position, nodes, this.document.version );
  *
  *			// Add operation to the delta. It is important to add operation before applying it.
  *			delta.addOperation( operation );
  *
  *			// Remember to apply every operation, no magic, you need to do it manually.
- *			this.doc.applyOperation( operation );
+ *			this.document.applyOperation( operation );
  *
  *			// Make this method chainable.
  *			return this;

+ 2 - 2
packages/ckeditor5-engine/src/model/delta/attributedelta.js

@@ -130,9 +130,9 @@ register( 'removeAttr', function( key, nodeOrRange ) {
 
 function attribute( batch, key, value, nodeOrRange ) {
 	if ( nodeOrRange instanceof Range ) {
-		changeRange( batch, batch.doc, key, value, nodeOrRange );
+		changeRange( batch, batch.document, key, value, nodeOrRange );
 	} else {
-		changeNode( batch, batch.doc, key, value, nodeOrRange );
+		changeNode( batch, batch.document, key, value, nodeOrRange );
 	}
 }
 

+ 2 - 2
packages/ckeditor5-engine/src/model/delta/insertdelta.js

@@ -73,11 +73,11 @@ export default class InsertDelta extends Delta {
  */
 register( 'insert', function( position, nodes ) {
 	const delta = new InsertDelta();
-	const insert = new InsertOperation( position, nodes, this.doc.version );
+	const insert = new InsertOperation( position, nodes, this.document.version );
 
 	this.addDelta( delta );
 	delta.addOperation( insert );
-	this.doc.applyOperation( insert );
+	this.document.applyOperation( insert );
 
 	return this;
 } );

+ 4 - 4
packages/ckeditor5-engine/src/model/delta/mergedelta.js

@@ -107,14 +107,14 @@ register( 'merge', function( position ) {
 	const positionAfter = Position.createFromParentAndOffset( nodeAfter, 0 );
 	const positionBefore = Position.createFromParentAndOffset( nodeBefore, nodeBefore.getChildCount() );
 
-	const move = new MoveOperation( positionAfter, nodeAfter.getChildCount(), positionBefore, this.doc.version );
+	const move = new MoveOperation( positionAfter, nodeAfter.getChildCount(), positionBefore, this.document.version );
 	move.isSticky = true;
 	delta.addOperation( move );
-	this.doc.applyOperation( move );
+	this.document.applyOperation( move );
 
-	const remove = new RemoveOperation( position, 1, this.doc.version );
+	const remove = new RemoveOperation( position, 1, this.document.version );
 	delta.addOperation( remove );
-	this.doc.applyOperation( remove );
+	this.document.applyOperation( remove );
 
 	return this;
 } );

+ 2 - 2
packages/ckeditor5-engine/src/model/delta/movedelta.js

@@ -79,9 +79,9 @@ export default class MoveDelta extends Delta {
 }
 
 function addMoveOperation( batch, delta, sourcePosition, howMany, targetPosition ) {
-	const operation = new MoveOperation( sourcePosition, howMany, targetPosition, batch.doc.version );
+	const operation = new MoveOperation( sourcePosition, howMany, targetPosition, batch.document.version );
 	delta.addOperation( operation );
-	batch.doc.applyOperation( operation );
+	batch.document.applyOperation( operation );
 }
 
 /**

+ 2 - 2
packages/ckeditor5-engine/src/model/delta/removedelta.js

@@ -29,9 +29,9 @@ export default class RemoveDelta extends MoveDelta {
 }
 
 function addRemoveOperation( batch, delta, position, howMany ) {
-	const operation = new RemoveOperation( position, howMany, batch.doc.version );
+	const operation = new RemoveOperation( position, howMany, batch.document.version );
 	delta.addOperation( operation );
-	batch.doc.applyOperation( operation );
+	batch.document.applyOperation( operation );
 }
 
 /**

+ 4 - 4
packages/ckeditor5-engine/src/model/delta/renamedelta.js

@@ -36,7 +36,7 @@ export default class RenameDelta extends Delta {
 function apply( batch, delta, operation ) {
 	batch.addDelta( delta );
 	delta.addOperation( operation );
-	batch.doc.applyOperation( operation );
+	batch.document.applyOperation( operation );
 }
 
 /**
@@ -53,17 +53,17 @@ register( 'rename', function( newName, element ) {
 
 	apply(
 		this, delta,
-		new InsertOperation( Position.createAfter( element ), newElement, this.doc.version )
+		new InsertOperation( Position.createAfter( element ), newElement, this.document.version )
 	);
 
 	apply(
 		this, delta,
-		new MoveOperation( Position.createAt( element ), element.getChildCount(), Position.createAt( newElement ), this.doc.version )
+		new MoveOperation( Position.createAt( element ), element.getChildCount(), Position.createAt( newElement ), this.document.version )
 	);
 
 	apply(
 		this, delta,
-		new RemoveOperation( Position.createBefore( element ), 1, this.doc.version )
+		new RemoveOperation( Position.createBefore( element ), 1, this.document.version )
 	);
 
 	return this;

+ 4 - 4
packages/ckeditor5-engine/src/model/delta/splitdelta.js

@@ -111,21 +111,21 @@ register( 'split', function( position ) {
 
 	const copy = new Element( splitElement.name, splitElement._attrs );
 
-	const insert = new InsertOperation( Position.createAfter( splitElement ), copy, this.doc.version );
+	const insert = new InsertOperation( Position.createAfter( splitElement ), copy, this.document.version );
 
 	delta.addOperation( insert );
-	this.doc.applyOperation( insert );
+	this.document.applyOperation( insert );
 
 	const move = new MoveOperation(
 		position,
 		splitElement.getChildCount() - position.offset,
 		Position.createFromParentAndOffset( copy, 0 ),
-		this.doc.version
+		this.document.version
 	);
 	move.isSticky = true;
 
 	delta.addOperation( move );
-	this.doc.applyOperation( move );
+	this.document.applyOperation( move );
 
 	return this;
 } );

+ 4 - 4
packages/ckeditor5-engine/src/model/delta/unwrapdelta.js

@@ -80,16 +80,16 @@ register( 'unwrap', function( element ) {
 
 	let sourcePosition = Position.createFromParentAndOffset( element, 0 );
 
-	const move = new MoveOperation( sourcePosition, element.getChildCount(), Position.createBefore( element ), this.doc.version );
+	const move = new MoveOperation( sourcePosition, element.getChildCount(), Position.createBefore( element ), this.document.version );
 	move.isSticky = true;
 	delta.addOperation( move );
-	this.doc.applyOperation( move );
+	this.document.applyOperation( move );
 
 	// Computing new position because we moved some nodes before `element`.
 	// If we would cache `Position.createBefore( element )` we remove wrong node.
-	const remove = new RemoveOperation( Position.createBefore( element ), 1, this.doc.version );
+	const remove = new RemoveOperation( Position.createBefore( element ), 1, this.document.version );
 	delta.addOperation( remove );
-	this.doc.applyOperation( remove );
+	this.document.applyOperation( remove );
 
 	return this;
 } );

+ 3 - 3
packages/ckeditor5-engine/src/model/delta/weakinsertdelta.js

@@ -53,12 +53,12 @@ register( 'weakInsert', function( position, nodes ) {
 	nodes = new NodeList( nodes );
 
 	for ( let node of nodes._nodes ) {
-		node._attrs = new Map( this.doc.selection.getAttributes() );
+		node._attrs = new Map( this.document.selection.getAttributes() );
 	}
 
-	const operation = new InsertOperation( position, nodes, this.doc.version );
+	const operation = new InsertOperation( position, nodes, this.document.version );
 	delta.addOperation( operation );
-	this.doc.applyOperation( operation );
+	this.document.applyOperation( operation );
 
 	return this;
 } );

+ 4 - 4
packages/ckeditor5-engine/src/model/delta/wrapdelta.js

@@ -121,14 +121,14 @@ register( 'wrap', function( range, elementOrString ) {
 	const delta = new WrapDelta();
 	this.addDelta( delta );
 
-	let insert = new InsertOperation( range.end, element, this.doc.version );
+	let insert = new InsertOperation( range.end, element, this.document.version );
 	delta.addOperation( insert );
-	this.doc.applyOperation( insert );
+	this.document.applyOperation( insert );
 
 	let targetPosition = Position.createFromParentAndOffset( element, 0 );
-	let move = new MoveOperation( range.start, range.end.offset - range.start.offset, targetPosition, this.doc.version );
+	let move = new MoveOperation( range.start, range.end.offset - range.start.offset, targetPosition, this.document.version );
 	delta.addOperation( move );
-	this.doc.applyOperation( move );
+	this.document.applyOperation( move );
 
 	return this;
 } );

+ 9 - 80
packages/ckeditor5-engine/src/model/liveselection.js

@@ -44,14 +44,6 @@ export default class LiveSelection extends Selection {
 		 * @member {engine.model.Document} engine.model.Selection#_document
 		 */
 		this._document = document;
-
-		/**
-		 * List of attributes set on current selection.
-		 *
-		 * @protected
-		 * @member {Map} engine.model.LiveSelection#_attrs
-		 */
-		this._attrs = new Map();
 	}
 
 	/**
@@ -128,94 +120,37 @@ export default class LiveSelection extends Selection {
 	}
 
 	/**
-	 * Removes all attributes from the selection.
-	 *
-	 * @fires engine.model.LiveSelection#change:attribute
+	 * @inheritDoc
 	 */
 	clearAttributes() {
-		this._attrs.clear();
 		this._setStoredAttributesTo( new Map() );
-
-		this.fire( 'change:attribute' );
-	}
-
-	/**
-	 * 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.
-	 * @returns {*} Attribute value or `undefined`.
-	 */
-	getAttribute( key ) {
-		return this._attrs.get( key );
-	}
-
-	/**
-	 * Returns iterator that iterates over this selection attributes.
-	 *
-	 * @returns {Iterable.<*>}
-	 */
-	getAttributes() {
-		return this._attrs[ Symbol.iterator ]();
+		super.clearAttributes();
 	}
 
 	/**
-	 * Checks if the selection has an attribute for given key.
-	 *
-	 * @param {String} key Key of attribute to check.
-	 * @returns {Boolean} `true` if attribute with given key is set on selection, `false` otherwise.
-	 */
-	hasAttribute( key ) {
-		return this._attrs.has( key );
-	}
-
-	/**
-	 * Removes an attribute with given key from the selection.
-	 *
-	 * @fires engine.model.LiveSelection#change:attribute
-	 * @param {String} key Key of attribute to remove.
+	 * @inheritDoc
 	 */
 	removeAttribute( key ) {
-		this._attrs.delete( key );
 		this._removeStoredAttribute( key );
-
-		this.fire( 'change:attribute' );
+		super.removeAttribute( key );
 	}
 
 	/**
-	 * Sets attribute on the selection. If attribute with the same key already is set, it overwrites its values.
-	 *
-	 * @fires engine.model.LiveSelection#change:attribute
-	 * @param {String} key Key of attribute to set.
-	 * @param {*} value Attribute value.
+	 * @inheritDoc
 	 */
 	setAttribute( key, value ) {
-		this._attrs.set( key, value );
 		this._storeAttribute( key, value );
-
-		this.fire( 'change:attribute' );
+		super.setAttribute( key, value );
 	}
 
 	/**
-	 * Removes all attributes from the selection and sets given attributes.
-	 *
-	 * @fires engine.model.LiveSelection#change:attribute
-	 * @param {Iterable|Object} attrs Iterable object containing attributes to be set.
+	 * @inheritDoc
 	 */
 	setAttributesTo( attrs ) {
-		this._attrs = toMap( attrs );
-		this._setStoredAttributesTo( this._attrs );
-
-		this.fire( 'change:attribute' );
+		this._setStoredAttributesTo( toMap( attrs ) );
+		super.setAttributesTo( attrs );
 	}
 
-	/**
-	 * Creates and returns an instance of {@link engine.model.LiveSelection} that is a clone of given selection,
-	 * meaning that it has same ranges and same direction as it.
-	 *
-	 * @params {engine.model.Selection} otherSelection Selection to be cloned.
-	 * @returns {engine.model.LiveSelection} `LiveSelection` instance that is a clone of given selection.
-	 */
-
 	/**
 	 * @inheritDoc
 	 */
@@ -431,9 +366,3 @@ export default class LiveSelection extends Selection {
 		return storePrefix + key;
 	}
 }
-
-/**
- * Fired whenever selection attributes are changed.
- *
- * @event engine.model.LiveSelection#change:attribute
- */

+ 92 - 0
packages/ckeditor5-engine/src/model/selection.js

@@ -10,6 +10,7 @@ import Range from './range.js';
 import EmitterMixin from '../../utils/emittermixin.js';
 import CKEditorError from '../../utils/ckeditorerror.js';
 import mix from '../../utils/mix.js';
+import toMap from '../../utils/tomap.js';
 
 /**
  * `Selection` is a group of {@link engine.model.Range ranges} which has a direction specified by
@@ -37,6 +38,91 @@ export default class Selection {
 		 * @member {Array.<engine.model.Range>} engine.model.Selection#_ranges
 		 */
 		this._ranges = [];
+
+		/**
+		 * List of attributes set on current selection.
+		 *
+		 * @protected
+		 * @member {Map} engine.model.LiveSelection#_attrs
+		 */
+		this._attrs = new Map();
+	}
+
+	/**
+	 * 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.
+	 * @returns {*} Attribute value or `undefined`.
+	 */
+	getAttribute( key ) {
+		return this._attrs.get( key );
+	}
+
+	/**
+	 * Returns iterator that iterates over this selection attributes.
+	 *
+	 * @returns {Iterable.<*>}
+	 */
+	getAttributes() {
+		return this._attrs[ Symbol.iterator ]();
+	}
+
+	/**
+	 * Checks if the selection has an attribute for given key.
+	 *
+	 * @param {String} key Key of attribute to check.
+	 * @returns {Boolean} `true` if attribute with given key is set on selection, `false` otherwise.
+	 */
+	hasAttribute( key ) {
+		return this._attrs.has( key );
+	}
+
+	/**
+	 * Removes all attributes from the selection.
+	 *
+	 * @fires engine.model.Selection#change:attribute
+	 */
+	clearAttributes() {
+		this._attrs.clear();
+
+		this.fire( 'change:attribute' );
+	}
+
+	/**
+	 * Removes an attribute with given key from the selection.
+	 *
+	 * @fires engine.model.Selection#change:attribute
+	 * @param {String} key Key of attribute to remove.
+	 */
+	removeAttribute( key ) {
+		this._attrs.delete( key );
+
+		this.fire( 'change:attribute' );
+	}
+
+	/**
+	 * Sets attribute on the selection. If attribute with the same key already is set, it overwrites its values.
+	 *
+	 * @fires engine.model.Selection#change:attribute
+	 * @param {String} key Key of attribute to set.
+	 * @param {*} value Attribute value.
+	 */
+	setAttribute( key, value ) {
+		this._attrs.set( key, value );
+
+		this.fire( 'change:attribute' );
+	}
+
+	/**
+	 * Removes all attributes from the selection and sets given attributes.
+	 *
+	 * @fires engine.model.Selection#change:attribute
+	 * @param {Iterable|Object} attrs Iterable object containing attributes to be set.
+	 */
+	setAttributesTo( attrs ) {
+		this._attrs = toMap( attrs );
+
+		this.fire( 'change:attribute' );
 	}
 
 	/**
@@ -345,3 +431,9 @@ mix( Selection, EmitterMixin );
  *
  * @event engine.model.Selection#change:range
  */
+
+/**
+ * Fired whenever selection attributes are changed.
+ *
+ * @event engine.model.Selection#change:attribute
+ */

+ 3 - 1
packages/ckeditor5-engine/src/view/document.js

@@ -162,6 +162,8 @@ export default class Document {
 	 *		document.createRoot( 'body' );
 	 *		document.attachDomRoot( document.querySelector( 'body#editor' ) );
 	 *
+	 * In both cases, {@link engine.view.RootEditableElement#name element name} is always transformed to lower case.
+	 *
 	 * @param {Element|String} domRoot DOM root element or the tag name of view root element if the DOM element will be
 	 * attached later.
 	 * @param {String} [name='main'] Name of the root.
@@ -170,7 +172,7 @@ export default class Document {
 	createRoot( domRoot, name = 'main' ) {
 		const rootTag = typeof domRoot == 'string' ? domRoot : domRoot.tagName;
 
-		const viewRoot = new RootEditableElement( this, rootTag, name );
+		const viewRoot = new RootEditableElement( this, rootTag.toLowerCase(), name );
 
 		this.roots.set( name, viewRoot );
 

+ 1 - 1
packages/ckeditor5-engine/src/view/node.js

@@ -123,7 +123,7 @@ export default class Node {
 		const ancestors = [];
 		let parent = options.includeNode ? this : this.parent;
 
-		while ( parent !== null ) {
+		while ( parent ) {
 			ancestors[ options.parentFirst ? 'push' : 'unshift' ]( parent );
 			parent = parent.parent;
 		}

+ 58 - 4
packages/ckeditor5-engine/tests/_utils-tests/model.js

@@ -177,6 +177,10 @@ describe( 'model test utils', () => {
 				);
 			} );
 
+			it( 'writes only requested element', () => {
+				expect( stringify( elA ) ).to.equal( '<a></a>' );
+			} );
+
 			it( 'writes selection collapsed in an element', () => {
 				selection.collapse( root );
 
@@ -297,6 +301,42 @@ describe( 'model test utils', () => {
 	} );
 
 	describe( 'parse', () => {
+		test( 'creates empty DocumentFragment from empty string', {
+			data: '',
+			check( fragment ) {
+				expect( fragment ).to.be.instanceOf( DocumentFragment );
+			}
+		} );
+
+		test( 'creates empty DocumentFragment with selection', {
+			data: '<selection />',
+			check( fragment, selection ) {
+				expect( fragment ).to.be.instanceOf( DocumentFragment );
+				expect( fragment.getChildCount() ).to.equal( 0 );
+				expect( selection.rangeCount ).to.equal( 1 );
+				expect( selection.getFirstRange().isEqual( Range.createFromParentsAndOffsets( fragment, 0, fragment, 0 ) ) ).to.be.true;
+			}
+		} );
+
+		test( 'returns Element if range is around single element', {
+			data: '<selection><a></a></selection>',
+			check( el, selection ) {
+				const fragment = el.parent;
+				expect( el ).to.be.instanceOf( Element );
+				expect( fragment ).to.be.instanceOf( DocumentFragment );
+				expect( selection.rangeCount ).to.equal( 1 );
+				expect( selection.getFirstRange().isEqual( Range.createFromParentsAndOffsets( fragment, 0, fragment, 1 ) ) ).to.be.true;
+			}
+		} );
+
+		test( 'returns DocumentFragment when multiple elements on root', {
+			data: '<a></a><b></b>',
+			check( fragment ) {
+				expect( fragment ).to.be.instanceOf( DocumentFragment );
+				expect( fragment.getChildCount() ).to.equal( 2 );
+			}
+		} );
+
 		test( 'creates elements', {
 			data: '<a></a><b><c></c></b>'
 		} );
@@ -308,15 +348,15 @@ describe( 'model test utils', () => {
 		test( 'sets elements attributes', {
 			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>',
-			check( root ) {
-				expect( root.getChild( 0 ).getAttribute( 'car' ) ).to.equal( 'x y' );
+			check( a ) {
+				expect( a.getAttribute( 'car' ) ).to.equal( 'x y' );
 			}
 		} );
 
 		test( 'sets complex attributes', {
 			data: '<a foo={"a":1,"b":"c"}></a>',
-			check( root ) {
-				expect( root.getChild( 0 ).getAttribute( 'foo' ) ).to.have.property( 'a', 1 );
+			check( a ) {
+				expect( a.getAttribute( 'foo' ) ).to.have.property( 'a', 1 );
 			}
 		} );
 
@@ -329,6 +369,20 @@ describe( 'model test utils', () => {
 			}
 		} );
 
+		test( 'returns single parsed element', {
+			data: '<paragraph></paragraph>',
+			check( p ) {
+				expect( p instanceof Element ).to.be.true;
+			}
+		} );
+
+		test( 'returns DocumentFragment for multiple parsed elements', {
+			data: '<paragraph></paragraph><paragraph></paragraph>',
+			check( fragment ) {
+				expect( fragment instanceof DocumentFragment ).to.be.true;
+			}
+		} );
+
 		it( 'throws when unexpected closing tag', () => {
 			expect( () => {
 				parse( '<a><b></a></b>' );

+ 33 - 15
packages/ckeditor5-engine/tests/_utils-tests/view.js

@@ -311,6 +311,39 @@ describe( 'view test utils', () => {
 	} );
 
 	describe( 'parse', () => {
+		it( 'should return empty DocumentFragment for empty string', () => {
+			const fragment = parse( '' );
+
+			expect( fragment ).to.be.instanceOf( DocumentFragment );
+			expect( fragment.getChildCount() ).to.equal( 0 );
+		} );
+
+		it( 'should return empty DocumentFragment and Selection for string containing range only', () => {
+			const { view, selection } = parse( '[]' );
+
+			expect( view ).to.be.instanceOf( DocumentFragment );
+			expect( selection.rangeCount ).to.equal( 1 );
+			expect( selection.getFirstRange().isEqual( Range.createFromParentsAndOffsets( view, 0, view, 0 ) ) ).to.be.true;
+		} );
+
+		it( 'should return Element if range is around single element', () => {
+			const { view, selection } = parse( '[<b>foobar</b>]' );
+			const parent = view.parent;
+
+			expect( view ).to.be.instanceOf( Element );
+			expect( parent ).to.be.instanceOf( DocumentFragment );
+			expect( selection.rangeCount ).to.equal( 1 );
+			expect( selection.getFirstRange().isEqual( Range.createFromParentsAndOffsets( parent, 0, parent, 1 ) ) ).to.be.true;
+		} );
+
+		it( 'should create DocumentFragment when multiple elements on root', () => {
+			const view = parse( '<b></b><i></i>' );
+			expect( view ).to.be.instanceOf( DocumentFragment );
+			expect( view.getChildCount() ).to.equal( 2 );
+			expect( view.getChild( 0 ).isSimilar( new Element( 'b' ) ) ).to.be.true;
+			expect( view.getChild( 1 ).isSimilar( new Element( 'i' ) ) ).to.be.true;
+		} );
+
 		it( 'should parse text', () => {
 			const text = parse( 'foobar' );
 			expect( text ).to.be.instanceOf( Text );
@@ -369,14 +402,6 @@ describe( 'view test utils', () => {
 			expect( parsed2.isSimilar( attribute2 ) ).to.be.true;
 		} );
 
-		it( 'should create DocumentFragment when multiple elements on root', () => {
-			const view = parse( '<b></b><i></i>' );
-			expect( view ).to.be.instanceOf( DocumentFragment );
-			expect( view.getChildCount() ).to.equal( 2 );
-			expect( view.getChild( 0 ).isSimilar( new Element( 'b' ) ) ).to.be.true;
-			expect( view.getChild( 1 ).isSimilar( new Element( 'i' ) ) ).to.be.true;
-		} );
-
 		it( 'should paste nested elements and texts', () => {
 			const parsed = parse( '<container:p>foo<b:12>bar<i:25>qux</i:25></b:12></container:p>' );
 			expect( parsed.isSimilar( new ContainerElement( 'p' ) ) ).to.be.true;
@@ -473,13 +498,6 @@ describe( 'view test utils', () => {
 			expect( selection.focus.isEqual( ranges[ 2 ].start ) ).to.be.true;
 		} );
 
-		it( 'should return DocumentFragment if range is around single element', () => {
-			const { view, selection } = parse( '[<b>foobar</b>]' );
-			expect( view ).to.be.instanceOf( DocumentFragment );
-			expect( selection.rangeCount ).to.equal( 1 );
-			expect( selection.getFirstRange().isEqual( Range.createFromParentsAndOffsets( view, 0, view, 1 ) ) ).to.be.true;
-		} );
-
 		it( 'should throw when ranges order does not include all ranges', () => {
 			expect( () => {
 				parse( '{}foobar{}', { order: [ 1 ] } );

+ 34 - 23
packages/ckeditor5-engine/tests/_utils/model.js

@@ -76,34 +76,39 @@ setData._parse = parse;
  * @returns {String} HTML-like string representing the model.
  */
 export function stringify( node, selectionOrPositionOrRange = null ) {
-	let selection;
-	let document;
-
-	if ( node instanceof RootElement ) {
-		document = node.document;
-	} else if ( node instanceof Element || node instanceof Text ) {
-		// If root is Element or Text - wrap it with DocumentFragment.
-		node = new DocumentFragment( node );
-	}
+	let selection, range;
 
-	document = document || new Document();
+	if ( node instanceof RootElement || node instanceof DocumentFragment ) {
+		range = Range.createFromElement( node );
+	} else {
+		// Node is detached - create new document fragment.
+		if ( !node.parent ) {
+			const fragment = new DocumentFragment( node );
+			range = Range.createFromElement( fragment );
+		} else {
+			range = new Range(
+				Position.createBefore( node ),
+				Position.createAfter( node )
+			);
+		}
+	}
 
 	const walker = new TreeWalker( {
-		boundaries: Range.createFromElement( node )
+		boundaries: range
 	} );
 
 	if ( selectionOrPositionOrRange instanceof Selection ) {
 		selection = selectionOrPositionOrRange;
 	} else if ( selectionOrPositionOrRange instanceof Range ) {
-		selection = document.selection;
+		selection = new Selection();
 		selection.addRange( selectionOrPositionOrRange );
 	} else if ( selectionOrPositionOrRange instanceof Position ) {
-		selection = document.selection;
+		selection = new Selection();
 		selection.addRange( new Range( selectionOrPositionOrRange, selectionOrPositionOrRange ) );
 	}
 
 	let ret = '';
-	let lastPosition = Position.createFromParentAndOffset( node, 0 );
+	let lastPosition = Position.createFromPosition( range.start );
 	const withSelection = !!selection;
 
 	for ( let value of walker ) {
@@ -136,17 +141,18 @@ export function stringify( node, selectionOrPositionOrRange = null ) {
  * and `selection` when selection ranges were included in data to parse.
  */
 export function parse( data, options = {} ) {
-	let document, root;
+	let root, selection;
 	let withSelection = false;
 	const rootName = options.rootName || 'main';
 
 	if ( options.document ) {
-		document = options.document;
+		const document = options.document;
 		root = document.getRoot( rootName );
 		root.removeChildren( 0, root.getChildCount() );
+		selection = document.selection;
 	} else {
-		document = new Document();
-		root = document.createRoot( '$root', rootName );
+		root = new DocumentFragment();
+		selection = new Selection();
 	}
 
 	const path = [];
@@ -189,8 +195,8 @@ export function parse( data, options = {} ) {
 
 		collapsedSelection( token ) {
 			withSelection = true;
-			document.selection.collapse( root, 'END' );
-			document.selection.setAttributesTo( token.attributes );
+			selection.collapse( root, 'END' );
+			selection.setAttributesTo( token.attributes );
 		},
 
 		selectionStart( token ) {
@@ -206,14 +212,14 @@ export function parse( data, options = {} ) {
 			withSelection = true;
 			selectionEnd = Position.createFromParentAndOffset( root, root.getChildCount() );
 
-			document.selection.setRanges(
+			selection.setRanges(
 				[ new Range( selectionStart, selectionEnd ) ],
 				selectionAttributes.backward
 			);
 
 			delete selectionAttributes.backward;
 
-			document.selection.setAttributesTo( selectionAttributes );
+			selection.setAttributesTo( selectionAttributes );
 		}
 	};
 
@@ -229,10 +235,15 @@ export function parse( data, options = {} ) {
 		throw new Error( 'Parse error - missing selection end.' );
 	}
 
+	// If root DocumentFragment contains only one element - return that element.
+	if ( root instanceof DocumentFragment && root.getChildCount() == 1 ) {
+		root = root.getChild( 0 );
+	}
+
 	if ( withSelection ) {
 		return {
 			model: root,
-			selection: document.selection
+			selection: selection
 		};
 	}
 

+ 28 - 1
packages/ckeditor5-engine/tests/_utils/view.js

@@ -235,6 +235,17 @@ export function stringify( node, selectionOrPositionOrRange = null, options = {}
  *
  *		const { root, selection } = parse( `{foo}bar{baz}`, { lastRangeBackward: true } );
  *
+ * Other examples and edge cases:
+ *
+ *		// Returns empty DocumentFragment.
+ *		parse( '' );
+ *
+ *		// Returns empty DocumentFragment and collapsed selection.
+ *		const { root, selection } = parse( '[]' );
+ *
+ *		// Returns Element and selection that is placed inside of DocumentFragment containing that element.
+ *		const { root, selection } = parse( '[<a></a>]' );
+ *
  * @param {String} data HTML-like string to be parsed.
  * @param {Object} options
  * @param {Array.<Number>} [options.order] Array with order of parsed ranges added to returned
@@ -254,9 +265,20 @@ export function parse( data, options = {} ) {
 	const viewParser = new ViewParser();
 	const rangeParser = new RangeParser();
 
-	const view = viewParser.parse( data, options.rootElement );
+	let view = viewParser.parse( data, options.rootElement );
+
+	// If single Element or Text is returned - move it to the DocumentFragment.
+	if ( !options.rootElement && ( view instanceof ViewText || view instanceof ViewElement ) ) {
+		view = new ViewDocumentFragment( view );
+	}
+
 	const ranges = rangeParser.parse( view, options.order );
 
+	// If only one element is returned inside DocumentFragment - return that element.
+	if ( view instanceof ViewDocumentFragment && view.getChildCount() === 1 ) {
+		view = view.getChild( 0 );
+	}
+
 	// When ranges are present - return object containing view, and selection.
 	if ( ranges.length ) {
 		const selection = new Selection();
@@ -268,6 +290,11 @@ export function parse( data, options = {} ) {
 		};
 	}
 
+	// If single element is returned without selection - remove it from parent and return detached element.
+	if ( view.parent ) {
+		view.remove();
+	}
+
 	return view;
 }
 

+ 1 - 1
packages/ckeditor5-engine/tests/model/document/document.js

@@ -158,7 +158,7 @@ describe( 'Document', () => {
 			const batch = doc.batch();
 
 			expect( batch ).to.be.instanceof( Batch );
-			expect( batch ).to.have.property( 'doc' ).that.equals( doc );
+			expect( batch ).to.have.property( 'document' ).that.equals( doc );
 		} );
 
 		it( 'should set given batch type', () => {

+ 0 - 101
packages/ckeditor5-engine/tests/model/liveselection.js

@@ -445,14 +445,6 @@ describe( 'LiveSelection', () => {
 		} );
 
 		describe( 'setAttribute', () => {
-			it( 'should set given attribute on the selection', () => {
-				selection.setRanges( [ rangeInFullP ] );
-				selection.setAttribute( 'foo', 'bar' );
-
-				expect( selection.getAttribute( 'foo' ) ).to.equal( 'bar' );
-				expect( fullP.hasAttribute( LiveSelection._getStoreAttributeKey( 'foo' ) ) ).to.be.false;
-			} );
-
 			it( 'should store attribute if the selection is in empty node', () => {
 				selection.setRanges( [ rangeInEmptyP ] );
 				selection.setAttribute( 'foo', 'bar' );
@@ -461,61 +453,9 @@ describe( 'LiveSelection', () => {
 
 				expect( emptyP.getAttribute( LiveSelection._getStoreAttributeKey( 'foo' ) ) ).to.equal( 'bar' );
 			} );
-
-			it( 'should fire change:attribute event', () => {
-				let spy = sinon.spy();
-				selection.on( 'change:attribute', spy );
-
-				selection.setAttribute( 'foo', 'bar' );
-
-				expect( spy.called ).to.be.true;
-			} );
-		} );
-
-		describe( 'hasAttribute', () => {
-			it( 'should return true if element contains attribute with given key', () => {
-				selection.setRanges( [ rangeInFullP ] );
-				selection.setAttribute( 'foo', 'bar' );
-
-				expect( selection.hasAttribute( 'foo' ) ).to.be.true;
-			} );
-
-			it( 'should return false if element does not contain attribute with given key', () => {
-				expect( selection.hasAttribute( 'abc' ) ).to.be.false;
-			} );
-		} );
-
-		describe( 'getAttribute', () => {
-			it( 'should return undefined if element does not contain given attribute', () => {
-				expect( selection.getAttribute( 'abc' ) ).to.be.undefined;
-			} );
-		} );
-
-		describe( 'getAttributes', () => {
-			it( 'should return an iterator that iterates over all attributes set on the text fragment', () => {
-				selection.setRanges( [ rangeInFullP ] );
-				selection.setAttribute( 'foo', 'bar' );
-				selection.setAttribute( 'abc', 'xyz' );
-
-				let attrs = Array.from( selection.getAttributes() );
-
-				expect( attrs ).to.deep.equal( [ [ 'foo', 'bar' ], [ 'abc', 'xyz' ] ] );
-			} );
 		} );
 
 		describe( 'setAttributesTo', () => {
-			it( 'should remove all attributes set on element and set the given ones', () => {
-				selection.setRanges( [ rangeInFullP ] );
-				selection.setAttribute( 'abc', 'xyz' );
-				selection.setAttributesTo( { foo: 'bar' } );
-
-				expect( selection.getAttribute( 'foo' ) ).to.equal( 'bar' );
-				expect( selection.getAttribute( 'abc' ) ).to.be.undefined;
-
-				expect( fullP.hasAttribute( LiveSelection._getStoreAttributeKey( 'foo' ) ) ).to.be.false;
-				expect( fullP.hasAttribute( LiveSelection._getStoreAttributeKey( 'abc' ) ) ).to.be.false;
-			} );
-
 			it( 'should remove all stored attributes and store the given ones if the selection is in empty node', () => {
 				selection.setRanges( [ rangeInEmptyP ] );
 				selection.setAttribute( 'abc', 'xyz' );
@@ -527,15 +467,6 @@ describe( 'LiveSelection', () => {
 				expect( emptyP.getAttribute( LiveSelection._getStoreAttributeKey( 'foo' ) ) ).to.equal( 'bar' );
 				expect( emptyP.hasAttribute( LiveSelection._getStoreAttributeKey( 'abc' ) ) ).to.be.false;
 			} );
-
-			it( 'should fire change:attribute event', () => {
-				let spy = sinon.spy();
-				selection.on( 'change:attribute', spy );
-
-				selection.setAttributesTo( { foo: 'bar' } );
-
-				expect( spy.called ).to.be.true;
-			} );
 		} );
 
 		describe( 'removeAttribute', () => {
@@ -558,32 +489,9 @@ describe( 'LiveSelection', () => {
 
 				expect( emptyP.hasAttribute( LiveSelection._getStoreAttributeKey( 'foo' ) ) ).to.be.false;
 			} );
-
-			it( 'should fire change:attribute event', () => {
-				let spy = sinon.spy();
-				selection.on( 'change:attribute', spy );
-
-				selection.removeAttribute( 'foo' );
-
-				expect( spy.called ).to.be.true;
-			} );
 		} );
 
 		describe( 'clearAttributes', () => {
-			it( 'should remove all attributes from the element', () => {
-				selection.setRanges( [ rangeInFullP ] );
-				selection.setAttribute( 'foo', 'bar' );
-				selection.setAttribute( 'abc', 'xyz' );
-
-				selection.clearAttributes();
-
-				expect( selection.getAttribute( 'foo' ) ).to.be.undefined;
-				expect( selection.getAttribute( 'abc' ) ).to.be.undefined;
-
-				expect( fullP.hasAttribute( LiveSelection._getStoreAttributeKey( 'foo' ) ) ).to.be.false;
-				expect( fullP.hasAttribute( LiveSelection._getStoreAttributeKey( 'abc' ) ) ).to.be.false;
-			} );
-
 			it( 'should remove all stored attributes if the selection is in empty node', () => {
 				selection.setRanges( [ rangeInEmptyP ] );
 				selection.setAttribute( 'foo', 'bar' );
@@ -597,15 +505,6 @@ describe( 'LiveSelection', () => {
 				expect( emptyP.hasAttribute( LiveSelection._getStoreAttributeKey( 'foo' ) ) ).to.be.false;
 				expect( emptyP.hasAttribute( LiveSelection._getStoreAttributeKey( 'abc' ) ) ).to.be.false;
 			} );
-
-			it( 'should fire change:attribute event', () => {
-				let spy = sinon.spy();
-				selection.on( 'change:attribute', spy );
-
-				selection.clearAttributes();
-
-				expect( spy.called ).to.be.true;
-			} );
 		} );
 	} );
 

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

@@ -254,6 +254,34 @@ describe( 'Selection', () => {
 		} );
 	} );
 
+	describe( 'focus', () => {
+		let r3;
+		beforeEach( () => {
+			const r1 = Range.createFromParentsAndOffsets( root, 2, root, 4 );
+			const r2 = Range.createFromParentsAndOffsets( root, 4, root, 6 );
+			r3 = Range.createFromParentsAndOffsets( root, 1, root, 2 );
+			selection.addRange( r1 );
+			selection.addRange( r2 );
+		} );
+
+		it( 'should return correct focus when last added range is not backward one', () => {
+			selection.addRange( r3 );
+
+			expect( selection.focus.isEqual( r3.end ) ).to.be.true;
+		} );
+
+		it( 'should return correct focus when last added range is backward one', () => {
+			selection.addRange( r3, true );
+
+			expect( selection.focus.isEqual( r3.start ) ).to.be.true;
+		} );
+
+		it( 'should return null if no ranges in selection', () => {
+			selection.removeAllRanges();
+			expect( selection.focus ).to.be.null;
+		} );
+	} );
+
 	describe( 'setFocus', () => {
 		it( 'keeps all existing ranges and fires no change:range when no modifications needed', () => {
 			selection.addRange( range );
@@ -565,4 +593,118 @@ describe( 'Selection', () => {
 			}
 		} );
 	} );
+
+	describe( 'attributes interface', () => {
+		let rangeInFullP;
+
+		beforeEach( () => {
+			root.insertChildren( 0, [
+				new Element( 'p', [], 'foobar' ),
+				new Element( 'p', [], [] )
+			] );
+
+			rangeInFullP = new Range( new Position( root, [ 0, 4 ] ), new Position( root, [ 0, 4 ] ) );
+		} );
+
+		describe( 'setAttribute', () => {
+			it( 'should set given attribute on the selection', () => {
+				selection.setRanges( [ rangeInFullP ] );
+				selection.setAttribute( 'foo', 'bar' );
+
+				expect( selection.getAttribute( 'foo' ) ).to.equal( 'bar' );
+			} );
+
+			it( 'should fire change:attribute event', () => {
+				let spy = sinon.spy();
+				selection.on( 'change:attribute', spy );
+
+				selection.setAttribute( 'foo', 'bar' );
+
+				expect( spy.called ).to.be.true;
+			} );
+		} );
+
+		describe( 'getAttribute', () => {
+			it( 'should return undefined if element does not contain given attribute', () => {
+				expect( selection.getAttribute( 'abc' ) ).to.be.undefined;
+			} );
+		} );
+
+		describe( 'getAttributes', () => {
+			it( 'should return an iterator that iterates over all attributes set on the text fragment', () => {
+				selection.setRanges( [ rangeInFullP ] );
+				selection.setAttribute( 'foo', 'bar' );
+				selection.setAttribute( 'abc', 'xyz' );
+
+				let attrs = Array.from( selection.getAttributes() );
+
+				expect( attrs ).to.deep.equal( [ [ 'foo', 'bar' ], [ 'abc', 'xyz' ] ] );
+			} );
+		} );
+
+		describe( 'hasAttribute', () => {
+			it( 'should return true if element contains attribute with given key', () => {
+				selection.setRanges( [ rangeInFullP ] );
+				selection.setAttribute( 'foo', 'bar' );
+
+				expect( selection.hasAttribute( 'foo' ) ).to.be.true;
+			} );
+
+			it( 'should return false if element does not contain attribute with given key', () => {
+				expect( selection.hasAttribute( 'abc' ) ).to.be.false;
+			} );
+		} );
+
+		describe( 'clearAttributes', () => {
+			it( 'should remove all attributes from the element', () => {
+				selection.setRanges( [ rangeInFullP ] );
+				selection.setAttribute( 'foo', 'bar' );
+				selection.setAttribute( 'abc', 'xyz' );
+
+				selection.clearAttributes();
+
+				expect( selection.getAttribute( 'foo' ) ).to.be.undefined;
+				expect( selection.getAttribute( 'abc' ) ).to.be.undefined;
+			} );
+
+			it( 'should fire change:attribute event', () => {
+				let spy = sinon.spy();
+				selection.on( 'change:attribute', spy );
+
+				selection.clearAttributes();
+
+				expect( spy.called ).to.be.true;
+			} );
+		} );
+
+		describe( 'removeAttribute', () => {
+			it( 'should remove attribute', () => {
+				selection.setRanges( [ rangeInFullP ] );
+				selection.setAttribute( 'foo', 'bar' );
+				selection.removeAttribute( 'foo' );
+
+				expect( selection.getAttribute( 'foo' ) ).to.be.undefined;
+			} );
+
+			it( 'should fire change:attribute event', () => {
+				let spy = sinon.spy();
+				selection.on( 'change:attribute', spy );
+
+				selection.removeAttribute( 'foo' );
+
+				expect( spy.called ).to.be.true;
+			} );
+		} );
+
+		describe( 'setAttributesTo', () => {
+			it( 'should remove all attributes set on element and set the given ones', () => {
+				selection.setRanges( [ rangeInFullP ] );
+				selection.setAttribute( 'abc', 'xyz' );
+				selection.setAttributesTo( { foo: 'bar' } );
+
+				expect( selection.getAttribute( 'foo' ) ).to.equal( 'bar' );
+				expect( selection.getAttribute( 'abc' ) ).to.be.undefined;
+			} );
+		} );
+	} );
 } );

+ 3 - 2
packages/ckeditor5-engine/tests/view/document/document.js

@@ -82,7 +82,7 @@ describe( 'Document', () => {
 			expect( domRoot ).to.equal( domDiv );
 			expect( viewDocument.domConverter.getCorrespondingDom( viewRoot ) ).to.equal( domDiv );
 
-			expect( viewRoot.name.toLowerCase() ).to.equal( 'div' );
+			expect( viewRoot.name ).to.equal( 'div' );
 			expect( viewDocument.renderer.markedChildren.has( viewRoot ) ).to.be.true;
 		} );
 
@@ -175,7 +175,7 @@ describe( 'Document', () => {
 			const domH1 = document.createElement( 'h1' );
 
 			const viewDocument = new Document();
-			viewDocument.createRoot( 'div' );
+			viewDocument.createRoot( 'DIV' );
 			const viewH1 = viewDocument.createRoot( 'h1', 'header' );
 
 			expect( count( viewDocument.domRoots ) ).to.equal( 0 );
@@ -190,6 +190,7 @@ describe( 'Document', () => {
 			expect( viewDocument.getDomRoot( 'header' ) ).to.equal( domH1 );
 			expect( viewDocument.domConverter.getCorrespondingDom( viewH1 ) ).to.equal( domH1 );
 
+			expect( viewDocument.getRoot().name ).to.equal( 'div' );
 			expect( viewDocument.renderer.markedChildren.has( viewH1 ) ).to.be.true;
 		} );
 	} );

+ 11 - 0
packages/ckeditor5-engine/tests/view/node.js

@@ -100,6 +100,17 @@ describe( 'Node', () => {
 			expect( result2[ 1 ] ).to.equal( two );
 			expect( result2[ 0 ] ).to.equal( charR );
 		} );
+
+		it( 'should return ancestors including DocumentFragment', () => {
+			const fragment = new DocumentFragment( root );
+			const result = img.getAncestors();
+			root.remove();
+
+			expect( result.length ).to.equal( 3 );
+			expect( result[ 0 ] ).to.equal( fragment );
+			expect( result[ 1 ] ).to.equal( root );
+			expect( result[ 2 ] ).to.equal( two );
+		} );
 	} );
 
 	describe( 'getIndex', () => {

+ 1 - 9
packages/ckeditor5-engine/tests/view/writer/breakAt.js

@@ -8,9 +8,6 @@
 'use strict';
 
 import { breakAt } from '/ckeditor5/engine/view/writer.js';
-import DocumentFragment from '/ckeditor5/engine/view/documentfragment.js';
-import AttributeElement from '/ckeditor5/engine/view/attributeelement.js';
-import Text from '/ckeditor5/engine/view/text.js';
 import { stringify, parse } from '/tests/engine/_utils/view.js';
 
 describe( 'writer', () => {
@@ -24,13 +21,8 @@ describe( 'writer', () => {
 	function test( input, expected ) {
 		let { view, selection } = parse( input );
 
-		// Wrap attributes and text into DocumentFragment.
-		if ( view instanceof AttributeElement || view instanceof Text ) {
-			view = new DocumentFragment( view );
-		}
-
 		const newPosition = breakAt( selection.getFirstPosition() );
-		expect( stringify( view, newPosition, { showType: true, showPriority: true } ) ).to.equal( expected );
+		expect( stringify( view.getRoot(), newPosition, { showType: true, showPriority: true } ) ).to.equal( expected );
 	}
 
 	describe( 'breakAt', () => {

+ 1 - 7
packages/ckeditor5-engine/tests/view/writer/breakrange.js

@@ -8,10 +8,8 @@
 'use strict';
 
 import { breakRange } from '/ckeditor5/engine/view/writer.js';
-import DocumentFragment from '/ckeditor5/engine/view/documentfragment.js';
 import ContainerElement from '/ckeditor5/engine/view/containerelement.js';
 import AttributeElement from '/ckeditor5/engine/view/attributeelement.js';
-import Text from '/ckeditor5/engine/view/text.js';
 import Range from '/ckeditor5/engine/view/range.js';
 import { stringify, parse } from '/tests/engine/_utils/view.js';
 import CKEditorError from '/ckeditor5/utils/ckeditorerror.js';
@@ -26,12 +24,8 @@ describe( 'writer', () => {
 	function test( input, expected ) {
 		let { view, selection } = parse( input );
 
-		if ( view instanceof AttributeElement || view instanceof Text ) {
-			view = new DocumentFragment( view );
-		}
-
 		const newRange = breakRange( selection.getFirstRange() );
-		expect( stringify( view, newRange, { showType: true } ) ).to.equal( expected );
+		expect( stringify( view.getRoot(), newRange, { showType: true } ) ).to.equal( expected );
 	}
 
 	describe( 'breakRange', () => {

+ 1 - 7
packages/ckeditor5-engine/tests/view/writer/insert.js

@@ -8,14 +8,12 @@
 'use strict';
 
 import { insert } from '/ckeditor5/engine/view/writer.js';
-import DocumentFragment from '/ckeditor5/engine/view/documentfragment.js';
 import ContainerElement from '/ckeditor5/engine/view/containerelement.js';
 import Element from '/ckeditor5/engine/view/element.js';
 import Position from '/ckeditor5/engine/view/position.js';
 import CKEditorError from '/ckeditor5/utils/ckeditorerror.js';
 import { stringify, parse } from '/tests/engine/_utils/view.js';
 import AttributeElement from '/ckeditor5/engine/view/attributeelement.js';
-import Text from '/ckeditor5/engine/view/text.js';
 
 describe( 'writer', () => {
 	/**
@@ -29,12 +27,8 @@ describe( 'writer', () => {
 		nodesToInsert = nodesToInsert.map( node => parse( node ) );
 		let { view, selection } = parse( input );
 
-		if ( view instanceof AttributeElement || view instanceof Text ) {
-			view = new DocumentFragment( view );
-		}
-
 		const newRange = insert( selection.getFirstPosition(), nodesToInsert );
-		expect( stringify( view, newRange, { showType: true, showPriority: true } ) ).to.equal( expected );
+		expect( stringify( view.getRoot(), newRange, { showType: true, showPriority: true } ) ).to.equal( expected );
 	}
 
 	describe( 'insert', () => {

+ 0 - 11
packages/ckeditor5-engine/tests/view/writer/move.js

@@ -8,10 +8,7 @@
 'use strict';
 
 import { move } from '/ckeditor5/engine/view/writer.js';
-import DocumentFragment from '/ckeditor5/engine/view/documentfragment.js';
 import { stringify, parse } from '/tests/engine/_utils/view.js';
-import AttributeElement from '/ckeditor5/engine/view/attributeelement.js';
-import Text from '/ckeditor5/engine/view/text.js';
 
 describe( 'writer', () => {
 	/**
@@ -26,14 +23,6 @@ describe( 'writer', () => {
 		let { view: srcView, selection: srcSelection } = parse( source );
 		let { view: dstView, selection: dstSelection } = parse( destination );
 
-		if ( srcView instanceof AttributeElement || srcView instanceof Text ) {
-			srcView = new DocumentFragment( srcView );
-		}
-
-		if ( dstView instanceof AttributeElement || dstView instanceof Text ) {
-			dstView = new DocumentFragment( dstView );
-		}
-
 		const newRange = move( srcSelection.getFirstRange(), dstSelection.getFirstPosition() );
 
 		expect( stringify( dstView, newRange, { showType: true, showPriority: true } ) ).to.equal( destinationAfterMove );

+ 0 - 5
packages/ckeditor5-engine/tests/view/writer/remove.js

@@ -13,7 +13,6 @@ import Range from '/ckeditor5/engine/view/range.js';
 import DocumentFragment from '/ckeditor5/engine/view/documentfragment.js';
 import { stringify, parse } from '/tests/engine/_utils/view.js';
 import AttributeElement from '/ckeditor5/engine/view/attributeelement.js';
-import Text from '/ckeditor5/engine/view/text.js';
 import CKEditorError from '/ckeditor5/utils/ckeditorerror.js';
 
 describe( 'writer', () => {
@@ -28,10 +27,6 @@ describe( 'writer', () => {
 	function test( input, expectedResult, expectedRemoved ) {
 		let { view, selection } = parse( input );
 
-		if ( view instanceof AttributeElement || view instanceof Text ) {
-			view = new DocumentFragment( view );
-		}
-
 		const range = selection.getFirstRange();
 		const removed = remove( range );
 		expect( stringify( view, range, { showType: true, showPriority: true } ) ).to.equal( expectedResult );

+ 1 - 6
packages/ckeditor5-engine/tests/view/writer/unwrap.js

@@ -11,7 +11,6 @@ import { unwrap } from '/ckeditor5/engine/view/writer.js';
 import Element from '/ckeditor5/engine/view/element.js';
 import ContainerElement from '/ckeditor5/engine/view/containerelement.js';
 import AttributeElement from '/ckeditor5/engine/view/attributeelement.js';
-import DocumentFragment from '/ckeditor5/engine/view/documentfragment.js';
 import Position from '/ckeditor5/engine/view/position.js';
 import Range from '/ckeditor5/engine/view/range.js';
 import Text from '/ckeditor5/engine/view/text.js';
@@ -29,12 +28,8 @@ describe( 'writer', () => {
 	function test( input, unwrapAttribute, expected ) {
 		let { view, selection } = parse( input );
 
-		if ( view instanceof AttributeElement || view instanceof Text ) {
-			view = new DocumentFragment( view );
-		}
-
 		const newRange = unwrap( selection.getFirstRange(), parse( unwrapAttribute ) );
-		expect( stringify( view, newRange, { showType: true, showPriority: true } ) ).to.equal( expected );
+		expect( stringify( view.getRoot(), newRange, { showType: true, showPriority: true } ) ).to.equal( expected );
 	}
 
 	describe( 'unwrap', () => {

+ 4 - 9
packages/ckeditor5-engine/tests/view/writer/wrap.js

@@ -11,7 +11,6 @@ import { wrap } from '/ckeditor5/engine/view/writer.js';
 import Element from '/ckeditor5/engine/view/element.js';
 import ContainerElement from '/ckeditor5/engine/view/containerelement.js';
 import AttributeElement from '/ckeditor5/engine/view/attributeelement.js';
-import DocumentFragment from '/ckeditor5/engine/view/documentfragment.js';
 import Position from '/ckeditor5/engine/view/position.js';
 import Range from '/ckeditor5/engine/view/range.js';
 import Text from '/ckeditor5/engine/view/text.js';
@@ -23,18 +22,14 @@ describe( 'writer', () => {
 	 * Executes test using `parse` and `stringify` utils functions.
 	 *
 	 * @param {String} input
-	 * @param {String} unwrapAttribute
+	 * @param {String} wrapAttribute
 	 * @param {String} expected
 	 */
-	function test( input, unwrapAttribute, expected ) {
+	function test( input, wrapAttribute, expected ) {
 		let { view, selection } = parse( input );
+		const newRange = wrap( selection.getFirstRange(), parse( wrapAttribute ) );
 
-		if ( view instanceof AttributeElement || view instanceof Text ) {
-			view = new DocumentFragment( view );
-		}
-
-		const newRange = wrap( selection.getFirstRange(), parse( unwrapAttribute ) );
-		expect( stringify( view, newRange, { showType: true, showPriority: true } ) ).to.equal( expected );
+		expect( stringify( view.getRoot(), newRange, { showType: true, showPriority: true } ) ).to.equal( expected );
 	}
 
 	describe( 'wrap', () => {

+ 0 - 6
packages/ckeditor5-engine/tests/view/writer/wrapposition.js

@@ -11,11 +11,9 @@ import { wrapPosition } from '/ckeditor5/engine/view/writer.js';
 import Text from '/ckeditor5/engine/view/text.js';
 import Element from '/ckeditor5/engine/view/element.js';
 import ContainerElement from '/ckeditor5/engine/view/containerelement.js';
-import DocumentFragment from '/ckeditor5/engine/view/documentfragment.js';
 import Position from '/ckeditor5/engine/view/position.js';
 import CKEditorError from '/ckeditor5/utils/ckeditorerror.js';
 import { stringify, parse } from '/tests/engine/_utils/view.js';
-import AttributeElement from '/ckeditor5/engine/view/attributeelement.js';
 
 describe( 'wrapPosition', () => {
 	/**
@@ -28,10 +26,6 @@ describe( 'wrapPosition', () => {
 	function test( input, unwrapAttribute, expected ) {
 		let { view, selection } = parse( input );
 
-		if ( view instanceof AttributeElement || view instanceof Text ) {
-			view = new DocumentFragment( view );
-		}
-
 		const newPosition = wrapPosition( selection.getFirstPosition(), parse( unwrapAttribute ) );
 		expect( stringify( view, newPosition, { showType: true, showPriority: true } ) ).to.equal( expected );
 	}