Explorar o código

Merge pull request #1215 from ckeditor/t/1208

Other: Cleaned up the model, document and controllers API. Closes #1208.
Piotrek Koszuliński %!s(int64=8) %!d(string=hai) anos
pai
achega
30ce808b0b

+ 0 - 129
packages/ckeditor5-engine/src/controller/datacontroller.js

@@ -21,12 +21,6 @@ import { convertText, convertToModelFragment } from '../conversion/view-to-model
 import ViewDocumentFragment from '../view/documentfragment';
 import ViewDocumentFragment from '../view/documentfragment';
 
 
 import ModelRange from '../model/range';
 import ModelRange from '../model/range';
-import ModelElement from '../model/element';
-
-import insertContent from './insertcontent';
-import deleteContent from './deletecontent';
-import modifySelection from './modifyselection';
-import getSelectedContent from './getselectedcontent';
 
 
 /**
 /**
  * Controller for the data pipeline. The data pipeline controls how data is retrieved from the document
  * Controller for the data pipeline. The data pipeline controls how data is retrieved from the document
@@ -116,9 +110,6 @@ export default class DataController {
 		this.viewToModel.on( 'text', convertText(), { priority: 'lowest' } );
 		this.viewToModel.on( 'text', convertText(), { priority: 'lowest' } );
 		this.viewToModel.on( 'element', convertToModelFragment(), { priority: 'lowest' } );
 		this.viewToModel.on( 'element', convertToModelFragment(), { priority: 'lowest' } );
 		this.viewToModel.on( 'documentFragment', convertToModelFragment(), { priority: 'lowest' } );
 		this.viewToModel.on( 'documentFragment', convertToModelFragment(), { priority: 'lowest' } );
-
-		[ 'insertContent', 'deleteContent', 'modifySelection', 'getSelectedContent' ]
-			.forEach( methodName => this.decorate( methodName ) );
 	}
 	}
 
 
 	/**
 	/**
@@ -240,126 +231,6 @@ export default class DataController {
 	 * Removes all event listeners set by the DataController.
 	 * Removes all event listeners set by the DataController.
 	 */
 	 */
 	destroy() {}
 	destroy() {}
-
-	/**
-	 * See {@link module:engine/controller/insertcontent.insertContent}.
-	 *
-	 * @fires insertContent
-	 * @param {module:engine/model/documentfragment~DocumentFragment|module:engine/model/item~Item} content The content to insert.
-	 * @param {module:engine/model/selection~Selection} selection Selection into which the content should be inserted.
-	 */
-	insertContent( content, selection ) {
-		insertContent( this, content, selection );
-	}
-
-	/**
-	 * See {@link module:engine/controller/deletecontent.deleteContent}.
-	 *
-	 * Note: For the sake of predictability, the resulting selection should always be collapsed.
-	 * In cases where a feature wants to modify deleting behavior so selection isn't collapsed
-	 * (e.g. a table feature may want to keep row selection after pressing <kbd>Backspace</kbd>),
-	 * then that behavior should be implemented in the view's listener. At the same time, the table feature
-	 * will need to modify this method's behavior too, e.g. to "delete contents and then collapse
-	 * the selection inside the last selected cell" or "delete the row and collapse selection somewhere near".
-	 * That needs to be done in order to ensure that other features which use `deleteContent()` will work well with tables.
-	 *
-	 * @fires deleteContent
-	 * @param {module:engine/model/selection~Selection} selection Selection of which the content should be deleted.
-	 * @param {Object} options See {@link module:engine/controller/deletecontent~deleteContent}'s options.
-	 */
-	deleteContent( selection, options ) {
-		deleteContent( this, selection, options );
-	}
-
-	/**
-	 * See {@link module:engine/controller/modifyselection.modifySelection}.
-	 *
-	 * @fires modifySelection
-	 * @param {module:engine/model/selection~Selection} selection The selection to modify.
-	 * @param {Object} options See {@link module:engine/controller/modifyselection~modifySelection}'s options.
-	 */
-	modifySelection( selection, options ) {
-		modifySelection( this, selection, options );
-	}
-
-	/**
-	 * See {@link module:engine/controller/getselectedcontent.getSelectedContent}.
-	 *
-	 * @fires module:engine/controller/datacontroller~DataController#getSelectedContent
-	 * @param {module:engine/model/selection~Selection} selection The selection of which content will be retrieved.
-	 * @returns {module:engine/model/documentfragment~DocumentFragment} Document fragment holding the clone of the selected content.
-	 */
-	getSelectedContent( selection ) {
-		return getSelectedContent( this, selection );
-	}
-
-	/**
-	 * Checks whether given {@link module:engine/model/range~Range range} or {@link module:engine/model/element~Element element}
-	 * has any content.
-	 *
-	 * Content is any text node or element which is registered in {@link module:engine/model/schema~Schema schema}.
-	 *
-	 * @param {module:engine/model/range~Range|module:engine/model/element~Element} rangeOrElement Range or element to check.
-	 * @returns {Boolean}
-	 */
-	hasContent( rangeOrElement ) {
-		if ( rangeOrElement instanceof ModelElement ) {
-			rangeOrElement = ModelRange.createIn( rangeOrElement );
-		}
-
-		if ( rangeOrElement.isCollapsed ) {
-			return false;
-		}
-
-		for ( const item of rangeOrElement.getItems() ) {
-			// Remember, `TreeWalker` returns always `textProxy` nodes.
-			if ( item.is( 'textProxy' ) || this.model.schema.objects.has( item.name ) ) {
-				return true;
-			}
-		}
-
-		return false;
-	}
 }
 }
 
 
 mix( DataController, ObservableMixin );
 mix( DataController, ObservableMixin );
-
-/**
- * Event fired when {@link #insertContent} method is called.
- *
- * The {@link #insertContent default action of that method} is implemented as a
- * listener to this event so it can be fully customized by the features.
- *
- * @event insertContent
- * @param {Array} args The arguments passed to the original method.
- */
-
-/**
- * Event fired when {@link #deleteContent} method is called.
- *
- * The {@link #deleteContent default action of that method} is implemented as a
- * listener to this event so it can be fully customized by the features.
- *
- * @event deleteContent
- * @param {Array} args The arguments passed to the original method.
- */
-
-/**
- * Event fired when {@link #modifySelection} method is called.
- *
- * The {@link #modifySelection default action of that method} is implemented as a
- * listener to this event so it can be fully customized by the features.
- *
- * @event modifySelection
- * @param {Array} args The arguments passed to the original method.
- */
-
-/**
- * Event fired when {@link #getSelectedContent} method is called.
- *
- * The {@link #getSelectedContent default action of that method} is implemented as a
- * listener to this event so it can be fully customized by the features.
- *
- * @event getSelectedContent
- * @param {Array} args The arguments passed to the original method.
- */

+ 0 - 25
packages/ckeditor5-engine/src/model/document.js

@@ -7,17 +7,12 @@
  * @module engine/model/document
  * @module engine/model/document
  */
  */
 
 
-// Load all basic deltas and transformations, they register themselves.
-import './delta/basic-deltas';
-import './delta/basic-transformations';
-
 import Range from './range';
 import Range from './range';
 import Position from './position';
 import Position from './position';
 import RootElement from './rootelement';
 import RootElement from './rootelement';
 import History from './history';
 import History from './history';
 import DocumentSelection from './documentselection';
 import DocumentSelection from './documentselection';
 import TreeWalker from './treewalker';
 import TreeWalker from './treewalker';
-import deltaTransform from './delta/transform';
 import clone from '@ckeditor/ckeditor5-utils/src/lib/lodash/clone';
 import clone from '@ckeditor/ckeditor5-utils/src/lib/lodash/clone';
 import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';
 import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
@@ -283,26 +278,6 @@ export default class Document {
 		return null;
 		return null;
 	}
 	}
 
 
-	/**
-	 * Transforms two sets of deltas by themselves. Returns both transformed sets.
-	 *
-	 * @param {Array.<module:engine/model/delta/delta~Delta>} deltasA Array with the first set of deltas to transform. These
-	 * deltas are considered more important (than `deltasB`) when resolving conflicts.
-	 * @param {Array.<module:engine/model/delta/delta~Delta>} deltasB Array with the second set of deltas to transform. These
-	 * deltas are considered less important (than `deltasA`) when resolving conflicts.
-	 * @param {Boolean} [useContext=false] When set to `true`, transformation will store and use additional context
-	 * information to guarantee more expected results. Should be used whenever deltas related to already applied
-	 * deltas are transformed (for example when undoing changes).
-	 * @returns {Object}
-	 * @returns {Array.<module:engine/model/delta/delta~Delta>} return.deltasA The first set of deltas transformed
-	 * by the second set of deltas.
-	 * @returns {Array.<module:engine/model/delta/delta~Delta>} return.deltasB The second set of deltas transformed
-	 * by the first set of deltas.
-	 */
-	transformDeltas( deltasA, deltasB, useContext = false ) {
-		return deltaTransform.transformDeltaSets( deltasA, deltasB, useContext ? this : null );
-	}
-
 	/**
 	/**
 	 * Custom toJSON method to solve child-parent circular dependencies.
 	 * Custom toJSON method to solve child-parent circular dependencies.
 	 *
 	 *

+ 180 - 27
packages/ckeditor5-engine/src/model/model.js

@@ -7,6 +7,10 @@
  * @module engine/model/model
  * @module engine/model/model
  */
  */
 
 
+// Load all basic deltas and transformations, they register themselves.
+import './delta/basic-deltas';
+import './delta/basic-transformations';
+
 import Batch from './batch';
 import Batch from './batch';
 import Writer from './writer';
 import Writer from './writer';
 import Schema from './schema';
 import Schema from './schema';
@@ -14,6 +18,14 @@ import Document from './document';
 import MarkerCollection from './markercollection';
 import MarkerCollection from './markercollection';
 import ObservableMixin from '@ckeditor/ckeditor5-utils/src/observablemixin';
 import ObservableMixin from '@ckeditor/ckeditor5-utils/src/observablemixin';
 import mix from '@ckeditor/ckeditor5-utils/src/mix';
 import mix from '@ckeditor/ckeditor5-utils/src/mix';
+import deltaTransform from './delta/transform';
+import ModelElement from './element';
+import ModelRange from './range';
+
+import insertContent from './utils/insertcontent';
+import deleteContent from './utils/deletecontent';
+import modifySelection from './utils/modifyselection';
+import getSelectedContent from './utils/getselectedcontent';
 
 
 /**
 /**
  * Editors data model class. Model defines all data: either nodes users see in editable roots, grouped as the
  * Editors data model class. Model defines all data: either nodes users see in editable roots, grouped as the
@@ -54,7 +66,8 @@ export default class Model {
 		 */
 		 */
 		this.markers = new MarkerCollection();
 		this.markers = new MarkerCollection();
 
 
-		this.decorate( 'applyOperation' );
+		[ 'insertContent', 'deleteContent', 'modifySelection', 'getSelectedContent', 'applyOperation' ]
+			.forEach( methodName => this.decorate( methodName ) );
 	}
 	}
 
 
 	/**
 	/**
@@ -192,49 +205,189 @@ export default class Model {
 	}
 	}
 
 
 	/**
 	/**
-	 * Removes all events listeners set by model instance and destroy Document.
+	 * Transforms two sets of deltas by themselves. Returns both transformed sets.
+	 *
+	 * @param {Array.<module:engine/model/delta/delta~Delta>} deltasA Array with the first set of deltas to transform. These
+	 * deltas are considered more important (than `deltasB`) when resolving conflicts.
+	 * @param {Array.<module:engine/model/delta/delta~Delta>} deltasB Array with the second set of deltas to transform. These
+	 * deltas are considered less important (than `deltasA`) when resolving conflicts.
+	 * @param {Boolean} [useContext=false] When set to `true`, transformation will store and use additional context
+	 * information to guarantee more expected results. Should be used whenever deltas related to already applied
+	 * deltas are transformed (for example when undoing changes).
+	 * @returns {Object}
+	 * @returns {Array.<module:engine/model/delta/delta~Delta>} return.deltasA The first set of deltas transformed
+	 * by the second set of deltas.
+	 * @returns {Array.<module:engine/model/delta/delta~Delta>} return.deltasB The second set of deltas transformed
+	 * by the first set of deltas.
 	 */
 	 */
-	destroy() {
-		this.document.destroy();
-		this.stopListening();
+	transformDeltas( deltasA, deltasB, useContext = false ) {
+		return deltaTransform.transformDeltaSets( deltasA, deltasB, useContext ? this.document : null );
 	}
 	}
 
 
 	/**
 	/**
-	 * Fired after leaving each {@link module:engine/model/model~Model#enqueueChange} block or outermost
-	 * {@link module:engine/model/model~Model#change} block.
-	 * Have the same parameters as {@link module:engine/model/model~Model#change}.
+	 * See {@link module:engine/model/utils/insertcontent~insertContent}.
 	 *
 	 *
-	 * @event change
+	 * @fires insertContent
+	 * @param {module:engine/model/documentfragment~DocumentFragment|module:engine/model/item~Item} content The content to insert.
+	 * @param {module:engine/model/selection~Selection} selection Selection into which the content should be inserted.
 	 */
 	 */
+	insertContent( content, selection ) {
+		insertContent( this, content, selection );
+	}
 
 
 	/**
 	/**
-	 * Fired when all queued model changes are done.
+	 * See {@link module:engine/model/utils/deletecontent.deleteContent}.
 	 *
 	 *
-	 * @see #change
-	 * @see #enqueueChange
-	 * @event changesDone
+	 * Note: For the sake of predictability, the resulting selection should always be collapsed.
+	 * In cases where a feature wants to modify deleting behavior so selection isn't collapsed
+	 * (e.g. a table feature may want to keep row selection after pressing <kbd>Backspace</kbd>),
+	 * then that behavior should be implemented in the view's listener. At the same time, the table feature
+	 * will need to modify this method's behavior too, e.g. to "delete contents and then collapse
+	 * the selection inside the last selected cell" or "delete the row and collapse selection somewhere near".
+	 * That needs to be done in order to ensure that other features which use `deleteContent()` will work well with tables.
+	 *
+	 * @fires deleteContent
+	 * @param {module:engine/model/selection~Selection} selection Selection of which the content should be deleted.
+	 * @param {Object} options See {@link module:engine/model/utils/deletecontent~deleteContent}'s options.
 	 */
 	 */
+	deleteContent( selection, options ) {
+		deleteContent( this, selection, options );
+	}
 
 
 	/**
 	/**
-	 * Fired every time any {@link module:engine/model/operation/operation~Operation operation} is applied on the model
-	 * using {@link #applyOperation}.
+	 * See {@link module:engine/model/utils/modifyselection~modifySelection}.
 	 *
 	 *
-	 * Note that this is an internal event for the specific use-cases. You can use it if you need to know about each operation
-	 * applied on the document, but in most cases {@link #change} event which is fired when all changes in a
-	 * {@link module:engine/model/batch~Batch} are applied, is a better choice.
-	 *
-	 * With the high priority operation is validated.
+	 * @fires modifySelection
+	 * @param {module:engine/model/selection~Selection} selection The selection to modify.
+	 * @param {Object} options See {@link module:engine/model/utils/modifyselection.modifySelection}'s options.
+	 */
+	modifySelection( selection, options ) {
+		modifySelection( this, selection, options );
+	}
+
+	/**
+	 * See {@link module:engine/model/utils/getselectedcontent~getSelectedContent}.
 	 *
 	 *
-	 * With the normal priority operation is executed. After that priority you will be able to get additional
-	 * information about the applied changes returned by {@link module:engine/model/operation/operation~Operation#_execute}
-	 * as `evt.return`.
+	 * @fires getSelectedContent
+	 * @param {module:engine/model/selection~Selection} selection The selection of which content will be retrieved.
+	 * @returns {module:engine/model/documentfragment~DocumentFragment} Document fragment holding the clone of the selected content.
+	 */
+	getSelectedContent( selection ) {
+		return getSelectedContent( this, selection );
+	}
+
+	/**
+	 * Checks whether given {@link module:engine/model/range~Range range} or {@link module:engine/model/element~Element element}
+	 * has any content.
 	 *
 	 *
-	 * With the low priority the {@link module:engine/model/document~Document} listen on this event and updates its version.
+	 * Content is any text node or element which is registered in {@link module:engine/model/schema~Schema schema}.
 	 *
 	 *
-	 * @event applyOperation
-	 * @param {Array} args Arguments of the `applyOperation` which are an array with a single element:
-	 * {@link module:engine/model/operation/operation~Operation operation}.
+	 * @param {module:engine/model/range~Range|module:engine/model/element~Element} rangeOrElement Range or element to check.
+	 * @returns {Boolean}
+	 */
+	hasContent( rangeOrElement ) {
+		if ( rangeOrElement instanceof ModelElement ) {
+			rangeOrElement = ModelRange.createIn( rangeOrElement );
+		}
+
+		if ( rangeOrElement.isCollapsed ) {
+			return false;
+		}
+
+		for ( const item of rangeOrElement.getItems() ) {
+			// Remember, `TreeWalker` returns always `textProxy` nodes.
+			if ( item.is( 'textProxy' ) || this.schema.objects.has( item.name ) ) {
+				return true;
+			}
+		}
+
+		return false;
+	}
+
+	/**
+	 * Removes all events listeners set by model instance and destroy Document.
 	 */
 	 */
+	destroy() {
+		this.document.destroy();
+		this.stopListening();
+	}
 }
 }
 
 
 mix( Model, ObservableMixin );
 mix( Model, ObservableMixin );
+
+/**
+ * Fired after leaving each {@link module:engine/model/model~Model#enqueueChange} block or outermost
+ * {@link module:engine/model/model~Model#change} block.
+ * Have the same parameters as {@link module:engine/model/model~Model#change}.
+ *
+ * @event change
+ */
+
+/**
+ * Fired when all queued model changes are done.
+ *
+ * @see #change
+ * @see #enqueueChange
+ * @event changesDone
+ */
+
+/**
+ * Fired every time any {@link module:engine/model/operation/operation~Operation operation} is applied on the model
+ * using {@link #applyOperation}.
+ *
+ * Note that this is an internal event for the specific use-cases. You can use it if you need to know about each operation
+ * applied on the document, but in most cases {@link #change} event which is fired when all changes in a
+ * {@link module:engine/model/batch~Batch} are applied, is a better choice.
+ *
+ * With the high priority operation is validated.
+ *
+ * With the normal priority operation is executed. After that priority you will be able to get additional
+ * information about the applied changes returned by {@link module:engine/model/operation/operation~Operation#_execute}
+ * as `evt.return`.
+ *
+ * With the low priority the {@link module:engine/model/document~Document} listen on this event and updates its version.
+ *
+ * @event applyOperation
+ * @param {Array} args Arguments of the `applyOperation` which are an array with a single element:
+ * {@link module:engine/model/operation/operation~Operation operation}.
+ */
+
+/**
+ * Event fired when {@link #insertContent} method is called.
+ *
+ * The {@link #insertContent default action of that method} is implemented as a
+ * listener to this event so it can be fully customized by the features.
+ *
+ * @event insertContent
+ * @param {Array} args The arguments passed to the original method.
+ */
+
+/**
+ * Event fired when {@link #deleteContent} method is called.
+ *
+ * The {@link #deleteContent default action of that method} is implemented as a
+ * listener to this event so it can be fully customized by the features.
+ *
+ * @event deleteContent
+ * @param {Array} args The arguments passed to the original method.
+ */
+
+/**
+ * Event fired when {@link #modifySelection} method is called.
+ *
+ * The {@link #modifySelection default action of that method} is implemented as a
+ * listener to this event so it can be fully customized by the features.
+ *
+ * @event modifySelection
+ * @param {Array} args The arguments passed to the original method.
+ */
+
+/**
+ * Event fired when {@link #getSelectedContent} method is called.
+ *
+ * The {@link #getSelectedContent default action of that method} is implemented as a
+ * listener to this event so it can be fully customized by the features.
+ *
+ * @event getSelectedContent
+ * @param {Array} args The arguments passed to the original method.
+ */

+ 8 - 8
packages/ckeditor5-engine/src/controller/deletecontent.js → packages/ckeditor5-engine/src/model/utils/deletecontent.js

@@ -4,17 +4,17 @@
  */
  */
 
 
 /**
 /**
- * @module engine/controller/deletecontent
+ * @module engine/model/utils/deletecontent
  */
  */
 
 
-import LivePosition from '../model/liveposition';
-import Position from '../model/position';
-import Range from '../model/range';
+import LivePosition from '../liveposition';
+import Position from '../position';
+import Range from '../range';
 
 
 /**
 /**
  * Deletes content of the selection and merge siblings. The resulting selection is always collapsed.
  * Deletes content of the selection and merge siblings. The resulting selection is always collapsed.
  *
  *
- * @param {module:engine/controller/datacontroller~DataController} dataController The data controller in context of which the insertion
+ * @param {module:engine/model/model~Model} model The model in context of which the insertion
  * should be performed.
  * should be performed.
  * @param {module:engine/model/selection~Selection} selection Selection of which the content should be deleted.
  * @param {module:engine/model/selection~Selection} selection Selection of which the content should be deleted.
  * @param {module:engine/model/batch~Batch} batch Batch to which the deltas will be added.
  * @param {module:engine/model/batch~Batch} batch Batch to which the deltas will be added.
@@ -37,14 +37,14 @@ import Range from '../model/range';
  * * `<paragraph>^</paragraph>` with the option disabled (`doNotResetEntireContent == false`)
  * * `<paragraph>^</paragraph>` with the option disabled (`doNotResetEntireContent == false`)
  * * `<heading>^</heading>` with enabled (`doNotResetEntireContent == true`).
  * * `<heading>^</heading>` with enabled (`doNotResetEntireContent == true`).
  */
  */
-export default function deleteContent( dataController, selection, options = {} ) {
+export default function deleteContent( model, selection, options = {} ) {
 	if ( selection.isCollapsed ) {
 	if ( selection.isCollapsed ) {
 		return;
 		return;
 	}
 	}
 
 
-	const schema = dataController.model.schema;
+	const schema = model.schema;
 
 
-	dataController.model.change( writer => {
+	model.change( writer => {
 		// 1. Replace the entire content with paragraph.
 		// 1. Replace the entire content with paragraph.
 		// See: https://github.com/ckeditor/ckeditor5-engine/issues/1012#issuecomment-315017594.
 		// See: https://github.com/ckeditor/ckeditor5-engine/issues/1012#issuecomment-315017594.
 		if ( !options.doNotResetEntireContent && shouldEntireContentBeReplacedWithParagraph( schema, selection ) ) {
 		if ( !options.doNotResetEntireContent && shouldEntireContentBeReplacedWithParagraph( schema, selection ) ) {

+ 6 - 6
packages/ckeditor5-engine/src/controller/getselectedcontent.js → packages/ckeditor5-engine/src/model/utils/getselectedcontent.js

@@ -4,11 +4,11 @@
  */
  */
 
 
 /**
 /**
- * @module engine/controller/getselectedcontent
+ * @module engine/model/utils/getselectedcontent
  */
  */
 
 
-import Range from '../model/range';
-import Position from '../model/position';
+import Range from '../range';
+import Position from '../position';
 
 
 /**
 /**
  * Gets a clone of the selected content.
  * Gets a clone of the selected content.
@@ -21,13 +21,13 @@ import Position from '../model/position';
  *
  *
  *		<quote><h>st</h></quote><p>se</p>
  *		<quote><h>st</h></quote><p>se</p>
  *
  *
- * @param {module:engine/controller/datacontroller~DataController} dataController The data controller in context of which
+ * @param {module:engine/model/model~Model} model The model in context of which
  * the selection modification should be performed.
  * the selection modification should be performed.
  * @param {module:engine/model/selection~Selection} selection The selection of which content will be returned.
  * @param {module:engine/model/selection~Selection} selection The selection of which content will be returned.
  * @returns {module:engine/model/documentfragment~DocumentFragment}
  * @returns {module:engine/model/documentfragment~DocumentFragment}
  */
  */
-export default function getSelectedContent( dataController, selection ) {
-	return dataController.model.change( writer => {
+export default function getSelectedContent( model, selection ) {
+	return model.change( writer => {
 		const frag = writer.createDocumentFragment();
 		const frag = writer.createDocumentFragment();
 		const range = selection.getFirstRange();
 		const range = selection.getFirstRange();
 
 

+ 18 - 18
packages/ckeditor5-engine/src/controller/insertcontent.js → packages/ckeditor5-engine/src/model/utils/insertcontent.js

@@ -4,36 +4,36 @@
  */
  */
 
 
 /**
 /**
- * @module engine/controller/insertcontent
+ * @module engine/model/utils/insertcontent
  */
  */
 
 
-import Position from '../model/position';
-import LivePosition from '../model/liveposition';
-import Element from '../model/element';
-import Range from '../model/range';
+import Position from '../position';
+import LivePosition from '../liveposition';
+import Element from '../element';
+import Range from '../range';
 import log from '@ckeditor/ckeditor5-utils/src/log';
 import log from '@ckeditor/ckeditor5-utils/src/log';
 
 
 /**
 /**
  * Inserts content into the editor (specified selection) as one would expect the paste
  * Inserts content into the editor (specified selection) as one would expect the paste
  * functionality to work.
  * functionality to work.
  *
  *
- * **Note:** Use {@link module:engine/controller/datacontroller~DataController#insertContent} instead of this function.
+ * **Note:** Use {@link module:engine/model/model~Model#insertContent} instead of this function.
  * This function is only exposed to be reusable in algorithms
  * This function is only exposed to be reusable in algorithms
- * which change the {@link module:engine/controller/datacontroller~DataController#insertContent}
+ * which change the {@link module:engine/model/model~Model#insertContent}
  * method's behavior.
  * method's behavior.
  *
  *
- * @param {module:engine/controller/datacontroller~DataController} dataController The data controller in context of which the insertion
+ * @param {module:engine/model/model~Model} model The model in context of which the insertion
  * should be performed.
  * should be performed.
  * @param {module:engine/model/documentfragment~DocumentFragment|module:engine/model/item~Item} content The content to insert.
  * @param {module:engine/model/documentfragment~DocumentFragment|module:engine/model/item~Item} content The content to insert.
  * @param {module:engine/model/selection~Selection} selection Selection into which the content should be inserted.
  * @param {module:engine/model/selection~Selection} selection Selection into which the content should be inserted.
  */
  */
-export default function insertContent( dataController, content, selection ) {
-	dataController.model.change( writer => {
+export default function insertContent( model, content, selection ) {
+	model.change( writer => {
 		if ( !selection.isCollapsed ) {
 		if ( !selection.isCollapsed ) {
-			dataController.deleteContent( selection );
+			model.deleteContent( selection );
 		}
 		}
 
 
-		const insertion = new Insertion( dataController, writer, selection.anchor );
+		const insertion = new Insertion( model, writer, selection.anchor );
 
 
 		let nodesToInsert;
 		let nodesToInsert;
 
 
@@ -75,13 +75,13 @@ export default function insertContent( dataController, content, selection ) {
  * @private
  * @private
  */
  */
 class Insertion {
 class Insertion {
-	constructor( dataController, writer, position ) {
+	constructor( model, writer, position ) {
 		/**
 		/**
-		 * The data controller in context of which the insertion should be performed.
+		 * The model in context of which the insertion should be performed.
 		 *
 		 *
-		 * @member {module:engine/controller/datacontroller~DataController} #dataController
+		 * @member {module:engine/model~Model} #model
 		 */
 		 */
-		this.dataController = dataController;
+		this.model = model;
 
 
 		/**
 		/**
 		 * Batch to which deltas will be added.
 		 * Batch to which deltas will be added.
@@ -115,7 +115,7 @@ class Insertion {
 		 *
 		 *
 		 * @member {module:engine/model/schema~Schema} #schema
 		 * @member {module:engine/model/schema~Schema} #schema
 		 */
 		 */
-		this.schema = dataController.model.schema;
+		this.schema = model.schema;
 	}
 	}
 
 
 	/**
 	/**
@@ -149,7 +149,7 @@ class Insertion {
 			return Range.createOn( this.nodeToSelect );
 			return Range.createOn( this.nodeToSelect );
 		}
 		}
 
 
-		return this.dataController.model.document.getNearestSelectionRange( this.position );
+		return this.model.document.getNearestSelectionRange( this.position );
 	}
 	}
 
 
 	/**
 	/**

+ 7 - 7
packages/ckeditor5-engine/src/controller/modifyselection.js → packages/ckeditor5-engine/src/model/utils/modifyselection.js

@@ -4,12 +4,12 @@
  */
  */
 
 
 /**
 /**
- * @module engine/controller/modifyselection
+ * @module engine/model/utils/modifyselection
  */
  */
 
 
-import Position from '../model/position';
-import TreeWalker from '../model/treewalker';
-import Range from '../model/range';
+import Position from '../position';
+import TreeWalker from '../treewalker';
+import Range from '../range';
 import { isInsideSurrogatePair, isInsideCombinedSymbol } from '@ckeditor/ckeditor5-utils/src/unicode';
 import { isInsideSurrogatePair, isInsideCombinedSymbol } from '@ckeditor/ckeditor5-utils/src/unicode';
 
 
 /**
 /**
@@ -33,15 +33,15 @@ import { isInsideSurrogatePair, isInsideCombinedSymbol } from '@ckeditor/ckedito
  *
  *
  * **Note:** if you extend a forward selection in a backward direction you will in fact shrink it.
  * **Note:** if you extend a forward selection in a backward direction you will in fact shrink it.
  *
  *
- * @param {module:engine/controller/datacontroller~DataController} dataController The data controller in context of which
+ * @param {module:engine/model/model~Model} model The model in context of which
  * the selection modification should be performed.
  * the selection modification should be performed.
  * @param {module:engine/model/selection~Selection} selection The selection to modify.
  * @param {module:engine/model/selection~Selection} selection The selection to modify.
  * @param {Object} [options]
  * @param {Object} [options]
  * @param {'forward'|'backward'} [options.direction='forward'] The direction in which the selection should be modified.
  * @param {'forward'|'backward'} [options.direction='forward'] The direction in which the selection should be modified.
  * @param {'character'|'codePoint'} [options.unit='character'] The unit by which selection should be modified.
  * @param {'character'|'codePoint'} [options.unit='character'] The unit by which selection should be modified.
  */
  */
-export default function modifySelection( dataController, selection, options = {} ) {
-	const schema = dataController.model.schema;
+export default function modifySelection( model, selection, options = {} ) {
+	const schema = model.schema;
 	const isForward = options.direction != 'backward';
 	const isForward = options.direction != 'backward';
 	const unit = options.unit ? options.unit : 'character';
 	const unit = options.unit ? options.unit : 'character';
 
 

+ 0 - 222
packages/ckeditor5-engine/tests/controller/datacontroller.js

@@ -11,9 +11,6 @@ import buildViewConverter from '../../src/conversion/buildviewconverter';
 import buildModelConverter from '../../src/conversion/buildmodelconverter';
 import buildModelConverter from '../../src/conversion/buildmodelconverter';
 
 
 import ModelDocumentFragment from '../../src/model/documentfragment';
 import ModelDocumentFragment from '../../src/model/documentfragment';
-import ModelText from '../../src/model/text';
-import ModelRange from '../../src/model/range';
-import ModelSelection from '../../src/model/selection';
 
 
 import ViewDocumentFragment from '../../src/view/documentfragment';
 import ViewDocumentFragment from '../../src/view/documentfragment';
 
 
@@ -336,223 +333,4 @@ describe( 'DataController', () => {
 			expect( data ).to.respondTo( 'destroy' );
 			expect( data ).to.respondTo( 'destroy' );
 		} );
 		} );
 	} );
 	} );
-
-	describe( 'insertContent()', () => {
-		it( 'should be decorated', () => {
-			schema.allow( { name: '$text', inside: '$root' } ); // To surpress warnings.
-
-			const spy = sinon.spy();
-
-			data.on( 'insertContent', spy );
-
-			data.insertContent( new ModelText( 'a' ), modelDocument.selection );
-
-			expect( spy.calledOnce ).to.be.true;
-		} );
-
-		it( 'should insert content (item)', () => {
-			schema.registerItem( 'paragraph', '$block' );
-
-			setData( model, '<paragraph>fo[]ar</paragraph>' );
-
-			data.insertContent( new ModelText( 'ob' ), modelDocument.selection );
-
-			expect( getData( model ) ).to.equal( '<paragraph>foob[]ar</paragraph>' );
-		} );
-
-		it( 'should insert content (document fragment)', () => {
-			schema.registerItem( 'paragraph', '$block' );
-
-			setData( model, '<paragraph>fo[]ar</paragraph>' );
-
-			data.insertContent( new ModelDocumentFragment( [ new ModelText( 'ob' ) ] ), modelDocument.selection );
-
-			expect( getData( model ) ).to.equal( '<paragraph>foob[]ar</paragraph>' );
-		} );
-
-		it( 'should use parent batch', () => {
-			schema.registerItem( 'paragraph', '$block' );
-			setData( model, '<paragraph>[]</paragraph>' );
-
-			model.change( writer => {
-				data.insertContent( new ModelText( 'abc' ), modelDocument.selection );
-				expect( writer.batch.deltas ).to.length( 1 );
-			} );
-		} );
-	} );
-
-	describe( 'deleteContent()', () => {
-		it( 'should be decorated', () => {
-			const spy = sinon.spy();
-
-			data.on( 'deleteContent', spy );
-
-			data.deleteContent( modelDocument.selection );
-
-			expect( spy.calledOnce ).to.be.true;
-		} );
-
-		it( 'should delete selected content', () => {
-			schema.registerItem( 'paragraph', '$block' );
-
-			setData( model, '<paragraph>fo[ob]ar</paragraph>' );
-
-			data.deleteContent( modelDocument.selection );
-
-			expect( getData( model ) ).to.equal( '<paragraph>fo[]ar</paragraph>' );
-		} );
-
-		it( 'should use parent batch', () => {
-			schema.registerItem( 'paragraph', '$block' );
-
-			setData( model, '<paragraph>fo[ob]ar</paragraph>' );
-
-			model.change( writer => {
-				data.deleteContent( modelDocument.selection );
-				expect( writer.batch.deltas ).to.length( 1 );
-			} );
-		} );
-	} );
-
-	describe( 'modifySelection()', () => {
-		it( 'should be decorated', () => {
-			schema.registerItem( 'paragraph', '$block' );
-			setData( model, '<paragraph>fo[ob]ar</paragraph>' );
-
-			const spy = sinon.spy();
-
-			data.on( 'modifySelection', spy );
-
-			data.modifySelection( modelDocument.selection );
-
-			expect( spy.calledOnce ).to.be.true;
-		} );
-
-		it( 'should modify a selection', () => {
-			schema.registerItem( 'paragraph', '$block' );
-
-			setData( model, '<paragraph>fo[ob]ar</paragraph>' );
-
-			data.modifySelection( modelDocument.selection, { direction: 'backward' } );
-
-			expect( getData( model ) ).to.equal( '<paragraph>fo[o]bar</paragraph>' );
-		} );
-	} );
-
-	describe( 'getSelectedContent()', () => {
-		it( 'should be decorated', () => {
-			const spy = sinon.spy();
-			const sel = new ModelSelection();
-
-			data.on( 'getSelectedContent', spy );
-
-			data.getSelectedContent( sel );
-
-			expect( spy.calledOnce ).to.be.true;
-		} );
-
-		it( 'should return selected content', () => {
-			schema.registerItem( 'paragraph', '$block' );
-
-			setData( model, '<paragraph>fo[ob]ar</paragraph>' );
-
-			const content = data.getSelectedContent( modelDocument.selection );
-
-			expect( stringify( content ) ).to.equal( 'ob' );
-		} );
-
-		it( 'should use parent batch', () => {
-			schema.registerItem( 'paragraph', '$block' );
-
-			setData( model, '<paragraph>fo[ob]ar</paragraph>' );
-
-			model.change( writer => {
-				data.getSelectedContent( modelDocument.selection );
-				expect( writer.batch.deltas ).to.length( 1 );
-			} );
-		} );
-	} );
-
-	describe( 'hasContent()', () => {
-		let root;
-
-		beforeEach( () => {
-			schema.registerItem( 'paragraph', '$block' );
-			schema.registerItem( 'div', '$block' );
-			schema.allow( { name: '$block', inside: 'div' } );
-			schema.registerItem( 'image' );
-			schema.allow( { name: 'image', inside: 'div' } );
-			schema.objects.add( 'image' );
-
-			setData(
-				model,
-
-				'<div>' +
-					'<paragraph></paragraph>' +
-				'</div>' +
-				'<paragraph>foo</paragraph>' +
-				'<div>' +
-					'<image></image>' +
-				'</div>'
-			);
-
-			root = modelDocument.getRoot();
-		} );
-
-		it( 'should return true if given element has text node', () => {
-			const pFoo = root.getChild( 1 );
-
-			expect( data.hasContent( pFoo ) ).to.be.true;
-		} );
-
-		it( 'should return true if given element has element that is an object', () => {
-			const divImg = root.getChild( 2 );
-
-			expect( data.hasContent( divImg ) ).to.be.true;
-		} );
-
-		it( 'should return false if given element has no elements', () => {
-			const pEmpty = root.getChild( 0 ).getChild( 0 );
-
-			expect( data.hasContent( pEmpty ) ).to.be.false;
-		} );
-
-		it( 'should return false if given element has only elements that are not objects', () => {
-			const divP = root.getChild( 0 );
-
-			expect( data.hasContent( divP ) ).to.be.false;
-		} );
-
-		it( 'should return true if there is a text node in given range', () => {
-			const range = ModelRange.createFromParentsAndOffsets( root, 1, root, 2 );
-
-			expect( data.hasContent( range ) ).to.be.true;
-		} );
-
-		it( 'should return true if there is a part of text node in given range', () => {
-			const pFoo = root.getChild( 1 );
-			const range = ModelRange.createFromParentsAndOffsets( pFoo, 1, pFoo, 2 );
-
-			expect( data.hasContent( range ) ).to.be.true;
-		} );
-
-		it( 'should return true if there is element that is an object in given range', () => {
-			const divImg = root.getChild( 2 );
-			const range = ModelRange.createFromParentsAndOffsets( divImg, 0, divImg, 1 );
-
-			expect( data.hasContent( range ) ).to.be.true;
-		} );
-
-		it( 'should return false if range is collapsed', () => {
-			const range = ModelRange.createFromParentsAndOffsets( root, 1, root, 1 );
-
-			expect( data.hasContent( range ) ).to.be.false;
-		} );
-
-		it( 'should return false if range has only elements that are not objects', () => {
-			const range = ModelRange.createFromParentsAndOffsets( root, 0, root, 1 );
-
-			expect( data.hasContent( range ) ).to.be.false;
-		} );
-	} );
 } );
 } );

+ 0 - 40
packages/ckeditor5-engine/tests/model/document/document.js

@@ -8,8 +8,6 @@ import Document from '../../../src/model/document';
 import RootElement from '../../../src/model/rootelement';
 import RootElement from '../../../src/model/rootelement';
 import Batch from '../../../src/model/batch';
 import Batch from '../../../src/model/batch';
 import Delta from '../../../src/model/delta/delta';
 import Delta from '../../../src/model/delta/delta';
-import NoOperation from '../../../src/model/operation/nooperation';
-import deltaTransform from '../../../src/model/delta/transform';
 import Range from '../../../src/model/range';
 import Range from '../../../src/model/range';
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 import count from '@ckeditor/ckeditor5-utils/src/count';
 import count from '@ckeditor/ckeditor5-utils/src/count';
@@ -472,44 +470,6 @@ describe( 'Document', () => {
 		}
 		}
 	} );
 	} );
 
 
-	describe( 'transformDeltas', () => {
-		it( 'should use deltaTransform.transformDeltaSets', () => {
-			sinon.spy( deltaTransform, 'transformDeltaSets' );
-
-			// Prepare some empty-ish deltas so the transformation won't throw an error.
-			const deltasA = [ new Delta() ];
-			deltasA[ 0 ].addOperation( new NoOperation( 0 ) );
-
-			const deltasB = [ new Delta() ];
-			deltasB[ 0 ].addOperation( new NoOperation( 0 ) );
-
-			doc.transformDeltas( deltasA, deltasB );
-
-			expect( deltaTransform.transformDeltaSets.calledOnce ).to.be.true;
-			expect( deltaTransform.transformDeltaSets.calledWith( deltasA, deltasB, null ) ).to.be.true;
-
-			deltaTransform.transformDeltaSets.restore();
-		} );
-
-		it( 'should pass itself to transformDeltaSets if useContext was set to true', () => {
-			sinon.spy( deltaTransform, 'transformDeltaSets' );
-
-			// Prepare some empty-ish deltas so the transformation won't throw an error.
-			const deltasA = [ new Delta() ];
-			deltasA[ 0 ].addOperation( new NoOperation( 0 ) );
-
-			const deltasB = [ new Delta() ];
-			deltasB[ 0 ].addOperation( new NoOperation( 0 ) );
-
-			doc.transformDeltas( deltasA, deltasB, true );
-
-			expect( deltaTransform.transformDeltaSets.calledOnce ).to.be.true;
-			expect( deltaTransform.transformDeltaSets.calledWith( deltasA, deltasB, doc ) ).to.be.true;
-
-			deltaTransform.transformDeltaSets.restore();
-		} );
-	} );
-
 	describe( '_getDefaultRoot()', () => {
 	describe( '_getDefaultRoot()', () => {
 		it( 'should return graveyard root if there are no other roots in the document', () => {
 		it( 'should return graveyard root if there are no other roots in the document', () => {
 			expect( doc._getDefaultRoot() ).to.equal( doc.graveyard );
 			expect( doc._getDefaultRoot() ).to.equal( doc.graveyard );

+ 272 - 3
packages/ckeditor5-engine/tests/model/model.js

@@ -3,15 +3,27 @@
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 
-import Model from '../../src/model/model';
 import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';
 import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';
+import Model from '../../src/model/model';
+import NoOperation from '../../src/model/operation/nooperation';
+import deltaTransform from '../../src/model/delta/transform';
+import Delta from '../../src/model/delta/delta';
+import ModelText from '../../src/model/text';
+import ModelRange from '../../src/model/range';
+import ModelSelection from '../../src/model/selection';
+import ModelDocumentFragment from '../../src/model/documentfragment';
+import { getData, setData, stringify } from '../../src/dev-utils/model';
 
 
 describe( 'Model', () => {
 describe( 'Model', () => {
-	let model;
-	let changes = '';
+	let model, schema, changes;
 
 
 	beforeEach( () => {
 	beforeEach( () => {
 		model = new Model();
 		model = new Model();
+		model.document.createRoot();
+		model.document.createRoot( '$root', 'title' );
+
+		schema = model.schema;
+
 		changes = '';
 		changes = '';
 	} );
 	} );
 
 
@@ -322,6 +334,263 @@ describe( 'Model', () => {
 		} );
 		} );
 	} );
 	} );
 
 
+	describe( 'transformDeltas', () => {
+		it( 'should use deltaTransform.transformDeltaSets', () => {
+			sinon.spy( deltaTransform, 'transformDeltaSets' );
+
+			// Prepare some empty-ish deltas so the transformation won't throw an error.
+			const deltasA = [ new Delta() ];
+			deltasA[ 0 ].addOperation( new NoOperation( 0 ) );
+
+			const deltasB = [ new Delta() ];
+			deltasB[ 0 ].addOperation( new NoOperation( 0 ) );
+
+			model.transformDeltas( deltasA, deltasB );
+
+			expect( deltaTransform.transformDeltaSets.calledOnce ).to.be.true;
+			expect( deltaTransform.transformDeltaSets.calledWith( deltasA, deltasB, null ) ).to.be.true;
+
+			deltaTransform.transformDeltaSets.restore();
+		} );
+
+		it( 'should pass itself to transformDeltaSets if useContext was set to true', () => {
+			sinon.spy( deltaTransform, 'transformDeltaSets' );
+
+			// Prepare some empty-ish deltas so the transformation won't throw an error.
+			const deltasA = [ new Delta() ];
+			deltasA[ 0 ].addOperation( new NoOperation( 0 ) );
+
+			const deltasB = [ new Delta() ];
+			deltasB[ 0 ].addOperation( new NoOperation( 0 ) );
+
+			model.transformDeltas( deltasA, deltasB, true );
+
+			expect( deltaTransform.transformDeltaSets.calledOnce ).to.be.true;
+			expect( deltaTransform.transformDeltaSets.calledWith( deltasA, deltasB, model.document ) ).to.be.true;
+
+			deltaTransform.transformDeltaSets.restore();
+		} );
+	} );
+
+	describe( 'insertContent()', () => {
+		it( 'should be decorated', () => {
+			schema.allow( { name: '$text', inside: '$root' } ); // To surpress warnings.
+
+			const spy = sinon.spy();
+
+			model.on( 'insertContent', spy );
+
+			model.insertContent( new ModelText( 'a' ), model.document.selection );
+
+			expect( spy.calledOnce ).to.be.true;
+		} );
+
+		it( 'should insert content (item)', () => {
+			schema.registerItem( 'paragraph', '$block' );
+
+			setData( model, '<paragraph>fo[]ar</paragraph>' );
+
+			model.insertContent( new ModelText( 'ob' ), model.document.selection );
+
+			expect( getData( model ) ).to.equal( '<paragraph>foob[]ar</paragraph>' );
+		} );
+
+		it( 'should insert content (document fragment)', () => {
+			schema.registerItem( 'paragraph', '$block' );
+
+			setData( model, '<paragraph>fo[]ar</paragraph>' );
+
+			model.insertContent( new ModelDocumentFragment( [ new ModelText( 'ob' ) ] ), model.document.selection );
+
+			expect( getData( model ) ).to.equal( '<paragraph>foob[]ar</paragraph>' );
+		} );
+
+		it( 'should use parent batch', () => {
+			schema.registerItem( 'paragraph', '$block' );
+			setData( model, '<paragraph>[]</paragraph>' );
+
+			model.change( writer => {
+				model.insertContent( new ModelText( 'abc' ), model.document.selection );
+				expect( writer.batch.deltas ).to.length( 1 );
+			} );
+		} );
+	} );
+
+	describe( 'deleteContent()', () => {
+		it( 'should be decorated', () => {
+			const spy = sinon.spy();
+
+			model.on( 'deleteContent', spy );
+
+			model.deleteContent( model.document.selection );
+
+			expect( spy.calledOnce ).to.be.true;
+		} );
+
+		it( 'should delete selected content', () => {
+			schema.registerItem( 'paragraph', '$block' );
+
+			setData( model, '<paragraph>fo[ob]ar</paragraph>' );
+
+			model.deleteContent( model.document.selection );
+
+			expect( getData( model ) ).to.equal( '<paragraph>fo[]ar</paragraph>' );
+		} );
+
+		it( 'should use parent batch', () => {
+			schema.registerItem( 'paragraph', '$block' );
+
+			setData( model, '<paragraph>fo[ob]ar</paragraph>' );
+
+			model.change( writer => {
+				model.deleteContent( model.document.selection );
+				expect( writer.batch.deltas ).to.length( 1 );
+			} );
+		} );
+	} );
+
+	describe( 'modifySelection()', () => {
+		it( 'should be decorated', () => {
+			schema.registerItem( 'paragraph', '$block' );
+			setData( model, '<paragraph>fo[ob]ar</paragraph>' );
+
+			const spy = sinon.spy();
+
+			model.on( 'modifySelection', spy );
+
+			model.modifySelection( model.document.selection );
+
+			expect( spy.calledOnce ).to.be.true;
+		} );
+
+		it( 'should modify a selection', () => {
+			schema.registerItem( 'paragraph', '$block' );
+
+			setData( model, '<paragraph>fo[ob]ar</paragraph>' );
+
+			model.modifySelection( model.document.selection, { direction: 'backward' } );
+
+			expect( getData( model ) ).to.equal( '<paragraph>fo[o]bar</paragraph>' );
+		} );
+	} );
+
+	describe( 'getSelectedContent()', () => {
+		it( 'should be decorated', () => {
+			const spy = sinon.spy();
+			const sel = new ModelSelection();
+
+			model.on( 'getSelectedContent', spy );
+
+			model.getSelectedContent( sel );
+
+			expect( spy.calledOnce ).to.be.true;
+		} );
+
+		it( 'should return selected content', () => {
+			schema.registerItem( 'paragraph', '$block' );
+
+			setData( model, '<paragraph>fo[ob]ar</paragraph>' );
+
+			const content = model.getSelectedContent( model.document.selection );
+
+			expect( stringify( content ) ).to.equal( 'ob' );
+		} );
+
+		it( 'should use parent batch', () => {
+			schema.registerItem( 'paragraph', '$block' );
+
+			setData( model, '<paragraph>fo[ob]ar</paragraph>' );
+
+			model.change( writer => {
+				model.getSelectedContent( model.document.selection );
+				expect( writer.batch.deltas ).to.length( 1 );
+			} );
+		} );
+	} );
+
+	describe( 'hasContent()', () => {
+		let root;
+
+		beforeEach( () => {
+			schema.registerItem( 'paragraph', '$block' );
+			schema.registerItem( 'div', '$block' );
+			schema.allow( { name: '$block', inside: 'div' } );
+			schema.registerItem( 'image' );
+			schema.allow( { name: 'image', inside: 'div' } );
+			schema.objects.add( 'image' );
+
+			setData(
+				model,
+
+				'<div>' +
+				'<paragraph></paragraph>' +
+				'</div>' +
+				'<paragraph>foo</paragraph>' +
+				'<div>' +
+				'<image></image>' +
+				'</div>'
+			);
+
+			root = model.document.getRoot();
+		} );
+
+		it( 'should return true if given element has text node', () => {
+			const pFoo = root.getChild( 1 );
+
+			expect( model.hasContent( pFoo ) ).to.be.true;
+		} );
+
+		it( 'should return true if given element has element that is an object', () => {
+			const divImg = root.getChild( 2 );
+
+			expect( model.hasContent( divImg ) ).to.be.true;
+		} );
+
+		it( 'should return false if given element has no elements', () => {
+			const pEmpty = root.getChild( 0 ).getChild( 0 );
+
+			expect( model.hasContent( pEmpty ) ).to.be.false;
+		} );
+
+		it( 'should return false if given element has only elements that are not objects', () => {
+			const divP = root.getChild( 0 );
+
+			expect( model.hasContent( divP ) ).to.be.false;
+		} );
+
+		it( 'should return true if there is a text node in given range', () => {
+			const range = ModelRange.createFromParentsAndOffsets( root, 1, root, 2 );
+
+			expect( model.hasContent( range ) ).to.be.true;
+		} );
+
+		it( 'should return true if there is a part of text node in given range', () => {
+			const pFoo = root.getChild( 1 );
+			const range = ModelRange.createFromParentsAndOffsets( pFoo, 1, pFoo, 2 );
+
+			expect( model.hasContent( range ) ).to.be.true;
+		} );
+
+		it( 'should return true if there is element that is an object in given range', () => {
+			const divImg = root.getChild( 2 );
+			const range = ModelRange.createFromParentsAndOffsets( divImg, 0, divImg, 1 );
+
+			expect( model.hasContent( range ) ).to.be.true;
+		} );
+
+		it( 'should return false if range is collapsed', () => {
+			const range = ModelRange.createFromParentsAndOffsets( root, 1, root, 1 );
+
+			expect( model.hasContent( range ) ).to.be.false;
+		} );
+
+		it( 'should return false if range has only elements that are not objects', () => {
+			const range = ModelRange.createFromParentsAndOffsets( root, 0, root, 1 );
+
+			expect( model.hasContent( range ) ).to.be.false;
+		} );
+	} );
+
 	describe( 'destroy()', () => {
 	describe( 'destroy()', () => {
 		it( 'should destroy document', () => {
 		it( 'should destroy document', () => {
 			sinon.spy( model.document, 'destroy' );
 			sinon.spy( model.document, 'destroy' );

+ 29 - 39
packages/ckeditor5-engine/tests/controller/deletecontent.js → packages/ckeditor5-engine/tests/model/utils/deletecontent.js

@@ -3,29 +3,27 @@
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 
-import Model from '../../src/model/model';
-import Position from '../../src/model/position';
-import Range from '../../src/model/range';
-import Element from '../../src/model/element';
-import DataController from '../../src/controller/datacontroller';
-import deleteContent from '../../src/controller/deletecontent';
-import { setData, getData } from '../../src/dev-utils/model';
+import Model from '../../../src/model/model';
+import Position from '../../../src/model/position';
+import Range from '../../../src/model/range';
+import Element from '../../../src/model/element';
+import deleteContent from '../../../src/model/utils/deletecontent';
+import { setData, getData } from '../../../src/dev-utils/model';
 
 
 describe( 'DataController utils', () => {
 describe( 'DataController utils', () => {
-	let model, doc, data;
+	let model, doc;
 
 
 	describe( 'deleteContent', () => {
 	describe( 'deleteContent', () => {
 		it( 'should use parent batch', () => {
 		it( 'should use parent batch', () => {
 			model = new Model();
 			model = new Model();
 			doc = model.document;
 			doc = model.document;
 			doc.createRoot();
 			doc.createRoot();
-			data = new DataController( model );
 
 
 			model.schema.allow( { name: '$text', inside: '$root' } );
 			model.schema.allow( { name: '$text', inside: '$root' } );
 			setData( model, 'x[abc]x' );
 			setData( model, 'x[abc]x' );
 
 
 			model.change( writer => {
 			model.change( writer => {
-				deleteContent( data, doc.selection );
+				deleteContent( model, doc.selection );
 				expect( writer.batch.deltas ).to.length( 1 );
 				expect( writer.batch.deltas ).to.length( 1 );
 			} );
 			} );
 		} );
 		} );
@@ -35,7 +33,6 @@ describe( 'DataController utils', () => {
 				model = new Model();
 				model = new Model();
 				doc = model.document;
 				doc = model.document;
 				doc.createRoot();
 				doc.createRoot();
-				data = new DataController( model );
 
 
 				const schema = model.schema;
 				const schema = model.schema;
 
 
@@ -60,7 +57,7 @@ describe( 'DataController utils', () => {
 			it( 'deletes single character (backward selection)', () => {
 			it( 'deletes single character (backward selection)', () => {
 				setData( model, 'f[o]o', { lastRangeBackward: true } );
 				setData( model, 'f[o]o', { lastRangeBackward: true } );
 
 
-				deleteContent( data, doc.selection );
+				deleteContent( model, doc.selection );
 
 
 				expect( getData( model ) ).to.equal( 'f[]o' );
 				expect( getData( model ) ).to.equal( 'f[]o' );
 			} );
 			} );
@@ -101,7 +98,6 @@ describe( 'DataController utils', () => {
 				model = new Model();
 				model = new Model();
 				doc = model.document;
 				doc = model.document;
 				doc.createRoot();
 				doc.createRoot();
-				data = new DataController( model );
 
 
 				const schema = model.schema;
 				const schema = model.schema;
 
 
@@ -115,7 +111,7 @@ describe( 'DataController utils', () => {
 			it( 'deletes characters (first half has attrs)', () => {
 			it( 'deletes characters (first half has attrs)', () => {
 				setData( model, '<$text bold="true">fo[o</$text>b]ar' );
 				setData( model, '<$text bold="true">fo[o</$text>b]ar' );
 
 
-				deleteContent( data, doc.selection );
+				deleteContent( model, doc.selection );
 
 
 				expect( getData( model ) ).to.equal( '<$text bold="true">fo[]</$text>ar' );
 				expect( getData( model ) ).to.equal( '<$text bold="true">fo[]</$text>ar' );
 				expect( doc.selection.getAttribute( 'bold' ) ).to.equal( true );
 				expect( doc.selection.getAttribute( 'bold' ) ).to.equal( true );
@@ -124,7 +120,7 @@ describe( 'DataController utils', () => {
 			it( 'deletes characters (2nd half has attrs)', () => {
 			it( 'deletes characters (2nd half has attrs)', () => {
 				setData( model, 'fo[o<$text bold="true">b]ar</$text>' );
 				setData( model, 'fo[o<$text bold="true">b]ar</$text>' );
 
 
-				deleteContent( data, doc.selection );
+				deleteContent( model, doc.selection );
 
 
 				expect( getData( model ) ).to.equal( 'fo[]<$text bold="true">ar</$text>' );
 				expect( getData( model ) ).to.equal( 'fo[]<$text bold="true">ar</$text>' );
 				expect( doc.selection.getAttribute( 'bold' ) ).to.undefined;
 				expect( doc.selection.getAttribute( 'bold' ) ).to.undefined;
@@ -133,7 +129,7 @@ describe( 'DataController utils', () => {
 			it( 'clears selection attrs when emptied content', () => {
 			it( 'clears selection attrs when emptied content', () => {
 				setData( model, '<paragraph>x</paragraph><paragraph>[<$text bold="true">foo</$text>]</paragraph><paragraph>y</paragraph>' );
 				setData( model, '<paragraph>x</paragraph><paragraph>[<$text bold="true">foo</$text>]</paragraph><paragraph>y</paragraph>' );
 
 
-				deleteContent( data, doc.selection );
+				deleteContent( model, doc.selection );
 
 
 				expect( getData( model ) ).to.equal( '<paragraph>x</paragraph><paragraph>[]</paragraph><paragraph>y</paragraph>' );
 				expect( getData( model ) ).to.equal( '<paragraph>x</paragraph><paragraph>[]</paragraph><paragraph>y</paragraph>' );
 				expect( doc.selection.getAttribute( 'bold' ) ).to.undefined;
 				expect( doc.selection.getAttribute( 'bold' ) ).to.undefined;
@@ -150,7 +146,7 @@ describe( 'DataController utils', () => {
 					}
 					}
 				);
 				);
 
 
-				deleteContent( data, doc.selection );
+				deleteContent( model, doc.selection );
 
 
 				expect( getData( model ) ).to.equal( '<paragraph>x<$text bold="true">a[]b</$text>y</paragraph>' );
 				expect( getData( model ) ).to.equal( '<paragraph>x<$text bold="true">a[]b</$text>y</paragraph>' );
 				expect( doc.selection.getAttribute( 'bold' ) ).to.equal( true );
 				expect( doc.selection.getAttribute( 'bold' ) ).to.equal( true );
@@ -171,7 +167,6 @@ describe( 'DataController utils', () => {
 				model = new Model();
 				model = new Model();
 				doc = model.document;
 				doc = model.document;
 				doc.createRoot();
 				doc.createRoot();
-				data = new DataController( model );
 
 
 				const schema = model.schema;
 				const schema = model.schema;
 
 
@@ -226,7 +221,7 @@ describe( 'DataController utils', () => {
 					{ lastRangeBackward: true }
 					{ lastRangeBackward: true }
 				);
 				);
 
 
-				deleteContent( data, doc.selection );
+				deleteContent( model, doc.selection );
 
 
 				expect( getData( model ) ).to.equal( '<paragraph>x</paragraph><heading1>fo[]ar</heading1><paragraph>y</paragraph>' );
 				expect( getData( model ) ).to.equal( '<paragraph>x</paragraph><heading1>fo[]ar</heading1><paragraph>y</paragraph>' );
 			} );
 			} );
@@ -262,7 +257,7 @@ describe( 'DataController utils', () => {
 
 
 				model.change( writer => {
 				model.change( writer => {
 					mergeSpy = sinon.spy( writer, 'merge' );
 					mergeSpy = sinon.spy( writer, 'merge' );
-					deleteContent( data, doc.selection );
+					deleteContent( model, doc.selection );
 				} );
 				} );
 
 
 				expect( getData( model ) ).to.equal( '<paragraph>ab[]</paragraph>' );
 				expect( getData( model ) ).to.equal( '<paragraph>ab[]</paragraph>' );
@@ -277,7 +272,7 @@ describe( 'DataController utils', () => {
 
 
 				model.change( writer => {
 				model.change( writer => {
 					mergeSpy = sinon.spy( writer, 'merge' );
 					mergeSpy = sinon.spy( writer, 'merge' );
-					deleteContent( data, doc.selection );
+					deleteContent( model, doc.selection );
 				} );
 				} );
 
 
 				expect( getData( model ) ).to.equal( '<paragraph>ab[]</paragraph>' );
 				expect( getData( model ) ).to.equal( '<paragraph>ab[]</paragraph>' );
@@ -293,7 +288,7 @@ describe( 'DataController utils', () => {
 				model.change( writer => {
 				model.change( writer => {
 					mergeSpy = sinon.spy( writer, 'merge' );
 					mergeSpy = sinon.spy( writer, 'merge' );
 					moveSpy = sinon.spy( writer, 'move' );
 					moveSpy = sinon.spy( writer, 'move' );
-					deleteContent( data, doc.selection );
+					deleteContent( model, doc.selection );
 				} );
 				} );
 
 
 				expect( getData( model ) ).to.equal( '<paragraph>ab[]gh</paragraph>' );
 				expect( getData( model ) ).to.equal( '<paragraph>ab[]gh</paragraph>' );
@@ -346,7 +341,7 @@ describe( 'DataController utils', () => {
 
 
 					doc.selection.setRanges( [ range ] );
 					doc.selection.setRanges( [ range ] );
 
 
-					deleteContent( data, doc.selection );
+					deleteContent( model, doc.selection );
 
 
 					expect( getData( model ) )
 					expect( getData( model ) )
 						.to.equal( '<pparent>x<paragraph>x<pchild>fo[]ar</pchild>y</paragraph>y</pparent>' );
 						.to.equal( '<pparent>x<paragraph>x<pchild>fo[]ar</pchild>y</paragraph>y</pparent>' );
@@ -391,7 +386,7 @@ describe( 'DataController utils', () => {
 
 
 					doc.selection.setRanges( [ range ] );
 					doc.selection.setRanges( [ range ] );
 
 
-					deleteContent( data, doc.selection );
+					deleteContent( model, doc.selection );
 
 
 					expect( getData( model ) )
 					expect( getData( model ) )
 						.to.equal( '<pparent>x<paragraph>foo<pchild>ba[]om</pchild></paragraph></pparent>' );
 						.to.equal( '<pparent>x<paragraph>foo<pchild>ba[]om</pchild></paragraph></pparent>' );
@@ -434,7 +429,7 @@ describe( 'DataController utils', () => {
 
 
 					doc.selection.setRanges( [ range ] );
 					doc.selection.setRanges( [ range ] );
 
 
-					deleteContent( data, doc.selection );
+					deleteContent( model, doc.selection );
 
 
 					expect( getData( model ) )
 					expect( getData( model ) )
 						.to.equal( '<paragraph>fo[]</paragraph>' );
 						.to.equal( '<paragraph>fo[]</paragraph>' );
@@ -528,8 +523,6 @@ describe( 'DataController utils', () => {
 				// Special root which allows only blockWidgets inside itself.
 				// Special root which allows only blockWidgets inside itself.
 				doc.createRoot( 'restrictedRoot', 'restrictedRoot' );
 				doc.createRoot( 'restrictedRoot', 'restrictedRoot' );
 
 
-				data = new DataController( model );
-
 				const schema = model.schema;
 				const schema = model.schema;
 
 
 				schema.limits.add( 'restrictedRoot' );
 				schema.limits.add( 'restrictedRoot' );
@@ -557,7 +550,7 @@ describe( 'DataController utils', () => {
 					{ rootName: 'paragraphRoot' }
 					{ rootName: 'paragraphRoot' }
 				);
 				);
 
 
-				deleteContent( data, doc.selection );
+				deleteContent( model, doc.selection );
 
 
 				expect( getData( model, { rootName: 'paragraphRoot' } ) )
 				expect( getData( model, { rootName: 'paragraphRoot' } ) )
 					.to.equal( 'x[]z' );
 					.to.equal( 'x[]z' );
@@ -570,7 +563,7 @@ describe( 'DataController utils', () => {
 					{ rootName: 'bodyRoot' }
 					{ rootName: 'bodyRoot' }
 				);
 				);
 
 
-				deleteContent( data, doc.selection );
+				deleteContent( model, doc.selection );
 
 
 				expect( getData( model, { rootName: 'bodyRoot' } ) )
 				expect( getData( model, { rootName: 'bodyRoot' } ) )
 					.to.equal( '<paragraph>x</paragraph><paragraph>[]</paragraph><paragraph>z</paragraph>' );
 					.to.equal( '<paragraph>x</paragraph><paragraph>[]</paragraph><paragraph>z</paragraph>' );
@@ -583,7 +576,7 @@ describe( 'DataController utils', () => {
 					{ rootName: 'bodyRoot' }
 					{ rootName: 'bodyRoot' }
 				);
 				);
 
 
-				deleteContent( data, doc.selection );
+				deleteContent( model, doc.selection );
 
 
 				expect( getData( model, { rootName: 'bodyRoot' } ) )
 				expect( getData( model, { rootName: 'bodyRoot' } ) )
 					.to.equal( '<paragraph>x</paragraph><paragraph>[]</paragraph><paragraph>z</paragraph>' );
 					.to.equal( '<paragraph>x</paragraph><paragraph>[]</paragraph><paragraph>z</paragraph>' );
@@ -596,7 +589,7 @@ describe( 'DataController utils', () => {
 					{ rootName: 'bodyRoot' }
 					{ rootName: 'bodyRoot' }
 				);
 				);
 
 
-				deleteContent( data, doc.selection );
+				deleteContent( model, doc.selection );
 
 
 				expect( getData( model, { rootName: 'bodyRoot' } ) )
 				expect( getData( model, { rootName: 'bodyRoot' } ) )
 					.to.equal( '<paragraph>x</paragraph><paragraph>[]</paragraph><paragraph>z</paragraph>' );
 					.to.equal( '<paragraph>x</paragraph><paragraph>[]</paragraph><paragraph>z</paragraph>' );
@@ -609,7 +602,7 @@ describe( 'DataController utils', () => {
 					{ rootName: 'bodyRoot' }
 					{ rootName: 'bodyRoot' }
 				);
 				);
 
 
-				deleteContent( data, doc.selection );
+				deleteContent( model, doc.selection );
 
 
 				expect( getData( model, { rootName: 'bodyRoot' } ) )
 				expect( getData( model, { rootName: 'bodyRoot' } ) )
 					.to.equal( '<paragraph>x</paragraph><paragraph>[]</paragraph><paragraph>z</paragraph>' );
 					.to.equal( '<paragraph>x</paragraph><paragraph>[]</paragraph><paragraph>z</paragraph>' );
@@ -622,7 +615,7 @@ describe( 'DataController utils', () => {
 					{ rootName: 'bodyRoot' }
 					{ rootName: 'bodyRoot' }
 				);
 				);
 
 
-				deleteContent( data, doc.selection );
+				deleteContent( model, doc.selection );
 
 
 				expect( getData( model, { rootName: 'bodyRoot' } ) )
 				expect( getData( model, { rootName: 'bodyRoot' } ) )
 					.to.equal( '<paragraph>[]</paragraph>' );
 					.to.equal( '<paragraph>[]</paragraph>' );
@@ -635,7 +628,7 @@ describe( 'DataController utils', () => {
 					{ rootName: 'restrictedRoot' }
 					{ rootName: 'restrictedRoot' }
 				);
 				);
 
 
-				deleteContent( data, doc.selection );
+				deleteContent( model, doc.selection );
 
 
 				expect( getData( model, { rootName: 'restrictedRoot' } ) )
 				expect( getData( model, { rootName: 'restrictedRoot' } ) )
 					.to.equal( '<blockWidget></blockWidget>[]<blockWidget></blockWidget>' );
 					.to.equal( '<blockWidget></blockWidget>[]<blockWidget></blockWidget>' );
@@ -647,7 +640,6 @@ describe( 'DataController utils', () => {
 				model = new Model();
 				model = new Model();
 				doc = model.document;
 				doc = model.document;
 				doc.createRoot();
 				doc.createRoot();
-				data = new DataController( model );
 
 
 				const schema = model.schema;
 				const schema = model.schema;
 
 
@@ -705,7 +697,6 @@ describe( 'DataController utils', () => {
 				model = new Model();
 				model = new Model();
 				doc = model.document;
 				doc = model.document;
 				doc.createRoot();
 				doc.createRoot();
-				data = new DataController( model );
 
 
 				const schema = model.schema;
 				const schema = model.schema;
 
 
@@ -754,7 +745,6 @@ describe( 'DataController utils', () => {
 				model = new Model();
 				model = new Model();
 				doc = model.document;
 				doc = model.document;
 				doc.createRoot();
 				doc.createRoot();
-				data = new DataController( model );
 
 
 				const schema = model.schema;
 				const schema = model.schema;
 
 
@@ -832,7 +822,7 @@ describe( 'DataController utils', () => {
 					{ rootName: 'paragraphRoot' }
 					{ rootName: 'paragraphRoot' }
 				);
 				);
 
 
-				deleteContent( data, doc.selection );
+				deleteContent( model, doc.selection );
 
 
 				expect( getData( model, { rootName: 'paragraphRoot' } ) )
 				expect( getData( model, { rootName: 'paragraphRoot' } ) )
 					.to.equal( 'x[]z' );
 					.to.equal( 'x[]z' );
@@ -852,7 +842,7 @@ describe( 'DataController utils', () => {
 			it( title, () => {
 			it( title, () => {
 				setData( model, input );
 				setData( model, input );
 
 
-				deleteContent( data, doc.selection, options );
+				deleteContent( model, doc.selection, options );
 
 
 				expect( getData( model ) ).to.equal( output );
 				expect( getData( model ) ).to.equal( output );
 			} );
 			} );

+ 40 - 45
packages/ckeditor5-engine/tests/controller/getselectedcontent.js → packages/ckeditor5-engine/tests/model/utils/getselectedcontent.js

@@ -3,27 +3,25 @@
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 
-import Model from '../../src/model/model';
-import DataController from '../../src/controller/datacontroller';
-import DocumentFragment from '../../src/model/documentfragment';
-import getSelectedContent from '../../src/controller/getselectedcontent';
-import { setData, stringify } from '../../src/dev-utils/model';
+import Model from '../../../src/model/model';
+import DocumentFragment from '../../../src/model/documentfragment';
+import getSelectedContent from '../../../src/model/utils/getselectedcontent';
+import { setData, stringify } from '../../../src/dev-utils/model';
 
 
 describe( 'DataController utils', () => {
 describe( 'DataController utils', () => {
-	let model, doc, data;
+	let model, doc;
 
 
 	describe( 'getSelectedContent', () => {
 	describe( 'getSelectedContent', () => {
 		it( 'should use parent batch', () => {
 		it( 'should use parent batch', () => {
 			model = new Model();
 			model = new Model();
 			doc = model.document;
 			doc = model.document;
 			doc.createRoot();
 			doc.createRoot();
-			data = new DataController( model );
 
 
 			model.schema.allow( { name: '$text', inside: '$root' } );
 			model.schema.allow( { name: '$text', inside: '$root' } );
 			setData( model, 'x[abc]x' );
 			setData( model, 'x[abc]x' );
 
 
 			model.change( writer => {
 			model.change( writer => {
-				getSelectedContent( data, doc.selection );
+				getSelectedContent( model, doc.selection );
 				expect( writer.batch.deltas ).to.length( 1 );
 				expect( writer.batch.deltas ).to.length( 1 );
 			} );
 			} );
 		} );
 		} );
@@ -33,7 +31,6 @@ describe( 'DataController utils', () => {
 				model = new Model();
 				model = new Model();
 				doc = model.document;
 				doc = model.document;
 				doc.createRoot();
 				doc.createRoot();
-				data = new DataController( model );
 
 
 				const schema = model.schema;
 				const schema = model.schema;
 
 
@@ -48,7 +45,7 @@ describe( 'DataController utils', () => {
 			it( 'returns empty fragment for no selection', () => {
 			it( 'returns empty fragment for no selection', () => {
 				setData( model, 'abc' );
 				setData( model, 'abc' );
 
 
-				const frag = getSelectedContent( data, doc.selection );
+				const frag = getSelectedContent( model, doc.selection );
 
 
 				expect( frag ).instanceOf( DocumentFragment );
 				expect( frag ).instanceOf( DocumentFragment );
 				expect( frag.isEmpty ).to.be.true;
 				expect( frag.isEmpty ).to.be.true;
@@ -57,7 +54,7 @@ describe( 'DataController utils', () => {
 			it( 'returns empty fragment for empty selection', () => {
 			it( 'returns empty fragment for empty selection', () => {
 				setData( model, 'a[]bc' );
 				setData( model, 'a[]bc' );
 
 
-				const frag = getSelectedContent( data, doc.selection );
+				const frag = getSelectedContent( model, doc.selection );
 
 
 				expect( frag ).instanceOf( DocumentFragment );
 				expect( frag ).instanceOf( DocumentFragment );
 				expect( frag.isEmpty ).to.be.true;
 				expect( frag.isEmpty ).to.be.true;
@@ -66,7 +63,7 @@ describe( 'DataController utils', () => {
 			it( 'gets one character', () => {
 			it( 'gets one character', () => {
 				setData( model, 'a[b]c' );
 				setData( model, 'a[b]c' );
 
 
-				const frag = getSelectedContent( data, doc.selection );
+				const frag = getSelectedContent( model, doc.selection );
 				const content = stringify( frag );
 				const content = stringify( frag );
 
 
 				expect( frag ).instanceOf( DocumentFragment );
 				expect( frag ).instanceOf( DocumentFragment );
@@ -76,49 +73,49 @@ describe( 'DataController utils', () => {
 			it( 'gets full text', () => {
 			it( 'gets full text', () => {
 				setData( model, '[abc]' );
 				setData( model, '[abc]' );
 
 
-				const content = stringify( getSelectedContent( data, doc.selection ) );
+				const content = stringify( getSelectedContent( model, doc.selection ) );
 				expect( content ).to.equal( 'abc' );
 				expect( content ).to.equal( 'abc' );
 			} );
 			} );
 
 
 			it( 'gets text with an attribute', () => {
 			it( 'gets text with an attribute', () => {
 				setData( model, 'xxx<$text bold="true">a[b]c</$text>' );
 				setData( model, 'xxx<$text bold="true">a[b]c</$text>' );
 
 
-				const content = stringify( getSelectedContent( data, doc.selection ) );
+				const content = stringify( getSelectedContent( model, doc.selection ) );
 				expect( content ).to.equal( '<$text bold="true">b</$text>' );
 				expect( content ).to.equal( '<$text bold="true">b</$text>' );
 			} );
 			} );
 
 
 			it( 'gets text with attributes', () => {
 			it( 'gets text with attributes', () => {
 				setData( model, 'x<$text bold="true">a[b</$text><$text italic="true">c]d</$text>x' );
 				setData( model, 'x<$text bold="true">a[b</$text><$text italic="true">c]d</$text>x' );
 
 
-				const content = stringify( getSelectedContent( data, doc.selection ) );
+				const content = stringify( getSelectedContent( model, doc.selection ) );
 				expect( content ).to.equal( '<$text bold="true">b</$text><$text italic="true">c</$text>' );
 				expect( content ).to.equal( '<$text bold="true">b</$text><$text italic="true">c</$text>' );
 			} );
 			} );
 
 
 			it( 'gets text with and without attribute', () => {
 			it( 'gets text with and without attribute', () => {
 				setData( model, '<$text bold="true">a[b</$text>c]d' );
 				setData( model, '<$text bold="true">a[b</$text>c]d' );
 
 
-				const content = stringify( getSelectedContent( data, doc.selection ) );
+				const content = stringify( getSelectedContent( model, doc.selection ) );
 				expect( content ).to.equal( '<$text bold="true">b</$text>c' );
 				expect( content ).to.equal( '<$text bold="true">b</$text>c' );
 			} );
 			} );
 
 
 			it( 'gets text and element', () => {
 			it( 'gets text and element', () => {
 				setData( model, '[ab<image></image>c]' );
 				setData( model, '[ab<image></image>c]' );
 
 
-				const content = stringify( getSelectedContent( data, doc.selection ) );
+				const content = stringify( getSelectedContent( model, doc.selection ) );
 				expect( content ).to.equal( 'ab<image></image>c' );
 				expect( content ).to.equal( 'ab<image></image>c' );
 			} );
 			} );
 
 
 			it( 'gets one element', () => {
 			it( 'gets one element', () => {
 				setData( model, 'a[<image></image>]b' );
 				setData( model, 'a[<image></image>]b' );
 
 
-				const content = stringify( getSelectedContent( data, doc.selection ) );
+				const content = stringify( getSelectedContent( model, doc.selection ) );
 				expect( content ).to.equal( '<image></image>' );
 				expect( content ).to.equal( '<image></image>' );
 			} );
 			} );
 
 
 			it( 'gets multiple elements', () => {
 			it( 'gets multiple elements', () => {
 				setData( model, '[<image></image><image></image>]' );
 				setData( model, '[<image></image><image></image>]' );
 
 
-				const content = stringify( getSelectedContent( data, doc.selection ) );
+				const content = stringify( getSelectedContent( model, doc.selection ) );
 				expect( content ).to.equal( '<image></image><image></image>' );
 				expect( content ).to.equal( '<image></image><image></image>' );
 			} );
 			} );
 		} );
 		} );
@@ -128,7 +125,6 @@ describe( 'DataController utils', () => {
 				model = new Model();
 				model = new Model();
 				doc = model.document;
 				doc = model.document;
 				doc.createRoot();
 				doc.createRoot();
-				data = new DataController( model );
 
 
 				const schema = model.schema;
 				const schema = model.schema;
 
 
@@ -148,63 +144,63 @@ describe( 'DataController utils', () => {
 			it( 'gets one character', () => {
 			it( 'gets one character', () => {
 				setData( model, '<paragraph>a[b]c</paragraph>' );
 				setData( model, '<paragraph>a[b]c</paragraph>' );
 
 
-				const content = stringify( getSelectedContent( data, doc.selection ) );
+				const content = stringify( getSelectedContent( model, doc.selection ) );
 				expect( content ).to.equal( 'b' );
 				expect( content ).to.equal( 'b' );
 			} );
 			} );
 
 
 			it( 'gets entire paragraph content', () => {
 			it( 'gets entire paragraph content', () => {
 				setData( model, '<paragraph>[a<image></image>b]</paragraph>' );
 				setData( model, '<paragraph>[a<image></image>b]</paragraph>' );
 
 
-				const content = stringify( getSelectedContent( data, doc.selection ) );
+				const content = stringify( getSelectedContent( model, doc.selection ) );
 				expect( content ).to.equal( 'a<image></image>b' );
 				expect( content ).to.equal( 'a<image></image>b' );
 			} );
 			} );
 
 
 			it( 'gets two blocks - partial, partial', () => {
 			it( 'gets two blocks - partial, partial', () => {
 				setData( model, '<heading1>a[bc</heading1><paragraph>de]f</paragraph>' );
 				setData( model, '<heading1>a[bc</heading1><paragraph>de]f</paragraph>' );
 
 
-				const content = stringify( getSelectedContent( data, doc.selection ) );
+				const content = stringify( getSelectedContent( model, doc.selection ) );
 				expect( content ).to.equal( '<heading1>bc</heading1><paragraph>de</paragraph>' );
 				expect( content ).to.equal( '<heading1>bc</heading1><paragraph>de</paragraph>' );
 			} );
 			} );
 
 
 			it( 'gets two blocks - full, partial', () => {
 			it( 'gets two blocks - full, partial', () => {
 				setData( model, '<heading1>[abc</heading1><paragraph>de]f</paragraph>' );
 				setData( model, '<heading1>[abc</heading1><paragraph>de]f</paragraph>' );
 
 
-				const content = stringify( getSelectedContent( data, doc.selection ) );
+				const content = stringify( getSelectedContent( model, doc.selection ) );
 				expect( content ).to.equal( '<heading1>abc</heading1><paragraph>de</paragraph>' );
 				expect( content ).to.equal( '<heading1>abc</heading1><paragraph>de</paragraph>' );
 			} );
 			} );
 
 
 			it( 'gets two blocks - full, partial 2', () => {
 			it( 'gets two blocks - full, partial 2', () => {
 				setData( model, '<heading1>[abc</heading1><paragraph>de<image></image>]f</paragraph>' );
 				setData( model, '<heading1>[abc</heading1><paragraph>de<image></image>]f</paragraph>' );
 
 
-				const content = stringify( getSelectedContent( data, doc.selection ) );
+				const content = stringify( getSelectedContent( model, doc.selection ) );
 				expect( content ).to.equal( '<heading1>abc</heading1><paragraph>de<image></image></paragraph>' );
 				expect( content ).to.equal( '<heading1>abc</heading1><paragraph>de<image></image></paragraph>' );
 			} );
 			} );
 
 
 			it( 'gets two blocks - full, partial 3', () => {
 			it( 'gets two blocks - full, partial 3', () => {
 				setData( model, '<heading1>x</heading1><heading1>[abc</heading1><paragraph><image></image>de]f</paragraph>' );
 				setData( model, '<heading1>x</heading1><heading1>[abc</heading1><paragraph><image></image>de]f</paragraph>' );
 
 
-				const content = stringify( getSelectedContent( data, doc.selection ) );
+				const content = stringify( getSelectedContent( model, doc.selection ) );
 				expect( content ).to.equal( '<heading1>abc</heading1><paragraph><image></image>de</paragraph>' );
 				expect( content ).to.equal( '<heading1>abc</heading1><paragraph><image></image>de</paragraph>' );
 			} );
 			} );
 
 
 			it( 'gets two blocks - full, partial 4', () => {
 			it( 'gets two blocks - full, partial 4', () => {
 				setData( model, '<heading1>[abc</heading1><paragraph>de]f<image></image></paragraph>' );
 				setData( model, '<heading1>[abc</heading1><paragraph>de]f<image></image></paragraph>' );
 
 
-				const content = stringify( getSelectedContent( data, doc.selection ) );
+				const content = stringify( getSelectedContent( model, doc.selection ) );
 				expect( content ).to.equal( '<heading1>abc</heading1><paragraph>de</paragraph>' );
 				expect( content ).to.equal( '<heading1>abc</heading1><paragraph>de</paragraph>' );
 			} );
 			} );
 
 
 			it( 'gets two blocks - partial, full', () => {
 			it( 'gets two blocks - partial, full', () => {
 				setData( model, '<heading1>a[bc</heading1><paragraph>def]</paragraph>' );
 				setData( model, '<heading1>a[bc</heading1><paragraph>def]</paragraph>' );
 
 
-				const content = stringify( getSelectedContent( data, doc.selection ) );
+				const content = stringify( getSelectedContent( model, doc.selection ) );
 				expect( content ).to.equal( '<heading1>bc</heading1><paragraph>def</paragraph>' );
 				expect( content ).to.equal( '<heading1>bc</heading1><paragraph>def</paragraph>' );
 			} );
 			} );
 
 
 			it( 'gets two blocks - partial, full 2', () => {
 			it( 'gets two blocks - partial, full 2', () => {
 				setData( model, '<heading1>a[<image></image>bc</heading1><paragraph>def]</paragraph>' );
 				setData( model, '<heading1>a[<image></image>bc</heading1><paragraph>def]</paragraph>' );
 
 
-				const content = stringify( getSelectedContent( data, doc.selection ) );
+				const content = stringify( getSelectedContent( model, doc.selection ) );
 				expect( content ).to.equal( '<heading1><image></image>bc</heading1><paragraph>def</paragraph>' );
 				expect( content ).to.equal( '<heading1><image></image>bc</heading1><paragraph>def</paragraph>' );
 			} );
 			} );
 
 
@@ -212,7 +208,7 @@ describe( 'DataController utils', () => {
 			it( 'gets two blocks - empty, full', () => {
 			it( 'gets two blocks - empty, full', () => {
 				setData( model, '<heading1>abc[</heading1><paragraph>def]</paragraph>' );
 				setData( model, '<heading1>abc[</heading1><paragraph>def]</paragraph>' );
 
 
-				const content = stringify( getSelectedContent( data, doc.selection ) );
+				const content = stringify( getSelectedContent( model, doc.selection ) );
 				expect( content ).to.equal( '<paragraph>def</paragraph>' );
 				expect( content ).to.equal( '<paragraph>def</paragraph>' );
 			} );
 			} );
 
 
@@ -220,28 +216,28 @@ describe( 'DataController utils', () => {
 			it( 'gets two blocks - partial, empty', () => {
 			it( 'gets two blocks - partial, empty', () => {
 				setData( model, '<heading1>a[bc</heading1><paragraph>]def</paragraph>' );
 				setData( model, '<heading1>a[bc</heading1><paragraph>]def</paragraph>' );
 
 
-				const content = stringify( getSelectedContent( data, doc.selection ) );
+				const content = stringify( getSelectedContent( model, doc.selection ) );
 				expect( content ).to.equal( '<heading1>bc</heading1>' );
 				expect( content ).to.equal( '<heading1>bc</heading1>' );
 			} );
 			} );
 
 
 			it( 'gets three blocks', () => {
 			it( 'gets three blocks', () => {
 				setData( model, '<heading1>a[bc</heading1><paragraph>x</paragraph><paragraph>de]f</paragraph>' );
 				setData( model, '<heading1>a[bc</heading1><paragraph>x</paragraph><paragraph>de]f</paragraph>' );
 
 
-				const content = stringify( getSelectedContent( data, doc.selection ) );
+				const content = stringify( getSelectedContent( model, doc.selection ) );
 				expect( content ).to.equal( '<heading1>bc</heading1><paragraph>x</paragraph><paragraph>de</paragraph>' );
 				expect( content ).to.equal( '<heading1>bc</heading1><paragraph>x</paragraph><paragraph>de</paragraph>' );
 			} );
 			} );
 
 
 			it( 'gets block image', () => {
 			it( 'gets block image', () => {
 				setData( model, '<paragraph>a</paragraph>[<blockImage><caption>Foo</caption></blockImage>]<paragraph>b</paragraph>' );
 				setData( model, '<paragraph>a</paragraph>[<blockImage><caption>Foo</caption></blockImage>]<paragraph>b</paragraph>' );
 
 
-				const content = stringify( getSelectedContent( data, doc.selection ) );
+				const content = stringify( getSelectedContent( model, doc.selection ) );
 				expect( content ).to.equal( '<blockImage><caption>Foo</caption></blockImage>' );
 				expect( content ).to.equal( '<blockImage><caption>Foo</caption></blockImage>' );
 			} );
 			} );
 
 
 			it( 'gets two blocks', () => {
 			it( 'gets two blocks', () => {
 				setData( model, '<paragraph>a</paragraph>[<blockImage></blockImage><blockImage></blockImage>]<paragraph>b</paragraph>' );
 				setData( model, '<paragraph>a</paragraph>[<blockImage></blockImage><blockImage></blockImage>]<paragraph>b</paragraph>' );
 
 
-				const content = stringify( getSelectedContent( data, doc.selection ) );
+				const content = stringify( getSelectedContent( model, doc.selection ) );
 				expect( content ).to.equal( '<blockImage></blockImage><blockImage></blockImage>' );
 				expect( content ).to.equal( '<blockImage></blockImage><blockImage></blockImage>' );
 			} );
 			} );
 
 
@@ -249,7 +245,7 @@ describe( 'DataController utils', () => {
 			it( 'gets content when multiple text items needs to be removed from the right excess', () => {
 			it( 'gets content when multiple text items needs to be removed from the right excess', () => {
 				setData( model, '<paragraph>a[b</paragraph><paragraph>c]d<$text bold="true">e</$text>f</paragraph>' );
 				setData( model, '<paragraph>a[b</paragraph><paragraph>c]d<$text bold="true">e</$text>f</paragraph>' );
 
 
-				const content = stringify( getSelectedContent( data, doc.selection ) );
+				const content = stringify( getSelectedContent( model, doc.selection ) );
 				expect( content )
 				expect( content )
 					.to.equal( '<paragraph>b</paragraph><paragraph>c</paragraph>' );
 					.to.equal( '<paragraph>b</paragraph><paragraph>c</paragraph>' );
 			} );
 			} );
@@ -258,7 +254,7 @@ describe( 'DataController utils', () => {
 			it( 'gets content when multiple text items needs to be removed from the left excess', () => {
 			it( 'gets content when multiple text items needs to be removed from the left excess', () => {
 				setData( model, '<paragraph>a<$text bold="true">b</$text>c[d</paragraph><paragraph>e]f</paragraph>' );
 				setData( model, '<paragraph>a<$text bold="true">b</$text>c[d</paragraph><paragraph>e]f</paragraph>' );
 
 
-				const content = stringify( getSelectedContent( data, doc.selection ) );
+				const content = stringify( getSelectedContent( model, doc.selection ) );
 				expect( content )
 				expect( content )
 					.to.equal( '<paragraph>d</paragraph><paragraph>e</paragraph>' );
 					.to.equal( '<paragraph>d</paragraph><paragraph>e</paragraph>' );
 			} );
 			} );
@@ -269,7 +265,6 @@ describe( 'DataController utils', () => {
 				model = new Model();
 				model = new Model();
 				doc = model.document;
 				doc = model.document;
 				doc.createRoot();
 				doc.createRoot();
-				data = new DataController( model );
 
 
 				const schema = model.schema;
 				const schema = model.schema;
 
 
@@ -284,28 +279,28 @@ describe( 'DataController utils', () => {
 			it( 'gets content when ends are equally deeply nested', () => {
 			it( 'gets content when ends are equally deeply nested', () => {
 				setData( model, '<heading1>x</heading1><quote><paragraph>a[bc</paragraph><paragraph>de]f</paragraph></quote>' );
 				setData( model, '<heading1>x</heading1><quote><paragraph>a[bc</paragraph><paragraph>de]f</paragraph></quote>' );
 
 
-				const content = stringify( getSelectedContent( data, doc.selection ) );
+				const content = stringify( getSelectedContent( model, doc.selection ) );
 				expect( content ).to.equal( '<paragraph>bc</paragraph><paragraph>de</paragraph>' );
 				expect( content ).to.equal( '<paragraph>bc</paragraph><paragraph>de</paragraph>' );
 			} );
 			} );
 
 
 			it( 'gets content when left end nested deeper', () => {
 			it( 'gets content when left end nested deeper', () => {
 				setData( model, '<quote><paragraph>a[bc</paragraph></quote><paragraph>de]f</paragraph>' );
 				setData( model, '<quote><paragraph>a[bc</paragraph></quote><paragraph>de]f</paragraph>' );
 
 
-				const content = stringify( getSelectedContent( data, doc.selection ) );
+				const content = stringify( getSelectedContent( model, doc.selection ) );
 				expect( content ).to.equal( '<quote><paragraph>bc</paragraph></quote><paragraph>de</paragraph>' );
 				expect( content ).to.equal( '<quote><paragraph>bc</paragraph></quote><paragraph>de</paragraph>' );
 			} );
 			} );
 
 
 			it( 'gets content when left end nested deeper 2', () => {
 			it( 'gets content when left end nested deeper 2', () => {
 				setData( model, '<quote><paragraph>a[bc</paragraph><heading1>x</heading1></quote><paragraph>de]f</paragraph>' );
 				setData( model, '<quote><paragraph>a[bc</paragraph><heading1>x</heading1></quote><paragraph>de]f</paragraph>' );
 
 
-				const content = stringify( getSelectedContent( data, doc.selection ) );
+				const content = stringify( getSelectedContent( model, doc.selection ) );
 				expect( content ).to.equal( '<quote><paragraph>bc</paragraph><heading1>x</heading1></quote><paragraph>de</paragraph>' );
 				expect( content ).to.equal( '<quote><paragraph>bc</paragraph><heading1>x</heading1></quote><paragraph>de</paragraph>' );
 			} );
 			} );
 
 
 			it( 'gets content when left end nested deeper 3', () => {
 			it( 'gets content when left end nested deeper 3', () => {
 				setData( model, '<quote><heading1>x</heading1><paragraph>a[bc</paragraph></quote><paragraph>de]f</paragraph>' );
 				setData( model, '<quote><heading1>x</heading1><paragraph>a[bc</paragraph></quote><paragraph>de]f</paragraph>' );
 
 
-				const content = stringify( getSelectedContent( data, doc.selection ) );
+				const content = stringify( getSelectedContent( model, doc.selection ) );
 				expect( content ).to.equal( '<quote><paragraph>bc</paragraph></quote><paragraph>de</paragraph>' );
 				expect( content ).to.equal( '<quote><paragraph>bc</paragraph></quote><paragraph>de</paragraph>' );
 			} );
 			} );
 
 
@@ -313,21 +308,21 @@ describe( 'DataController utils', () => {
 			it( 'gets content when left end nested deeper 4', () => {
 			it( 'gets content when left end nested deeper 4', () => {
 				setData( model, '<quote><heading1>x[</heading1><paragraph>abc</paragraph></quote><paragraph>de]f</paragraph>' );
 				setData( model, '<quote><heading1>x[</heading1><paragraph>abc</paragraph></quote><paragraph>de]f</paragraph>' );
 
 
-				const content = stringify( getSelectedContent( data, doc.selection ) );
+				const content = stringify( getSelectedContent( model, doc.selection ) );
 				expect( content ).to.equal( '<quote><paragraph>abc</paragraph></quote><paragraph>de</paragraph>' );
 				expect( content ).to.equal( '<quote><paragraph>abc</paragraph></quote><paragraph>de</paragraph>' );
 			} );
 			} );
 
 
 			it( 'gets content when right end nested deeper', () => {
 			it( 'gets content when right end nested deeper', () => {
 				setData( model, '<paragraph>a[bc</paragraph><quote><paragraph>de]f</paragraph></quote>' );
 				setData( model, '<paragraph>a[bc</paragraph><quote><paragraph>de]f</paragraph></quote>' );
 
 
-				const content = stringify( getSelectedContent( data, doc.selection ) );
+				const content = stringify( getSelectedContent( model, doc.selection ) );
 				expect( content ).to.equal( '<paragraph>bc</paragraph><quote><paragraph>de</paragraph></quote>' );
 				expect( content ).to.equal( '<paragraph>bc</paragraph><quote><paragraph>de</paragraph></quote>' );
 			} );
 			} );
 
 
 			it( 'gets content when both ends nested deeper than the middle element', () => {
 			it( 'gets content when both ends nested deeper than the middle element', () => {
 				setData( model, '<quote><heading1>a[bc</heading1></quote><heading1>x</heading1><quote><heading1>de]f</heading1></quote>' );
 				setData( model, '<quote><heading1>a[bc</heading1></quote><heading1>x</heading1><quote><heading1>de]f</heading1></quote>' );
 
 
-				const content = stringify( getSelectedContent( data, doc.selection ) );
+				const content = stringify( getSelectedContent( model, doc.selection ) );
 				expect( content )
 				expect( content )
 					.to.equal( '<quote><heading1>bc</heading1></quote><heading1>x</heading1><quote><heading1>de</heading1></quote>' );
 					.to.equal( '<quote><heading1>bc</heading1></quote><heading1>x</heading1><quote><heading1>de</heading1></quote>' );
 			} );
 			} );
@@ -347,7 +342,7 @@ describe( 'DataController utils', () => {
 					'</quote>'
 					'</quote>'
 				);
 				);
 
 
-				const content = stringify( getSelectedContent( data, doc.selection ) );
+				const content = stringify( getSelectedContent( model, doc.selection ) );
 				expect( content )
 				expect( content )
 					.to.equal( '<paragraph>ar</paragraph>bo' );
 					.to.equal( '<paragraph>ar</paragraph>bo' );
 			} );
 			} );

+ 13 - 23
packages/ckeditor5-engine/tests/controller/insertcontent.js → packages/ckeditor5-engine/tests/model/utils/insertcontent.js

@@ -3,31 +3,28 @@
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 
-import Model from '../../src/model/model';
-import DataController from '../../src/controller/datacontroller';
-import insertContent from '../../src/controller/insertcontent';
+import Model from '../../../src/model/model';
+import insertContent from '../../../src/model/utils/insertcontent';
+import DocumentFragment from '../../../src/model/documentfragment';
+import Text from '../../../src/model/text';
+import Element from '../../../src/model/element';
 
 
-import DocumentFragment from '../../src/model/documentfragment';
-import Text from '../../src/model/text';
-import Element from '../../src/model/element';
-
-import { setData, getData, parse } from '../../src/dev-utils/model';
+import { setData, getData, parse } from '../../../src/dev-utils/model';
 
 
 describe( 'DataController utils', () => {
 describe( 'DataController utils', () => {
-	let model, doc, data;
+	let model, doc;
 
 
 	describe( 'insertContent', () => {
 	describe( 'insertContent', () => {
 		it( 'should use parent batch', () => {
 		it( 'should use parent batch', () => {
 			model = new Model();
 			model = new Model();
 			doc = model.document;
 			doc = model.document;
 			doc.createRoot();
 			doc.createRoot();
-			data = new DataController( model );
 
 
 			model.schema.allow( { name: '$text', inside: '$root' } );
 			model.schema.allow( { name: '$text', inside: '$root' } );
 			setData( model, 'x[]x' );
 			setData( model, 'x[]x' );
 
 
 			model.change( writer => {
 			model.change( writer => {
-				insertContent( data, new Text( 'a' ), doc.selection );
+				insertContent( model, new Text( 'a' ), doc.selection );
 				expect( writer.batch.deltas ).to.length( 1 );
 				expect( writer.batch.deltas ).to.length( 1 );
 			} );
 			} );
 		} );
 		} );
@@ -36,13 +33,12 @@ describe( 'DataController utils', () => {
 			model = new Model();
 			model = new Model();
 			doc = model.document;
 			doc = model.document;
 			doc.createRoot();
 			doc.createRoot();
-			data = new DataController( model );
 
 
 			model.schema.allow( { name: '$text', inside: '$root' } );
 			model.schema.allow( { name: '$text', inside: '$root' } );
 
 
 			setData( model, 'x[]x' );
 			setData( model, 'x[]x' );
 
 
-			insertContent( data, new DocumentFragment( [ new Text( 'a' ) ] ), doc.selection );
+			insertContent( model, new DocumentFragment( [ new Text( 'a' ) ] ), doc.selection );
 
 
 			expect( getData( model ) ).to.equal( 'xa[]x' );
 			expect( getData( model ) ).to.equal( 'xa[]x' );
 		} );
 		} );
@@ -51,13 +47,12 @@ describe( 'DataController utils', () => {
 			model = new Model();
 			model = new Model();
 			doc = model.document;
 			doc = model.document;
 			doc.createRoot();
 			doc.createRoot();
-			data = new DataController( model );
 
 
 			model.schema.allow( { name: '$text', inside: '$root' } );
 			model.schema.allow( { name: '$text', inside: '$root' } );
 
 
 			setData( model, 'x[]x' );
 			setData( model, 'x[]x' );
 
 
-			insertContent( data, new Text( 'a' ), doc.selection );
+			insertContent( model, new Text( 'a' ), doc.selection );
 
 
 			expect( getData( model ) ).to.equal( 'xa[]x' );
 			expect( getData( model ) ).to.equal( 'xa[]x' );
 		} );
 		} );
@@ -66,7 +61,6 @@ describe( 'DataController utils', () => {
 			model = new Model();
 			model = new Model();
 			doc = model.document;
 			doc = model.document;
 			doc.createRoot();
 			doc.createRoot();
-			data = new DataController( model );
 
 
 			const content = new Element( 'image' );
 			const content = new Element( 'image' );
 
 
@@ -76,7 +70,7 @@ describe( 'DataController utils', () => {
 
 
 			setData( model, '<paragraph>foo[]</paragraph>' );
 			setData( model, '<paragraph>foo[]</paragraph>' );
 
 
-			insertContent( data, content, doc.selection );
+			insertContent( model, content, doc.selection );
 
 
 			expect( doc.getRoot().getChild( 0 ).getChild( 1 ) ).to.equal( content );
 			expect( doc.getRoot().getChild( 0 ).getChild( 1 ) ).to.equal( content );
 		} );
 		} );
@@ -86,7 +80,6 @@ describe( 'DataController utils', () => {
 				model = new Model();
 				model = new Model();
 				doc = model.document;
 				doc = model.document;
 				doc.createRoot();
 				doc.createRoot();
-				data = new DataController( model );
 
 
 				const schema = model.schema;
 				const schema = model.schema;
 
 
@@ -217,7 +210,6 @@ describe( 'DataController utils', () => {
 				model = new Model();
 				model = new Model();
 				doc = model.document;
 				doc = model.document;
 				doc.createRoot();
 				doc.createRoot();
-				data = new DataController( model );
 
 
 				const schema = model.schema;
 				const schema = model.schema;
 
 
@@ -289,7 +281,7 @@ describe( 'DataController utils', () => {
 				] );
 				] );
 
 
 				setData( model, '[<heading2>foo</heading2>]' );
 				setData( model, '[<heading2>foo</heading2>]' );
-				insertContent( data, content, doc.selection );
+				insertContent( model, content, doc.selection );
 				expect( getData( model ) ).to.equal( '<heading1>bar[]</heading1>' );
 				expect( getData( model ) ).to.equal( '<heading1>bar[]</heading1>' );
 			} );
 			} );
 
 
@@ -590,7 +582,6 @@ describe( 'DataController utils', () => {
 				model = new Model();
 				model = new Model();
 				doc = model.document;
 				doc = model.document;
 				doc.createRoot();
 				doc.createRoot();
-				data = new DataController( model );
 
 
 				const schema = model.schema;
 				const schema = model.schema;
 
 
@@ -697,7 +688,6 @@ describe( 'DataController utils', () => {
 			model = new Model();
 			model = new Model();
 			doc = model.document;
 			doc = model.document;
 			doc.createRoot();
 			doc.createRoot();
-			data = new DataController( model );
 
 
 			const schema = model.schema;
 			const schema = model.schema;
 
 
@@ -764,6 +754,6 @@ describe( 'DataController utils', () => {
 			} );
 			} );
 		}
 		}
 
 
-		insertContent( data, content, doc.selection );
+		insertContent( model, content, doc.selection );
 	}
 	}
 } );
 } );

+ 25 - 27
packages/ckeditor5-engine/tests/controller/modifyselection.js → packages/ckeditor5-engine/tests/model/utils/modifyselection.js

@@ -3,19 +3,17 @@
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 
-import Model from '../../src/model/model';
-import DataController from '../../src/controller/datacontroller';
-import Selection from '../../src/model/selection';
-import modifySelection from '../../src/controller/modifyselection';
-import { setData, stringify } from '../../src/dev-utils/model';
+import Model from '../../../src/model/model';
+import Selection from '../../../src/model/selection';
+import modifySelection from '../../../src/model/utils/modifyselection';
+import { setData, stringify } from '../../../src/dev-utils/model';
 
 
 describe( 'DataController utils', () => {
 describe( 'DataController utils', () => {
-	let model, doc, data;
+	let model, doc;
 
 
 	beforeEach( () => {
 	beforeEach( () => {
 		model = new Model();
 		model = new Model();
 		doc = model.document;
 		doc = model.document;
-		data = new DataController( model );
 
 
 		model.schema.registerItem( 'p', '$block' );
 		model.schema.registerItem( 'p', '$block' );
 		model.schema.registerItem( 'x', '$block' );
 		model.schema.registerItem( 'x', '$block' );
@@ -68,7 +66,7 @@ describe( 'DataController utils', () => {
 				it( 'extends one character backward', () => {
 				it( 'extends one character backward', () => {
 					setData( model, '<p>fo[]o</p>', { lastRangeBackward: true } );
 					setData( model, '<p>fo[]o</p>', { lastRangeBackward: true } );
 
 
-					modifySelection( data, doc.selection, { direction: 'backward' } );
+					modifySelection( model, doc.selection, { direction: 'backward' } );
 
 
 					expect( stringify( doc.getRoot(), doc.selection ) ).to.equal( '<p>f[o]o</p>' );
 					expect( stringify( doc.getRoot(), doc.selection ) ).to.equal( '<p>f[o]o</p>' );
 					expect( doc.selection.isBackward ).to.true;
 					expect( doc.selection.isBackward ).to.true;
@@ -83,7 +81,7 @@ describe( 'DataController utils', () => {
 				it( 'extends one character backward (non-collapsed)', () => {
 				it( 'extends one character backward (non-collapsed)', () => {
 					setData( model, '<p>foob[a]r</p>', { lastRangeBackward: true } );
 					setData( model, '<p>foob[a]r</p>', { lastRangeBackward: true } );
 
 
-					modifySelection( data, doc.selection, { direction: 'backward' } );
+					modifySelection( model, doc.selection, { direction: 'backward' } );
 
 
 					expect( stringify( doc.getRoot(), doc.selection ) ).to.equal( '<p>foo[ba]r</p>' );
 					expect( stringify( doc.getRoot(), doc.selection ) ).to.equal( '<p>foo[ba]r</p>' );
 					expect( doc.selection.isBackward ).to.true;
 					expect( doc.selection.isBackward ).to.true;
@@ -98,7 +96,7 @@ describe( 'DataController utils', () => {
 				it( 'extends to element boundary (backward)', () => {
 				it( 'extends to element boundary (backward)', () => {
 					setData( model, '<p>f[]oo</p>' );
 					setData( model, '<p>f[]oo</p>' );
 
 
-					modifySelection( data, doc.selection, { direction: 'backward' } );
+					modifySelection( model, doc.selection, { direction: 'backward' } );
 
 
 					expect( stringify( doc.getRoot(), doc.selection ) ).to.equal( '<p>[f]oo</p>' );
 					expect( stringify( doc.getRoot(), doc.selection ) ).to.equal( '<p>[f]oo</p>' );
 					expect( doc.selection.isBackward ).to.true;
 					expect( doc.selection.isBackward ).to.true;
@@ -114,7 +112,7 @@ describe( 'DataController utils', () => {
 				it( 'shrinks backward selection (to collapsed)', () => {
 				it( 'shrinks backward selection (to collapsed)', () => {
 					setData( model, '<p>foo[b]ar</p>', { lastRangeBackward: true } );
 					setData( model, '<p>foo[b]ar</p>', { lastRangeBackward: true } );
 
 
-					modifySelection( data, doc.selection );
+					modifySelection( model, doc.selection );
 
 
 					expect( stringify( doc.getRoot(), doc.selection ) ).to.equal( '<p>foob[]ar</p>' );
 					expect( stringify( doc.getRoot(), doc.selection ) ).to.equal( '<p>foob[]ar</p>' );
 					expect( doc.selection.isBackward ).to.false;
 					expect( doc.selection.isBackward ).to.false;
@@ -129,7 +127,7 @@ describe( 'DataController utils', () => {
 				it( 'unicode support - combining mark backward', () => {
 				it( 'unicode support - combining mark backward', () => {
 					setData( model, '<p>foob̂[]ar</p>' );
 					setData( model, '<p>foob̂[]ar</p>' );
 
 
-					modifySelection( data, doc.selection, { direction: 'backward' } );
+					modifySelection( model, doc.selection, { direction: 'backward' } );
 
 
 					expect( stringify( doc.getRoot(), doc.selection ) ).to.equal( '<p>foo[b̂]ar</p>' );
 					expect( stringify( doc.getRoot(), doc.selection ) ).to.equal( '<p>foo[b̂]ar</p>' );
 					expect( doc.selection.isBackward ).to.true;
 					expect( doc.selection.isBackward ).to.true;
@@ -144,7 +142,7 @@ describe( 'DataController utils', () => {
 				it( 'unicode support - combining mark multiple backward', () => {
 				it( 'unicode support - combining mark multiple backward', () => {
 					setData( model, '<p>foo̻̐ͩ[]bar</p>' );
 					setData( model, '<p>foo̻̐ͩ[]bar</p>' );
 
 
-					modifySelection( data, doc.selection, { direction: 'backward' } );
+					modifySelection( model, doc.selection, { direction: 'backward' } );
 
 
 					expect( stringify( doc.getRoot(), doc.selection ) ).to.equal( '<p>fo[o̻̐ͩ]bar</p>' );
 					expect( stringify( doc.getRoot(), doc.selection ) ).to.equal( '<p>fo[o̻̐ͩ]bar</p>' );
 					expect( doc.selection.isBackward ).to.true;
 					expect( doc.selection.isBackward ).to.true;
@@ -165,7 +163,7 @@ describe( 'DataController utils', () => {
 				it( 'unicode support - surrogate pairs backward', () => {
 				it( 'unicode support - surrogate pairs backward', () => {
 					setData( model, '<p>\uD83D\uDCA9[]</p>' );
 					setData( model, '<p>\uD83D\uDCA9[]</p>' );
 
 
-					modifySelection( data, doc.selection, { direction: 'backward' } );
+					modifySelection( model, doc.selection, { direction: 'backward' } );
 
 
 					expect( stringify( doc.getRoot(), doc.selection ) ).to.equal( '<p>[\uD83D\uDCA9]</p>' );
 					expect( stringify( doc.getRoot(), doc.selection ) ).to.equal( '<p>[\uD83D\uDCA9]</p>' );
 					expect( doc.selection.isBackward ).to.true;
 					expect( doc.selection.isBackward ).to.true;
@@ -182,7 +180,7 @@ describe( 'DataController utils', () => {
 				it( 'extends over boundary of empty elements (backward)', () => {
 				it( 'extends over boundary of empty elements (backward)', () => {
 					setData( model, '<p></p><p></p><p>[]</p>' );
 					setData( model, '<p></p><p></p><p>[]</p>' );
 
 
-					modifySelection( data, doc.selection, { direction: 'backward' } );
+					modifySelection( model, doc.selection, { direction: 'backward' } );
 
 
 					expect( stringify( doc.getRoot(), doc.selection ) ).to.equal( '<p></p><p>[</p><p>]</p>' );
 					expect( stringify( doc.getRoot(), doc.selection ) ).to.equal( '<p></p><p>[</p><p>]</p>' );
 					expect( doc.selection.isBackward ).to.true;
 					expect( doc.selection.isBackward ).to.true;
@@ -197,7 +195,7 @@ describe( 'DataController utils', () => {
 				it( 'extends over boundary of non-empty elements (backward)', () => {
 				it( 'extends over boundary of non-empty elements (backward)', () => {
 					setData( model, '<p>a</p><p>[]bcd</p>' );
 					setData( model, '<p>a</p><p>[]bcd</p>' );
 
 
-					modifySelection( data, doc.selection, { direction: 'backward' } );
+					modifySelection( model, doc.selection, { direction: 'backward' } );
 
 
 					expect( stringify( doc.getRoot(), doc.selection ) ).to.equal( '<p>a[</p><p>]bcd</p>' );
 					expect( stringify( doc.getRoot(), doc.selection ) ).to.equal( '<p>a[</p><p>]bcd</p>' );
 					expect( doc.selection.isBackward ).to.true;
 					expect( doc.selection.isBackward ).to.true;
@@ -212,7 +210,7 @@ describe( 'DataController utils', () => {
 				it( 'extends over character after boundary (backward)', () => {
 				it( 'extends over character after boundary (backward)', () => {
 					setData( model, '<p>abc[</p><p>]d</p>', { lastRangeBackward: true } );
 					setData( model, '<p>abc[</p><p>]d</p>', { lastRangeBackward: true } );
 
 
-					modifySelection( data, doc.selection, { direction: 'backward' } );
+					modifySelection( model, doc.selection, { direction: 'backward' } );
 
 
 					expect( stringify( doc.getRoot(), doc.selection ) ).to.equal( '<p>ab[c</p><p>]d</p>' );
 					expect( stringify( doc.getRoot(), doc.selection ) ).to.equal( '<p>ab[c</p><p>]d</p>' );
 					expect( doc.selection.isBackward ).to.true;
 					expect( doc.selection.isBackward ).to.true;
@@ -246,7 +244,7 @@ describe( 'DataController utils', () => {
 				it( 'shrinks over boundary of empty elements', () => {
 				it( 'shrinks over boundary of empty elements', () => {
 					setData( model, '<p>[</p><p>]</p>', { lastRangeBackward: true } );
 					setData( model, '<p>[</p><p>]</p>', { lastRangeBackward: true } );
 
 
-					modifySelection( data, doc.selection );
+					modifySelection( model, doc.selection );
 
 
 					expect( stringify( doc.getRoot(), doc.selection ) ).to.equal( '<p></p><p>[]</p>' );
 					expect( stringify( doc.getRoot(), doc.selection ) ).to.equal( '<p></p><p>[]</p>' );
 					expect( doc.selection.isBackward ).to.false;
 					expect( doc.selection.isBackward ).to.false;
@@ -255,7 +253,7 @@ describe( 'DataController utils', () => {
 				it( 'shrinks over boundary of empty elements (backward)', () => {
 				it( 'shrinks over boundary of empty elements (backward)', () => {
 					setData( model, '<p>[</p><p>]</p>' );
 					setData( model, '<p>[</p><p>]</p>' );
 
 
-					modifySelection( data, doc.selection, { direction: 'backward' } );
+					modifySelection( model, doc.selection, { direction: 'backward' } );
 
 
 					expect( stringify( doc.getRoot(), doc.selection ) ).to.equal( '<p>[]</p><p></p>' );
 					expect( stringify( doc.getRoot(), doc.selection ) ).to.equal( '<p>[]</p><p></p>' );
 					expect( doc.selection.isBackward ).to.false;
 					expect( doc.selection.isBackward ).to.false;
@@ -264,7 +262,7 @@ describe( 'DataController utils', () => {
 				it( 'shrinks over boundary of non-empty elements', () => {
 				it( 'shrinks over boundary of non-empty elements', () => {
 					setData( model, '<p>a[</p><p>]b</p>', { lastRangeBackward: true } );
 					setData( model, '<p>a[</p><p>]b</p>', { lastRangeBackward: true } );
 
 
-					modifySelection( data, doc.selection );
+					modifySelection( model, doc.selection );
 
 
 					expect( stringify( doc.getRoot(), doc.selection ) ).to.equal( '<p>a</p><p>[]b</p>' );
 					expect( stringify( doc.getRoot(), doc.selection ) ).to.equal( '<p>a</p><p>[]b</p>' );
 					expect( doc.selection.isBackward ).to.false;
 					expect( doc.selection.isBackward ).to.false;
@@ -280,7 +278,7 @@ describe( 'DataController utils', () => {
 				it( 'updates selection attributes', () => {
 				it( 'updates selection attributes', () => {
 					setData( model, '<p><$text bold="true">foo</$text>[b]</p>' );
 					setData( model, '<p><$text bold="true">foo</$text>[b]</p>' );
 
 
-					modifySelection( data, doc.selection, { direction: 'backward' } );
+					modifySelection( model, doc.selection, { direction: 'backward' } );
 
 
 					expect( stringify( doc.getRoot(), doc.selection ) ).to.equal( '<p><$text bold="true">foo[]</$text>b</p>' );
 					expect( stringify( doc.getRoot(), doc.selection ) ).to.equal( '<p><$text bold="true">foo[]</$text>b</p>' );
 					expect( doc.selection.getAttribute( 'bold' ) ).to.equal( true );
 					expect( doc.selection.getAttribute( 'bold' ) ).to.equal( true );
@@ -341,7 +339,7 @@ describe( 'DataController utils', () => {
 
 
 				setData( model, '' );
 				setData( model, '' );
 
 
-				modifySelection( data, doc.selection, { unit: 'codePoint' } );
+				modifySelection( model, doc.selection, { unit: 'codePoint' } );
 
 
 				expect( stringify( doc.getRoot(), doc.selection ) ).to.equal( '[]' );
 				expect( stringify( doc.getRoot(), doc.selection ) ).to.equal( '[]' );
 			} );
 			} );
@@ -363,7 +361,7 @@ describe( 'DataController utils', () => {
 			it( 'extends one user-perceived character backward - latin letters', () => {
 			it( 'extends one user-perceived character backward - latin letters', () => {
 				setData( model, '<p>fo[]o</p>' );
 				setData( model, '<p>fo[]o</p>' );
 
 
-				modifySelection( data, doc.selection, { unit: 'codePoint', direction: 'backward' } );
+				modifySelection( model, doc.selection, { unit: 'codePoint', direction: 'backward' } );
 
 
 				expect( stringify( doc.getRoot(), doc.selection ) ).to.equal( '<p>f[o]o</p>' );
 				expect( stringify( doc.getRoot(), doc.selection ) ).to.equal( '<p>f[o]o</p>' );
 				expect( doc.selection.isBackward ).to.true;
 				expect( doc.selection.isBackward ).to.true;
@@ -383,7 +381,7 @@ describe( 'DataController utils', () => {
 				// Document's selection will throw errors in some test cases (which are correct cases, but only for
 				// Document's selection will throw errors in some test cases (which are correct cases, but only for
 				// non-document selections).
 				// non-document selections).
 				const testSelection = Selection.createFromSelection( doc.selection );
 				const testSelection = Selection.createFromSelection( doc.selection );
-				modifySelection( data, testSelection, { unit: 'codePoint', direction: 'backward' } );
+				modifySelection( model, testSelection, { unit: 'codePoint', direction: 'backward' } );
 
 
 				expect( stringify( doc.getRoot(), testSelection ) ).to.equal( '<p>foob[̂]ar</p>' );
 				expect( stringify( doc.getRoot(), testSelection ) ).to.equal( '<p>foob[̂]ar</p>' );
 				expect( testSelection.isBackward ).to.true;
 				expect( testSelection.isBackward ).to.true;
@@ -406,7 +404,7 @@ describe( 'DataController utils', () => {
 			it( 'unicode support surrogate pairs backward', () => {
 			it( 'unicode support surrogate pairs backward', () => {
 				setData( model, '<p>\uD83D\uDCA9[]</p>' );
 				setData( model, '<p>\uD83D\uDCA9[]</p>' );
 
 
-				modifySelection( data, doc.selection, { unit: 'codePoint', direction: 'backward' } );
+				modifySelection( model, doc.selection, { unit: 'codePoint', direction: 'backward' } );
 
 
 				expect( stringify( doc.getRoot(), doc.selection ) ).to.equal( '<p>[\uD83D\uDCA9]</p>' );
 				expect( stringify( doc.getRoot(), doc.selection ) ).to.equal( '<p>[\uD83D\uDCA9]</p>' );
 				expect( doc.selection.isBackward ).to.true;
 				expect( doc.selection.isBackward ).to.true;
@@ -447,7 +445,7 @@ describe( 'DataController utils', () => {
 			it( 'extends over object elements - backward', () => {
 			it( 'extends over object elements - backward', () => {
 				setData( model, '<obj></obj>[<obj></obj>]', { lastRangeBackward: true } );
 				setData( model, '<obj></obj>[<obj></obj>]', { lastRangeBackward: true } );
 
 
-				modifySelection( data, doc.selection, { direction: 'backward' } );
+				modifySelection( model, doc.selection, { direction: 'backward' } );
 
 
 				expect( stringify( doc.getRoot(), doc.selection ) ).to.equal( '[<obj></obj><obj></obj>]' );
 				expect( stringify( doc.getRoot(), doc.selection ) ).to.equal( '[<obj></obj><obj></obj>]' );
 				expect( doc.selection.isBackward ).to.true;
 				expect( doc.selection.isBackward ).to.true;
@@ -549,7 +547,7 @@ describe( 'DataController utils', () => {
 			// Document's selection will throw errors in some test cases (which are correct cases, but only for
 			// Document's selection will throw errors in some test cases (which are correct cases, but only for
 			// non-document selections).
 			// non-document selections).
 			const testSelection = Selection.createFromSelection( doc.selection );
 			const testSelection = Selection.createFromSelection( doc.selection );
-			modifySelection( data, testSelection, options );
+			modifySelection( model, testSelection, options );
 
 
 			expect( stringify( doc.getRoot(), testSelection ) ).to.equal( output );
 			expect( stringify( doc.getRoot(), testSelection ) ).to.equal( output );
 		} );
 		} );