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

Moved model.change() block out of upcast dispatcher.

Szymon Kupś 8 лет назад
Родитель
Сommit
2b10f9c526

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

@@ -87,7 +87,7 @@ export default class DataController {
 		 * @readonly
 		 * @member {module:engine/conversion/upcastdispatcher~UpcastDispatcher}
 		 */
-		this.upcastDispatcher = new UpcastDispatcher( this.model, {
+		this.upcastDispatcher = new UpcastDispatcher( {
 			schema: model.schema
 		} );
 
@@ -227,7 +227,9 @@ export default class DataController {
 	 * @returns {module:engine/model/documentfragment~DocumentFragment} Output document fragment.
 	 */
 	toModel( viewElementOrFragment, context = '$root' ) {
-		return this.upcastDispatcher.convert( viewElementOrFragment, context );
+		return this.model.change( writer => {
+			return this.upcastDispatcher.convert( viewElementOrFragment, writer, context );
+		} );
 	}
 
 	/**

+ 38 - 48
packages/ckeditor5-engine/src/conversion/upcastdispatcher.js

@@ -97,19 +97,10 @@ export default class UpcastDispatcher {
 	 * Creates a `UpcastDispatcher` that operates using passed API.
 	 *
 	 * @see module:engine/conversion/upcastdispatcher~ViewConversionApi
-	 * @param {module:engine/model/model~Model} model Data model.
 	 * @param {Object} [conversionApi] Additional properties for interface that will be passed to events fired
 	 * by `UpcastDispatcher`.
 	 */
-	constructor( model, conversionApi = {} ) {
-		/**
-		 * Data model.
-		 *
-		 * @private
-		 * @type {module:engine/model/model~Model}
-		 */
-		this._model = model;
-
+	constructor( conversionApi = {} ) {
 		/**
 		 * List of elements that will be checked after conversion process and if element in the list will be empty it
 		 * will be removed from conversion result.
@@ -153,62 +144,61 @@ export default class UpcastDispatcher {
 	 * @fires documentFragment
 	 * @param {module:engine/view/documentfragment~DocumentFragment|module:engine/view/element~Element} viewItem
 	 * Part of the view to be converted.
+	 * @param {module:engine/model/writer~Writer} writer Instance of model writer.
 	 * @param {module:engine/model/schema~SchemaContextDefinition} [context=['$root']] Elements will be converted according to this context.
 	 * @returns {module:engine/model/documentfragment~DocumentFragment} Model data that is a result of the conversion process
 	 * wrapped in `DocumentFragment`. Converted marker elements will be set as that document fragment's
 	 * {@link module:engine/model/documentfragment~DocumentFragment#markers static markers map}.
 	 */
-	convert( viewItem, context = [ '$root' ] ) {
-		return this._model.change( writer => {
-			this.fire( 'viewCleanup', viewItem );
+	convert( viewItem, writer, context = [ '$root' ] ) {
+		this.fire( 'viewCleanup', viewItem );
 
-			// Create context tree and set position in the top element.
-			// Items will be converted according to this position.
-			this._modelCursor = createContextTree( context, writer );
+		// Create context tree and set position in the top element.
+		// Items will be converted according to this position.
+		this._modelCursor = createContextTree( context, writer );
 
-			// Store writer in conversion as a conversion API
-			// to be sure that conversion process will use the same batch.
-			this.conversionApi.writer = writer;
+		// 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 );
+		// Create consumable values list for conversion process.
+		this.conversionApi.consumable = ViewConsumable.createFrom( viewItem );
 
-			// Custom data stored by converter for conversion process.
-			this.conversionApi.store = {};
+		// Custom data stored by converter for conversion process.
+		this.conversionApi.store = {};
 
-			// Do the conversion.
-			const { modelRange } = this._convertItem( viewItem, this._modelCursor );
+		// Do the conversion.
+		const { modelRange } = this._convertItem( viewItem, this._modelCursor );
 
-			// Conversion result is always a document fragment so let's create this fragment.
-			const documentFragment = writer.createDocumentFragment();
+		// Conversion result is always a document fragment so let's create this fragment.
+		const documentFragment = writer.createDocumentFragment();
 
-			// When there is a conversion result.
-			if ( modelRange ) {
-				// Remove all empty elements that was added to #_removeIfEmpty list.
-				this._removeEmptyElements();
-
-				// Move all items that was converted to context tree to document fragment.
-				for ( const item of Array.from( this._modelCursor.parent.getChildren() ) ) {
-					writer.append( item, documentFragment );
-				}
+		// When there is a conversion result.
+		if ( modelRange ) {
+			// Remove all empty elements that was added to #_removeIfEmpty list.
+			this._removeEmptyElements();
 
-				// Extract temporary markers elements from model and set as static markers collection.
-				documentFragment.markers = extractMarkersFromModelFragment( documentFragment, writer );
+			// Move all items that was converted to context tree to document fragment.
+			for ( const item of Array.from( this._modelCursor.parent.getChildren() ) ) {
+				writer.append( item, documentFragment );
 			}
 
-			// Clear context position.
-			this._modelCursor = null;
+			// Extract temporary markers elements from model and set as static markers collection.
+			documentFragment.markers = extractMarkersFromModelFragment( documentFragment, writer );
+		}
+
+		// Clear context position.
+		this._modelCursor = null;
 
-			// Clear split elements.
-			this._removeIfEmpty.clear();
+		// Clear split elements.
+		this._removeIfEmpty.clear();
 
-			// Clear conversion API.
-			this.conversionApi.writer = null;
-			this.conversionApi.store = null;
+		// Clear conversion API.
+		this.conversionApi.writer = null;
+		this.conversionApi.store = null;
 
-			// Return fragment as conversion result.
-			return documentFragment;
-		} );
+		// Return fragment as conversion result.
+		return documentFragment;
 	}
 
 	/**

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

@@ -285,7 +285,8 @@ export function parse( data, schema, options = {} ) {
 	}
 
 	// Set up upcast dispatcher.
-	const upcastDispatcher = new UpcastDispatcher( new Model(), { schema, mapper } );
+	const modelController = new Model();
+	const upcastDispatcher = new UpcastDispatcher( { schema, mapper } );
 
 	upcastDispatcher.on( 'documentFragment', convertToModelFragment() );
 	upcastDispatcher.on( 'element:model-text-with-attributes', convertToModelText( true ) );
@@ -295,7 +296,9 @@ export function parse( data, schema, options = {} ) {
 	upcastDispatcher.isDebug = true;
 
 	// Convert view to model.
-	let model = upcastDispatcher.convert( viewDocumentFragment.root, options.context || '$root' );
+	let model = modelController.change(
+		writer => upcastDispatcher.convert( viewDocumentFragment.root, writer, options.context || '$root' )
+	);
 
 	mapper.bindElements( model, viewDocumentFragment.root );
 

+ 7 - 8
packages/ckeditor5-engine/tests/conversion/two-way-converters.js

@@ -21,7 +21,7 @@ import { stringify as viewStringify, parse as viewParse } from '../../src/dev-ut
 import { stringify as modelStringify } from '../../src/dev-utils/model';
 
 describe( 'two-way-converters', () => {
-	let viewDispatcher, model, schema, conversion, modelRoot, viewRoot;
+	let upcastDispatcher, model, schema, conversion, modelRoot, viewRoot;
 
 	beforeEach( () => {
 		model = new Model();
@@ -45,13 +45,13 @@ describe( 'two-way-converters', () => {
 			inheritAllFrom: '$block'
 		} );
 
-		viewDispatcher = new UpcastDispatcher( model, { schema } );
-		viewDispatcher.on( 'text', convertText() );
-		viewDispatcher.on( 'element', convertToModelFragment(), { priority: 'lowest' } );
-		viewDispatcher.on( 'documentFragment', convertToModelFragment(), { priority: 'lowest' } );
+		upcastDispatcher = new UpcastDispatcher( { schema } );
+		upcastDispatcher.on( 'text', convertText() );
+		upcastDispatcher.on( 'element', convertToModelFragment(), { priority: 'lowest' } );
+		upcastDispatcher.on( 'documentFragment', convertToModelFragment(), { priority: 'lowest' } );
 
 		conversion = new Conversion();
-		conversion.register( 'upcast', [ viewDispatcher ] );
+		conversion.register( 'upcast', [ upcastDispatcher ] );
 		conversion.register( 'downcast', [ controller.downcastDispatcher ] );
 	} );
 
@@ -527,9 +527,8 @@ describe( 'two-way-converters', () => {
 	function loadData( input ) {
 		const parsedView = viewParse( input );
 
-		const convertedModel = viewDispatcher.convert( parsedView );
-
 		model.change( writer => {
+			const convertedModel = upcastDispatcher.convert( parsedView, writer );
 			writer.remove( ModelRange.createFromParentsAndOffsets( modelRoot, 0, modelRoot, modelRoot.maxOffset ) );
 			writer.insert( convertedModel, modelRoot, 0 );
 		} );

+ 19 - 20
packages/ckeditor5-engine/tests/conversion/upcast-converters.js

@@ -27,7 +27,7 @@ import {
 import { stringify } from '../../src/dev-utils/model';
 
 describe( 'upcast-helpers', () => {
-	let dispatcher, model, schema, conversion;
+	let upcastDispatcher, model, schema, conversion;
 
 	beforeEach( () => {
 		model = new Model();
@@ -50,13 +50,13 @@ describe( 'upcast-helpers', () => {
 			allowAttributes: [ 'bold' ]
 		} );
 
-		dispatcher = new UpcastDispatcher( model, { schema } );
-		dispatcher.on( 'text', convertText() );
-		dispatcher.on( 'element', convertToModelFragment(), { priority: 'lowest' } );
-		dispatcher.on( 'documentFragment', convertToModelFragment(), { priority: 'lowest' } );
+		upcastDispatcher = new UpcastDispatcher( { schema } );
+		upcastDispatcher.on( 'text', convertText() );
+		upcastDispatcher.on( 'element', convertToModelFragment(), { priority: 'lowest' } );
+		upcastDispatcher.on( 'documentFragment', convertToModelFragment(), { priority: 'lowest' } );
 
 		conversion = new Conversion();
-		conversion.register( 'upcast', [ dispatcher ] );
+		conversion.register( 'upcast', [ upcastDispatcher ] );
 	} );
 
 	describe( 'upcastElementToElement', () => {
@@ -600,18 +600,18 @@ describe( 'upcast-helpers', () => {
 	} );
 
 	function expectResult( viewToConvert, modelString, marker ) {
-		const model = dispatcher.convert( viewToConvert );
+		const conversionResult = model.change( writer => upcastDispatcher.convert( viewToConvert, writer ) );
 
 		if ( marker ) {
-			expect( model.markers.has( marker.name ) ).to.be.true;
+			expect( conversionResult.markers.has( marker.name ) ).to.be.true;
 
-			const convertedMarker = model.markers.get( marker.name );
+			const convertedMarker = conversionResult.markers.get( marker.name );
 
 			expect( convertedMarker.start.path ).to.deep.equal( marker.start );
 			expect( convertedMarker.end.path ).to.deep.equal( marker.end );
 		}
 
-		expect( stringify( model ) ).to.equal( modelString );
+		expect( stringify( conversionResult ) ).to.equal( modelString );
 	}
 } );
 
@@ -627,7 +627,7 @@ describe( 'upcast-converters', () => {
 
 		context = [ '$root' ];
 
-		dispatcher = new UpcastDispatcher( model, { schema } );
+		dispatcher = new UpcastDispatcher( { schema } );
 	} );
 
 	describe( 'convertText()', () => {
@@ -636,7 +636,7 @@ describe( 'upcast-converters', () => {
 
 			dispatcher.on( 'text', convertText() );
 
-			const conversionResult = dispatcher.convert( viewText );
+			const conversionResult = model.change( writer => dispatcher.convert( viewText, writer ) );
 
 			expect( conversionResult ).to.be.instanceof( ModelDocumentFragment );
 			expect( conversionResult.getChild( 0 ) ).to.be.instanceof( ModelText );
@@ -658,7 +658,7 @@ describe( 'upcast-converters', () => {
 				}
 			} );
 
-			const conversionResult = dispatcher.convert( viewText, context );
+			const conversionResult = model.change( writer => dispatcher.convert( viewText, writer, context ) );
 
 			expect( conversionResult ).to.be.instanceof( ModelDocumentFragment );
 			expect( conversionResult.getChild( 0 ) ).to.be.instanceof( ModelText );
@@ -674,13 +674,12 @@ describe( 'upcast-converters', () => {
 
 			const viewText = new ViewText( 'foobar' );
 			dispatcher.on( 'text', convertText() );
-
-			let conversionResult = dispatcher.convert( viewText, context );
+			let conversionResult = model.change( writer => dispatcher.convert( viewText, writer, context ) );
 
 			expect( conversionResult ).to.be.instanceof( ModelDocumentFragment );
 			expect( conversionResult.childCount ).to.equal( 0 );
 
-			conversionResult = dispatcher.convert( viewText, [ '$block' ] );
+			conversionResult = model.change( writer => dispatcher.convert( viewText, writer, [ '$block' ] ) );
 
 			expect( conversionResult ).to.be.instanceof( ModelDocumentFragment );
 			expect( conversionResult.childCount ).to.equal( 1 );
@@ -693,7 +692,7 @@ describe( 'upcast-converters', () => {
 
 			dispatcher.on( 'text', convertText() );
 
-			const conversionResult = dispatcher.convert( viewText, context );
+			const conversionResult = model.change( writer => dispatcher.convert( viewText, writer, context ) );
 
 			expect( conversionResult ).to.be.instanceof( ModelDocumentFragment );
 			expect( conversionResult.getChild( 0 ) ).to.be.instanceof( ModelText );
@@ -714,7 +713,7 @@ describe( 'upcast-converters', () => {
 			dispatcher.on( 'element', convertToModelFragment() );
 			dispatcher.on( 'documentFragment', convertToModelFragment() );
 
-			const conversionResult = dispatcher.convert( viewFragment, context );
+			const conversionResult = model.change( writer => dispatcher.convert( viewFragment, writer, context ) );
 
 			expect( conversionResult ).to.be.instanceof( ModelDocumentFragment );
 			expect( conversionResult.maxOffset ).to.equal( 6 );
@@ -741,7 +740,7 @@ describe( 'upcast-converters', () => {
 				}
 			} );
 
-			const conversionResult = dispatcher.convert( viewP, context );
+			const conversionResult = model.change( writer => dispatcher.convert( viewP, writer, context ) );
 
 			expect( conversionResult ).to.be.instanceof( ModelDocumentFragment );
 			expect( conversionResult.getChild( 0 ) ).to.be.instanceof( ModelElement );
@@ -775,7 +774,7 @@ describe( 'upcast-converters', () => {
 				spy();
 			} );
 
-			dispatcher.convert( view );
+			model.change( writer => dispatcher.convert( view, writer ) );
 
 			sinon.assert.calledTwice( spy );
 		} );

+ 28 - 26
packages/ckeditor5-engine/tests/conversion/upcastdispatcher.js

@@ -30,7 +30,7 @@ describe( 'UpcastDispatcher', () => {
 	describe( 'constructor()', () => {
 		it( 'should create UpcastDispatcher with passed api', () => {
 			const apiObj = {};
-			const dispatcher = new UpcastDispatcher( model, { apiObj } );
+			const dispatcher = new UpcastDispatcher( { apiObj } );
 
 			expect( dispatcher.conversionApi.apiObj ).to.equal( apiObj );
 			expect( dispatcher.conversionApi ).to.have.property( 'convertItem' ).that.is.instanceof( Function );
@@ -39,7 +39,7 @@ describe( 'UpcastDispatcher', () => {
 		} );
 
 		it( 'should have properties', () => {
-			const dispatcher = new UpcastDispatcher( model );
+			const dispatcher = new UpcastDispatcher();
 
 			expect( dispatcher._removeIfEmpty ).to.instanceof( Set );
 		} );
@@ -49,7 +49,7 @@ describe( 'UpcastDispatcher', () => {
 		let dispatcher;
 
 		beforeEach( () => {
-			dispatcher = new UpcastDispatcher( model );
+			dispatcher = new UpcastDispatcher();
 		} );
 
 		it( 'should create api for current conversion process', () => {
@@ -100,7 +100,7 @@ describe( 'UpcastDispatcher', () => {
 				spy();
 			} );
 
-			dispatcher.convert( viewElement );
+			model.change( writer => dispatcher.convert( viewElement, writer ) );
 
 			// To be sure that both converters was called.
 			sinon.assert.calledTwice( spy );
@@ -115,7 +115,7 @@ describe( 'UpcastDispatcher', () => {
 			sinon.spy( dispatcher, 'fire' );
 
 			const viewP = new ViewContainerElement( 'p' );
-			dispatcher.convert( viewP );
+			model.change( writer => dispatcher.convert( viewP, writer ) );
 
 			expect( dispatcher.fire.calledWith( 'viewCleanup', viewP ) ).to.be.true;
 		} );
@@ -127,9 +127,11 @@ describe( 'UpcastDispatcher', () => {
 
 			sinon.spy( dispatcher, 'fire' );
 
-			dispatcher.convert( viewText );
-			dispatcher.convert( viewElement );
-			dispatcher.convert( viewFragment );
+			model.change( writer => {
+				dispatcher.convert( viewText, writer );
+				dispatcher.convert( viewElement, writer );
+				dispatcher.convert( viewFragment, writer );
+			} );
 
 			expect( dispatcher.fire.calledWith( 'text' ) ).to.be.true;
 			expect( dispatcher.fire.calledWith( 'element:p' ) ).to.be.true;
@@ -163,7 +165,7 @@ describe( 'UpcastDispatcher', () => {
 				data.modelRange = ModelRange.createOn( text );
 			} );
 
-			const conversionResult = dispatcher.convert( viewText );
+			const conversionResult = model.change( writer => dispatcher.convert( viewText, writer ) );
 
 			// Check conversion result.
 			// Result should be wrapped in document fragment.
@@ -201,7 +203,7 @@ describe( 'UpcastDispatcher', () => {
 			} );
 
 			// Use `additionalData` parameter to check if it was passed to the event.
-			const conversionResult = dispatcher.convert( viewElement );
+			const conversionResult = model.change( writer => dispatcher.convert( viewElement, writer ) );
 
 			// Check conversion result.
 			// Result should be wrapped in document fragment.
@@ -237,7 +239,7 @@ describe( 'UpcastDispatcher', () => {
 				data.modelRange = ModelRange.createOn( text );
 			} );
 
-			const conversionResult = dispatcher.convert( viewFragment );
+			const conversionResult = model.change( writer => dispatcher.convert( viewFragment, writer ) );
 
 			// Check conversion result.
 			expect( conversionResult ).to.be.instanceof( ModelDocumentFragment );
@@ -288,7 +290,7 @@ describe( 'UpcastDispatcher', () => {
 				spy();
 			} );
 
-			const result = dispatcher.convert( viewElement );
+			const result = model.change( writer => dispatcher.convert( viewElement, writer ) );
 
 			// Empty split elements should be removed and we should have the following result:
 			// [<p></p>]<p>foo</p>
@@ -322,7 +324,7 @@ describe( 'UpcastDispatcher', () => {
 				data.modelRange = ModelRange.createIn( data.modelCursor.parent );
 			} );
 
-			const conversionResult = dispatcher.convert( viewFragment );
+			const conversionResult = model.change( writer => dispatcher.convert( viewFragment, writer ) );
 
 			expect( conversionResult.markers.size ).to.equal( 2 );
 
@@ -335,7 +337,7 @@ describe( 'UpcastDispatcher', () => {
 		} );
 
 		it( 'should convert according to given context', () => {
-			dispatcher = new UpcastDispatcher( model, { schema: model.schema } );
+			dispatcher = new UpcastDispatcher( { schema: model.schema } );
 
 			const spy = sinon.spy();
 			const viewElement = new ViewContainerElement( 'third' );
@@ -358,12 +360,12 @@ describe( 'UpcastDispatcher', () => {
 			} );
 
 			// Default context $root.
-			dispatcher.convert( viewElement );
+			model.change( writer => dispatcher.convert( viewElement, writer ) );
 			sinon.assert.calledOnce( spy );
 			expect( checkChildResult ).to.false;
 
 			// SchemaDefinition as context.
-			dispatcher.convert( viewElement, [ 'first' ] );
+			model.change( writer => dispatcher.convert( viewElement, writer, [ 'first' ] ) );
 			sinon.assert.calledTwice( spy );
 			expect( checkChildResult ).to.false;
 
@@ -374,7 +376,7 @@ describe( 'UpcastDispatcher', () => {
 				] )
 			] );
 
-			dispatcher.convert( viewElement, new ModelPosition( fragment, [ 0, 0, 0 ] ) );
+			model.change( writer => dispatcher.convert( viewElement, writer, new ModelPosition( fragment, [ 0, 0, 0 ] ) ) );
 			sinon.assert.calledThrice( spy );
 			expect( checkChildResult ).to.true;
 		} );
@@ -397,7 +399,7 @@ describe( 'UpcastDispatcher', () => {
 			// Put nodes to documentFragment, this will mock root element and makes possible to create range on them.
 			rootMock = new ModelDocumentFragment( [ modelP, modelText ] );
 
-			dispatcher = new UpcastDispatcher( model, { schema: model.schema } );
+			dispatcher = new UpcastDispatcher( { schema: model.schema } );
 
 			dispatcher.on( 'element:p', ( evt, data ) => {
 				spyP();
@@ -456,7 +458,7 @@ describe( 'UpcastDispatcher', () => {
 					expect( textResult.modelCursor.path ).to.deep.equal( [ 7 ] );
 				} );
 
-				dispatcher.convert( new ViewDocumentFragment() );
+				model.change( writer => dispatcher.convert( new ViewDocumentFragment(), writer ) );
 
 				expect( spy.calledOnce ).to.be.true;
 				expect( spyP.calledOnce ).to.be.true;
@@ -471,7 +473,7 @@ describe( 'UpcastDispatcher', () => {
 					expect( conversionApi.convertItem( viewNull, data.modelCursor ).modelRange ).to.equal( null );
 				} );
 
-				dispatcher.convert( new ViewDocumentFragment() );
+				model.change( writer => dispatcher.convert( new ViewDocumentFragment(), writer ) );
 
 				expect( spy.calledOnce ).to.be.true;
 				expect( spyNull.calledOnce ).to.be.true;
@@ -485,7 +487,7 @@ describe( 'UpcastDispatcher', () => {
 				} );
 
 				expect( () => {
-					dispatcher.convert( new ViewDocumentFragment() );
+					model.change( writer => dispatcher.convert( new ViewDocumentFragment(), writer ) );
 				} ).to.throw( CKEditorError, /^view-conversion-dispatcher-incorrect-result/ );
 
 				expect( spy.calledOnce ).to.be.true;
@@ -513,7 +515,7 @@ describe( 'UpcastDispatcher', () => {
 					expect( result.modelCursor.path ).to.deep.equal( [ 7 ] );
 				} );
 
-				dispatcher.convert( new ViewDocumentFragment( [ viewP, viewText ] ) );
+				model.change( writer => dispatcher.convert( new ViewDocumentFragment( [ viewP, viewText ] ), writer ) );
 
 				expect( spy.calledOnce ).to.be.true;
 				expect( spyP.calledOnce ).to.be.true;
@@ -546,7 +548,7 @@ describe( 'UpcastDispatcher', () => {
 					spy();
 				} );
 
-				dispatcher.convert( new ViewDocumentFragment() );
+				model.change( writer => dispatcher.convert( new ViewDocumentFragment(), writer ) );
 				sinon.assert.calledOnce( spy );
 			} );
 
@@ -583,7 +585,7 @@ describe( 'UpcastDispatcher', () => {
 					spy();
 				} );
 
-				dispatcher.convert( new ViewDocumentFragment() );
+				model.change( writer => dispatcher.convert( new ViewDocumentFragment(), writer ) );
 				sinon.assert.calledOnce( spy );
 			} );
 
@@ -603,7 +605,7 @@ describe( 'UpcastDispatcher', () => {
 					spy();
 				} );
 
-				dispatcher.convert( new ViewDocumentFragment() );
+				model.change( writer => dispatcher.convert( new ViewDocumentFragment(), writer ) );
 				sinon.assert.calledOnce( spy );
 			} );
 
@@ -622,7 +624,7 @@ describe( 'UpcastDispatcher', () => {
 					spy();
 				} );
 
-				dispatcher.convert( new ViewDocumentFragment(), [ '$root', 'paragraph' ] );
+				model.change( writer => dispatcher.convert( new ViewDocumentFragment(), writer, [ '$root', 'paragraph' ] ) );
 				sinon.assert.calledOnce( spy );
 			} );
 		} );