Browse Source

Merge branch 'master' into t/841

Piotrek Koszuliński 8 năm trước cách đây
mục cha
commit
bbd7659887

+ 1 - 1
packages/ckeditor5-engine/src/controller/datacontroller.js

@@ -86,7 +86,7 @@ export default class DataController {
 		 * @readonly
 		 * @member {module:engine/conversion/modelconversiondispatcher~ModelConversionDispatcher}
 		 */
-		this.modelToView = new ModelConversionDispatcher( {
+		this.modelToView = new ModelConversionDispatcher( this.model, {
 			mapper: this.mapper
 		} );
 		this.modelToView.on( 'insert:$text', insertText(), { priority: 'lowest' } );

+ 5 - 18
packages/ckeditor5-engine/src/controller/editingcontroller.js

@@ -12,12 +12,7 @@ import Mapper from '../conversion/mapper';
 import ModelConversionDispatcher from '../conversion/modelconversiondispatcher';
 import {
 	insertText,
-	remove,
-	move,
-	rename,
-	insertRangeIntoMarker,
-	insertRangeWithMarker,
-	moveInOutOfMarker
+	remove
 } from '../conversion/model-to-view-converters';
 import { convertSelectionChange } from '../conversion/view-selection-to-model-converters';
 import {
@@ -80,7 +75,7 @@ export default class EditingController {
 		 * @readonly
 		 * @member {module:engine/conversion/modelconversiondispatcher~ModelConversionDispatcher} #modelToView
 		 */
-		this.modelToView = new ModelConversionDispatcher( {
+		this.modelToView = new ModelConversionDispatcher( this.model, {
 			mapper: this.mapper,
 			viewSelection: this.view.selection
 		} );
@@ -101,10 +96,9 @@ export default class EditingController {
 
 		// Convert model selection to view.
 		this._listener.listenTo( this.model, 'changesDone', () => {
-			const selection = model.selection;
-			const markers = Array.from( model.markers.getMarkersAtPosition( selection.getFirstPosition() ) );
+			const selection = this.model.selection;
 
-			this.modelToView.convertSelection( selection, markers );
+			this.modelToView.convertSelection( selection );
 			this.view.render();
 		}, { priority: 'low' } );
 
@@ -118,18 +112,11 @@ export default class EditingController {
 		} );
 
 		// Convert view selection to model.
-		this._listener.listenTo( this.view, 'selectionChange', convertSelectionChange( model, this.mapper ) );
+		this._listener.listenTo( this.view, 'selectionChange', convertSelectionChange( this.model, this.mapper ) );
 
 		// Attach default content converters.
 		this.modelToView.on( 'insert:$text', insertText(), { priority: 'lowest' } );
 		this.modelToView.on( 'remove', remove(), { priority: 'low' } );
-		this.modelToView.on( 'move', move(), { priority: 'low' } );
-		this.modelToView.on( 'rename', rename(), { priority: 'low' } );
-
-		// Attach default markers converters.
-		this.modelToView.on( 'insert', insertRangeIntoMarker( this.model.markers ), { priority: 'lowest' } );
-		this.modelToView.on( 'insert', insertRangeWithMarker( this.model.markers ), { priority: 'lowest' } );
-		this.modelToView.on( 'move', moveInOutOfMarker( this.model.markers ), { priority: 'lowest' } );
 
 		// Attach default selection converters.
 		this.modelToView.on( 'selection', clearAttributes(), { priority: 'low' } );

+ 2 - 2
packages/ckeditor5-engine/src/conversion/buildmodelconverter.js

@@ -157,7 +157,7 @@ class ModelConverterBuilder {
 	 *
 	 * @chainable
 	 * @param {String} markerName Name of marker to convert.
-	 * @returns {module:engine/conversion/modelconverterbuilder~ModelConverterBuilder}
+	 * @returns {module:engine/conversion/buildmodelconverter~ModelConverterBuilder}
 	 */
 	fromMarker( markerName ) {
 		this._from = {
@@ -176,7 +176,7 @@ class ModelConverterBuilder {
 	 *
 	 * @chainable
 	 * @param {String} markerName Name of marker to convert.
-	 * @returns {module:engine/conversion/modelconverterbuilder~ModelConverterBuilder}
+	 * @returns {module:engine/conversion/buildmodelconverter~ModelConverterBuilder}
 	 */
 	fromCollapsedMarker( markerName ) {
 		this._from = {

+ 1 - 1
packages/ckeditor5-engine/src/conversion/mapper.js

@@ -314,7 +314,7 @@ export default class Mapper {
 	 * Gets the length of the view element in the model.
 	 *
 	 * The length is calculated as follows:
-	 * * if {@link ~registerViewToModelLength length mapping callback} is provided for given `viewNode` it is used to
+	 * * if {@link #registerViewToModelLength length mapping callback} is provided for given `viewNode` it is used to
 	 * evaluate model length (`viewNode` is used as first and only parameter passed to the callback),
 	 * * length of a {@link module:engine/view/text~Text text node} is equal to the length of it's
 	 * {@link module:engine/view/text~Text#data data},

+ 9 - 1
packages/ckeditor5-engine/src/conversion/model-selection-to-view-converters.js

@@ -114,7 +114,7 @@ export function convertCollapsedSelection() {
  *		modelDispatcher.on( 'selectionAttribute:style', convertSelectionAttribute( styleCreator ) );
  *
  * **Note:** You can use the same `elementCreator` function for this converter factory
- * and {@link module:engine/conversion/model-to-view-converters~wrap}
+ * and {@link module:engine/conversion/model-to-view-converters~wrapRange}
  * model to view converter, as long as the `elementCreator` function uses only the first parameter (attribute value).
  *
  *		modelDispatcher.on( 'selection', convertCollapsedSelection() );
@@ -149,6 +149,10 @@ export function convertSelectionAttribute( elementCreator ) {
 			elementCreator.clone( true ) :
 			elementCreator( data.value, data, data.selection, consumable, conversionApi );
 
+		if ( !viewElement ) {
+			return;
+		}
+
 		const consumableName = 'selectionAttribute:' + data.key;
 
 		wrapCollapsedSelectionPosition( data.selection, conversionApi.viewSelection, viewElement, consumable, consumableName );
@@ -175,6 +179,10 @@ export function convertSelectionMarker( elementCreator ) {
 			elementCreator.clone( true ) :
 			elementCreator( data, consumable, conversionApi );
 
+		if ( !viewElement ) {
+			return;
+		}
+
 		const consumableName = 'selectionMarker:' + data.name;
 
 		wrapCollapsedSelectionPosition( data.selection, conversionApi.viewSelection, viewElement, consumable, consumableName );

+ 1 - 151
packages/ckeditor5-engine/src/conversion/model-to-view-converters.js

@@ -4,8 +4,6 @@
  */
 
 import ModelRange from '../model/range';
-import ModelPosition from '../model/position';
-import ModelElement from '../model/element';
 
 import ViewElement from '../view/element';
 import ViewText from '../view/text';
@@ -374,7 +372,7 @@ export function wrapRange( elementCreator ) {
  * was passed, it will be used to look for similar element in the view for unwrapping. If `Function` is provided, it is passed all
  * the parameters of the
  * {@link module:engine/conversion/modelconversiondispatcher~ModelConversionDispatcher#event:removeMarker removeMarker event}.
- * It's expected that the function returns a {@link module:/engine/view/attributeelement~AttributeElement}. The result of
+ * It's expected that the function returns a {@link module:engine/view/attributeelement~AttributeElement}. The result of
  * the function will be used to look for similar element in the view for unwrapping.
  *
  * The converter automatically consumes corresponding value from consumables list, stops the event (see
@@ -410,28 +408,6 @@ export function unwrapRange( elementCreator ) {
 	};
 }
 
-/**
- * Function factory, creates a default model-to-view converter for nodes move changes.
- *
- *		modelDispatcher.on( 'move', move() );
- *
- * @returns {Function} Move event converter.
- */
-export function move() {
-	return ( evt, data, consumable, conversionApi ) => {
-		if ( !consumable.consume( data.item, 'move' ) ) {
-			return;
-		}
-
-		const modelRange = ModelRange.createFromPositionAndShift( data.sourcePosition, data.item.offsetSize );
-		const sourceViewRange = conversionApi.mapper.toViewRange( modelRange );
-
-		const targetViewPosition = conversionApi.mapper.toViewPosition( data.targetPosition );
-
-		viewWriter.move( sourceViewRange, targetViewPosition );
-	};
-}
-
 /**
  * Function factory, creates a default model-to-view converter for node remove changes.
  *
@@ -482,132 +458,6 @@ export function removeUIElement( elementCreator ) {
 	};
 }
 
-/**
- * Function factory, creates default model-to-view converter for elements which name has changed.
- *
- *		modelDispatcher.on( 'rename', rename() );
- *
- * This converter re-uses converters added for `insert`, `move` and `remove` change types.
- *
- * @fires module:engine/conversion/modelconversiondispatcher~ModelConversionDispatcher#event:insert
- * @fires module:engine/conversion/modelconversiondispatcher~ModelConversionDispatcher#event:move
- * @fires module:engine/conversion/modelconversiondispatcher~ModelConversionDispatcher#event:remove
- * @returns {Function}
- */
-export function rename() {
-	return ( evt, data, consumable, conversionApi ) => {
-		if ( !consumable.consume( data.element, 'rename' ) ) {
-			return;
-		}
-
-		// Create fake model element that will represent "old version" of renamed element.
-		const fakeElement = new ModelElement( data.oldName, data.element.getAttributes() );
-		// Append the fake element to model document to enable making range on it.
-		data.element.parent.insertChildren( data.element.index, fakeElement );
-
-		// Check what was bound to renamed element.
-		const oldViewElement = conversionApi.mapper.toViewElement( data.element );
-		// Unbind renamed element.
-		conversionApi.mapper.unbindModelElement( data.element );
-		// Bind view element to the fake element.
-		conversionApi.mapper.bindElements( fakeElement, oldViewElement );
-
-		// The range that includes only the renamed element. Will be used to insert an empty element in the view.
-		const insertRange = ModelRange.createFromParentsAndOffsets( data.element.parent, data.element.startOffset, data.element, 0 );
-
-		// Move source position and range of moved nodes. Will be used to move nodes from original view element to renamed one.
-		const moveSourcePosition = ModelPosition.createAt( fakeElement, 0 );
-		const moveRange = ModelRange.createIn( data.element );
-
-		// Remove range containing the fake element. Will be used to remove original view element from the view.
-		const removeRange = ModelRange.createOn( fakeElement );
-
-		// Start the conversion. Use already defined converters by firing insertion, move and remove conversion
-		// on correct ranges / positions.
-		conversionApi.dispatcher.convertInsertion( insertRange );
-		conversionApi.dispatcher.convertMove( moveSourcePosition, moveRange );
-		conversionApi.dispatcher.convertRemove( removeRange.start, removeRange );
-
-		// Cleanup.
-		fakeElement.remove();
-	};
-}
-
-/**
- * Function factory, creates a default converter for inserting {@link module:engine/model/item~Item model item}
- * into a marker range.
- *
- *		modelDispatcher.on( 'insert', insertRangeIntoMarker( modelDocument.markers ) );
- *
- * @param {module:engine/model/markercollection~MarkerCollection} markerCollection Markers collection to check when
- * inserting.
- * @returns {Function}
- */
-export function insertRangeIntoMarker( markerCollection ) {
-	return ( evt, data, consumable, conversionApi ) => {
-		for ( let marker of markerCollection ) {
-			const range = marker.getRange();
-
-			if ( range.containsPosition( data.range.start ) ) {
-				conversionApi.dispatcher.convertMarker( 'addMarker', marker.name, data.range );
-			}
-		}
-	};
-}
-
-/**
- * Function factory, creates a default converter for inserting a {@link module:engine/model/range~Range model range}
- * that contains a marker. This happens when marker was in a graveyard (so it was removed from the view) or was
- * created in a {@link module:engine/model/documentfragment~DocumentFragment DocumentFragment} (so it was not in the view before).
- *
- *		modelDispatcher.on( 'insert', insertRangeWithMarker( modelDocument.markers ) );
- *
- * @param {module:engine/model/markercollection~MarkerCollection} markerCollection Markers collection to check when
- * inserting.
- * @returns {Function}
- */
-export function insertRangeWithMarker( markerCollection ) {
-	return ( evt, data, consumable, conversionApi ) => {
-		for ( let marker of markerCollection ) {
-			const range = marker.getRange();
-
-			if ( data.range.containsRange( range ) || data.range.isEqual( range ) ) {
-				conversionApi.dispatcher.convertMarker( 'addMarker', marker.name, range );
-			}
-		}
-	};
-}
-
-/**
- * Function factory, creates a default converter for move changes that move {@link module:engine/model/item~Item model items}
- * into or outside of a marker range.
- *
- *		modelDispatcher.on( 'move', moveInOutOfMarker( modelDocument.markers ) );
- *
- * @param {module:engine/model/markercollection~MarkerCollection} markerCollection Markers collection to check when
- * moving.
- * @returns {Function}
- */
-export function moveInOutOfMarker( markerCollection ) {
-	return ( evt, data, consumable, conversionApi ) => {
-		const sourcePos = data.sourcePosition._getTransformedByInsertion( data.targetPosition, data.item.offsetSize );
-		const movedRange = ModelRange.createOn( data.item );
-
-		for ( let marker of markerCollection ) {
-			const range = marker.getRange();
-
-			const wasInMarker = range.containsPosition( sourcePos ) || range.start.isEqual( sourcePos ) || range.end.isEqual( sourcePos );
-			const common = movedRange.getIntersection( range );
-
-			if ( wasInMarker && common === null ) {
-				conversionApi.dispatcher.convertMarker( 'removeMarker', marker.name, movedRange );
-			} else if ( common !== null ) {
-				conversionApi.dispatcher.convertMarker( 'addMarker', marker.name, common );
-			}
-		}
-	};
-}
-
 /**
  * Returns the consumable type that is to be consumed in an event, basing on that event name.
  *

+ 116 - 98
packages/ckeditor5-engine/src/conversion/modelconversiondispatcher.js

@@ -9,6 +9,8 @@
 
 import Consumable from './modelconsumable';
 import Range from '../model/range';
+import Position from '../model/position';
+import DocumentFragment from '../model/documentfragment';
 import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';
 import mix from '@ckeditor/ckeditor5-utils/src/mix';
 import extend from '@ckeditor/ckeditor5-utils/src/lib/lodash/extend';
@@ -93,8 +95,8 @@ import extend from '@ckeditor/ckeditor5-utils/src/lib/lodash/extend';
  *			viewSourceBtn.addClass( 'source' );
  *
  *			// Insert the button using writer API.
- *			// If `addAttribute` event is fired by `module:engine/conversion/modelconversiondispatcher~ModelConversionDispatcher#convertInsert` it
- *			// is fired
+ *			// If `addAttribute` event is fired by
+ *			// `module:engine/conversion/modelconversiondispatcher~ModelConversionDispatcher#convertInsert` it is fired
  *			// after `data.item` insert conversion was done. If the event is fired due to attribute insertion coming from
  *			// different source, `data.item` already existed. This means we are safe to get `viewQuote` from mapper.
  *			const viewQuote = conversionApi.mapper.toViewElement( data.item );
@@ -108,9 +110,18 @@ export default class ModelConversionDispatcher {
 	/**
 	 * Creates a `ModelConversionDispatcher` that operates using passed API.
 	 *
+	 * @param {module:engine/model/document~Document} modelDocument Model document instance bound with this dispatcher.
 	 * @param {Object} [conversionApi] Interface passed by dispatcher to the events callbacks.
 	 */
-	constructor( conversionApi = {} ) {
+	constructor( modelDocument, conversionApi = {} ) {
+		/**
+		 * Model document instance bound with this dispatcher.
+		 *
+		 * @private
+		 * @member {module:engine/model/document~Document}
+		 */
+		this._modelDocument = modelDocument;
+
 		/**
 		 * Interface passed by dispatcher to the events callbacks.
 		 *
@@ -127,12 +138,11 @@ export default class ModelConversionDispatcher {
 	 *
 	 * @see module:engine/model/document~Document#event:change
 	 * @fires insert
-	 * @fires move
 	 * @fires remove
-	 * @fires rename
 	 * @fires addAttribute
 	 * @fires removeAttribute
 	 * @fires changeAttribute
+	 * @fires addMarker
 	 * @param {String} type Change type.
 	 * @param {Object} data Additional information about the change.
 	 */
@@ -166,6 +176,8 @@ export default class ModelConversionDispatcher {
 	}
 
 	/**
+	 * Starts conversion of insertion-change on given `range`.
+	 *
 	 * Analyzes given range and fires insertion-connected events with data based on that range.
 	 *
 	 * **Note**: This method will fire separate events for node insertion and attributes insertion. All
@@ -173,6 +185,7 @@ export default class ModelConversionDispatcher {
 	 *
 	 * @fires insert
 	 * @fires addAttribute
+	 * @fires addMarker
 	 * @param {module:engine/model/range~Range} range Inserted range.
 	 */
 	convertInsertion( range ) {
@@ -198,48 +211,50 @@ export default class ModelConversionDispatcher {
 				data.attributeOldValue = null;
 				data.attributeNewValue = item.getAttribute( key );
 
-				this._testAndFire( 'addAttribute:' + key, data, consumable );
+				this._testAndFire( `addAttribute:${ key }`, data, consumable );
+			}
+		}
+
+		for ( let marker of this._modelDocument.markers ) {
+			const markerRange = marker.getRange();
+
+			// Check if inserted content is inserted into a marker.
+			if ( markerRange.containsPosition( range.start ) ) {
+				this.convertMarker( 'addMarker', marker.name, markerRange.getIntersection( range ) );
+			}
+
+			// Check if inserted content contains a marker.
+			if ( range.containsRange( markerRange ) || range.isEqual( markerRange ) ) {
+				this.convertMarker( 'addMarker', marker.name, markerRange );
 			}
 		}
 	}
 
 	/**
-	 * Fires move event with data based on passed values.
+	 * Starts conversion of move-change of given `range`, that was moved from given `sourcePosition`.
+	 *
+	 * Fires {@link ~#event:remove remove event} and {@link ~#event:insert insert event} based on passed parameters.
 	 *
-	 * @fires move
-	 * @param {module:engine/model/position~Position} sourcePosition Position from where the range has been moved.
-	 * @param {module:engine/model/range~Range} range Moved range (after move).
+	 * @fires remove
+	 * @fires insert
+	 * @param {module:engine/model/position~Position} sourcePosition Position from where the range has been removed.
+	 * @param {module:engine/model/range~Range} range Removed range (after remove, in
+	 * {@link module:engine/model/document~Document#graveyard graveyard root}).
 	 */
 	convertMove( sourcePosition, range ) {
-		// Keep in mind that move dispatcher expects flat range.
-		const consumable = this._createConsumableForRange( range, 'move' );
-
-		const items = Array.from( range.getItems( { shallow: true } ) );
-		const inSameParent = sourcePosition.parent == range.start.parent;
-		const targetsAfter = range.start.isAfter( sourcePosition );
-
-		let offset = 0;
-
-		for ( let item of items ) {
-			const data = {
-				sourcePosition: sourcePosition,
-				targetPosition: inSameParent && targetsAfter ? range.end : range.start.getShiftedBy( offset ),
-				item: item
-			};
-
-			offset += data.item.offsetSize;
-
-			this._testAndFire( 'move', data, consumable );
-		}
+		this.convertRemove( sourcePosition, range );
+		this.convertInsertion( range );
 	}
 
 	/**
-	 * Fires remove event with data based on passed values.
+	 * Starts conversion of remove-change of given `range`, that was removed from given `sourcePosition`.
+	 *
+	 * Fires {@link ~#event:remove remove event} with data based on passed values.
 	 *
 	 * @fires remove
 	 * @param {module:engine/model/position~Position} sourcePosition Position from where the range has been removed.
-	 * @param {module:engine/model/range~Range} range Removed range (after remove, in {@link module:engine/model/document~Document#graveyard
-	 * graveyard root}).
+	 * @param {module:engine/model/range~Range} range Removed range (after remove, in
+	 * {@link module:engine/model/document~Document#graveyard graveyard root}).
 	 */
 	convertRemove( sourcePosition, range ) {
 		const consumable = this._createConsumableForRange( range, 'remove' );
@@ -250,11 +265,13 @@ export default class ModelConversionDispatcher {
 				item: item
 			};
 
-			this._testAndFire( 'remove', data, consumable );
+			this._testAndFire( `remove`, data, consumable );
 		}
 	}
 
 	/**
+	 * Starts conversion of attribute-change on given `range`.
+	 *
 	 * Analyzes given attribute change and fires attributes-connected events with data based on passed values.
 	 *
 	 * @fires addAttribute
@@ -282,34 +299,50 @@ export default class ModelConversionDispatcher {
 				attributeNewValue: newValue
 			};
 
-			this._testAndFire( type + ':' + key, data, consumable, this.conversionApi );
+			this._testAndFire( `${ type }:${ key }`, data, consumable );
 		}
 	}
 
 	/**
-	 * Fires rename event with data based on passed values.
+	 * Starts conversion of rename-change of given `element` that had given `oldName`.
 	 *
-	 * @fires rename
-	 * @param {module:engine/view/element~Element} element Renamed element.
+	 * Fires {@link ~#event:remove remove event} and {@link ~#event:insert insert event} based on passed values.
+	 *
+	 * @fires remove
+	 * @fires insert
+	 * @param {module:engine/model/element~Element} element Renamed element.
 	 * @param {String} oldName Name of the renamed element before it was renamed.
 	 */
 	convertRename( element, oldName ) {
-		const consumable = new Consumable();
-		consumable.add( element, 'rename' );
-
-		const data = { element, oldName };
-		this.fire( 'rename:' + element.name + ':' + oldName, data, consumable, this.conversionApi );
+		// Create fake element that will be used to fire remove event. The fake element will have the old element name.
+		const fakeElement = element.clone( true );
+		fakeElement.name = oldName;
+
+		// Bind fake element with original view element so the view element will be removed.
+		this.conversionApi.mapper.bindElements(
+			fakeElement,
+			this.conversionApi.mapper.toViewElement( element )
+		);
+
+		// Create fake document fragment so a range can be created on fake element.
+		const fakeDocumentFragment = new DocumentFragment();
+		fakeDocumentFragment.appendChildren( fakeElement );
+
+		this.convertRemove( Position.createBefore( element ), Range.createOn( fakeElement ) );
+		this.convertInsertion( Range.createOn( element ) );
 	}
 
 	/**
+	 * Starts selection conversion.
+	 *
 	 * Fires events for given {@link module:engine/model/selection~Selection selection} to start selection conversion.
 	 *
 	 * @fires selection
 	 * @fires selectionAttribute
 	 * @param {module:engine/model/selection~Selection} selection Selection to convert.
-	 * @param {Array.<module:engine/model/markercollection~Marker>} markers Markers which contains selection.
 	 */
-	convertSelection( selection, markers ) {
+	convertSelection( selection ) {
+		const markers = Array.from( this._modelDocument.markers.getMarkersAtPosition( selection.getFirstPosition() ) );
 		const consumable = this._createSelectionConsumable( selection, markers );
 
 		this.fire( 'selection', { selection }, consumable, this.conversionApi );
@@ -340,11 +373,14 @@ export default class ModelConversionDispatcher {
 	}
 
 	/**
-	 * Fires event for given marker change.
+	 * Starts marker-conversion.
+	 *
+	 * Fires {@link ~#event:addMarker addMarker event} or {@link ~#event:removeMarker removeMarker event} based on
+	 * given `type` with data based on passed parameters.
 	 *
 	 * @fires addMarker
 	 * @fires removeMarker
-	 * @param {String} type Change type.
+	 * @param {'addMarker'|'removeMarker'} type Change type.
 	 * @param {String} name Marker name.
 	 * @param {module:engine/model/range~Range} range Marker range.
 	 */
@@ -448,6 +484,7 @@ export default class ModelConversionDispatcher {
 	 *
 	 * @private
 	 * @fires insert
+	 * @fires remove
 	 * @fires addAttribute
 	 * @fires removeAttribute
 	 * @fires changeAttribute
@@ -461,32 +498,19 @@ export default class ModelConversionDispatcher {
 			return;
 		}
 
-		if ( type === 'insert' || type === 'remove' || type == 'move' ) {
-			if ( data.item.is( 'textProxy' ) ) {
-				// Example: insert:$text.
-				this.fire( type + ':$text', data, consumable, this.conversionApi );
-			} else {
-				// Example: insert:paragraph.
-				this.fire( type + ':' + data.item.name, data, consumable, this.conversionApi );
-			}
-		} else {
-			// Example addAttribute:alt:img.
-			// Example addAttribute:bold:$text.
-			const name = data.item.name || '$text';
+		const name = data.item.name || '$text';
 
-			this.fire( type + ':' + name, data, consumable, this.conversionApi );
-		}
+		this.fire( type + ':' + name, data, consumable, this.conversionApi );
 	}
 
 	/**
 	 * Fired for inserted nodes.
 	 *
 	 * `insert` is a namespace for a class of events. Names of actually called events follow this pattern:
-	 * `insert:<type>:<elementName>`. `type` is either `text` when one or more characters has been inserted or `element`
-	 * when {@link module:engine/model/element~Element} has been inserted. If `type` is `element`, `elementName` is added and is
-	 * equal to the {@link module:engine/model/element~Element#name name} of inserted element. This way listeners can either
-	 * listen to very general `insert` event or, i.e., very specific `insert:paragraph` event, which is fired only for
-	 * model elements with name `paragraph`.
+	 * `insert:<name>`. `name` is either `'$text'` when one or more characters has been inserted or
+	 * {@link module:engine/model/element~Element#name name} of inserted element.
+	 *
+	 * This way listeners can either listen to a general `insert` event or specific event (for example `insert:paragraph`).
 	 *
 	 * @event insert
 	 * @param {Object} data Additional information about the change.
@@ -496,35 +520,20 @@ export default class ModelConversionDispatcher {
 	 * @param {Object} conversionApi Conversion interface to be used by callback, passed in `ModelConversionDispatcher` constructor.
 	 */
 
-	/**
-	 * Fired for moved nodes.
-	 *
-	 * @event move
-	 * @param {Object} data Additional information about the change.
-	 * @param {module:engine/model/position~Position} data.sourcePosition Position from where the range has been moved.
-	 * @param {module:engine/model/range~Range} data.range Moved range (after move).
-	 * @param {Object} conversionApi Conversion interface to be used by callback, passed in `ModelConversionDispatcher` constructor.
-	 */
-
 	/**
 	 * Fired for removed nodes.
 	 *
+	 * `remove` is a namespace for a class of events. Names of actually called events follow this pattern:
+	 * `remove:<name>`. `name` is either `'$text'` when one or more characters has been removed or the
+	 * {@link module:engine/model/element~Element#name name} of removed element.
+	 *
+	 * This way listeners can either listen to a general `remove` event or specific event (for example `remove:paragraph`).
+	 *
 	 * @event remove
 	 * @param {Object} data Additional information about the change.
 	 * @param {module:engine/model/position~Position} data.sourcePosition Position from where the range has been removed.
-	 * @param {module:engine/model/range~Range} data.range Removed range (in {@link module:engine/model/document~Document#graveyard graveyard
-	 * root}).
-	 * @param {Object} conversionApi Conversion interface to be used by callback, passed in `ModelConversionDispatcher` constructor.
-	 */
-
-	/**
-	 * Fired for renamed element.
-	 *
-	 * @event rename
-	 * @param {Object} data Additional information about the change.
-	 * @param {module:engine/model/element~Element} data.element Renamed element.
-	 * @param {String} data.oldName Old name of the renamed element.
-	 * @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable Values to consume.
+	 * @param {module:engine/model/range~Range} data.range Removed range (in {@link module:engine/model/document~Document#graveyard
+	 * graveyard root}).
 	 * @param {Object} conversionApi Conversion interface to be used by callback, passed in `ModelConversionDispatcher` constructor.
 	 */
 
@@ -532,9 +541,12 @@ export default class ModelConversionDispatcher {
 	 * Fired when attribute has been added on a node.
 	 *
 	 * `addAttribute` is a namespace for a class of events. Names of actually called events follow this pattern:
-	 * `addAttribute:<attributeKey>:<elementName>`. `attributeKey` is the key of added attribute. `elementName` is
-	 * equal to the {@link module:engine/model/element~Element#name name} of the element which got the attribute. This way listeners
-	 * can either listen to adding certain attribute, i.e. `addAttribute:bold`, or be more specific, i.e. `addAttribute:link:img`.
+	 * `addAttribute:<attributeKey>:<name>`. `attributeKey` is the key of added attribute. `name` is either `'$text'`
+	 * if attribute was added on one or more characters, or the {@link module:engine/model/element~Element#name name} of
+	 * the element on which attribute was added.
+	 *
+	 * This way listeners can either listen to a general `addAttribute:bold` event or specific event
+	 * (for example `addAttribute:link:image`).
 	 *
 	 * @event addAttribute
 	 * @param {Object} data Additional information about the change.
@@ -551,9 +563,12 @@ export default class ModelConversionDispatcher {
 	 * Fired when attribute has been removed from a node.
 	 *
 	 * `removeAttribute` is a namespace for a class of events. Names of actually called events follow this pattern:
-	 * `removeAttribute:<attributeKey>:<elementName>`. `attributeKey` is the key of removed attribute. `elementName` is
-	 * equal to the {@link module:engine/model/element~Element#name name} of the element which got the attribute removed. This way listeners
-	 * can either listen to removing certain attribute, i.e. `removeAttribute:bold`, or be more specific, i.e. `removeAttribute:link:img`.
+	 * `removeAttribute:<attributeKey>:<name>`. `attributeKey` is the key of removed attribute. `name` is either `'$text'`
+	 * if attribute was removed from one or more characters, or the {@link module:engine/model/element~Element#name name} of
+	 * the element from which attribute was removed.
+	 *
+	 * This way listeners can either listen to a general `removeAttribute:bold` event or specific event
+	 * (for example `removeAttribute:link:image`).
 	 *
 	 * @event removeAttribute
 	 * @param {Object} data Additional information about the change.
@@ -570,9 +585,12 @@ export default class ModelConversionDispatcher {
 	 * Fired when attribute of a node has been changed.
 	 *
 	 * `changeAttribute` is a namespace for a class of events. Names of actually called events follow this pattern:
-	 * `changeAttribute:<attributeKey>:<elementName>`. `attributeKey` is the key of changed attribute. `elementName` is
-	 * equal to the {@link module:engine/model/element~Element#name name} of the element which got the attribute changed. This way listeners
-	 * can either listen to changing certain attribute, i.e. `changeAttribute:link`, or be more specific, i.e. `changeAttribute:link:img`.
+	 * `changeAttribute:<attributeKey>:<name>`. `attributeKey` is the key of changed attribute. `name` is either `'$text'`
+	 * if attribute was changed on one or more characters, or the {@link module:engine/model/element~Element#name name} of
+	 * the element on which attribute was changed.
+	 *
+	 * This way listeners can either listen to a general `changeAttribute:link` event or specific event
+	 * (for example `changeAttribute:link:image`).
 	 *
 	 * @event changeAttribute
 	 * @param {Object} data Additional information about the change.

+ 6 - 5
packages/ckeditor5-engine/src/dev-utils/model.js

@@ -25,7 +25,7 @@ import modelWriter from '../model/writer';
 import ViewConversionDispatcher from '../conversion/viewconversiondispatcher';
 import ViewSelection from '../view/selection';
 import ViewDocumentFragment from '../view/documentfragment';
-import ViewElement from '../view/containerelement';
+import ViewContainerElement from '../view/containerelement';
 import ViewAttributeElement from '../view/attributeelement';
 
 import Mapper from '../conversion/mapper';
@@ -167,14 +167,15 @@ setData._parse = parse;
  *
  * @param {module:engine/model/rootelement~RootElement|module:engine/model/element~Element|module:engine/model/text~Text|
  * module:engine/model/documentfragment~DocumentFragment} node Node to stringify.
- * @param {module:engine/model/selection~Selection|module:engine/model/position~Position|module:engine/model/range~Range}
- * [selectionOrPositionOrRange=null]
+ * @param {module:engine/model/selection~Selection|module:engine/model/position~Position|
+ * module:engine/model/range~Range} [selectionOrPositionOrRange=null]
  * Selection instance which ranges will be included in returned string data. If Range instance is provided - it will be
  * converted to selection containing this range. If Position instance is provided - it will be converted to selection
  * containing one range collapsed at this position.
  * @returns {String} HTML-like string representing the model.
  */
 export function stringify( node, selectionOrPositionOrRange = null ) {
+	const modelDoc = new ModelDocument();
 	const mapper = new Mapper();
 	let selection, range;
 
@@ -208,7 +209,7 @@ export function stringify( node, selectionOrPositionOrRange = null ) {
 	// Setup model to view converter.
 	const viewDocumentFragment = new ViewDocumentFragment();
 	const viewSelection = new ViewSelection();
-	const modelToView = new ModelConversionDispatcher( { mapper, viewSelection } );
+	const modelToView = new ModelConversionDispatcher( modelDoc, { mapper, viewSelection } );
 
 	// Bind root elements.
 	mapper.bindElements( node.root, viewDocumentFragment );
@@ -223,7 +224,7 @@ export function stringify( node, selectionOrPositionOrRange = null ) {
 		// Stringify object types values for properly display as an output string.
 		const attributes = convertAttributes( data.item.getAttributes(), stringifyAttributeValue );
 
-		return new ViewElement( data.item.name, attributes );
+		return new ViewContainerElement( data.item.name, attributes );
 	} ) );
 	modelToView.on( 'selection', convertRangeSelection() );
 	modelToView.on( 'selection', convertCollapsedSelection() );

+ 6 - 117
packages/ckeditor5-engine/tests/controller/editingcontroller.js

@@ -277,47 +277,18 @@ describe( 'EditingController', () => {
 		it( 'should forward add marker event if content is inserted into a marker range', () => {
 			const markerRange = ModelRange.createFromParentsAndOffsets( modelRoot, 0, modelRoot, 3 );
 			const innerRange = ModelRange.createFromParentsAndOffsets( modelRoot, 1, modelRoot, 2 );
-			const consumableMock = {
-				consume: () => true,
-				test: () => true
-			};
 
 			model.markers.set( 'name', markerRange );
 
 			sinon.spy( editing.modelToView, 'convertMarker' );
 
-			editing.modelToView.fire( 'insert', {
-				range: innerRange
-			}, consumableMock, { dispatcher: editing.modelToView } );
+			editing.modelToView.convertInsertion( innerRange );
 
 			expect( editing.modelToView.convertMarker.calledWithExactly( 'addMarker', 'name', innerRange ) ).to.be.true;
 
 			editing.modelToView.convertMarker.restore();
 		} );
 
-		it( 'should forward add marker event if inserted content has a marker (reinsert from graveyard)', () => {
-			const gyHolder = new ModelElement( '$graveyardHolder', [], new ModelText( 'foo' ) );
-			model.graveyard.appendChildren( gyHolder );
-
-			const markerRange = ModelRange.createIn( gyHolder );
-			const consumableMock = {
-				consume: () => true,
-				test: () => true
-			};
-
-			model.markers.set( 'name', markerRange );
-
-			sinon.spy( editing.modelToView, 'convertMarker' );
-
-			editing.modelToView.fire( 'insert', {
-				range: markerRange
-			}, consumableMock, { dispatcher: editing.modelToView } );
-
-			expect( editing.modelToView.convertMarker.calledWithExactly( 'addMarker', 'name', markerRange ) ).to.be.true;
-
-			editing.modelToView.convertMarker.restore();
-		} );
-
 		it( 'should forward add marker event if inserted content has a marker (inserted element from outside of tree)', () => {
 			const element = new ModelElement( new ModelText( 'foo' ) );
 			modelRoot.appendChildren( element );
@@ -325,18 +296,11 @@ describe( 'EditingController', () => {
 			const markerRange = ModelRange.createFromParentsAndOffsets( element, 1, element, 2 );
 			const outerRange = ModelRange.createOn( element );
 
-			const consumableMock = {
-				consume: () => true,
-				test: () => true
-			};
-
 			model.markers.set( 'name', markerRange );
 
 			sinon.spy( editing.modelToView, 'convertMarker' );
 
-			editing.modelToView.fire( 'insert', {
-				range: outerRange
-			}, consumableMock, { dispatcher: editing.modelToView } );
+			editing.modelToView.convertInsertion( outerRange );
 
 			expect( editing.modelToView.convertMarker.calledWithExactly( 'addMarker', 'name', markerRange ) ).to.be.true;
 
@@ -364,101 +328,26 @@ describe( 'EditingController', () => {
 			editing.modelToView.convertMarker.restore();
 		} );
 
-		it( 'should forward remove marker event if part of marker range is moved - intersecting', () => {
-			const markerRange = ModelRange.createFromParentsAndOffsets( modelRoot, 0, modelRoot, 1 );
-			const consumableMock = {
-				consume: () => true,
-				test: () => true
-			};
-
-			model.markers.set( 'name', markerRange );
-
-			sinon.spy( editing.modelToView, 'convertMarker' );
-
-			editing.modelToView.fire( 'move', {
-				sourcePosition: ModelPosition.createAt( modelRoot, 1 ),
-				targetPosition: ModelPosition.createAt( modelRoot, 2 ),
-				item: modelRoot.getChild( 2 )
-			}, consumableMock, { dispatcher: editing.modelToView, mapper: editing.mapper } );
-
-			expect( editing.modelToView.convertMarker.calledWith( 'removeMarker', 'name' ) ).to.be.true;
-
-			editing.modelToView.convertMarker.restore();
-		} );
-
-		it( 'should forward remove marker event if part of marker range is moved - inside', () => {
-			model.enqueueChanges( () => {
-				model.batch().insert( ModelPosition.createAt( model.getRoot(), 'end' ), new ModelElement( 'paragraph' ) );
-			} );
-
-			const markerRange = ModelRange.createFromParentsAndOffsets( modelRoot, 0, modelRoot, 2 );
-			const consumableMock = {
-				consume: () => true,
-				test: () => true
-			};
-
-			model.markers.set( 'name', markerRange );
-
-			sinon.spy( editing.modelToView, 'convertMarker' );
-
-			editing.modelToView.fire( 'move', {
-				sourcePosition: ModelPosition.createAt( modelRoot, 1 ),
-				targetPosition: ModelPosition.createAt( modelRoot, 3 ),
-				item: modelRoot.getChild( 3 )
-			}, consumableMock, { dispatcher: editing.modelToView, mapper: editing.mapper } );
-
-			expect( editing.modelToView.convertMarker.calledWith( 'removeMarker', 'name' ) ).to.be.true;
-
-			editing.modelToView.convertMarker.restore();
-		} );
-
 		it( 'should forward add marker event if content is moved into a marker range', () => {
 			model.enqueueChanges( () => {
 				model.batch().insert( ModelPosition.createAt( model.getRoot(), 'end' ), new ModelElement( 'paragraph' ) );
 			} );
 
 			const markerRange = ModelRange.createFromParentsAndOffsets( modelRoot, 0, modelRoot, 3 );
-			const consumableMock = {
-				consume: () => true,
-				test: () => true
-			};
 
 			model.markers.set( 'name', markerRange );
 
 			sinon.spy( editing.modelToView, 'convertMarker' );
 
-			editing.modelToView.fire( 'move', {
-				sourcePosition: ModelPosition.createAt( modelRoot, 3 ),
-				targetPosition: ModelPosition.createAt( modelRoot, 1 ),
-				item: modelRoot.getChild( 1 )
-			}, consumableMock, { dispatcher: editing.modelToView, mapper: editing.mapper } );
+			editing.modelToView.convertMove(
+				ModelPosition.createAt( modelRoot, 3 ),
+				ModelRange.createOn( modelRoot.getChild( 1 ) )
+			);
 
 			expect( editing.modelToView.convertMarker.calledWith( 'addMarker', 'name' ) ).to.be.true;
 
 			editing.modelToView.convertMarker.restore();
 		} );
-
-		it( 'should not start marker conversion if moved content does not affect the marker', () => {
-			const markerRange = ModelRange.createFromParentsAndOffsets( modelRoot, 0, modelRoot, 1 );
-			const consumableMock = {
-				consume: () => true,
-				test: () => true
-			};
-
-			model.markers.set( 'name', markerRange );
-
-			sinon.spy( editing.modelToView, 'convertMarker' );
-
-			editing.modelToView.fire( 'move', {
-				sourcePosition: ModelPosition.createAt( modelRoot, 2 ),
-				targetPosition: ModelPosition.createAt( modelRoot, 0 ),
-				item: modelRoot.getChild( 2 )
-			}, consumableMock, { dispatcher: editing.modelToView, mapper: editing.mapper } );
-
-			expect( editing.modelToView.convertMarker.called ).to.be.false;
-
-			editing.modelToView.convertMarker.restore();
-		} );
 	} );
 
 	describe( 'destroy', () => {

+ 1 - 3
packages/ckeditor5-engine/tests/conversion/advanced-converters.js

@@ -32,7 +32,6 @@ import {
 	removeAttribute,
 	wrapItem,
 	unwrapItem,
-	move,
 	remove,
 	eventNameToConsumableType
 } from '../../src/conversion/model-to-view-converters';
@@ -51,12 +50,11 @@ describe( 'advanced-converters', () => {
 		mapper = new Mapper();
 		mapper.bindElements( modelRoot, viewRoot );
 
-		modelDispatcher = new ModelConversionDispatcher( { mapper } );
+		modelDispatcher = new ModelConversionDispatcher( modelDoc, { mapper } );
 		// Schema is mocked up because we don't care about it in those tests.
 		viewDispatcher = new ViewConversionDispatcher( { schema: { check: () => true } } );
 
 		modelDispatcher.on( 'insert:$text', insertText() );
-		modelDispatcher.on( 'move', move() );
 		modelDispatcher.on( 'remove', remove() );
 		viewDispatcher.on( 'text', convertText() );
 		viewDispatcher.on( 'documentFragment', convertToModelFragment() );

+ 1 - 3
packages/ckeditor5-engine/tests/conversion/buildmodelconverter.js

@@ -24,7 +24,6 @@ import ModelConversionDispatcher from '../../src/conversion/modelconversiondispa
 
 import {
 	insertText,
-	move,
 	remove
 } from '../../src/conversion/model-to-view-converters';
 
@@ -86,10 +85,9 @@ describe( 'Model converter builder', () => {
 		mapper = new Mapper();
 		mapper.bindElements( modelRoot, viewRoot );
 
-		dispatcher = new ModelConversionDispatcher( { mapper, viewSelection } );
+		dispatcher = new ModelConversionDispatcher( modelDoc, { mapper, viewSelection } );
 
 		dispatcher.on( 'insert:$text', insertText() );
-		dispatcher.on( 'move', move() );
 		dispatcher.on( 'remove', remove() );
 	} );
 

+ 39 - 1
packages/ckeditor5-engine/tests/conversion/model-selection-to-view-converters.js

@@ -57,7 +57,7 @@ describe( 'model-selection-to-view-converters', () => {
 		mapper = new Mapper();
 		mapper.bindElements( modelRoot, viewRoot );
 
-		dispatcher = new ModelConversionDispatcher( { mapper, viewSelection } );
+		dispatcher = new ModelConversionDispatcher( modelDoc, { mapper, viewSelection } );
 
 		dispatcher.on( 'insert:$text', insertText() );
 		dispatcher.on( 'addAttribute:bold', wrapItem( new ViewAttributeElement( 'strong' ) ) );
@@ -291,6 +291,31 @@ describe( 'model-selection-to-view-converters', () => {
 					.to.equal( '<div>foo<span class="marker2">[]</span>bar</div>' );
 			} );
 
+			it( 'should do nothing if creator return null', () => {
+				dispatcher.on( 'selectionMarker:marker3', convertSelectionMarker( () => {
+					return;
+				} ) );
+
+				setModelData( modelDoc, 'foobar' );
+				const marker = modelDoc.markers.set( 'marker3', ModelRange.createFromParentsAndOffsets( modelRoot, 1, modelRoot, 5 ) );
+
+				modelSelection.setRanges( [ new ModelRange( ModelPosition.createAt( modelRoot, 3 ) ) ] );
+
+				// Remove view children manually (without firing additional conversion).
+				viewRoot.removeChildren( 0, viewRoot.childCount );
+
+				// Convert model to view.
+				dispatcher.convertInsertion( ModelRange.createIn( modelRoot ) );
+				dispatcher.convertMarker( 'addMarker', marker.name, marker.getRange() );
+
+				const markers = Array.from( modelDoc.markers.getMarkersAtPosition( modelSelection.getFirstPosition() ) );
+				dispatcher.convertSelection( modelSelection, markers );
+
+				// Stringify view and check if it is same as expected.
+				expect( stringifyView( viewRoot, viewSelection, { showType: false } ) )
+					.to.equal( '<div>foo{}bar</div>' );
+			} );
+
 			it( 'consumes consumable values properly', () => {
 				// Add callbacks that will fire before default ones.
 				// This should prevent default callbacks doing anything.
@@ -503,6 +528,19 @@ describe( 'model-selection-to-view-converters', () => {
 					{ theme: 'important' }
 				);
 			} );
+
+			it( 'convertSelectionAttribute should do nothing if creator return null', () => {
+				dispatcher.on( 'selectionAttribute:bold', convertSelectionAttribute( () => {
+					return;
+				} ) );
+
+				test(
+					[ 3, 3 ],
+					'foobar',
+					'foo{}bar',
+					{ bold: 'true' }
+				);
+			} );
 		} );
 	} );
 

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

@@ -28,10 +28,8 @@ import {
 	unwrapItem,
 	wrapRange,
 	unwrapRange,
-	move,
 	remove,
-	removeUIElement,
-	rename
+	removeUIElement
 } from '../../src/conversion/model-to-view-converters';
 
 import { createRangeOnElementOnly } from '../../tests/model/_utils/utils';
@@ -47,7 +45,7 @@ describe( 'model-to-view-converters', () => {
 		mapper = new Mapper();
 		mapper.bindElements( modelRoot, viewRoot );
 
-		dispatcher = new ModelConversionDispatcher( { mapper } );
+		dispatcher = new ModelConversionDispatcher( modelDoc, { mapper } );
 	} );
 
 	function viewAttributesToString( item ) {
@@ -775,87 +773,6 @@ describe( 'model-to-view-converters', () => {
 		} );
 	} );
 
-	describe( 'move', () => {
-		it( 'should move items in view accordingly to changes in model', () => {
-			const modelDivA = new ModelElement( 'div', null, [
-				new ModelText( 'foo' ),
-				new ModelElement( 'image' ),
-				new ModelText( 'bar' )
-			] );
-
-			const modelDivB = new ModelElement( 'div', null, new ModelText( 'xxyy' ) );
-
-			modelRoot.appendChildren( [ modelDivA, modelDivB ] );
-			dispatcher.on( 'insert:div', insertElement( new ViewContainerElement( 'div' ) ) );
-			dispatcher.on( 'insert:image', insertElement( new ViewContainerElement( 'img' ) ) );
-			dispatcher.on( 'insert:$text', insertText() );
-			dispatcher.on( 'move', move() );
-
-			dispatcher.convertInsertion( ModelRange.createIn( modelRoot ) );
-
-			const removedNodes = modelDivA.removeChildren( 0, 2 );
-			modelDivB.insertChildren( 0, removedNodes );
-
-			dispatcher.convertMove(
-				ModelPosition.createFromParentAndOffset( modelDivA, 0 ),
-				ModelRange.createFromParentsAndOffsets( modelDivB, 0, modelDivB, 4 )
-			);
-
-			expect( viewToString( viewRoot ) ).to.equal( '<div><div>bar</div><div>foo<img></img>xxyy</div></div>' );
-		} );
-
-		it( 'should not execute if value was already consumed', () => {
-			const modelDivA = new ModelElement( 'div', null, new ModelText( 'foo' ) );
-			const modelDivB = new ModelElement( 'div', null, new ModelText( 'xxyy' ) );
-
-			modelRoot.appendChildren( [ modelDivA, modelDivB ] );
-			dispatcher.on( 'insert:div', insertElement( new ViewContainerElement( 'div' ) ) );
-			dispatcher.on( 'insert:$text', insertText() );
-			dispatcher.on( 'move', move() );
-			dispatcher.on( 'move', ( evt, data, consumable ) => {
-				consumable.consume( data.item, 'move' );
-			}, { priority: 'high' } );
-
-			dispatcher.convertInsertion( ModelRange.createIn( modelRoot ) );
-
-			expect( viewToString( viewRoot ) ).to.equal( '<div><div>foo</div><div>xxyy</div></div>' );
-
-			const removedNodes = modelDivA.removeChildren( 0, 1 );
-			modelDivB.insertChildren( 0, removedNodes );
-
-			dispatcher.convertMove(
-				ModelPosition.createFromParentAndOffset( modelDivA, 0 ),
-				ModelRange.createFromParentsAndOffsets( modelDivB, 0, modelDivB, 3 )
-			);
-
-			expect( viewToString( viewRoot ) ).to.equal( '<div><div>foo</div><div>xxyy</div></div>' );
-		} );
-
-		it( 'should support unicode', () => {
-			const modelDivA = new ModelElement( 'div', null, 'நிலைக்கு' );
-			const modelDivB = new ModelElement( 'div' );
-
-			modelRoot.appendChildren( [ modelDivA, modelDivB ] );
-			dispatcher.on( 'insert:div', insertElement( new ViewContainerElement( 'div' ) ) );
-			dispatcher.on( 'insert:$text', insertText() );
-			dispatcher.on( 'move', move() );
-
-			dispatcher.convertInsertion( ModelRange.createIn( modelRoot ) );
-
-			modelWriter.move(
-				ModelRange.createFromParentsAndOffsets( modelDivA, 2, modelDivA, 6 ),
-				ModelPosition.createAt( modelDivB, 'end' )
-			);
-
-			dispatcher.convertMove(
-				ModelPosition.createFromParentAndOffset( modelDivA, 2 ),
-				ModelRange.createFromParentsAndOffsets( modelDivB, 0, modelDivB, 4 )
-			);
-
-			expect( viewToString( viewRoot ) ).to.equal( '<div><div>நிகு</div><div>லைக்</div></div>' );
-		} );
-	} );
-
 	describe( 'remove', () => {
 		it( 'should remove items from view accordingly to changes in model', () => {
 			const modelDiv = new ModelElement( 'div', null, [
@@ -932,70 +849,4 @@ describe( 'model-to-view-converters', () => {
 			expect( viewToString( viewRoot ) ).to.equal( '<div><div>கு</div></div>' );
 		} );
 	} );
-
-	describe( 'rename', () => {
-		const oldName = 'oldName';
-		const newName = 'newName';
-
-		let element, converters;
-
-		beforeEach( () => {
-			converters = {
-				insertText: insertText(),
-				insert:	insertElement( ( data ) => new ViewContainerElement( data.item.name ) ),
-				move: move(),
-				remove: remove(),
-				rename: rename()
-			};
-
-			sinon.spy( converters, 'insert' );
-			sinon.spy( converters, 'move' );
-			sinon.spy( converters, 'remove' );
-
-			element = new ModelElement( oldName, null, new ModelText( 'foo' ) );
-			modelRoot.appendChildren( element );
-
-			dispatcher.on( 'insert:$text', converters.insertText );
-			dispatcher.on( 'insert', converters.insert );
-			dispatcher.on( 'move', converters.move );
-			dispatcher.on( 'remove', converters.remove );
-			dispatcher.on( 'rename', converters.rename );
-
-			dispatcher.convertInsertion( ModelRange.createOn( element ) );
-
-			element.name = newName;
-		} );
-
-		afterEach( () => {
-			converters.insert.restore();
-			converters.move.restore();
-			converters.remove.restore();
-		} );
-
-		it( 'should enable default rename conversion, that uses already registered callbacks', () => {
-			const insertCallCount = converters.insert.callCount;
-
-			expect( viewRoot.getChild( 0 ).name ).to.equal( 'oldName' );
-			dispatcher.convertRename( element, oldName );
-
-			expect( converters.insert.callCount - insertCallCount ).to.equal( 1 );
-			expect( converters.move.calledOnce ).to.be.true;
-			expect( converters.remove.calledOnce ).to.be.true;
-
-			expect( viewRoot.getChild( 0 ).name ).to.equal( 'newName' );
-			expect( viewRoot.getChild( 0 ).getChild( 0 ).data ).to.equal( 'foo' );
-		} );
-
-		it( 'should not execute if converted value was already consumed', () => {
-			dispatcher.on( 'rename', ( evt, data, consumable ) => {
-				consumable.consume( data.element, 'rename' );
-			}, { priority: 'high' } );
-
-			dispatcher.on( 'rename', ( evt, data ) => {
-				expect( data.fakeElement ).to.be.undefined;
-			} );
-
-			dispatcher.convertRename( element, oldName );
-		} );
-	} );
 } );

+ 5 - 72
packages/ckeditor5-engine/tests/conversion/modelconversiondispatcher.js

@@ -16,15 +16,15 @@ describe( 'ModelConversionDispatcher', () => {
 	let dispatcher, doc, root;
 
 	beforeEach( () => {
-		dispatcher = new ModelConversionDispatcher();
 		doc = new ModelDocument();
+		dispatcher = new ModelConversionDispatcher( doc );
 		root = doc.createRoot();
 	} );
 
 	describe( 'constructor()', () => {
 		it( 'should create ModelConversionDispatcher with given api', () => {
 			const apiObj = {};
-			const dispatcher = new ModelConversionDispatcher( { apiObj } );
+			const dispatcher = new ModelConversionDispatcher( doc, { apiObj } );
 
 			expect( dispatcher.conversionApi.apiObj ).to.equal( apiObj );
 		} );
@@ -89,16 +89,6 @@ describe( 'ModelConversionDispatcher', () => {
 			expect( cbInsertText.called ).to.be.false;
 		} );
 
-		it( 'should fire move callback for move changes', () => {
-			const cbMove = sinon.spy();
-
-			dispatcher.on( 'move', cbMove );
-
-			doc.batch().move( image, imagePos.getShiftedBy( 3 ) );
-
-			expect( cbMove.called );
-		} );
-
 		it( 'should fire remove callback for remove changes', () => {
 			const cbRemove = sinon.spy();
 
@@ -106,17 +96,7 @@ describe( 'ModelConversionDispatcher', () => {
 
 			doc.batch().remove( image );
 
-			expect( cbRemove.called );
-		} );
-
-		it( 'should fire rename callback for rename changes', () => {
-			const cbRename = sinon.spy();
-
-			dispatcher.on( 'rename', cbRename );
-
-			doc.batch().rename( image, 'figure' );
-
-			expect( cbRename.called );
+			expect( cbRemove.called ).to.be.true;
 		} );
 
 		it( 'should fire addAttribute callbacks for add attribute change', () => {
@@ -309,37 +289,6 @@ describe( 'ModelConversionDispatcher', () => {
 	} );
 
 	describe( 'convertMove', () => {
-		it( 'should fire event for moved range - move before source position', () => {
-			root.appendChildren( new ModelText( 'barfoo' ) );
-
-			const range = ModelRange.createFromParentsAndOffsets( root, 0, root, 3 );
-			const loggedEvents = [];
-
-			dispatcher.on( 'move', ( evt, data ) => {
-				const log = 'move:' + data.sourcePosition.path + ':' + data.targetPosition.path + ':' + data.item.offsetSize;
-				loggedEvents.push( log );
-			} );
-
-			dispatcher.convertMove( ModelPosition.createFromParentAndOffset( root , 3 ), range );
-
-			expect( loggedEvents ).to.deep.equal( [ 'move:3:0:3' ] );
-		} );
-
-		it( 'should fire event for moved range - move after source position', () => {
-			root.appendChildren( new ModelText( 'barfoo' ) );
-
-			const range = ModelRange.createFromParentsAndOffsets( root, 3, root, 6 );
-			const loggedEvents = [];
-
-			dispatcher.on( 'move', ( evt, data ) => {
-				const log = 'move:' + data.sourcePosition.path + ':' + data.targetPosition.path + ':' + data.item.offsetSize;
-				loggedEvents.push( log );
-			} );
-
-			dispatcher.convertMove( ModelPosition.createFromParentAndOffset( root , 0 ), range );
-
-			expect( loggedEvents ).to.deep.equal( [ 'move:0:6:3' ] );
-		} );
 	} );
 
 	describe( 'convertRemove', () => {
@@ -362,22 +311,6 @@ describe( 'ModelConversionDispatcher', () => {
 	} );
 
 	describe( 'convertRename', () => {
-		it( 'should fire rename event with correct name, consumable, and renamed element and it\'s old name in data', ( done ) => {
-			const oldName = 'oldName';
-			const element = new ModelElement( oldName );
-			element.name = 'newName';
-
-			dispatcher.on( 'rename', ( evt, data, consumable ) => {
-				expect( evt.name ).to.equal( 'rename:newName:oldName' );
-				expect( data.element ).to.equal( element );
-				expect( data.oldName ).to.equal( oldName );
-				expect( consumable.test( data.element, 'rename' ) ).to.be.true;
-
-				done();
-			} );
-
-			dispatcher.convertRename( element, oldName );
-		} );
 	} );
 
 	describe( 'convertAttribute', () => {
@@ -545,11 +478,11 @@ describe( 'ModelConversionDispatcher', () => {
 
 			dispatcher.convertMarker( 'addMarker', 'name', range );
 
-			expect( dispatcher.fire.calledWith( 'addMarker:name', 'name', range ) );
+			expect( dispatcher.fire.calledWith( 'addMarker:name' ) ).to.be.true;
 
 			dispatcher.convertMarker( 'removeMarker', 'name', range );
 
-			expect( dispatcher.fire.calledWith( 'removeMarker:name', 'name', range ) );
+			expect( dispatcher.fire.calledWith( 'removeMarker:name' ) ).to.be.true;
 		} );
 
 		it( 'should not convert marker if it is added in graveyard', () => {

+ 2 - 2
packages/ckeditor5-engine/tests/manual/markers.js

@@ -81,11 +81,11 @@ function removeHighlight() {
 
 		for ( let i = 0; i < markerNames.length; i++ ) {
 			const name = markerNames[ i ];
-			const range = model.markers.get( name );
+			const marker = model.markers.get( name );
+			const range = marker.getRange();
 
 			if ( range.containsPosition( pos ) || range.start.isEqual( pos ) || range.end.isEqual( pos ) ) {
 				model.markers.remove( name );
-				range.detach();
 
 				markerNames.splice( i, 1 );
 				break;