ソースを参照

Aligned engine files to latest ViewConversionDispatcher#convert() API.

Oskar Wróbel 8 年 前
コミット
9f55b94d0e

+ 19 - 56
packages/ckeditor5-engine/src/controller/datacontroller.js

@@ -22,15 +22,12 @@ import ViewDocumentFragment from '../view/documentfragment';
 
 import ModelRange from '../model/range';
 import ModelPosition from '../model/position';
-import ModelTreeWalker from '../model/treewalker';
 
 import insertContent from './insertcontent';
 import deleteContent from './deletecontent';
 import modifySelection from './modifyselection';
 import getSelectedContent from './getselectedcontent';
 
-import { remove } from '../model/writer';
-
 /**
  * Controller for the data pipeline. The data pipeline controls how data is retrieved from the document
  * and set inside it. Hence, the controller features two methods which allow to {@link ~DataController#get get}
@@ -205,8 +202,7 @@ export default class DataController {
 			this.model.selection.clearAttributes();
 
 			// Parse data to model and extract markers from parsed document fragment.
-			const documentFragment = this.parse( data );
-			const markersData = extractMarkersFromModelFragment( documentFragment );
+			const { modelItem, markersData } = this.parse( data );
 
 			// Initial batch should be ignored by features like undo, etc.
 			const batch = this.model.batch( 'transparent' );
@@ -214,15 +210,15 @@ export default class DataController {
 			// Replace current editor data by the new one.
 			batch
 				.remove( ModelRange.createIn( modelRoot ) )
-				.insert( ModelPosition.createAt( modelRoot, 0 ), documentFragment );
+				.insert( ModelPosition.createAt( modelRoot, 0 ), modelItem );
 
 			// Add markers to the document.
 			for ( const marker of markersData ) {
 				const markerName = marker[ 0 ];
-				const markerData = marker[ 1 ];
+				const markerPositionData = marker[ 1 ];
 				const range = new ModelRange(
-					new ModelPosition( modelRoot, markerData.startPath ),
-					markerData.endPath ? new ModelPosition( modelRoot, markerData.endPath ) : null
+					new ModelPosition( modelRoot, markerPositionData.startPath ),
+					markerPositionData.endPath ? new ModelPosition( modelRoot, markerPositionData.endPath ) : null
 				);
 
 				batch.setMarker( markerName, range );
@@ -238,7 +234,7 @@ export default class DataController {
 	 * @param {String} data Data to parse.
 	 * @param {String} [context='$root'] Base context in which the view will be converted to the model. See:
 	 * {@link module:engine/conversion/viewconversiondispatcher~ViewConversionDispatcher#convert}.
-	 * @returns {module:engine/model/documentfragment~DocumentFragment} Parsed data.
+	 * @returns {ParsedModel} Result of parsing view to model.
 	 */
 	parse( data, context = '$root' ) {
 		// data -> view
@@ -258,10 +254,15 @@ export default class DataController {
 	 * Element or document fragment which content will be converted.
 	 * @param {String} [context='$root'] Base context in which the view will be converted to the model. See:
 	 * {@link module:engine/conversion/viewconversiondispatcher~ViewConversionDispatcher#convert}.
-	 * @returns {module:engine/model/documentfragment~DocumentFragment} Output document fragment.
+	 * @returns {ParsedModel} Result of parsing view to model.
 	 */
 	toModel( viewElementOrFragment, context = '$root' ) {
-		return this.viewToModel.convert( viewElementOrFragment, { context: [ context ] } );
+		const conversionData = this.viewToModel.convert( viewElementOrFragment, { context: [ context ] } );
+
+		return {
+			modelItem: conversionData.conversionResult,
+			markersData: conversionData.markersData
+		};
 	}
 
 	/**
@@ -283,7 +284,6 @@ export default class DataController {
 	 * changes will be added to a new batch.
 	 */
 	insertContent( content, selection, batch ) {
-		extractMarkersFromModelFragment( content );
 		this.fire( 'insertContent', { content, selection, batch } );
 	}
 
@@ -336,49 +336,12 @@ export default class DataController {
 
 mix( DataController, EmitterMixin );
 
-// Traverses given DocumentFragment and searches elements which marks marker range. Found element is removed from
-// DocumentFragment but path of this element is stored in a Map which is then returned.
-//
-// @param {module:engine/view/documentfragment~DocumentFragment} documentFragment Model DocumentFragment.
-// @returns {Map} Map with markers data.
-function extractMarkersFromModelFragment( documentFragment ) {
-	const markerStamps = new Set();
-	const result = new Map();
-
-	// Create ModelTreeWalker.
-	const walker = new ModelTreeWalker( {
-		startPosition: ModelPosition.createAt( documentFragment, 0 ),
-		ignoreElementEnd: true,
-		shallow: false
-	} );
-
-	// Walk through DocumentFragment and collect marker elements.
-	for ( const value of walker ) {
-		// Check if current element is a marker stamp.
-		if ( value.item.name == '$marker' ) {
-			markerStamps.add( value.item );
-		}
-	}
-
-	// Walk through collected marker elements store its path and remove its from the DocumentFragment.
-	for ( const stamp of markerStamps ) {
-		const markerName = stamp.getAttribute( 'data-name' );
-		const currentPosition = ModelPosition.createBefore( stamp );
-
-		// When marker of given name is not stored it means that we have found the beginning of the range.
-		if ( !result.has( markerName ) ) {
-			result.set( markerName, { startPath: currentPosition.path } );
-		// Otherwise is means that we have found end of the marker range.
-		} else {
-			result.get( markerName ).endPath = currentPosition.path;
-		}
-
-		// Remove marker stamp element from DocumentFragment.
-		remove( ModelRange.createOn( stamp ) );
-	}
-
-	return result;
-}
+/**
+ * @typedef {Object} ParsedModel Result of parsing view to model.
+ * @property {module:engine/model/documentfragment~DocumentFragment|module:engine/model/node~Node} modelItem
+ * Fragment of parsed model.
+ * @property {Map} markersData List of parsed marker stamps in format [ 'markerName', { startPath: [ 1, 1 ], endPath: [ 1, 3 ] } ]
+ */
 
 /**
  * Event fired when {@link #insertContent} method is called.

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

@@ -296,7 +296,9 @@ export function parse( data, schema, options = {} ) {
 	viewToModel.on( 'text', convertToModelText() );
 
 	// Convert view to model.
-	let model = viewToModel.convert( viewDocumentFragment.root, { context: options.context || [ '$root' ] } );
+	let model = viewToModel.convert( viewDocumentFragment.root, {
+		context: options.context || [ '$root' ]
+	} ).conversionResult;
 
 	// If root DocumentFragment contains only one element - return that element.
 	if ( model.is( 'documentFragment' ) && model.childCount == 1 ) {

+ 50 - 63
packages/ckeditor5-engine/tests/controller/datacontroller.js

@@ -123,9 +123,9 @@ describe( 'DataController', () => {
 	describe( 'parse', () => {
 		it( 'should set text', () => {
 			schema.allow( { name: '$text', inside: '$root' } );
-			const model = data.parse( '<p>foo<b>bar</b></p>' );
+			const { modelItem } = data.parse( '<p>foo<b>bar</b></p>' );
 
-			expect( stringify( model ) ).to.equal( 'foobar' );
+			expect( stringify( modelItem ) ).to.equal( 'foobar' );
 		} );
 
 		it( 'should set paragraph', () => {
@@ -133,9 +133,9 @@ describe( 'DataController', () => {
 
 			buildViewConverter().for( data.viewToModel ).fromElement( 'p' ).toElement( 'paragraph' );
 
-			const model = data.parse( '<p>foo<b>bar</b></p>' );
+			const { modelItem } = data.parse( '<p>foo<b>bar</b></p>' );
 
-			expect( stringify( model ) ).to.equal( '<paragraph>foobar</paragraph>' );
+			expect( stringify( modelItem ) ).to.equal( '<paragraph>foobar</paragraph>' );
 		} );
 
 		it( 'should set two paragraphs', () => {
@@ -143,9 +143,9 @@ describe( 'DataController', () => {
 
 			buildViewConverter().for( data.viewToModel ).fromElement( 'p' ).toElement( 'paragraph' );
 
-			const model = data.parse( '<p>foo</p><p>bar</p>' );
+			const { modelItem } = data.parse( '<p>foo</p><p>bar</p>' );
 
-			expect( stringify( model ) ).to.equal(
+			expect( stringify( modelItem ) ).to.equal(
 				'<paragraph>foo</paragraph><paragraph>bar</paragraph>' );
 		} );
 
@@ -156,22 +156,35 @@ describe( 'DataController', () => {
 			buildViewConverter().for( data.viewToModel ).fromElement( 'p' ).toElement( 'paragraph' );
 			buildViewConverter().for( data.viewToModel ).fromElement( 'b' ).toAttribute( 'bold', true );
 
-			const model = data.parse( '<p>foo<b>bar</b></p>' );
+			const { modelItem } = data.parse( '<p>foo<b>bar</b></p>' );
 
-			expect( stringify( model ) ).to.equal(
+			expect( stringify( modelItem ) ).to.equal(
 				'<paragraph>foo<$text bold="true">bar</$text></paragraph>' );
 		} );
 
+		it( 'should return markers data', () => {
+			schema.registerItem( 'paragraph', '$block' );
+
+			buildViewConverter().for( data.viewToModel ).fromElement( 'p' ).toElement( 'paragraph' );
+			buildViewConverter().for( data.viewToModel ).fromElement( 'marker' ).toMarker();
+
+			const { markersData } = data.parse( '<p>f<marker data-name="search"></marker>oob<marker data-name="search"></marker>ar</p>' );
+
+			expect( markersData ).to.instanceof( Map );
+			expect( markersData.size ).to.equal( 1 );
+			expect( markersData.get( 'search'  ) ).to.deep.equal( { startPath: [ 0, 1 ], endPath: [ 0, 4 ] } );
+		} );
+
 		it( 'should parse in the root context by default', () => {
-			const model = data.parse( 'foo' );
+			const { modelItem } = data.parse( 'foo' );
 
-			expect( stringify( model ) ).to.equal( '' );
+			expect( stringify( modelItem ) ).to.equal( '' );
 		} );
 
 		it( 'should accept parsing context', () => {
-			const model = data.parse( 'foo', '$block' );
+			const { modelItem } = data.parse( 'foo', '$block' );
 
-			expect( stringify( model ) ).to.equal( 'foo' );
+			expect( stringify( modelItem ) ).to.equal( 'foo' );
 		} );
 	} );
 
@@ -184,18 +197,30 @@ describe( 'DataController', () => {
 
 		it( 'should convert content of an element #1', () => {
 			const viewElement = parseView( '<p>foo</p>' );
-			const modelElement = data.toModel( viewElement );
+			const { modelItem } = data.toModel( viewElement );
 
-			expect( modelElement ).to.be.instanceOf( ModelElement );
-			expect( stringify( modelElement ) ).to.equal( '<paragraph>foo</paragraph>' );
+			expect( modelItem ).to.be.instanceOf( ModelElement );
+			expect( stringify( modelItem ) ).to.equal( '<paragraph>foo</paragraph>' );
 		} );
 
 		it( 'should convert content of an element #2', () => {
 			const viewFragment = parseView( '<p>foo</p><p>bar</p>' );
-			const modelFragment = data.toModel( viewFragment );
+			const { modelItem } = data.toModel( viewFragment );
 
-			expect( modelFragment ).to.be.instanceOf( ModelDocumentFragment );
-			expect( stringify( modelFragment ) ).to.equal( '<paragraph>foo</paragraph><paragraph>bar</paragraph>' );
+			expect( modelItem ).to.be.instanceOf( ModelDocumentFragment );
+			expect( stringify( modelItem ) ).to.equal( '<paragraph>foo</paragraph><paragraph>bar</paragraph>' );
+		} );
+
+		it( 'should convert marker stamps', () => {
+			const viewElement = parseView( '<p>f<marker data-name="search"></marker>o<marker data-name="search"></marker>o</p>' );
+
+			buildViewConverter().for( data.viewToModel ).fromElement( 'marker' ).toMarker();
+
+			const { markersData } = data.toModel( viewElement );
+
+			expect( markersData ).to.instanceof( Map );
+			expect( markersData.size ).to.equal( 1 );
+			expect( markersData.get( 'search'  ) ).to.deep.equal( { startPath: [ 1 ], endPath: [ 2 ] } );
 		} );
 
 		it( 'should accept parsing context', () => {
@@ -205,13 +230,12 @@ describe( 'DataController', () => {
 			schema.allow( { name: '$text', inside: 'inlineRoot' } );
 
 			const viewFragment = new ViewDocumentFragment( [ parseView( 'foo' ) ] );
-			const modelFragmentInRoot = data.toModel( viewFragment );
-
-			expect( stringify( modelFragmentInRoot ) ).to.equal( '' );
 
-			const modelFragmentInInlineRoot = data.toModel( viewFragment, 'inlineRoot' );
+			// Model fragment in root.
+			expect( stringify( data.toModel( viewFragment ).modelItem ) ).to.equal( '' );
 
-			expect( stringify( modelFragmentInInlineRoot ) ).to.equal( 'foo' );
+			// Model fragment in inline root.
+			expect( stringify( data.toModel( viewFragment, 'inlineRoot' ).modelItem ) ).to.equal( 'foo' );
 		} );
 	} );
 
@@ -223,16 +247,14 @@ describe( 'DataController', () => {
 			expect( getData( modelDocument, { withoutSelection: true } ) ).to.equal( 'foo' );
 		} );
 
-		it( 'should extract markers stamps from converted data and set to `modelDocument#markers` collection', () => {
+		it( 'should set markers to `modelDocument#markers` collection', () => {
 			modelDocument.schema.registerItem( 'paragraph', '$block' );
 			buildViewConverter().for( data.viewToModel ).fromElement( 'p' ).toElement( 'paragraph' );
 			buildViewConverter().for( data.viewToModel ).fromElement( 'm' ).toMarker();
 
 			data.set(
 				'<p>' +
-					'F' +
-					'<m data-name="comment"></m>' +
-					'o' +
+					'Fo' +
 					'<m data-name="search"></m>' +
 					'o ba' +
 					'<m data-name="comment"></m>' +
@@ -242,35 +264,16 @@ describe( 'DataController', () => {
 				'</p>'
 			);
 
-			expect( getData( modelDocument, { withoutSelection: true } ) ).to.equal( '<paragraph>Foo bar biz</paragraph>' );
 			expect( Array.from( modelDocument.markers ).length ).to.equal( 2 );
 
 			const paragraph = modelDocument.getRoot().getChild( 0 );
-			const commentMarkerRange = ModelRange.createFromParentsAndOffsets( paragraph, 1, paragraph, 6 );
+			const commentMarkerRange = ModelRange.createFromParentsAndOffsets( paragraph, 6, paragraph, 6 );
 			const searchMarkerRange = ModelRange.createFromParentsAndOffsets( paragraph, 2, paragraph, 10 );
 
 			expect( modelDocument.markers.get( 'comment' ).getRange().isEqual( commentMarkerRange ) ).to.true;
 			expect( modelDocument.markers.get( 'search' ).getRange().isEqual( searchMarkerRange ) ).to.true;
 		} );
 
-		it( 'should extract collapsed markers stamps from converted data and set to `modelDocument#markers` collection', () => {
-			modelDocument.schema.registerItem( 'paragraph', '$block' );
-			buildViewConverter().for( data.viewToModel ).fromElement( 'p' ).toElement( 'paragraph' );
-			buildViewConverter().for( data.viewToModel ).fromElement( 'm' ).toMarker();
-
-			data.set( '<p>F<m data-name="comment"></m>o<m data-name="search"></m>o ba</m>r biz</p>' );
-
-			expect( getData( modelDocument, { withoutSelection: true } ) ).to.equal( '<paragraph>Foo bar biz</paragraph>' );
-			expect( Array.from( modelDocument.markers ).length ).to.equal( 2 );
-
-			const paragraph = modelDocument.getRoot().getChild( 0 );
-			const commentMarkerRange = ModelRange.createFromParentsAndOffsets( paragraph, 1, paragraph, 1 );
-			const searchMarkerRange = ModelRange.createFromParentsAndOffsets( paragraph, 2, paragraph, 2 );
-
-			expect( modelDocument.markers.get( 'comment' ).getRange().isEqual( commentMarkerRange ) ).to.true;
-			expect( modelDocument.markers.get( 'search' ).getRange().isEqual( searchMarkerRange ) ).to.true;
-		} );
-
 		it( 'should create a batch', () => {
 			schema.allow( { name: '$text', inside: '$root' } );
 			data.set( 'foo' );
@@ -476,22 +479,6 @@ describe( 'DataController', () => {
 				content: content
 			} );
 		} );
-
-		it( 'should remove markers stamps from content', () => {
-			const spy = sinon.spy();
-			const content = new ModelDocumentFragment( [
-				new ModelText( 'x' ),
-				new ModelElement( '$marker', { 'data-name': 'search' } ),
-				new ModelText( 'y' ),
-				new ModelElement( '$marker', { 'data-name': 'search' } ),
-				new ModelText( 'z' )
-			] );
-
-			data.on( 'insertContent', spy );
-			data.insertContent( content, modelDocument.selection );
-
-			expect( stringify( spy.args[ 0 ][ 1 ].content ) ).to.equal( 'xyz' );
-		} );
 	} );
 
 	describe( 'deleteContent', () => {

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

@@ -284,7 +284,7 @@ describe( 'advanced-converters', () => {
 
 		it( 'should convert view image to model', () => {
 			let viewElement = new ViewContainerElement( 'img', { src: 'bar.jpg', title: 'bar' } );
-			let modelElement = viewDispatcher.convert( viewElement );
+			let modelElement = viewDispatcher.convert( viewElement ).conversionResult;
 			// Attaching to tree so tree walker works fine in `modelToString`.
 			modelRoot.appendChildren( modelElement );
 
@@ -300,7 +300,7 @@ describe( 'advanced-converters', () => {
 					new ViewContainerElement( 'figcaption', null, new ViewText( 'foobar' ) )
 				]
 			);
-			let modelElement = viewDispatcher.convert( viewElement );
+			let modelElement = viewDispatcher.convert( viewElement ).conversionResult;
 			// Attaching to tree so tree walker works fine in `modelToString`.
 			modelRoot.appendChildren( modelElement );
 
@@ -519,7 +519,7 @@ describe( 'advanced-converters', () => {
 		it( 'should convert a view element to model', () => {
 			let viewElement = new ViewAttributeElement( 'a', { href: 'foo.html', title: 'Foo title' }, new ViewText( 'foo' ) );
 
-			let modelText = viewDispatcher.convert( viewElement )[ 0 ];
+			let modelText = viewDispatcher.convert( viewElement ).conversionResult[ 0 ];
 
 			expect( modelText ).to.be.instanceof( ModelText );
 			expect( modelText.data ).to.equal( 'foo' );
@@ -578,7 +578,7 @@ describe( 'advanced-converters', () => {
 				]
 			);
 
-			let modelElement = viewDispatcher.convert( viewElement );
+			let modelElement = viewDispatcher.convert( viewElement ).conversionResult;
 			modelRoot.appendChildren( modelElement );
 
 			expect( modelToString( modelElement ) ).to.equal( '<quote linkHref="foo.html" linkTitle="Foo source">foo</quote>' );
@@ -639,7 +639,7 @@ describe( 'advanced-converters', () => {
 			] )
 		] );
 
-		let model = viewDispatcher.convert( viewTable );
+		let model = viewDispatcher.convert( viewTable ).conversionResult;
 		let modelFragment = new ModelDocumentFragment( model );
 
 		expect( modelToString( modelFragment ) )
@@ -741,7 +741,7 @@ describe( 'advanced-converters', () => {
 				] )
 			] );
 
-			let modelElement = viewDispatcher.convert( viewElement );
+			let modelElement = viewDispatcher.convert( viewElement ).conversionResult;
 			modelRoot.appendChildren( modelElement );
 
 			expect( modelToString( modelElement ) ).to.equal(

+ 68 - 54
packages/ckeditor5-engine/tests/conversion/buildviewconverter.js

@@ -97,10 +97,10 @@ describe( 'View converter builder', () => {
 	it( 'should convert from view element to model element', () => {
 		buildViewConverter().for( dispatcher ).fromElement( 'p' ).toElement( 'paragraph' );
 
-		const result = dispatcher.convert( new ViewContainerElement( 'p', null, new ViewText( 'foo' ) ), objWithContext );
-		modelRoot.appendChildren( result );
+		const { conversionResult } = dispatcher.convert( new ViewContainerElement( 'p', null, new ViewText( 'foo' ) ), objWithContext );
+		modelRoot.appendChildren( conversionResult );
 
-		expect( modelToString( result ) ).to.equal( '<paragraph>foo</paragraph>' );
+		expect( modelToString( conversionResult ) ).to.equal( '<paragraph>foo</paragraph>' );
 	} );
 
 	it( 'should convert from view element to model element using creator function', () => {
@@ -108,17 +108,19 @@ describe( 'View converter builder', () => {
 			.fromElement( 'img' )
 			.toElement( ( viewElement ) => new ModelElement( 'image', { src: viewElement.getAttribute( 'src' ) } ) );
 
-		const result = dispatcher.convert( new ViewContainerElement( 'img', { src: 'foo.jpg' } ), objWithContext );
-		modelRoot.appendChildren( result );
+		const { conversionResult } = dispatcher.convert( new ViewContainerElement( 'img', { src: 'foo.jpg' } ), objWithContext );
+		modelRoot.appendChildren( conversionResult );
 
-		expect( modelToString( result ) ).to.equal( '<image src="foo.jpg"></image>' );
+		expect( modelToString( conversionResult ) ).to.equal( '<image src="foo.jpg"></image>' );
 	} );
 
 	it( 'should convert from view element to model attribute', () => {
 		buildViewConverter().for( dispatcher ).fromElement( 'strong' ).toAttribute( 'bold', true );
 
-		const result = dispatcher.convert( new ViewAttributeElement( 'strong', null, new ViewText( 'foo' ) ), objWithContext );
-		modelRoot.appendChildren( result );
+		const { conversionResult } = dispatcher.convert(
+			new ViewAttributeElement( 'strong', null, new ViewText( 'foo' ) ), objWithContext
+		);
+		modelRoot.appendChildren( conversionResult );
 
 		// Have to check root because result is a ModelText.
 		expect( modelToString( modelRoot ) ).to.equal( '<$root><$text bold="true">foo</$text></$root>' );
@@ -129,8 +131,10 @@ describe( 'View converter builder', () => {
 			.fromElement( 'a' )
 			.toAttribute( ( viewElement ) => ( { key: 'linkHref', value: viewElement.getAttribute( 'href' ) } ) );
 
-		const result = dispatcher.convert( new ViewAttributeElement( 'a', { href: 'foo.html' }, new ViewText( 'foo' ) ), objWithContext );
-		modelRoot.appendChildren( result );
+		const { conversionResult } = dispatcher.convert(
+			new ViewAttributeElement( 'a', { href: 'foo.html' }, new ViewText( 'foo' ) ), objWithContext
+		);
+		modelRoot.appendChildren( conversionResult );
 
 		// Have to check root because result is a ModelText.
 		expect( modelToString( modelRoot ) ).to.equal( '<$root><$text linkHref="foo.html">foo</$text></$root>' );
@@ -143,10 +147,12 @@ describe( 'View converter builder', () => {
 			.fromAttribute( 'class' )
 			.toAttribute( ( viewElement ) => ( { key: 'class', value: viewElement.getAttribute( 'class' ) } ) );
 
-		const result = dispatcher.convert( new ViewContainerElement( 'p', { class: 'myClass' }, new ViewText( 'foo' ) ), objWithContext );
-		modelRoot.appendChildren( result );
+		const { conversionResult } = dispatcher.convert(
+			new ViewContainerElement( 'p', { class: 'myClass' }, new ViewText( 'foo' ) ), objWithContext
+		);
+		modelRoot.appendChildren( conversionResult );
 
-		expect( modelToString( result ) ).to.equal( '<paragraph class="myClass">foo</paragraph>' );
+		expect( modelToString( conversionResult ) ).to.equal( '<paragraph class="myClass">foo</paragraph>' );
 	} );
 
 	it( 'should convert from view attribute and key to model attribute', () => {
@@ -161,9 +167,9 @@ describe( 'View converter builder', () => {
 			new ViewContainerElement( 'p', { class: 'important theme-nice' }, new ViewText( 'bar' ) )
 		] );
 
-		const result = dispatcher.convert( viewStructure, objWithContext );
+		const { conversionResult } = dispatcher.convert( viewStructure, objWithContext );
 
-		expect( modelToString( result ) )
+		expect( modelToString( conversionResult ) )
 			.to.equal( '<paragraph important="true">foo</paragraph><paragraph important="true" theme="nice">bar</paragraph>' );
 	} );
 
@@ -184,10 +190,10 @@ describe( 'View converter builder', () => {
 			new ViewContainerElement( 'span', { style: 'font-weight:bold; font-size:20px' }, new ViewText( 'ddd' ) )
 		] );
 
-		const result = dispatcher.convert( viewElement, objWithContext );
-		modelRoot.appendChildren( result );
+		const { conversionResult } = dispatcher.convert( viewElement, objWithContext );
+		modelRoot.appendChildren( conversionResult );
 
-		expect( modelToString( result ) ).to.equal( '<paragraph><$text bold="true">aaabbbcccddd</$text></paragraph>' );
+		expect( modelToString( conversionResult ) ).to.equal( '<paragraph><$text bold="true">aaabbbcccddd</$text></paragraph>' );
 	} );
 
 	it( 'should convert from pattern to marker', () => {
@@ -195,10 +201,10 @@ describe( 'View converter builder', () => {
 
 		const viewElement = new ViewAttributeElement( 'span', { 'data-name': 'search' } );
 
-		const result = dispatcher.convert( viewElement, objWithContext );
-		modelRoot.appendChildren( result );
+		const { conversionResult } = dispatcher.convert( viewElement, objWithContext );
+		modelRoot.appendChildren( conversionResult );
 
-		expect( modelToString( result ) ).to.equal( '<$marker data-name="search"></$marker>' );
+		expect( modelToString( conversionResult ) ).to.equal( '<$marker data-name="search"></$marker>' );
 	} );
 
 	it( 'should convert from element to marker using creator function', () => {
@@ -208,10 +214,10 @@ describe( 'View converter builder', () => {
 
 		const element = new ViewAttributeElement( 'marker', { class: 'search' } );
 
-		const result = dispatcher.convert( element, objWithContext );
-		modelRoot.appendChildren( result );
+		const { conversionResult } = dispatcher.convert( element, objWithContext );
+		modelRoot.appendChildren( conversionResult );
 
-		expect( modelToString( result ) ).to.equal( '<$marker data-name="search"></$marker>' );
+		expect( modelToString( conversionResult ) ).to.equal( '<$marker data-name="search"></$marker>' );
 	} );
 
 	it( 'should convert from multiple view entities to marker', () => {
@@ -229,14 +235,14 @@ describe( 'View converter builder', () => {
 			new ViewAttributeElement( 'span', { 'foo': 'marker', 'bar': 'marker', 'data-name': 'marker3' } )
 		] );
 
-		const result = dispatcher.convert( viewElement, objWithContext );
-		modelRoot.appendChildren( result );
+		const { conversionResult, markersData } = dispatcher.convert( viewElement, objWithContext );
 
-		expect( modelToString( result ) ).to.equal(
-			'<paragraph>' +
-				'<$marker data-name="marker1"></$marker><$marker data-name="marker2"></$marker><$marker data-name="marker3"></$marker>' +
-			'</paragraph>'
-		);
+		expect( modelToString( conversionResult ) ).to.equal( '<paragraph></paragraph>' );
+
+		expect( markersData.size ).to.equal( 3 );
+		expect( markersData.has( 'marker1' ) ).to.true;
+		expect( markersData.has( 'marker2' ) ).to.true;
+		expect( markersData.has( 'marker3' ) ).to.true;
 	} );
 
 	it( 'should do nothing when there is no element matching to marker pattern', () => {
@@ -244,7 +250,7 @@ describe( 'View converter builder', () => {
 
 		const element = new ViewAttributeElement( 'span' );
 
-		expect( dispatcher.convert( element, objWithContext ) ).to.null;
+		expect( dispatcher.convert( element, objWithContext ).conversionResult ).to.null;
 	} );
 
 	it( 'should throw an error when view element in not valid to convert to marker', () => {
@@ -293,7 +299,9 @@ describe( 'View converter builder', () => {
 		let result;
 
 		// Not quite megatron.
-		result = dispatcher.convert( new ViewContainerElement( 'span', { class: 'megatron' }, new ViewText( 'foo' ) ), objWithContext );
+		result = dispatcher.convert(
+			new ViewContainerElement( 'span', { class: 'megatron' }, new ViewText( 'foo' ) ), objWithContext
+		).conversionResult;
 		modelRoot.appendChildren( result );
 		expect( modelToString( result ) ).to.equal( '<span>foo</span>' );
 
@@ -301,7 +309,7 @@ describe( 'View converter builder', () => {
 		result = dispatcher.convert(
 			new ViewContainerElement( 'span', { class: 'megatron', body: 'megatron', legs: 'megatron' }, new ViewText( 'foo' ) ),
 			objWithContext
-		);
+		).conversionResult;
 
 		modelRoot.appendChildren( result );
 		expect( modelToString( result ) ).to.equal( '<span>foo</span>' );
@@ -314,7 +322,7 @@ describe( 'View converter builder', () => {
 				new ViewText( 'foo' )
 			),
 			objWithContext
-		);
+		).conversionResult;
 
 		modelRoot.appendChildren( result );
 		expect( modelToString( result ) ).to.equal( '<paragraph>foo</paragraph>' );
@@ -327,7 +335,7 @@ describe( 'View converter builder', () => {
 				new ViewText( 'foo' )
 			),
 			objWithContext
-		);
+		).conversionResult;
 
 		modelRoot.appendChildren( result );
 		expect( modelToString( result ) ).to.equal( '<MEGATRON>foo</MEGATRON>' );
@@ -347,10 +355,10 @@ describe( 'View converter builder', () => {
 			new ViewText( 'foo' )
 		);
 
-		let result = dispatcher.convert( viewElement, objWithContext );
+		let { conversionResult } = dispatcher.convert( viewElement, objWithContext );
 
-		modelRoot.appendChildren( result );
-		expect( modelToString( result ) ).to.equal( '<span transformer="megatron">foo</span>' );
+		modelRoot.appendChildren( conversionResult );
+		expect( modelToString( conversionResult ) ).to.equal( '<span transformer="megatron">foo</span>' );
 	} );
 
 	it( 'should set different priorities for `toElement` and `toAttribute` conversion', () => {
@@ -359,11 +367,13 @@ describe( 'View converter builder', () => {
 			.toAttribute( ( viewElement ) => ( { key: 'class', value: viewElement.getAttribute( 'class' ) } ) );
 		buildViewConverter().for( dispatcher ).fromElement( 'p' ).toElement( 'paragraph' );
 
-		let result = dispatcher.convert( new ViewContainerElement( 'p', { class: 'myClass' }, new ViewText( 'foo' ) ), objWithContext );
-		modelRoot.appendChildren( result );
+		let { conversionResult } = dispatcher.convert(
+			new ViewContainerElement( 'p', { class: 'myClass' }, new ViewText( 'foo' ) ), objWithContext
+		);
+		modelRoot.appendChildren( conversionResult );
 
 		// Element converter was fired first even though attribute converter was added first.
-		expect( modelToString( result ) ).to.equal( '<paragraph class="myClass">foo</paragraph>' );
+		expect( modelToString( conversionResult ) ).to.equal( '<paragraph class="myClass">foo</paragraph>' );
 	} );
 
 	it( 'should overwrite default priorities for converters', () => {
@@ -374,7 +384,9 @@ describe( 'View converter builder', () => {
 
 		let result;
 
-		result = dispatcher.convert( new ViewContainerElement( 'p', { class: 'myClass' }, new ViewText( 'foo' ) ), objWithContext );
+		result = dispatcher.convert(
+			new ViewContainerElement( 'p', { class: 'myClass' }, new ViewText( 'foo' ) ), objWithContext
+		).conversionResult;
 		modelRoot.appendChildren( result );
 		expect( modelToString( result ) ).to.equal( '<paragraph class="myClass">foo</paragraph>' );
 
@@ -382,7 +394,9 @@ describe( 'View converter builder', () => {
 			.from( { name: 'p', class: 'myClass' } ).withPriority( 'high' )
 			.toElement( 'customP' );
 
-		result = dispatcher.convert( new ViewContainerElement( 'p', { class: 'myClass' }, new ViewText( 'foo' ) ), objWithContext );
+		result = dispatcher.convert(
+			new ViewContainerElement( 'p', { class: 'myClass' }, new ViewText( 'foo' ) ), objWithContext
+		).conversionResult;
 		modelRoot.appendChildren( result );
 		expect( modelToString( result ) ).to.equal( '<customP>foo</customP>' );
 	} );
@@ -403,13 +417,13 @@ describe( 'View converter builder', () => {
 
 		const viewElement = new ViewContainerElement( 'p', { class: 'decorated small' }, new ViewText( 'foo' ) );
 
-		const result = dispatcher.convert( viewElement, objWithContext );
-		modelRoot.appendChildren( result );
+		const { conversionResult } = dispatcher.convert( viewElement, objWithContext );
+		modelRoot.appendChildren( conversionResult );
 
 		// P element and it's children got converted by the converter (1) and the converter (1) got fired
 		// because P name was not consumed in converter (2). Converter (3) could consume class="small" because
 		// only class="decorated" was consumed in converter (2).
-		expect( modelToString( result ) ).to.equal( '<paragraph decorated="true" size="small">foo</paragraph>' );
+		expect( modelToString( conversionResult ) ).to.equal( '<paragraph decorated="true" size="small">foo</paragraph>' );
 	} );
 
 	it( 'should convert from matcher instance to model', () => {
@@ -427,10 +441,10 @@ describe( 'View converter builder', () => {
 			new ViewContainerElement( 'abcd', null, new ViewText( 'foo' ) )
 		] );
 
-		let result = dispatcher.convert( viewStructure, objWithContext );
-		modelRoot.appendChildren( result );
+		let { conversionResult } = dispatcher.convert( viewStructure, objWithContext );
+		modelRoot.appendChildren( conversionResult );
 
-		expect( modelToString( result ) ).to.equal( '<div class="myClass"><abcd>foo</abcd></div>' );
+		expect( modelToString( conversionResult ) ).to.equal( '<div class="myClass"><abcd>foo</abcd></div>' );
 	} );
 
 	it( 'should filter out structure that is wrong with schema - elements', () => {
@@ -447,9 +461,9 @@ describe( 'View converter builder', () => {
 			)
 		);
 
-		let result = dispatcher.convert( viewElement, objWithContext );
+		let { conversionResult } = dispatcher.convert( viewElement, objWithContext );
 
-		expect( modelToString( result ) ).to.equal( '<paragraph>foo</paragraph>' );
+		expect( modelToString( conversionResult ) ).to.equal( '<paragraph>foo</paragraph>' );
 	} );
 
 	it( 'should filter out structure that is wrong with schema - attributes', () => {
@@ -466,8 +480,8 @@ describe( 'View converter builder', () => {
 			)
 		);
 
-		let result = dispatcher.convert( viewElement, objWithContext );
+		let { conversionResult } = dispatcher.convert( viewElement, objWithContext );
 
-		expect( modelToString( result ) ).to.equal( '<paragraph>foo</paragraph>' );
+		expect( modelToString( conversionResult ) ).to.equal( '<paragraph>foo</paragraph>' );
 	} );
 } );

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

@@ -32,10 +32,10 @@ describe( 'view-to-model-converters', () => {
 
 			dispatcher.on( 'text', convertText() );
 
-			const result = dispatcher.convert( viewText, objWithContext );
+			const { conversionResult } = dispatcher.convert( viewText, objWithContext );
 
-			expect( result ).to.be.instanceof( ModelText );
-			expect( result.data ).to.equal( 'foobar' );
+			expect( conversionResult ).to.be.instanceof( ModelText );
+			expect( conversionResult.data ).to.equal( 'foobar' );
 		} );
 
 		it( 'should not convert already consumed texts', () => {
@@ -50,10 +50,10 @@ describe( 'view-to-model-converters', () => {
 				}
 			} );
 
-			const result = dispatcher.convert( viewText, objWithContext );
+			const { conversionResult } = dispatcher.convert( viewText, objWithContext );
 
-			expect( result ).to.be.instanceof( ModelText );
-			expect( result.data ).to.equal( 'foo****ba****r' );
+			expect( conversionResult ).to.be.instanceof( ModelText );
+			expect( conversionResult.data ).to.equal( 'foo****ba****r' );
 		} );
 
 		it( 'should not convert text if it is wrong with schema', () => {
@@ -62,11 +62,11 @@ describe( 'view-to-model-converters', () => {
 			const viewText = new ViewText( 'foobar' );
 			dispatcher.on( 'text', convertText() );
 
-			let result = dispatcher.convert( viewText, objWithContext );
+			let result = dispatcher.convert( viewText, objWithContext ).conversionResult;
 
 			expect( result ).to.be.null;
 
-			result = dispatcher.convert( viewText, { context: [ '$block' ] } );
+			result = dispatcher.convert( viewText, { context: [ '$block' ] } ).conversionResult;
 			expect( result ).to.be.instanceof( ModelText );
 			expect( result.data ).to.equal( 'foobar' );
 		} );
@@ -76,10 +76,10 @@ describe( 'view-to-model-converters', () => {
 
 			dispatcher.on( 'text', convertText() );
 
-			const result = dispatcher.convert( viewText, objWithContext );
+			const { conversionResult } = dispatcher.convert( viewText, objWithContext );
 
-			expect( result ).to.be.instanceof( ModelText );
-			expect( result.data ).to.equal( 'நிலைக்கு' );
+			expect( conversionResult ).to.be.instanceof( ModelText );
+			expect( conversionResult.data ).to.equal( 'நிலைக்கு' );
 		} );
 	} );
 
@@ -96,11 +96,11 @@ describe( 'view-to-model-converters', () => {
 			dispatcher.on( 'element', convertToModelFragment() );
 			dispatcher.on( 'documentFragment', convertToModelFragment() );
 
-			const result = dispatcher.convert( viewFragment, objWithContext );
+			const { conversionResult } = dispatcher.convert( viewFragment, objWithContext );
 
-			expect( result ).to.be.instanceof( ModelDocumentFragment );
-			expect( result.maxOffset ).to.equal( 6 );
-			expect( result.getChild( 0 ).data ).to.equal( 'foobar' );
+			expect( conversionResult ).to.be.instanceof( ModelDocumentFragment );
+			expect( conversionResult.maxOffset ).to.equal( 6 );
+			expect( conversionResult.getChild( 0 ).data ).to.equal( 'foobar' );
 		} );
 
 		it( 'should not convert already consumed (converted) changes', () => {
@@ -121,12 +121,12 @@ describe( 'view-to-model-converters', () => {
 				}
 			} );
 
-			const result = dispatcher.convert( viewP, objWithContext );
+			const { conversionResult } = dispatcher.convert( viewP, objWithContext );
 
-			expect( result ).to.be.instanceof( ModelElement );
-			expect( result.name ).to.equal( 'paragraph' );
-			expect( result.maxOffset ).to.equal( 3 );
-			expect( result.getChild( 0 ).data ).to.equal( 'foo' );
+			expect( conversionResult ).to.be.instanceof( ModelElement );
+			expect( conversionResult.name ).to.equal( 'paragraph' );
+			expect( conversionResult.maxOffset ).to.equal( 3 );
+			expect( conversionResult.getChild( 0 ).data ).to.equal( 'foo' );
 		} );
 	} );
 } );