Browse Source

Changed structure of data object passed to conversion callbacks.

Oskar Wróbel 7 years ago
parent
commit
010fdd348a

+ 75 - 82
packages/ckeditor5-engine/src/conversion/viewconversiondispatcher.js

@@ -26,25 +26,22 @@ import log from '@ckeditor/ckeditor5-utils/src/log';
  * `ViewConversionDispatcher` fires corresponding events. Special callbacks called "converters" should listen to
  * `ViewConversionDispatcher` for those events.
  *
- * Each callback, as a first argument, is passed a special object `data` that has `input` and `output` properties.
- * `input` property contains {@link module:engine/view/node~Node view node} or
+ * Each callback, as a first argument, is passed a special object `data` that has `viewItem`, `cursorPosition` and
+ * `modelRange` properties. `viewItem` property contains {@link module:engine/view/node~Node view node} or
  * {@link module:engine/view/documentfragment~DocumentFragment view document fragment}
- * that is converted at the moment and might be handled by the callback. `output` property should be used to save the result
- * of conversion. Keep in mind that the `data` parameter is customizable and may contain other values - see
- * {@link ~ViewConversionDispatcher#convert}. It is also shared by reference by all callbacks
- * listening to given event. **Note**: in view to model conversion - `data` contains `context` property that is an array
- * of {@link module:engine/model/element~Element model elements}. These are model elements that will be the parent of currently
- * converted view item. `context` property is used in examples below.
- *
- * The second parameter passed to a callback is an instance of {@link module:engine/conversion/viewconsumable~ViewConsumable}. It stores
- * information about what parts of processed view item are still waiting to be handled. After a piece of view item
- * was converted, appropriate consumable value should be {@link module:engine/conversion/viewconsumable~ViewConsumable#consume consumed}.
+ * that is converted at the moment and might be handled by the callback. `modelRange` property should be used to save the result
+ * of conversion and is always a {@link module:engine/model/range~Range} when conversion result is correct.
+ * `cursorPosition` property is a {@link module:engine/model/position~Position position} on which conversion result will be inserted
+ * and is a context according to {@link module:engine/model/schema~Schema schema} will be checked before the conversion.
+ * See also {@link ~ViewConversionDispatcher#convert}. It is also shared by reference by all callbacks listening to given event.
  *
  * The third parameter passed to a callback is an instance of {@link ~ViewConversionDispatcher}
  * which provides additional tools for converters.
  *
  * Examples of providing callbacks for `ViewConversionDispatcher`:
  *
+ * TODO - update samples.
+ *
  *		// Converter for paragraphs (<p>).
  *		viewDispatcher.on( 'element:p', ( evt, data, consumable, conversionApi ) => {
  *			const paragraph = new ModelElement( 'paragraph' );
@@ -166,20 +163,22 @@ export default class ViewConversionDispatcher {
 		return this._model.change( writer => {
 			this.fire( 'viewCleanup', viewItem );
 
-			const consumable = ViewConsumable.createFrom( viewItem );
-
 			// Create context tree and store position in the top element.
 			// Items will be converted according to this position.
 			this._contextPosition = contextToPosition( context, writer );
 
-			// Store writer in current conversion as a conversion API.
+			// Store writer in conversion as a conversion API
+			// to be sure that conversion process will use the same batch.
 			this.conversionApi.writer = writer;
 
+			// Create consumable values list for conversion process.
+			this.conversionApi.consumable = ViewConsumable.createFrom( viewItem );
+
 			// Custom data stored by converter for conversion process.
 			this.conversionApi.data = {};
 
 			// Do the conversion.
-			const range = this._convertItem( viewItem, consumable, this._contextPosition );
+			const range = this._convertItem( viewItem, this._contextPosition );
 
 			// Conversion result is always a document fragment so let's create this fragment.
 			const documentFragment = writer.createDocumentFragment();
@@ -217,19 +216,19 @@ export default class ViewConversionDispatcher {
 	 * @private
 	 * @see module:engine/conversion/viewconversiondispatcher~ViewConversionApi#convertItem
 	 */
-	_convertItem( input, consumable, position ) {
-		const data = Object.assign( { input, position, output: null } );
+	_convertItem( viewItem, cursorPosition ) {
+		const data = Object.assign( { viewItem, cursorPosition, modelRange: null } );
 
-		if ( input.is( 'element' ) ) {
-			this.fire( 'element:' + input.name, data, consumable, this.conversionApi );
-		} else if ( input.is( 'text' ) ) {
-			this.fire( 'text', data, consumable, this.conversionApi );
+		if ( viewItem.is( 'element' ) ) {
+			this.fire( 'element:' + viewItem.name, data, this.conversionApi );
+		} else if ( viewItem.is( 'text' ) ) {
+			this.fire( 'text', data, this.conversionApi );
 		} else {
-			this.fire( 'documentFragment', data, consumable, this.conversionApi );
+			this.fire( 'documentFragment', data, this.conversionApi );
 		}
 
 		// Handle incorrect `data.output`.
-		if ( data.output && !( data.output instanceof ModelRange ) ) {
+		if ( data.modelRange && !( data.modelRange instanceof ModelRange ) ) {
 			/**
 			 * Incorrect conversion result was dropped.
 			 *
@@ -238,39 +237,42 @@ export default class ViewConversionDispatcher {
 			 *
 			 * @error view-conversion-dispatcher-incorrect-result
 			 */
-			log.warn( 'view-conversion-dispatcher-incorrect-result: Incorrect conversion result was dropped.', [ input, data.output ] );
+			log.warn(
+				'view-conversion-dispatcher-incorrect-result: Incorrect conversion result was dropped.',
+				[ viewItem, data.modelRange ]
+			);
 
 			return null;
 		}
 
-		return data.output;
+		return data.modelRange;
 	}
 
 	/**
 	 * @private
 	 * @see module:engine/conversion/viewconversiondispatcher~ViewConversionApi#convertChildren
 	 */
-	_convertChildren( input, consumable, position ) {
-		const output = new ModelRange( position );
+	_convertChildren( viewItem, cursorPosition ) {
+		const childrenRange = new ModelRange( cursorPosition );
 
-		for ( const viewChild of Array.from( input.getChildren() ) ) {
-			const range = this._convertItem( viewChild, consumable, output.end );
+		for ( const viewChild of Array.from( viewItem.getChildren() ) ) {
+			const itemRange = this._convertItem( viewChild, childrenRange.end );
 
-			if ( range instanceof ModelRange ) {
-				output.end = range.end;
+			if ( itemRange instanceof ModelRange ) {
+				childrenRange.end = itemRange.end;
 			}
 		}
 
-		return output;
+		return childrenRange;
 	}
 
 	/**
 	 * @private
 	 * @see module:engine/conversion/viewconversiondispatcher~ViewConversionApi#splitToAllowedParent
 	 */
-	_splitToAllowedParent( element, position ) {
+	_splitToAllowedParent( element, cursorPosition ) {
 		// Try to find allowed parent.
-		const allowedParent = this.conversionApi.schema.findAllowedParent( element, position, this._contextPosition.parent );
+		const allowedParent = this.conversionApi.schema.findAllowedParent( element, cursorPosition, this._contextPosition.parent );
 
 		// When there is no parent that allows to insert element then return `null`.
 		if ( !allowedParent ) {
@@ -278,24 +280,24 @@ export default class ViewConversionDispatcher {
 		}
 
 		// When current position parent allows to insert element then return this position.
-		if ( allowedParent === position.parent ) {
-			return { position };
+		if ( allowedParent === cursorPosition.parent ) {
+			return { position: cursorPosition };
 		}
 
 		// Split element to allowed parent.
-		const data = this.conversionApi.writer.split( position, allowedParent );
+		const splitResult = this.conversionApi.writer.split( cursorPosition, allowedParent );
 
 		// Remember all elements that are created as a result of split.
 		// This is important because at the end of conversion we want to remove all empty split elements.
-		for ( const pos of data.range.getPositions() ) {
-			if ( !pos.isEqual( data.position ) ) {
-				this._splitElements.add( pos.parent );
+		for ( const position of splitResult.range.getPositions() ) {
+			if ( !position.isEqual( splitResult.position ) ) {
+				this._splitElements.add( position.parent );
 			}
 		}
 
 		return {
-			position: data.position,
-			cursorParent: data.range.end.parent
+			position: splitResult.position,
+			cursorParent: splitResult.range.end.parent
 		};
 	}
 
@@ -339,20 +341,16 @@ export default class ViewConversionDispatcher {
 	 * all elements conversion or to conversion of specific elements.
 	 *
 	 * @event element
-	 * @param {Object} data Object containing conversion input and a placeholder for conversion output and possibly other
-	 * values (see {@link #convert}).
-	 * Keep in mind that this object is shared by reference between all callbacks that will be called.
-	 * This means that callbacks can add their own values if needed,
-	 * and those values will be available in other callbacks.
-	 * @param {module:engine/view/element~Element} data.input Converted element.
-	 * @param {*} data.output The current state of conversion result. Every change to converted element should
-	 * be reflected by setting or modifying this property.
-	 * @param {module:engine/conversion/viewconsumable~ViewConsumable} consumable Values to consume.
-	 * @param {Object} conversionApi Conversion interface to be used by callback, passed in `ViewConversionDispatcher` constructor.
-	 * Besides of properties passed in constructor, it also has `convertItem` and `convertChildren` methods which are references
-	 * to {@link #_convertItem} and
-	 * {@link ~ViewConversionDispatcher#_convertChildren}. Those methods are needed to convert
-	 * the whole view-tree they were exposed in `conversionApi` for callbacks.
+	 * @param {Object} data Object containing viewItem to convert, cursorPosition as a conversion position and placeholder
+	 * for modelRange that is a conversion result. Keep in mind that this object is shared by reference between all
+	 * callbacks that will be called. This means that callbacks can override values if needed, and those values will
+	 * be available in other callbacks.
+	 * @param {module:engine/view/item~Item} data.viewItem Converted item.
+	 * @param {module:engine/model/position~Position} data.cursorPosition Target position for current item.
+	 * @param {module:engine/model/range~Range} data.modelRange The current state of conversion result. Every change to
+	 * converted element should be reflected by setting or modifying this property.
+	 * @param {ViewConversionApi} conversionApi Conversion interface to be used by callback, passed in
+	 * `ViewConversionDispatcher` constructor.
 	 */
 
 	/**
@@ -442,8 +440,7 @@ function contextToPosition( contextDefinition, writer ) {
  * and is passed as one of parameters when {@link module:engine/conversion/viewconversiondispatcher~ViewConversionDispatcher dispatcher}
  * fires it's events.
  *
- * `ViewConversionApi` object is built by {@link module:engine/conversion/viewconversiondispatcher~ViewConversionDispatcher} constructor.
- * The exact list of properties of this object is determined by the object passed to the constructor.
+ * TODO more explanation.
  *
  * @interface ViewConversionApi
  */
@@ -451,20 +448,16 @@ function contextToPosition( contextDefinition, writer ) {
 /**
  * Starts conversion of given item by firing an appropriate event.
  *
- * Every fired event is passed (as first parameter) an object with `output` property. Every event may set and/or
- * modify that property. When all callbacks are done, the final value of `output` property is returned by this method.
- * The `output` must be {@link module:engine/model/range~Range model range} or `null` (as set by default).
+ * Every fired event is passed (as first parameter) an object with `modelRange` property. Every event may set and/or
+ * modify that property. When all callbacks are done, the final value of `modelRange` property is returned by this method.
+ * The `modelRange` must be {@link module:engine/model/range~Range model range} or `null` (as set by default).
  *
  * @method #convertItem
  * @fires module:engine/conversion/viewconversiondispatcher~ViewConversionDispatcher#event:element
  * @fires module:engine/conversion/viewconversiondispatcher~ViewConversionDispatcher#event:text
  * @fires module:engine/conversion/viewconversiondispatcher~ViewConversionDispatcher#event:documentFragment
- * @param {module:engine/view/documentfragment~DocumentFragment|module:engine/view/element~Element|module:engine/view/text~Text}
- * input Item to convert.
- * @param {module:engine/conversion/viewconsumable~ViewConsumable} consumable Values to consume.
- * @param {module:engine/model/position~Position} position Position of conversion.
- * @param {Object} [additionalData] Additional data to be passed in `data` argument when firing `ViewConversionDispatcher`
- * events. See also {@link module:engine/conversion/viewconversiondispatcher~ViewConversionDispatcher#event:element element event}.
+ * @param {module:engine/view/item~Item} viewItem Item to convert.
+ * @param {module:engine/model/position~Position} cursorPosition Position of conversion.
  * @returns {module:engine/model/range~Range|null} Model range containing result of item conversion,
  * created and modified by callbacks attached to fired event, or `null` if the conversion result was incorrect.
  */
@@ -476,14 +469,10 @@ function contextToPosition( contextDefinition, writer ) {
  * @fires module:engine/conversion/viewconversiondispatcher~ViewConversionDispatcher#event:element
  * @fires module:engine/conversion/viewconversiondispatcher~ViewConversionDispatcher#event:text
  * @fires module:engine/conversion/viewconversiondispatcher~ViewConversionDispatcher#event:documentFragment
- * @param {module:engine/view/documentfragment~DocumentFragment|module:engine/view/element~Element}
- * input Item which children will be converted.
- * @param {module:engine/conversion/viewconsumable~ViewConsumable} consumable Values to consume.
- * @param {module:engine/model/position~Position} position Position of conversion.
- * @param {Object} [additionalData] Additional data to be passed in `data` argument when firing `ViewConversionDispatcher`
- * events. See also {@link module:engine/conversion/viewconversiondispatcher~ViewConversionDispatcher#event:element element event}.
+ * @param {module:engine/view/item~Item} viewItem Item to convert.
+ * @param {module:engine/model/position~Position} cursorPosition Position of conversion.
  * @returns {module:engine/model/range~Range} Model range containing results of conversion of all children of given item.
- * When none of children was converted
+ * When no children was converted then range is collapsed.
  */
 
 /**
@@ -493,18 +482,22 @@ function contextToPosition( contextDefinition, writer ) {
  * @method #splitToAllowedParent
  * @param {module:engine/model/position~Position} position Position on which element is going to be inserted.
  * @param {module:engine/model/element~Node} element Element to insert.
- * @returns {SplitToAllowedParentResult} Split result.
+ * @returns {Object} Split result.
+ * @returns {module:engine/model/position~Position} position between split elements.
+ * @returns {module:engine/model/element~Element} [cursorParent] Element inside which cursor should be placed to
+ * continue conversion. When element is not defined it means that there was no split.
  */
 
 /**
- * Custom data stored by converter for conversion process.
+ * Instance of {@link module:engine/conversion/viewconsumable~ViewConsumable}. It stores
+ * information about what parts of processed view item are still waiting to be handled. After a piece of view item
+ * was converted, appropriate consumable value should be {@link module:engine/conversion/viewconsumable~ViewConsumable#consume consumed}.
  *
- * @param {Object} #data
+ * @param {Object} #consumable
  */
 
 /**
- * @typedef {Object} SplitToAllowedParentResult
- * @property {module:engine/model/position~Position} position between split elements.
- * @property {module:engine/model/element~Element} [cursorParent] Element inside which cursor should be placed to
- * continue conversion. When element is not defined it means that there was no split.
+ * Custom data stored by converter for conversion process.
+ *
+ * @param {Object} #data
  */

+ 128 - 132
packages/ckeditor5-engine/tests/conversion/viewconversiondispatcher.js

@@ -41,6 +41,12 @@ describe( 'ViewConversionDispatcher', () => {
 			expect( dispatcher.conversionApi ).to.have.property( 'convertChildren' ).that.is.instanceof( Function );
 			expect( dispatcher.conversionApi ).to.have.property( 'splitToAllowedParent' ).that.is.instanceof( Function );
 		} );
+
+		it( 'should have properties', () => {
+			const dispatcher = new ViewConversionDispatcher( model );
+
+			expect( dispatcher._splitElements ).to.instanceof( Set );
+		} );
 	} );
 
 	describe( 'convert()', () => {
@@ -50,6 +56,65 @@ describe( 'ViewConversionDispatcher', () => {
 			dispatcher = new ViewConversionDispatcher( model );
 		} );
 
+		it( 'should create api for current conversion process', () => {
+			const viewElement = new ViewContainerElement( 'p', null, new ViewText( 'foobar' ) );
+
+			// To be sure that both converters was called.
+			const spy = sinon.spy();
+
+			// To check that the same writer instance.
+			let writer;
+
+			// Conversion process properties should be undefined/empty before conversion.
+			expect( dispatcher.conversionApi.writer ).to.not.ok;
+			expect( dispatcher.conversionApi.data ).to.not.ok;
+			expect( dispatcher._splitElements.size ).to.equal( 0 );
+
+			dispatcher.on( 'element', ( evt, data, conversionApi ) => {
+				// Check conversion api params.
+				expect( conversionApi.writer ).to.instanceof( ModelWriter );
+				expect( conversionApi.data ).to.deep.equal( {} );
+				expect( dispatcher._splitElements.size ).to.equal( 0 );
+
+				// Remember writer to check in next converter that is exactly the same instance (the same undo step).
+				writer = conversionApi.writer;
+
+				// Add some data to conversion storage to verify them in next converter.
+				conversionApi.data.foo = 'bar';
+
+				// Add empty element and mark as a split result to check in next converter.
+				dispatcher._splitElements.add( conversionApi.writer.createElement( 'paragraph' ) );
+
+				// Convert children - this will call second converter.
+				conversionApi.convertChildren( data.viewItem, data.cursorPosition );
+
+				spy();
+			} );
+
+			dispatcher.on( 'text', ( evt, data, conversionApi ) => {
+				// The same writer is used in converters during one conversion.
+				expect( conversionApi.writer ).to.equal( writer );
+
+				// Data set by previous converter are remembered.
+				expect( conversionApi.data ).to.deep.equal( { foo: 'bar' } );
+
+				// Split element is remembered as well.
+				expect( dispatcher._splitElements.size ).to.equal( 1 );
+
+				spy();
+			} );
+
+			dispatcher.convert( viewElement );
+
+			// To be sure that both converters was called.
+			sinon.assert.calledTwice( spy );
+
+			// Conversion process properties should be cleared after conversion.
+			expect( dispatcher.conversionApi.writer ).to.not.ok;
+			expect( dispatcher.conversionApi.data ).to.not.ok;
+			expect( dispatcher._splitElements.size ).to.equal( 0 );
+		} );
+
 		it( 'should fire viewCleanup event on converted view part', () => {
 			silenceWarnings();
 
@@ -83,27 +148,27 @@ describe( 'ViewConversionDispatcher', () => {
 			const spy = sinon.spy();
 			const viewText = new ViewText( 'foobar' );
 
-			dispatcher.on( 'text', ( evt, data, consumable, conversionApi ) => {
+			dispatcher.on( 'text', ( evt, data, conversionApi ) => {
 				// Check if this method has been fired.
 				spy();
 
 				// Check correctness of passed parameters.
 				expect( evt.name ).to.equal( 'text' );
-				expect( data.input ).to.equal( viewText );
-				expect( data.position ).to.instanceof( ModelPosition );
+				expect( data.viewItem ).to.equal( viewText );
+				expect( data.cursorPosition ).to.instanceof( ModelPosition );
 
 				// Check whether consumable has appropriate value to consume.
-				expect( consumable.consume( data.input ) ).to.be.true;
+				expect( conversionApi.consumable.consume( data.viewItem ) ).to.be.true;
 
 				// Check whether conversionApi of `dispatcher` has been passed.
 				expect( conversionApi ).to.equal( dispatcher.conversionApi );
 
-				const text = conversionApi.writer.createText( data.input.data );
-				conversionApi.writer.insert( text, data.position );
+				const text = conversionApi.writer.createText( data.viewItem.data );
+				conversionApi.writer.insert( text, data.cursorPosition );
 
-				// Set conversion result to `output` property of `data`.
+				// Set conversion result to `modelRange` property of `data`.
 				// Later we will check if it was returned by `convert` method.
-				data.output = ModelRange.createOn( text );
+				data.modelRange = ModelRange.createOn( text );
 			} );
 
 			const conversionResult = dispatcher.convert( viewText );
@@ -119,28 +184,28 @@ describe( 'ViewConversionDispatcher', () => {
 			const spy = sinon.spy();
 			const viewElement = new ViewContainerElement( 'p', { attrKey: 'attrValue' } );
 
-			dispatcher.on( 'element', ( evt, data, consumable, conversionApi ) => {
+			dispatcher.on( 'element', ( evt, data, conversionApi ) => {
 				// Check if this method has been fired.
 				spy();
 
 				// Check correctness of passed parameters.
 				expect( evt.name ).to.equal( 'element:p' );
-				expect( data.input ).to.equal( viewElement );
-				expect( data.position ).to.instanceof( ModelPosition );
+				expect( data.viewItem ).to.equal( viewElement );
+				expect( data.cursorPosition ).to.instanceof( ModelPosition );
 
 				// Check whether consumable has appropriate value to consume.
-				expect( consumable.consume( data.input, { name: true } ) ).to.be.true;
-				expect( consumable.consume( data.input, { attribute: 'attrKey' } ) ).to.be.true;
+				expect( conversionApi.consumable.consume( data.viewItem, { name: true } ) ).to.be.true;
+				expect( conversionApi.consumable.consume( data.viewItem, { attribute: 'attrKey' } ) ).to.be.true;
 
 				// Check whether conversionApi of `dispatcher` has been passed.
 				expect( conversionApi ).to.equal( dispatcher.conversionApi );
 
 				const paragraph = conversionApi.writer.createElement( 'paragraph' );
-				conversionApi.writer.insert( paragraph, data.position );
+				conversionApi.writer.insert( paragraph, data.cursorPosition );
 
-				// Set conversion result to `output` property of `data`.
+				// Set conversion result to `modelRange` property of `data`.
 				// Later we will check if it was returned by `convert` method.
-				data.output = ModelRange.createOn( paragraph );
+				data.modelRange = ModelRange.createOn( paragraph );
 			} );
 
 			// Use `additionalData` parameter to check if it was passed to the event.
@@ -157,27 +222,27 @@ describe( 'ViewConversionDispatcher', () => {
 			const spy = sinon.spy();
 			const viewFragment = new ViewDocumentFragment();
 
-			dispatcher.on( 'documentFragment', ( evt, data, consumable, conversionApi ) => {
+			dispatcher.on( 'documentFragment', ( evt, data, conversionApi ) => {
 				// Check if this method has been fired.
 				spy();
 
 				// Check correctness of passed parameters.
 				expect( evt.name ).to.equal( 'documentFragment' );
-				expect( data.input ).to.equal( viewFragment );
-				expect( data.position ).to.instanceof( ModelPosition );
+				expect( data.viewItem ).to.equal( viewFragment );
+				expect( data.cursorPosition ).to.instanceof( ModelPosition );
 
 				// Check whether consumable has appropriate value to consume.
-				expect( consumable.consume( data.input ) ).to.be.true;
+				expect( conversionApi.consumable.consume( data.viewItem ) ).to.be.true;
 
 				// Check whether conversionApi of `dispatcher` has been passed.
 				expect( conversionApi ).to.equal( dispatcher.conversionApi );
 
 				const text = conversionApi.writer.createText( 'foo' );
-				conversionApi.writer.insert( text, data.position );
+				conversionApi.writer.insert( text, data.cursorPosition );
 
-				// Set conversion result to `output` property of `data`.
+				// Set conversion result to `modelRange` property of `data`.
 				// Later we will check if it was returned by `convert` method.
-				data.output = ModelRange.createOn( text );
+				data.modelRange = ModelRange.createOn( text );
 			} );
 
 			// Use `additionalData` parameter to check if it was passed to the event.
@@ -189,85 +254,16 @@ describe( 'ViewConversionDispatcher', () => {
 			expect( spy.calledOnce ).to.be.true;
 		} );
 
-		it( 'should add contextual properties to conversion api', () => {
-			const viewElement = new ViewContainerElement( 'p', null, new ViewText( 'foobar' ) );
-
-			// To be sure that both converters was called.
-			const spy = sinon.spy();
-
-			// To check that the same batch is used across conversion.
-			let batch;
-
-			// Contextual properties of ConversionApi should be undefined/empty before conversion.
-			expect( dispatcher.conversionApi.writer ).to.not.ok;
-			expect( dispatcher.conversionApi.data ).to.not.ok;
-			expect( dispatcher._splitElements.size ).to.equal( 0 );
-
-			dispatcher.on( 'element', ( evt, data, consumable, conversionApi ) => {
-				// Check conversion api params.
-				expect( conversionApi.writer ).to.instanceof( ModelWriter );
-				expect( dispatcher._splitElements ).to.instanceof( Set );
-				expect( dispatcher._splitElements.size ).to.equal( 0 );
-				expect( conversionApi.data ).to.deep.equal( {} );
-
-				// Remember writer batch to check in next converter that is exactly the same batch.
-				batch = conversionApi.writer.batch;
-
-				// Add some data to conversion API to verify them in next converter.
-				// Set some custom data to conversion api data object.
-				conversionApi.data.foo = 'bar';
-
-				// Do the conversion.
-				const paragraph = conversionApi.writer.createElement( 'paragraph' );
-				conversionApi.writer.insert( paragraph, data.position );
-
-				// Add empty element and mark as a split result to check in next converter.
-				const splitElement = conversionApi.writer.createElement( 'paragraph' );
-				dispatcher._splitElements.add( splitElement );
-				conversionApi.writer.insert( splitElement, ModelPosition.createAfter( paragraph ) );
-
-				// Convert children - this wil call second converter.
-				conversionApi.convertChildren( data.input, consumable, ModelPosition.createAt( paragraph ) );
-
-				data.output = ModelRange.createOn( paragraph );
-
-				spy();
-			} );
-
-			dispatcher.on( 'text', ( evt, data, consumable, conversionApi ) => {
-				// The same batch is used in converters during one conversion.
-				expect( conversionApi.writer.batch ).to.equal( batch );
-
-				// Data set by previous converter are remembered.
-				expect( conversionApi.data ).to.deep.equal( { foo: 'bar' } );
-
-				// Split element is remembered as well.
-				expect( dispatcher._splitElements.size ).to.equal( 1 );
-
-				spy();
-			} );
-
-			dispatcher.convert( viewElement );
-
-			// To be sure that both converters was called.
-			sinon.assert.calledTwice( spy );
-
-			// Contextual properties of ConversionApi should be cleared after conversion.
-			expect( dispatcher.conversionApi.writer ).to.not.ok;
-			expect( dispatcher.conversionApi.data ).to.not.ok;
-			expect( dispatcher._splitElements.size ).to.equal( 0 );
-		} );
-
 		it( 'should remove empty elements that was created as a result of split', () => {
 			const viewElement = new ViewContainerElement( 'p' );
 
 			// To be sure that converter was called.
 			const spy = sinon.spy();
 
-			dispatcher.on( 'element', ( evt, data, consumable, conversionApi ) => {
+			dispatcher.on( 'element', ( evt, data, conversionApi ) => {
 				// First let's convert target element.
 				const paragraph = conversionApi.writer.createElement( 'paragraph' );
-				conversionApi.writer.insert( paragraph, data.position );
+				conversionApi.writer.insert( paragraph, data.cursorPosition );
 
 				// Then add some elements and mark as split.
 
@@ -291,7 +287,8 @@ describe( 'ViewConversionDispatcher', () => {
 				dispatcher._splitElements.add( outerSplit );
 				dispatcher._splitElements.add( innerSplit );
 
-				data.output = ModelRange.createOn( paragraph );
+				data.modelRange = ModelRange.createOn( paragraph );
+				data.cursorPosition = data.modelRange.end;
 
 				// We have the following result:
 				// <p><p></p></p>[<p></p>]<p></p><p>foo</p>
@@ -315,7 +312,8 @@ describe( 'ViewConversionDispatcher', () => {
 		it( 'should extract temporary markers elements from converter element and create static markers list', () => {
 			const viewFragment = new ViewDocumentFragment();
 
-			dispatcher.on( 'documentFragment', ( evt, data, consumable, conversionApi ) => {
+			dispatcher.on( 'documentFragment', ( evt, data, conversionApi ) => {
+				// Create model fragment.
 				const fragment = new ModelDocumentFragment( [
 					new ModelText( 'fo' ),
 					new ModelElement( '$marker', { 'data-name': 'marker1' } ),
@@ -326,9 +324,11 @@ describe( 'ViewConversionDispatcher', () => {
 					new ModelText( 'ar' ),
 				] );
 
-				conversionApi.writer.insert( fragment, data.position );
+				// Insert to conversion tree.
+				conversionApi.writer.insert( fragment, data.cursorPosition );
 
-				data.output = ModelRange.createIn( data.position.parent );
+				// Create range on this fragment as a conversion result.
+				data.modelRange = ModelRange.createIn( data.cursorPosition.parent );
 			} );
 
 			const conversionResult = dispatcher.convert( viewFragment );
@@ -361,9 +361,9 @@ describe( 'ViewConversionDispatcher', () => {
 				disallowIn: 'first'
 			} );
 
-			dispatcher.on( 'element:third', ( evt, data, consumable, conversionApi ) => {
+			dispatcher.on( 'element:third', ( evt, data, conversionApi ) => {
 				spy();
-				checkChildResult = conversionApi.schema.checkChild( data.position, 'third' );
+				checkChildResult = conversionApi.schema.checkChild( data.cursorPosition, 'third' );
 			} );
 
 			// Default context $root.
@@ -390,7 +390,7 @@ describe( 'ViewConversionDispatcher', () => {
 	} );
 
 	describe( 'conversionApi', () => {
-		let spy, spyP, spyText, viewP, viewText, modelP, modelText, consumableMock, rootMock, dispatcher,
+		let spy, spyP, spyText, viewP, viewText, modelP, modelText, rootMock, dispatcher,
 			spyNull, spyArray, viewDiv, viewNull, viewArray;
 
 		beforeEach( () => {
@@ -406,60 +406,56 @@ describe( 'ViewConversionDispatcher', () => {
 			// Put nodes to documentFragment, this will mock root element and makes possible to create range on them.
 			rootMock = new ModelDocumentFragment( [ modelP, modelText ] );
 
-			consumableMock = {};
-
 			dispatcher = new ViewConversionDispatcher( model, { schema: model.schema } );
 
-			dispatcher.on( 'element:p', ( evt, data, consumable ) => {
+			dispatcher.on( 'element:p', ( evt, data ) => {
 				spyP();
 
-				expect( consumable ).to.equal( consumableMock );
-
-				data.output = ModelRange.createOn( modelP );
+				data.modelRange = ModelRange.createOn( modelP );
+				data.cursorPosition = data.modelRange.end;
 			} );
 
-			dispatcher.on( 'text', ( evt, data, consumable ) => {
+			dispatcher.on( 'text', ( evt, data ) => {
 				spyText();
 
-				expect( consumable ).to.equal( consumableMock );
-
-				data.output = ModelRange.createOn( modelText );
+				data.modelRange = ModelRange.createOn( modelText );
+				data.cursorPosition = data.modelRange.end;
 			} );
 
 			spyNull = sinon.spy();
 			spyArray = sinon.spy();
 
 			viewDiv = new ViewContainerElement( 'div' ); // Will not be recognized and not converted.
-			viewNull = new ViewContainerElement( 'null' ); // Will return `null` in `data.output` upon conversion.
-			viewArray = new ViewContainerElement( 'array' ); // Will return an array in `data.output` upon conversion.
+			viewNull = new ViewContainerElement( 'null' ); // Will return `null` in `data.modelRange` upon conversion.
+			viewArray = new ViewContainerElement( 'array' ); // Will return an array in `data.modelRange` upon conversion.
 
 			dispatcher.on( 'element:null', ( evt, data ) => {
 				spyNull();
 
-				data.output = null;
+				data.modelRange = null;
 			} );
 
 			dispatcher.on( 'element:array', ( evt, data ) => {
 				spyArray();
 
-				data.output = [ new ModelText( 'foo' ) ];
+				data.modelRange = [ new ModelText( 'foo' ) ];
 			} );
 		} );
 
 		describe( 'convertItem()', () => {
-			it( 'should pass consumable and additional data to proper converter and return data.output', () => {
+			it( 'should call proper converter and return correct data', () => {
 				silenceWarnings();
 
-				dispatcher.on( 'documentFragment', ( evt, data, consumable, conversionApi ) => {
+				dispatcher.on( 'documentFragment', ( evt, data, conversionApi ) => {
 					spy();
 
-					const result1 = conversionApi.convertItem( viewP, consumableMock, data.position );
+					const result1 = conversionApi.convertItem( viewP, data.cursorPosition );
 					expect( result1 ).instanceof( ModelRange );
 					expect( result1.start.path ).to.deep.equal( [ 0 ] );
 					expect( result1.end.path ).to.deep.equal( [ 1 ] );
 					expect( first( result1.getItems() ) ).to.equal( modelP );
 
-					const result2 = conversionApi.convertItem( viewText, consumableMock, data.position );
+					const result2 = conversionApi.convertItem( viewText, data.cursorPosition );
 					expect( result2 ).instanceof( ModelRange );
 					expect( result2.start.path ).to.deep.equal( [ 1 ] );
 					expect( result2.end.path ).to.deep.equal( [ 7 ] );
@@ -477,11 +473,11 @@ describe( 'ViewConversionDispatcher', () => {
 			it( 'should do nothing if element was not converted', () => {
 				sinon.stub( log, 'warn' );
 
-				dispatcher.on( 'documentFragment', ( evt, data, consumable, conversionApi ) => {
+				dispatcher.on( 'documentFragment', ( evt, data, conversionApi ) => {
 					spy();
 
-					expect( conversionApi.convertItem( viewDiv ) ).to.equal( null );
-					expect( conversionApi.convertItem( viewNull ) ).to.equal( null );
+					expect( conversionApi.convertItem( viewDiv, data.cursorPosition ) ).to.equal( null );
+					expect( conversionApi.convertItem( viewNull, data.cursorPosition ) ).to.equal( null );
 				} );
 
 				dispatcher.convert( new ViewDocumentFragment() );
@@ -496,10 +492,10 @@ describe( 'ViewConversionDispatcher', () => {
 			it( 'should return null if element was incorrectly converted and log a warning', () => {
 				sinon.stub( log, 'warn' );
 
-				dispatcher.on( 'documentFragment', ( evt, data, consumable, conversionApi ) => {
+				dispatcher.on( 'documentFragment', ( evt, data, conversionApi ) => {
 					spy();
 
-					expect( conversionApi.convertItem( viewArray ) ).to.equal( null );
+					expect( conversionApi.convertItem( viewArray, data.cursorPosition ) ).to.equal( null );
 				} );
 
 				dispatcher.convert( new ViewDocumentFragment() );
@@ -517,10 +513,10 @@ describe( 'ViewConversionDispatcher', () => {
 				'wrapped in document fragment', () => {
 				silenceWarnings();
 
-				dispatcher.on( 'documentFragment', ( evt, data, consumable, conversionApi ) => {
+				dispatcher.on( 'documentFragment', ( evt, data, conversionApi ) => {
 					spy();
 
-					const result = conversionApi.convertChildren( data.input, consumableMock, ModelPosition.createAt( rootMock ) );
+					const result = conversionApi.convertChildren( data.viewItem, ModelPosition.createAt( rootMock ) );
 
 					expect( result ).to.be.instanceof( ModelRange );
 					expect( result.start.path ).to.deep.equal( [ 0 ] );
@@ -541,10 +537,10 @@ describe( 'ViewConversionDispatcher', () => {
 			it( 'should filter out incorrectly converted elements and log warnings', () => {
 				sinon.stub( log, 'warn' );
 
-				dispatcher.on( 'documentFragment', ( evt, data, consumable, conversionApi ) => {
+				dispatcher.on( 'documentFragment', ( evt, data, conversionApi ) => {
 					spy();
 
-					const result = conversionApi.convertChildren( data.input, consumableMock, ModelPosition.createAt( rootMock ) );
+					const result = conversionApi.convertChildren( data.viewItem, ModelPosition.createAt( rootMock ) );
 
 					expect( result ).to.be.instanceof( ModelRange );
 					expect( result.start.path ).to.deep.equal( [ 0 ] );
@@ -580,7 +576,7 @@ describe( 'ViewConversionDispatcher', () => {
 					allowIn: 'paragraph'
 				} );
 
-				dispatcher.on( 'documentFragment', ( evt, data, consumable, conversionApi ) => {
+				dispatcher.on( 'documentFragment', ( evt, data, conversionApi ) => {
 					const paragraph = conversionApi.writer.createElement( 'paragraph' );
 					const span = conversionApi.writer.createElement( 'span' );
 					const position = ModelPosition.createAt( paragraph );
@@ -608,7 +604,7 @@ describe( 'ViewConversionDispatcher', () => {
 					allowIn: 'section'
 				} );
 
-				dispatcher.on( 'documentFragment', ( evt, data, consumable, conversionApi ) => {
+				dispatcher.on( 'documentFragment', ( evt, data, conversionApi ) => {
 					const section = conversionApi.writer.createElement( 'section' );
 					const paragraph = conversionApi.writer.createElement( 'paragraph' );
 					const span = conversionApi.writer.createElement( 'span' );
@@ -622,7 +618,7 @@ describe( 'ViewConversionDispatcher', () => {
 
 					expect( result ).to.deep.equal( {
 						position: ModelPosition.createAfter( paragraph ),
-						endElement: paragraph.parent.getChild( 1 ).getChild( 0 )
+						cursorParent: paragraph.parent.getChild( 1 ).getChild( 0 )
 					} );
 
 					spy();
@@ -637,7 +633,7 @@ describe( 'ViewConversionDispatcher', () => {
 
 				model.schema.register( 'span' );
 
-				dispatcher.on( 'documentFragment', ( evt, data, consumable, conversionApi ) => {
+				dispatcher.on( 'documentFragment', ( evt, data, conversionApi ) => {
 					const paragraph = conversionApi.writer.createElement( 'paragraph' );
 					const span = conversionApi.writer.createElement( 'span' );
 					const position = ModelPosition.createAt( paragraph );