Selaa lähdekoodia

Removed view and change() block from ModelConversionDispatcher. Now writer is passed as parameter to conversion methods.

Szymon Kupś 8 vuotta sitten
vanhempi
sitoutus
3bcf0419ee

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

@@ -19,6 +19,7 @@ import ViewConversionDispatcher from '../conversion/viewconversiondispatcher';
 import { convertText, convertToModelFragment } from '../conversion/view-to-model-converters';
 
 import ViewDocumentFragment from '../view/documentfragment';
+import ViewWriter from '../view/writer';
 
 import ModelRange from '../model/range';
 
@@ -158,7 +159,7 @@ export default class DataController {
 		const viewDocumentFragment = new ViewDocumentFragment();
 		this.mapper.bindElements( modelElementOrFragment, viewDocumentFragment );
 
-		this.modelToView.convertInsert( modelRange );
+		this.modelToView.convertInsert( modelRange, new ViewWriter() );
 
 		this.mapper.clearBindings();
 

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

@@ -78,7 +78,7 @@ export default class EditingController {
 		 * @readonly
 		 * @member {module:engine/conversion/modelconversiondispatcher~ModelConversionDispatcher} #modelToView
 		 */
-		this.modelToView = new ModelConversionDispatcher( this.model, this.view, {
+		this.modelToView = new ModelConversionDispatcher( this.model, {
 			mapper: this.mapper,
 			viewSelection: this.view.document.selection
 		} );
@@ -87,11 +87,13 @@ export default class EditingController {
 
 		// When all changes are done, get the model diff containing all the changes and convert them to view and then render to DOM.
 		this.listenTo( doc, 'change', () => {
-			// Convert changes stored in `modelDiffer`.
-			this.modelToView.convertChanges( doc.differ );
+			this.view.change( writer => {
+				// Convert changes stored in `modelDiffer`.
+				this.modelToView.convertChanges( doc.differ, writer );
 
-			// After the view is ready, convert selection from model to view.
-			this.modelToView.convertSelection( doc.selection );
+				// After the view is ready, convert selection from model to view.
+				this.modelToView.convertSelection( doc.selection, writer );
+			} );
 		}, { priority: 'low' } );
 
 		// Convert selection from view to model.

+ 132 - 148
packages/ckeditor5-engine/src/conversion/modelconversiondispatcher.js

@@ -105,7 +105,7 @@ export default class ModelConversionDispatcher {
 	 * @param {module:engine/model/model~Model} model Data model.
 	 * @param {Object} [conversionApi] Interface passed by dispatcher to the events calls.
 	 */
-	constructor( model, view, conversionApi = {} ) {
+	constructor( model, conversionApi = {} ) {
 		/**
 		 * Data model instance bound with this dispatcher.
 		 *
@@ -114,8 +114,6 @@ export default class ModelConversionDispatcher {
 		 */
 		this._model = model;
 
-		this._view = view;
-
 		/**
 		 * Interface passed by dispatcher to the events callbacks.
 		 *
@@ -129,32 +127,30 @@ export default class ModelConversionDispatcher {
 	 *
 	 * @param {module:engine/model/differ~Differ} differ Differ object with buffered changes.
 	 */
-	convertChanges( differ ) {
-		this._view.change( writer => {
-			this.conversionApi.writer = writer;
+	convertChanges( differ, writer ) {
+		this.conversionApi.writer = writer;
 
-			// First, before changing view structure, remove all markers that has changed.
-			for ( const change of differ.getMarkersToRemove() ) {
-				this.convertMarkerRemove( change.name, change.range );
-			}
+		// First, before changing view structure, remove all markers that has changed.
+		for ( const change of differ.getMarkersToRemove() ) {
+			this.convertMarkerRemove( change.name, change.range, writer );
+		}
 
-			// Convert changes that happened on model tree.
-			for ( const entry of differ.getChanges() ) {
-				if ( entry.type == 'insert' ) {
-					this.convertInsert( Range.createFromPositionAndShift( entry.position, entry.length ) );
-				} else if ( entry.type == 'remove' ) {
-					this.convertRemove( entry.position, entry.length, entry.name );
-				} else {
-					// entry.type == 'attribute'.
-					this.convertAttribute( entry.range, entry.attributeKey, entry.attributeOldValue, entry.attributeNewValue );
-				}
+		// Convert changes that happened on model tree.
+		for ( const entry of differ.getChanges() ) {
+			if ( entry.type == 'insert' ) {
+				this.convertInsert( Range.createFromPositionAndShift( entry.position, entry.length ), writer );
+			} else if ( entry.type == 'remove' ) {
+				this.convertRemove( entry.position, entry.length, entry.name, writer );
+			} else {
+				// entry.type == 'attribute'.
+				this.convertAttribute( entry.range, entry.attributeKey, entry.attributeOldValue, entry.attributeNewValue, writer );
 			}
+		}
 
-			// After the view is updated, convert markers which has changed.
-			for ( const change of differ.getMarkersToAdd() ) {
-				this.convertMarkerAdd( change.name, change.range );
-			}
-		} );
+		// After the view is updated, convert markers which has changed.
+		for ( const change of differ.getMarkersToAdd() ) {
+			this.convertMarkerAdd( change.name, change.range, writer );
+		}
 	}
 
 	/**
@@ -167,36 +163,34 @@ export default class ModelConversionDispatcher {
 	 * @fires attribute
 	 * @param {module:engine/model/range~Range} range Inserted range.
 	 */
-	convertInsert( range ) {
-		this._view.change( writer => {
-			this.conversionApi.writer = writer;
-
-			// Create a list of things that can be consumed, consisting of nodes and their attributes.
-			const consumable = this._createInsertConsumable( range );
-
-			// Fire a separate insert event for each node and text fragment contained in the range.
-			for ( const value of range ) {
-				const item = value.item;
-				const itemRange = Range.createFromPositionAndShift( value.previousPosition, value.length );
-				const data = {
-					item,
-					range: itemRange
-				};
-
-				this._testAndFire( 'insert', data, consumable );
-
-				// Fire a separate addAttribute event for each attribute that was set on inserted items.
-				// This is important because most attributes converters will listen only to add/change/removeAttribute events.
-				// If we would not add this part, attributes on inserted nodes would not be converted.
-				for ( const key of item.getAttributeKeys() ) {
-					data.attributeKey = key;
-					data.attributeOldValue = null;
-					data.attributeNewValue = item.getAttribute( key );
-
-					this._testAndFire( `attribute:${ key }`, data, consumable );
-				}
+	convertInsert( range, writer ) {
+		this.conversionApi.writer = writer;
+
+		// Create a list of things that can be consumed, consisting of nodes and their attributes.
+		const consumable = this._createInsertConsumable( range );
+
+		// Fire a separate insert event for each node and text fragment contained in the range.
+		for ( const value of range ) {
+			const item = value.item;
+			const itemRange = Range.createFromPositionAndShift( value.previousPosition, value.length );
+			const data = {
+				item,
+				range: itemRange
+			};
+
+			this._testAndFire( 'insert', data, consumable );
+
+			// Fire a separate addAttribute event for each attribute that was set on inserted items.
+			// This is important because most attributes converters will listen only to add/change/removeAttribute events.
+			// If we would not add this part, attributes on inserted nodes would not be converted.
+			for ( const key of item.getAttributeKeys() ) {
+				data.attributeKey = key;
+				data.attributeOldValue = null;
+				data.attributeNewValue = item.getAttribute( key );
+
+				this._testAndFire( `attribute:${ key }`, data, consumable );
 			}
-		} );
+		}
 	}
 
 	/**
@@ -206,12 +200,10 @@ export default class ModelConversionDispatcher {
 	 * @param {Number} length Offset size of removed node.
 	 * @param {String} name Name of removed node.
 	 */
-	convertRemove( position, length, name ) {
-		this._view.change( writer => {
-			this.conversionApi.writer = writer;
+	convertRemove( position, length, name, writer ) {
+		this.conversionApi.writer = writer;
 
-			this.fire( 'remove:' + name, { position, length }, this.conversionApi );
-		} );
+		this.fire( 'remove:' + name, { position, length }, this.conversionApi );
 	}
 
 	/**
@@ -225,28 +217,26 @@ export default class ModelConversionDispatcher {
 	 * @param {*} oldValue Attribute value before the change or `null` if the attribute has not been set before.
 	 * @param {*} newValue New attribute value or `null` if the attribute has been removed.
 	 */
-	convertAttribute( range, key, oldValue, newValue ) {
-		this._view.change( writer => {
-			this.conversionApi.writer = writer;
-
-			// Create a list with attributes to consume.
-			const consumable = this._createConsumableForRange( range, `attribute:${ key }` );
-
-			// Create a separate attribute event for each node in the range.
-			for ( const value of range ) {
-				const item = value.item;
-				const itemRange = Range.createFromPositionAndShift( value.previousPosition, value.length );
-				const data = {
-					item,
-					range: itemRange,
-					attributeKey: key,
-					attributeOldValue: oldValue,
-					attributeNewValue: newValue
-				};
+	convertAttribute( range, key, oldValue, newValue, writer ) {
+		this.conversionApi.writer = writer;
 
-				this._testAndFire( `attribute:${ key }`, data, consumable );
-			}
-		} );
+		// Create a list with attributes to consume.
+		const consumable = this._createConsumableForRange( range, `attribute:${ key }` );
+
+		// Create a separate attribute event for each node in the range.
+		for ( const value of range ) {
+			const item = value.item;
+			const itemRange = Range.createFromPositionAndShift( value.previousPosition, value.length );
+			const data = {
+				item,
+				range: itemRange,
+				attributeKey: key,
+				attributeOldValue: oldValue,
+				attributeNewValue: newValue
+			};
+
+			this._testAndFire( `attribute:${ key }`, data, consumable );
+		}
 	}
 
 	/**
@@ -259,45 +249,43 @@ export default class ModelConversionDispatcher {
 	 * @fires selectionAttribute
 	 * @param {module:engine/model/selection~Selection} selection Selection to convert.
 	 */
-	convertSelection( selection ) {
-		this._view.change( writer => {
-			this.conversionApi.writer = writer;
-			const markers = Array.from( this._model.markers.getMarkersAtPosition( selection.getFirstPosition() ) );
-			const consumable = this._createSelectionConsumable( selection, markers );
-
-			this.fire( 'selection', { selection }, consumable, this.conversionApi );
-
-			for ( const marker of markers ) {
-				const markerRange = marker.getRange();
-
-				if ( !shouldMarkerChangeBeConverted( selection.getFirstPosition(), marker, this.conversionApi.mapper ) ) {
-					continue;
-				}
-
-				const data = {
-					selection,
-					markerName: marker.name,
-					markerRange
-				};
-
-				if ( consumable.test( selection, 'selectionMarker:' + marker.name ) ) {
-					this.fire( 'selectionMarker:' + marker.name, data, consumable, this.conversionApi );
-				}
+	convertSelection( selection, writer ) {
+		this.conversionApi.writer = writer;
+		const markers = Array.from( this._model.markers.getMarkersAtPosition( selection.getFirstPosition() ) );
+		const consumable = this._createSelectionConsumable( selection, markers );
+
+		this.fire( 'selection', { selection }, consumable, this.conversionApi );
+
+		for ( const marker of markers ) {
+			const markerRange = marker.getRange();
+
+			if ( !shouldMarkerChangeBeConverted( selection.getFirstPosition(), marker, this.conversionApi.mapper ) ) {
+				continue;
 			}
 
-			for ( const key of selection.getAttributeKeys() ) {
-				const data = {
-					selection,
-					key,
-					value: selection.getAttribute( key )
-				};
-
-				// Do not fire event if the attribute has been consumed.
-				if ( consumable.test( selection, 'selectionAttribute:' + data.key ) ) {
-					this.fire( 'selectionAttribute:' + data.key, data, consumable, this.conversionApi );
-				}
+			const data = {
+				selection,
+				markerName: marker.name,
+				markerRange
+			};
+
+			if ( consumable.test( selection, 'selectionMarker:' + marker.name ) ) {
+				this.fire( 'selectionMarker:' + marker.name, data, consumable, this.conversionApi );
 			}
-		} );
+		}
+
+		for ( const key of selection.getAttributeKeys() ) {
+			const data = {
+				selection,
+				key,
+				value: selection.getAttribute( key )
+			};
+
+			// Do not fire event if the attribute has been consumed.
+			if ( consumable.test( selection, 'selectionAttribute:' + data.key ) ) {
+				this.fire( 'selectionAttribute:' + data.key, data, consumable, this.conversionApi );
+			}
+		}
 	}
 
 	/**
@@ -308,46 +296,44 @@ export default class ModelConversionDispatcher {
 	 * @param {String} markerName Marker name.
 	 * @param {module:engine/model/range~Range} markerRange Marker range.
 	 */
-	convertMarkerAdd( markerName, markerRange ) {
+	convertMarkerAdd( markerName, markerRange, writer ) {
 		// Do not convert if range is in graveyard or not in the document (e.g. in DocumentFragment).
 		if ( !markerRange.root.document || markerRange.root.rootName == '$graveyard' ) {
 			return;
 		}
 
-		this._view.change( writer => {
-			this.conversionApi.writer = writer;
+		this.conversionApi.writer = writer;
 
-			// In markers' case, event name == consumable name.
-			const eventName = 'addMarker:' + markerName;
+		// In markers' case, event name == consumable name.
+		const eventName = 'addMarker:' + markerName;
 
-			// When range is collapsed - fire single event with collapsed range in consumable.
-			if ( markerRange.isCollapsed ) {
-				const consumable = new Consumable();
-				consumable.add( markerRange, eventName );
+		// When range is collapsed - fire single event with collapsed range in consumable.
+		if ( markerRange.isCollapsed ) {
+			const consumable = new Consumable();
+			consumable.add( markerRange, eventName );
 
-				this.fire( eventName, {
-					markerName,
-					markerRange
-				}, consumable, this.conversionApi );
+			this.fire( eventName, {
+				markerName,
+				markerRange
+			}, consumable, this.conversionApi );
 
-				return;
-			}
+			return;
+		}
 
-			// Create consumable for each item in range.
-			const consumable = this._createConsumableForRange( markerRange, eventName );
+		// Create consumable for each item in range.
+		const consumable = this._createConsumableForRange( markerRange, eventName );
 
-			// Create separate event for each node in the range.
-			for ( const item of markerRange.getItems() ) {
-				// Do not fire event for already consumed items.
-				if ( !consumable.test( item, eventName ) ) {
-					continue;
-				}
+		// Create separate event for each node in the range.
+		for ( const item of markerRange.getItems() ) {
+			// Do not fire event for already consumed items.
+			if ( !consumable.test( item, eventName ) ) {
+				continue;
+			}
 
-				const data = { item, range: Range.createOn( item ), markerName, markerRange };
+			const data = { item, range: Range.createOn( item ), markerName, markerRange };
 
-				this.fire( eventName, data, consumable, this.conversionApi );
-			}
-		} );
+			this.fire( eventName, data, consumable, this.conversionApi );
+		}
 	}
 
 	/**
@@ -357,17 +343,15 @@ export default class ModelConversionDispatcher {
 	 * @param {String} markerName Marker name.
 	 * @param {module:engine/model/range~Range} markerRange Marker range.
 	 */
-	convertMarkerRemove( markerName, markerRange ) {
+	convertMarkerRemove( markerName, markerRange, writer ) {
 		// Do not convert if range is in graveyard or not in the document (e.g. in DocumentFragment).
 		if ( !markerRange.root.document || markerRange.root.rootName == '$graveyard' ) {
 			return;
 		}
 
-		this._view.change( writer => {
-			this.conversionApi.writer = writer;
+		this.conversionApi.writer = writer;
 
-			this.fire( 'removeMarker:' + markerName, { markerName, markerRange }, this.conversionApi );
-		} );
+		this.fire( 'removeMarker:' + markerName, { markerName, markerRange }, this.conversionApi );
 	}
 
 	/**

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

@@ -20,7 +20,7 @@ import ModelConversionDispatcher from '../conversion/modelconversiondispatcher';
 import ModelSelection from '../model/selection';
 import ModelDocumentFragment from '../model/documentfragment';
 
-import View from '../view/view';
+import ViewWriter from '../view/writer';
 import ViewConversionDispatcher from '../conversion/viewconversiondispatcher';
 import ViewSelection from '../view/selection';
 import ViewDocumentFragment from '../view/documentfragment';
@@ -160,7 +160,6 @@ setData._parse = parse;
  */
 export function stringify( node, selectionOrPositionOrRange = null ) {
 	const model = new Model();
-	const view = new View();
 	const mapper = new Mapper();
 	let selection, range;
 
@@ -194,7 +193,7 @@ export function stringify( node, selectionOrPositionOrRange = null ) {
 	// Setup model to view converter.
 	const viewDocumentFragment = new ViewDocumentFragment();
 	const viewSelection = new ViewSelection();
-	const modelToView = new ModelConversionDispatcher( model, view, { mapper, viewSelection } );
+	const modelToView = new ModelConversionDispatcher( model, { mapper, viewSelection } );
 
 	// Bind root elements.
 	mapper.bindElements( node.root, viewDocumentFragment );
@@ -218,11 +217,12 @@ export function stringify( node, selectionOrPositionOrRange = null ) {
 	} ) );
 
 	// Convert model to view.
-	modelToView.convertInsert( range );
+	const writer = new ViewWriter();
+	modelToView.convertInsert( range, writer );
 
 	// Convert model selection to view selection.
 	if ( selection ) {
-		modelToView.convertSelection( selection, [] );
+		modelToView.convertSelection( selection, writer );
 	}
 
 	// Parse view to data string.

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

@@ -513,7 +513,7 @@ describe( 'advanced-converters', () => {
 			] );
 
 			modelRoot.appendChildren( modelElement );
-			modelDispatcher.convertInsert( ModelRange.createIn( modelRoot ) );
+			modelDispatcher.convertInsert( ModelRange.createIn( modelRoot ), viewWriter );
 
 			expect( viewToString( viewRoot ) ).to.equal(
 				'<div>' +

+ 63 - 53
packages/ckeditor5-engine/tests/conversion/model-selection-to-view-converters.js

@@ -12,7 +12,6 @@ import View from '../../src/view/view';
 import ViewContainerElement from '../../src/view/containerelement';
 import ViewAttributeElement from '../../src/view/attributeelement';
 import ViewUIElement from '../../src/view/uielement';
-import ViewWriter from '../../src/view/writer';
 
 import Mapper from '../../src/conversion/mapper';
 import ModelConversionDispatcher from '../../src/conversion/modelconversiondispatcher';
@@ -59,7 +58,7 @@ describe( 'model-selection-to-view-converters', () => {
 
 		highlightDescriptor = { class: 'marker', priority: 1 };
 
-		dispatcher = new ModelConversionDispatcher( model, view, { mapper, viewSelection } );
+		dispatcher = new ModelConversionDispatcher( model, { mapper, viewSelection } );
 
 		dispatcher.on( 'insert:$text', insertText() );
 		dispatcher.on( 'attribute:bold', wrap( new ViewAttributeElement( 'strong' ) ) );
@@ -233,11 +232,11 @@ describe( 'model-selection-to-view-converters', () => {
 				viewRoot.removeChildren( 0, viewRoot.childCount );
 
 				// Convert model to view.
-				dispatcher.convertInsert( ModelRange.createIn( modelRoot ) );
-				dispatcher.convertMarkerAdd( marker.name, marker.getRange() );
-
-				const markers = Array.from( model.markers.getMarkersAtPosition( modelSelection.getFirstPosition() ) );
-				dispatcher.convertSelection( modelSelection, markers );
+				view.change( writer => {
+					dispatcher.convertInsert( ModelRange.createIn( modelRoot ), writer );
+					dispatcher.convertMarkerAdd( marker.name, marker.getRange(), writer );
+					dispatcher.convertSelection( modelSelection, writer );
+				} );
 
 				// Stringify view and check if it is same as expected.
 				expect( stringifyView( viewRoot, viewSelection, { showType: false } ) ).to.equal(
@@ -260,11 +259,11 @@ describe( 'model-selection-to-view-converters', () => {
 				viewRoot.removeChildren( 0, viewRoot.childCount );
 
 				// Convert model to view.
-				dispatcher.convertInsert( ModelRange.createIn( modelRoot ) );
-				dispatcher.convertMarkerAdd( marker.name, marker.getRange() );
-
-				const markers = Array.from( model.markers.getMarkersAtPosition( modelSelection.getFirstPosition() ) );
-				dispatcher.convertSelection( modelSelection, markers );
+				view.change( writer => {
+					dispatcher.convertInsert( ModelRange.createIn( modelRoot ), writer );
+					dispatcher.convertMarkerAdd( marker.name, marker.getRange(), writer );
+					dispatcher.convertSelection( modelSelection, writer );
+				} );
 
 				// Stringify view and check if it is same as expected.
 				expect( stringifyView( viewRoot, viewSelection, { showType: false } ) )
@@ -285,11 +284,11 @@ describe( 'model-selection-to-view-converters', () => {
 				viewRoot.removeChildren( 0, viewRoot.childCount );
 
 				// Convert model to view.
-				dispatcher.convertInsert( ModelRange.createIn( modelRoot ) );
-				dispatcher.convertMarkerAdd( marker.name, marker.getRange() );
-
-				const markers = Array.from( model.markers.getMarkersAtPosition( modelSelection.getFirstPosition() ) );
-				dispatcher.convertSelection( modelSelection, markers );
+				view.change( writer => {
+					dispatcher.convertInsert( ModelRange.createIn( modelRoot ), writer );
+					dispatcher.convertMarkerAdd( marker.name, marker.getRange(), writer );
+					dispatcher.convertSelection( modelSelection, writer );
+				} );
 
 				// Stringify view and check if it is same as expected.
 				expect( stringifyView( viewRoot, viewSelection, { showType: false } ) )
@@ -310,11 +309,11 @@ describe( 'model-selection-to-view-converters', () => {
 				viewRoot.removeChildren( 0, viewRoot.childCount );
 
 				// Convert model to view.
-				dispatcher.convertInsert( ModelRange.createIn( modelRoot ) );
-				dispatcher.convertMarkerAdd( marker.name, marker.getRange() );
-
-				const markers = Array.from( model.markers.getMarkersAtPosition( modelSelection.getFirstPosition() ) );
-				dispatcher.convertSelection( modelSelection, markers );
+				view.change( writer => {
+					dispatcher.convertInsert( ModelRange.createIn( modelRoot ), writer );
+					dispatcher.convertMarkerAdd( marker.name, marker.getRange(), writer );
+					dispatcher.convertSelection( modelSelection, writer );
+				} );
 
 				// Stringify view and check if it is same as expected.
 				expect( stringifyView( viewRoot, viewSelection, { showType: false } ) )
@@ -335,11 +334,11 @@ describe( 'model-selection-to-view-converters', () => {
 				viewRoot.removeChildren( 0, viewRoot.childCount );
 
 				// Convert model to view.
-				dispatcher.convertInsert( ModelRange.createIn( modelRoot ) );
-				dispatcher.convertMarkerAdd( marker.name, marker.getRange() );
-
-				const markers = Array.from( model.markers.getMarkersAtPosition( modelSelection.getFirstPosition() ) );
-				dispatcher.convertSelection( modelSelection, markers );
+				view.change( writer => {
+					dispatcher.convertInsert( ModelRange.createIn( modelRoot ), writer );
+					dispatcher.convertMarkerAdd( marker.name, marker.getRange(), writer );
+					dispatcher.convertSelection( modelSelection, writer );
+				} );
 
 				// Stringify view and check if it is same as expected.
 				expect( stringifyView( viewRoot, viewSelection, { showType: false } ) )
@@ -360,7 +359,9 @@ describe( 'model-selection-to-view-converters', () => {
 				modelSelection.setAttribute( 'bold', true );
 
 				// Convert model to view.
-				dispatcher.convertSelection( modelSelection, [] );
+				view.change( writer => {
+					dispatcher.convertSelection( modelSelection, writer );
+				} );
 
 				// Stringify view and check if it is same as expected.
 				expect( stringifyView( viewRoot, viewSelection, { showType: false } ) )
@@ -375,13 +376,15 @@ describe( 'model-selection-to-view-converters', () => {
 				modelSelection.setAttribute( 'bold', true );
 
 				// Convert model to view.
-				dispatcher.convertInsert( ModelRange.createIn( modelRoot ) );
+				view.change( writer => {
+					dispatcher.convertInsert( ModelRange.createIn( modelRoot ), writer );
 
-				// Add ui element to view.
-				const uiElement = new ViewUIElement( 'span' );
-				viewRoot.insertChildren( 1, uiElement );
+					// Add ui element to view.
+					const uiElement = new ViewUIElement( 'span' );
+					viewRoot.insertChildren( 1, uiElement );
 
-				dispatcher.convertSelection( modelSelection, [] );
+					dispatcher.convertSelection( modelSelection, writer );
+				} );
 
 				// Stringify view and check if it is same as expected.
 				expect( stringifyView( viewRoot, viewSelection, { showType: false } ) )
@@ -396,13 +399,14 @@ describe( 'model-selection-to-view-converters', () => {
 				modelSelection.setAttribute( 'bold', true );
 
 				// Convert model to view.
-				dispatcher.convertInsert( ModelRange.createIn( modelRoot ) );
-
-				// Add ui element to view.
-				const uiElement = new ViewUIElement( 'span' );
-				viewRoot.insertChildren( 1, uiElement );
+				view.change( writer => {
+					dispatcher.convertInsert( ModelRange.createIn( modelRoot ), writer );
 
-				dispatcher.convertSelection( modelSelection, [] );
+					// Add ui element to view.
+					const uiElement = new ViewUIElement( 'span' );
+					viewRoot.insertChildren( 1, uiElement, writer );
+					dispatcher.convertSelection( modelSelection, writer );
+				} );
 
 				// Stringify view and check if it is same as expected.
 				expect( stringifyView( viewRoot, viewSelection, { showType: false } ) )
@@ -479,10 +483,12 @@ describe( 'model-selection-to-view-converters', () => {
 					{ bold: 'true' }
 				);
 
-				const modelRange = ModelRange.createFromParentsAndOffsets( modelRoot, 1, modelRoot, 1 );
-				modelDoc.selection.setRanges( [ modelRange ] );
+				view.change( writer => {
+					const modelRange = ModelRange.createFromParentsAndOffsets( modelRoot, 1, modelRoot, 1 );
+					modelDoc.selection.setRanges( [ modelRange ] );
 
-				dispatcher.convertSelection( modelDoc.selection, [] );
+					dispatcher.convertSelection( modelDoc.selection, writer );
+				} );
 
 				expect( viewSelection.rangeCount ).to.equal( 1 );
 
@@ -501,14 +507,15 @@ describe( 'model-selection-to-view-converters', () => {
 					{ bold: 'true' }
 				);
 
-				// Remove <b></b> manually.
-				const viewWriter = new ViewWriter();
-				viewWriter.mergeAttributes( viewSelection.getFirstPosition() );
+				view.change( writer => {
+					// Remove <b></b> manually.
+					writer.mergeAttributes( viewSelection.getFirstPosition() );
 
-				const modelRange = ModelRange.createFromParentsAndOffsets( modelRoot, 1, modelRoot, 1 );
-				modelDoc.selection.setRanges( [ modelRange ] );
+					const modelRange = ModelRange.createFromParentsAndOffsets( modelRoot, 1, modelRoot, 1 );
+					modelDoc.selection.setRanges( [ modelRange ] );
 
-				dispatcher.convertSelection( modelDoc.selection, [] );
+					dispatcher.convertSelection( modelDoc.selection, writer );
+				} );
 
 				expect( viewSelection.rangeCount ).to.equal( 1 );
 
@@ -520,10 +527,11 @@ describe( 'model-selection-to-view-converters', () => {
 		describe( 'clearFakeSelection', () => {
 			it( 'should clear fake selection', () => {
 				dispatcher.on( 'selection', clearFakeSelection() );
-				viewSelection.setFake( true );
-
-				dispatcher.convertSelection( modelSelection, [] );
 
+				view.change( writer => {
+					viewSelection.setFake( true );
+					dispatcher.convertSelection( modelSelection, writer );
+				} );
 				expect( viewSelection.isFake ).to.be.false;
 			} );
 		} );
@@ -740,8 +748,10 @@ describe( 'model-selection-to-view-converters', () => {
 		viewRoot.removeChildren( 0, viewRoot.childCount );
 
 		// Convert model to view.
-		dispatcher.convertInsert( ModelRange.createIn( modelRoot ) );
-		dispatcher.convertSelection( modelSelection, [] );
+		view.change( writer => {
+			dispatcher.convertInsert( ModelRange.createIn( modelRoot ), writer );
+			dispatcher.convertSelection( modelSelection, writer );
+		} );
 
 		// Stringify view and check if it is same as expected.
 		expect( stringifyView( viewRoot, viewSelection, { showType: false } ) ).to.equal( '<div>' + expectedView + '</div>' );

+ 20 - 8
packages/ckeditor5-engine/tests/conversion/modelconversiondispatcher.js

@@ -20,7 +20,7 @@ describe( 'ModelConversionDispatcher', () => {
 		model = new Model();
 		view = new View();
 		doc = model.document;
-		dispatcher = new ModelConversionDispatcher( model, view );
+		dispatcher = new ModelConversionDispatcher( model );
 		root = doc.createRoot();
 
 		differStub = {
@@ -33,7 +33,7 @@ describe( 'ModelConversionDispatcher', () => {
 	describe( 'constructor()', () => {
 		it( 'should create ModelConversionDispatcher with given api', () => {
 			const apiObj = {};
-			const dispatcher = new ModelConversionDispatcher( model, view, { apiObj } );
+			const dispatcher = new ModelConversionDispatcher( model, { apiObj } );
 
 			expect( dispatcher.conversionApi.apiObj ).to.equal( apiObj );
 		} );
@@ -48,7 +48,9 @@ describe( 'ModelConversionDispatcher', () => {
 
 			differStub.getChanges = () => [ { type: 'insert', position, length: 1 } ];
 
-			dispatcher.convertChanges( differStub );
+			view.change( writer => {
+				dispatcher.convertChanges( differStub, writer );
+			} );
 
 			expect( dispatcher.convertInsert.calledOnce ).to.be.true;
 			expect( dispatcher.convertInsert.firstCall.args[ 0 ].isEqual( range ) ).to.be.true;
@@ -61,7 +63,9 @@ describe( 'ModelConversionDispatcher', () => {
 
 			differStub.getChanges = () => [ { type: 'remove', position, length: 2, name: '$text' } ];
 
-			dispatcher.convertChanges( differStub );
+			view.change( writer => {
+				dispatcher.convertChanges( differStub, writer );
+			} );
 
 			expect( dispatcher.convertRemove.calledWith( position, 2, '$text' ) ).to.be.true;
 		} );
@@ -76,7 +80,9 @@ describe( 'ModelConversionDispatcher', () => {
 				{ type: 'attribute', position, range, attributeKey: 'key', attributeOldValue: null, attributeNewValue: 'foo' }
 			];
 
-			dispatcher.convertChanges( differStub );
+			view.change( writer => {
+				dispatcher.convertChanges( differStub, writer );
+			} );
 
 			expect( dispatcher.convertAttribute.calledWith( range, 'key', null, 'foo' ) ).to.be.true;
 		} );
@@ -96,7 +102,9 @@ describe( 'ModelConversionDispatcher', () => {
 				{ type: 'insert', position, length: 3 },
 			];
 
-			dispatcher.convertChanges( differStub );
+			view.change( writer => {
+				dispatcher.convertChanges( differStub, writer );
+			} );
 
 			expect( dispatcher.convertInsert.calledTwice ).to.be.true;
 			expect( dispatcher.convertRemove.calledOnce ).to.be.true;
@@ -114,7 +122,9 @@ describe( 'ModelConversionDispatcher', () => {
 				{ name: 'bar', range: barRange }
 			];
 
-			dispatcher.convertChanges( differStub );
+			view.change( writer => {
+				dispatcher.convertChanges( differStub, writer );
+			} );
 
 			expect( dispatcher.convertMarkerAdd.calledWith( 'foo', fooRange ) );
 			expect( dispatcher.convertMarkerAdd.calledWith( 'bar', barRange ) );
@@ -131,7 +141,9 @@ describe( 'ModelConversionDispatcher', () => {
 				{ name: 'bar', range: barRange }
 			];
 
-			dispatcher.convertChanges( differStub );
+			view.change( writer => {
+				dispatcher.convertChanges( differStub, writer );
+			} );
 
 			expect( dispatcher.convertMarkerRemove.calledWith( 'foo', fooRange ) );
 			expect( dispatcher.convertMarkerRemove.calledWith( 'bar', barRange ) );