diff --git a/src/conversion/viewconversiondispatcher.js b/src/conversion/viewconversiondispatcher.js
index bd4b8fe59..8c3079f2f 100644
--- a/src/conversion/viewconversiondispatcher.js
+++ b/src/conversion/viewconversiondispatcher.js
@@ -8,9 +8,6 @@
*/
import ViewConsumable from './viewconsumable';
-import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';
-import mix from '@ckeditor/ckeditor5-utils/src/mix';
-import extend from '@ckeditor/ckeditor5-utils/src/lib/lodash/extend';
import ModelRange from '../model/range';
import ModelPosition from '../model/position';
import ModelTreeWalker from '../model/treewalker';
@@ -18,6 +15,11 @@ import ModelNode from '../model/node';
import ModelDocumentFragment from '../model/documentfragment';
import { remove } from '../model/writer';
+import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';
+import mix from '@ckeditor/ckeditor5-utils/src/mix';
+import extend from '@ckeditor/ckeditor5-utils/src/lib/lodash/extend';
+import log from '@ckeditor/ckeditor5-utils/src/log';
+
/**
* `ViewConversionDispatcher` is a central point of {@link module:engine/view/view view} conversion, which is a process of
* converting given {@link module:engine/view/documentfragment~DocumentFragment view document fragment} or
@@ -130,36 +132,35 @@ export default class ViewConversionDispatcher {
* @fires element
* @fires text
* @fires documentFragment
- * @param {module:engine/view/documentfragment~DocumentFragment|module:engine/view/element~Element}
- * viewItem Part of the view to be converted.
+ * @param {module:engine/view/documentfragment~DocumentFragment|module:engine/view/element~Element} viewItem
+ * Part of the view to be converted.
* @param {Object} [additionalData] Additional data to be passed in `data` argument when firing `ViewConversionDispatcher`
* events. See also {@link ~ViewConversionDispatcher#event:element element event}.
* @returns {module:engine/model/documentfragment~DocumentFragment} Model data that is a result of the conversion process
- * wrapped by DocumentFragment. Converted marker stamps will be set as DocumentFragment
+ * wrapped in `DocumentFragment`. Converted marker stamps will be set as that document fragment's
* {@link module:engine/view/documentfragment~DocumentFragment#markers static markers map}.
*/
convert( viewItem, additionalData = {} ) {
this.fire( 'viewCleanup', viewItem );
const consumable = ViewConsumable.createFrom( viewItem );
- const conversionResult = this._convertItem( viewItem, consumable, additionalData );
+ let conversionResult = this._convertItem( viewItem, consumable, additionalData );
- // In some cases conversion output doesn't have to be a node and in this case we do nothing additional with this data.
- if ( !( conversionResult instanceof ModelNode || conversionResult instanceof ModelDocumentFragment ) ) {
- return conversionResult;
+ // We can get a null here if conversion failed (see _convertItem())
+ // or simply if an item could not be converted (e.g. due to the schema).
+ if ( !conversionResult ) {
+ return new ModelDocumentFragment();
}
- let documentFragment = conversionResult;
-
- // When conversion result is not a DocumentFragment we need to wrap it by DocumentFragment.
- if ( !documentFragment.is( 'documentFragment' ) ) {
- documentFragment = new ModelDocumentFragment( [ documentFragment ] );
+ // When conversion result is not a document fragment we need to wrap it in document fragment.
+ if ( !conversionResult.is( 'documentFragment' ) ) {
+ conversionResult = new ModelDocumentFragment( [ conversionResult ] );
}
// Extract temporary markers stamp from model and set as static markers collection.
- documentFragment.markers = extractMarkersFromModelFragment( documentFragment );
+ conversionResult.markers = extractMarkersFromModelFragment( conversionResult );
- return documentFragment;
+ return conversionResult;
}
/**
@@ -180,6 +181,21 @@ export default class ViewConversionDispatcher {
this.fire( 'documentFragment', data, consumable, this.conversionApi );
}
+ // Handle incorrect `data.output`.
+ if ( data.output && !( data.output instanceof ModelNode || data.output instanceof ModelDocumentFragment ) ) {
+ /**
+ * Dropped incorrect conversion result.
+ *
+ * Item may be converted to either {@link module:engine/model/node~Node model node} or
+ * {@link module:engine/model/documentfragment~DocumentFragment model document fragment}.
+ *
+ * @error view-conversion-dispatcher-incorrect-result
+ */
+ log.warn( 'view-conversion-dispatcher-incorrect-result: Dropped incorrect conversion result.', [ input, data.output ] );
+
+ return null;
+ }
+
return data.output;
}
@@ -188,11 +204,23 @@ export default class ViewConversionDispatcher {
* @see module:engine/conversion/viewconversiondispatcher~ViewConversionApi#convertChildren
*/
_convertChildren( input, consumable, additionalData = {} ) {
+ // Get all children of view input item.
const viewChildren = Array.from( input.getChildren() );
- const convertedChildren = viewChildren.map( ( viewChild ) => this._convertItem( viewChild, consumable, additionalData ) );
- // Flatten and remove nulls.
- return convertedChildren.reduce( ( a, b ) => b ? a.concat( b ) : a, [] );
+ // 1. Map those children to model.
+ // 2. Filter out items that has not been converted or for which conversion returned wrong result (for those warning is logged).
+ // 3. Extract children from document fragments to flatten results.
+ const convertedChildren = viewChildren
+ .map( ( viewChild ) => this._convertItem( viewChild, consumable, additionalData ) )
+ .filter( ( converted ) => converted instanceof ModelNode || converted instanceof ModelDocumentFragment )
+ .reduce( ( result, filtered ) => {
+ return result.concat(
+ filtered.is( 'documentFragment' ) ? Array.from( filtered.getChildren() ) : filtered
+ );
+ }, [] );
+
+ // Normalize array to model document fragment.
+ return new ModelDocumentFragment( convertedChildren );
}
/**
@@ -304,6 +332,8 @@ function extractMarkersFromModelFragment( modelItem ) {
*
* 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 either {@link module:engine/model/node~Node model node} or
+ * {@link module:engine/model/documentfragment~DocumentFragment model document fragment} or `null` (as set by default).
*
* @method #convertItem
* @fires module:engine/conversion/viewconversiondispatcher~ViewConversionDispatcher#event:element
@@ -314,7 +344,8 @@ function extractMarkersFromModelFragment( modelItem ) {
* @param {module:engine/conversion/viewconsumable~ViewConsumable} consumable Values to consume.
* @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}.
- * @returns {*} The result of item conversion, created and modified by callbacks attached to fired event.
+ * @returns {module:engine/model/node~Node|module:engine/model/documentfragment~DocumentFragment|null} The result of item conversion,
+ * created and modified by callbacks attached to fired event, or `null` if the conversion result was incorrect.
*/
/**
@@ -329,5 +360,6 @@ function extractMarkersFromModelFragment( modelItem ) {
* @param {module:engine/conversion/viewconsumable~ViewConsumable} consumable Values to consume.
* @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}.
- * @returns {Array.<*>} Array containing results of conversion of all children of given item.
+ * @returns {module:engine/model/documentfragment~DocumentFragment} Model document fragment containing results of conversion
+ * of all children of given item.
*/
diff --git a/tests/conversion/advanced-converters.js b/tests/conversion/advanced-converters.js
index 755c9fd1b..bb0a75cc1 100644
--- a/tests/conversion/advanced-converters.js
+++ b/tests/conversion/advanced-converters.js
@@ -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 ).getChild( 0 );
expect( modelText ).to.be.instanceof( ModelText );
expect( modelText.data ).to.equal( 'foo' );
@@ -603,11 +603,14 @@ describe( 'advanced-converters', () => {
viewDispatcher.on( 'element:tr', ( evt, data, consumable, conversionApi ) => {
if ( consumable.consume( data.input, { name: true } ) ) {
data.output = new ModelElement( 'paragraph' );
+
const children = conversionApi.convertChildren( data.input, consumable );
- for ( let i = 1; i < children.length; i++ ) {
- if ( children[ i ] instanceof ModelText && children[ i - 1 ] instanceof ModelText ) {
- children.splice( i, 0, new ModelText( ' ' ) );
+ for ( let i = 1; i < children.childCount; i++ ) {
+ const child = children.getChild( i );
+
+ if ( child instanceof ModelText && child.previousSibling instanceof ModelText ) {
+ children.insertChildren( i, new ModelText( ' ' ) );
i++;
}
}
diff --git a/tests/conversion/buildviewconverter.js b/tests/conversion/buildviewconverter.js
index f166ff5f7..8f2f09b13 100644
--- a/tests/conversion/buildviewconverter.js
+++ b/tests/conversion/buildviewconverter.js
@@ -6,6 +6,7 @@
import buildViewConverter from '../../src/conversion/buildviewconverter';
import ModelSchema from '../../src/model/schema';
+import ModelDocumentFragment from '../../src/model/documentfragment';
import ModelDocument from '../../src/model/document';
import ModelElement from '../../src/model/element';
import ModelTextProxy from '../../src/model/textproxy';
@@ -281,7 +282,10 @@ describe( 'View converter builder', () => {
const element = new ViewAttributeElement( 'span' );
- expect( dispatcher.convert( element, objWithContext ) ).to.null;
+ const result = dispatcher.convert( element, objWithContext );
+
+ expect( result ).to.be.instanceof( ModelDocumentFragment );
+ expect( result.childCount ).to.equal( 0 );
} );
it( 'should throw an error when view element in not valid to convert to marker', () => {
@@ -392,6 +396,16 @@ describe( 'View converter builder', () => {
expect( modelToString( conversionResult ) ).to.equal( 'foo' );
} );
+ it( 'should return model document fragment when converting attributes on text', () => {
+ buildViewConverter().for( dispatcher ).fromElement( 'strong' ).toAttribute( 'bold', true );
+
+ let viewElement = new ViewAttributeElement( 'strong', null, new ViewText( 'foo' ) );
+
+ let conversionResult = dispatcher.convert( viewElement, objWithContext );
+
+ expect( conversionResult.is( 'documentFragment' ) ).to.be.true;
+ } );
+
it( 'should set different priorities for `toElement` and `toAttribute` conversion', () => {
buildViewConverter().for( dispatcher )
.fromAttribute( 'class' )
@@ -527,7 +541,9 @@ describe( 'View converter builder', () => {
viewElement.setAttribute( 'stop', true );
conversionResult = dispatcher.convert( viewElement, objWithContext );
- expect( conversionResult ).to.be.null;
+
+ expect( conversionResult ).to.be.instanceof( ModelDocumentFragment );
+ expect( conversionResult.childCount ).to.equal( 0 );
} );
it( 'should stop to attribute conversion if creating function returned null', () => {
diff --git a/tests/conversion/view-to-model-converters.js b/tests/conversion/view-to-model-converters.js
index fc9a9bd80..fae127644 100644
--- a/tests/conversion/view-to-model-converters.js
+++ b/tests/conversion/view-to-model-converters.js
@@ -66,11 +66,13 @@ describe( 'view-to-model-converters', () => {
let conversionResult = dispatcher.convert( viewText, objWithContext );
- expect( conversionResult ).to.be.null;
+ expect( conversionResult ).to.be.instanceof( ModelDocumentFragment );
+ expect( conversionResult.childCount ).to.equal( 0 );
conversionResult = dispatcher.convert( viewText, { context: [ '$block' ] } );
expect( conversionResult ).to.be.instanceof( ModelDocumentFragment );
+ expect( conversionResult.childCount ).to.equal( 1 );
expect( conversionResult.getChild( 0 ) ).to.be.instanceof( ModelText );
expect( conversionResult.getChild( 0 ).data ).to.equal( 'foobar' );
} );
diff --git a/tests/conversion/viewconversiondispatcher.js b/tests/conversion/viewconversiondispatcher.js
index 9c3a712f3..333b3a42a 100644
--- a/tests/conversion/viewconversiondispatcher.js
+++ b/tests/conversion/viewconversiondispatcher.js
@@ -5,7 +5,6 @@
import ViewConversionDispatcher from '../../src/conversion/viewconversiondispatcher';
import ViewContainerElement from '../../src/view/containerelement';
-import ViewAttributeElement from '../../src/view/attributeelement';
import ViewDocumentFragment from '../../src/view/documentfragment';
import ViewText from '../../src/view/text';
@@ -14,7 +13,16 @@ import ModelElement from '../../src/model/element';
import ModelDocumentFragment from '../../src/model/documentfragment';
import { stringify } from '../../src/dev-utils/model';
+import log from '@ckeditor/ckeditor5-utils/src/log';
+
+// Stored in case it is silenced and has to be restored.
+const logWarn = log.warn;
+
describe( 'ViewConversionDispatcher', () => {
+ afterEach( () => {
+ log.warn = logWarn;
+ } );
+
describe( 'constructor()', () => {
it( 'should create ViewConversionDispatcher with passed api', () => {
const apiObj = {};
@@ -34,6 +42,8 @@ describe( 'ViewConversionDispatcher', () => {
} );
it( 'should fire viewCleanup event on converted view part', () => {
+ silenceWarnings();
+
sinon.spy( dispatcher, 'fire' );
const viewP = new ViewContainerElement( 'p' );
@@ -43,6 +53,8 @@ describe( 'ViewConversionDispatcher', () => {
} );
it( 'should fire proper events', () => {
+ silenceWarnings();
+
const viewText = new ViewText( 'foobar' );
const viewElement = new ViewContainerElement( 'p', null, viewText );
const viewFragment = new ViewDocumentFragment( viewElement );
@@ -59,15 +71,17 @@ describe( 'ViewConversionDispatcher', () => {
} );
it( 'should convert ViewText', () => {
+ const spy = sinon.spy();
const viewText = new ViewText( 'foobar' );
dispatcher.on( 'text', ( evt, data, consumable, conversionApi ) => {
- const result = {
- eventName: evt.name,
- input: data.input,
- // Check whether additional data has been passed.
- foo: data.foo
- };
+ // 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.foo ).to.equal( 'bar' );
// Check whether consumable has appropriate value to consume.
expect( consumable.consume( data.input ) ).to.be.true;
@@ -77,30 +91,31 @@ describe( 'ViewConversionDispatcher', () => {
// Set conversion result to `output` property of `data`.
// Later we will check if it was returned by `convert` method.
- data.output = result;
+ data.output = new ModelText( data.foo );
} );
// Use `additionalData` parameter to check if it was passed to the event.
const conversionResult = dispatcher.convert( viewText, { foo: 'bar' } );
// Check conversion result.
- expect( conversionResult ).to.deep.equal( {
- eventName: 'text',
- input: viewText,
- foo: 'bar'
- } );
+ // Result should be wrapped in document fragment.
+ expect( conversionResult ).to.be.instanceof( ModelDocumentFragment );
+ expect( conversionResult.getChild( 0 ).data ).to.equal( 'bar' );
+ expect( spy.calledOnce ).to.be.true;
} );
it( 'should convert ViewContainerElement', () => {
+ const spy = sinon.spy();
const viewElement = new ViewContainerElement( 'p', { attrKey: 'attrValue' } );
dispatcher.on( 'element', ( evt, data, consumable, conversionApi ) => {
- const result = {
- eventName: evt.name,
- input: data.input,
- // Check whether additional data has been passed.
- foo: data.foo
- };
+ // 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.foo ).to.equal( 'bar' );
// Check whether consumable has appropriate value to consume.
expect( consumable.consume( data.input, { name: true } ) ).to.be.true;
@@ -111,30 +126,31 @@ describe( 'ViewConversionDispatcher', () => {
// Set conversion result to `output` property of `data`.
// Later we will check if it was returned by `convert` method.
- data.output = result;
+ data.output = new ModelElement( 'paragraph' );
} );
// Use `additionalData` parameter to check if it was passed to the event.
const conversionResult = dispatcher.convert( viewElement, { foo: 'bar' } );
// Check conversion result.
- expect( conversionResult ).to.deep.equal( {
- eventName: 'element:p',
- input: viewElement,
- foo: 'bar'
- } );
+ // Result should be wrapped in document fragment.
+ expect( conversionResult ).to.be.instanceof( ModelDocumentFragment );
+ expect( conversionResult.getChild( 0 ).name ).to.equal( 'paragraph' );
+ expect( spy.calledOnce ).to.be.true;
} );
it( 'should convert ViewDocumentFragment', () => {
+ const spy = sinon.spy();
const viewFragment = new ViewDocumentFragment();
dispatcher.on( 'documentFragment', ( evt, data, consumable, conversionApi ) => {
- const result = {
- eventName: evt.name,
- input: data.input,
- // Check whether additional data has been passed.
- foo: data.foo
- };
+ // 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.foo ).to.equal( 'bar' );
// Check whether consumable has appropriate value to consume.
expect( consumable.consume( data.input ) ).to.be.true;
@@ -144,44 +160,16 @@ describe( 'ViewConversionDispatcher', () => {
// Set conversion result to `output` property of `data`.
// Later we will check if it was returned by `convert` method.
- data.output = result;
+ data.output = new ModelDocumentFragment( [ new ModelText( 'foo' ) ] );
} );
// Use `additionalData` parameter to check if it was passed to the event.
const conversionResult = dispatcher.convert( viewFragment, { foo: 'bar' } );
// Check conversion result.
- expect( conversionResult ).to.deep.equal( {
- eventName: 'documentFragment',
- input: viewFragment,
- foo: 'bar'
- } );
- } );
-
- it( 'should always wrap converted element by ModelDocumentFragment', () => {
- const viewElement = new ViewContainerElement( 'p' );
-
- dispatcher.on( 'element', ( evt, data ) => {
- data.output = new ModelElement( 'paragraph' );
- } );
-
- const documentFragment = dispatcher.convert( viewElement, { foo: 'bar' } );
-
- expect( documentFragment ).to.instanceof( ModelDocumentFragment );
- expect( stringify( documentFragment ) ).to.equal( '' );
- } );
-
- it( 'should not wrap ModelDocumentFragment', () => {
- const viewFragment = new ViewDocumentFragment();
-
- dispatcher.on( 'documentFragment', ( evt, data ) => {
- data.output = new ModelDocumentFragment();
- } );
-
- const documentFragment = dispatcher.convert( viewFragment );
-
- expect( documentFragment ).to.instanceof( ModelDocumentFragment );
- expect( documentFragment.childCount ).to.equal( 0 );
+ expect( conversionResult ).to.be.instanceof( ModelDocumentFragment );
+ expect( conversionResult.getChild( 0 ).data ).to.equal( 'foo' );
+ expect( spy.calledOnce ).to.be.true;
} );
it( 'should extract temporary markers stamps from converter element and create static markers list', () => {
@@ -207,91 +195,169 @@ describe( 'ViewConversionDispatcher', () => {
} );
} );
- describe( 'conversionApi#convertItem', () => {
- it( 'should convert view elements and view text', () => {
- const dispatcher = new ViewConversionDispatcher();
- const viewFragment = new ViewDocumentFragment( [
- new ViewContainerElement( 'p' ), new ViewText( 'foobar' )
- ] );
+ describe( 'conversionApi', () => {
+ let spy, spyP, spyText, viewP, viewText, modelP, modelText, consumableMock, dispatcher;
+ let spyNull, spyArray, viewDiv, viewNull, viewArray;
+
+ beforeEach( () => {
+ spy = sinon.spy();
+ spyP = sinon.spy();
+ spyText = sinon.spy();
+
+ viewP = new ViewContainerElement( 'p' );
+ viewText = new ViewText( 'foobar' );
+ modelP = new ModelElement( 'paragraph' );
+ modelText = new ModelText( 'foobar' );
+
+ consumableMock = {};
+
+ dispatcher = new ViewConversionDispatcher();
+
+ dispatcher.on( 'element:p', ( evt, data, consumable ) => {
+ spyP();
+
+ expect( data.foo ).to.equal( 'bar' );
+ expect( consumable ).to.equal( consumableMock );
- dispatcher.on( 'text', ( evt, data ) => {
- data.output = { text: data.input.data };
+ data.output = modelP;
} );
- dispatcher.on( 'element:p', ( evt, data ) => {
- data.output = { name: 'p' };
+ dispatcher.on( 'text', ( evt, data, consumable ) => {
+ spyText();
+
+ expect( data.foo ).to.equal( 'bar' );
+ expect( consumable ).to.equal( consumableMock );
+
+ data.output = modelText;
} );
- dispatcher.on( 'documentFragment', ( evt, data, consumable, conversionApi ) => {
- data.output = [];
+ 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.
+
+ dispatcher.on( 'element:null', ( evt, data ) => {
+ spyNull();
- for ( let child of data.input.getChildren() ) {
- data.output.push( conversionApi.convertItem( child ) );
- }
+ data.output = null;
} );
- expect( dispatcher.convert( viewFragment ) ).to.deep.equal( [
- { name: 'p' },
- { text: 'foobar' }
- ] );
+ dispatcher.on( 'element:array', ( evt, data ) => {
+ spyArray();
+
+ data.output = [ new ModelText( 'foo' ) ];
+ } );
} );
- } );
- describe( 'conversionApi#convertChildren', () => {
- it( 'should fire proper events for all children of passed view part', () => {
- const dispatcher = new ViewConversionDispatcher();
- const viewFragment = new ViewDocumentFragment( [
- new ViewContainerElement( 'p' ), new ViewText( 'foobar' )
- ] );
+ describe( 'convertItem', () => {
+ it( 'should pass consumable and additional data to proper converter and return data.output', () => {
+ silenceWarnings();
- dispatcher.on( 'text', ( evt, data ) => {
- data.output = { text: data.input.data };
- } );
+ dispatcher.on( 'documentFragment', ( evt, data, consumable, conversionApi ) => {
+ spy();
- dispatcher.on( 'element:p', ( evt, data ) => {
- data.output = { name: 'p' };
+ expect( conversionApi.convertItem( viewP, consumableMock, data ) ).to.equal( modelP );
+ expect( conversionApi.convertItem( viewText, consumableMock, data ) ).to.equal( modelText );
+ } );
+
+ dispatcher.convert( new ViewDocumentFragment(), { foo: 'bar' } );
+
+ expect( spy.calledOnce ).to.be.true;
+ expect( spyP.calledOnce ).to.be.true;
+ expect( spyText.calledOnce ).to.be.true;
} );
- dispatcher.on( 'documentFragment', ( evt, data, consumable, conversionApi ) => {
- data.output = conversionApi.convertChildren( data.input );
+ it( 'should do nothing if element was not converted', () => {
+ sinon.spy( log, 'warn' );
+
+ dispatcher.on( 'documentFragment', ( evt, data, consumable, conversionApi ) => {
+ spy();
+
+ expect( conversionApi.convertItem( viewDiv ) ).to.equal( null );
+ expect( conversionApi.convertItem( viewNull ) ).to.equal( null );
+ } );
+
+ dispatcher.convert( new ViewDocumentFragment() );
+
+ expect( spy.calledOnce ).to.be.true;
+ expect( spyNull.calledOnce ).to.be.true;
+ expect( log.warn.called ).to.be.false;
+
+ log.warn.restore();
} );
- expect( dispatcher.convert( viewFragment ) ).to.deep.equal( [
- { name: 'p' },
- { text: 'foobar' }
- ] );
- } );
+ it( 'should return null if element was incorrectly converted and log a warning', () => {
+ sinon.spy( log, 'warn' );
- it( 'should flatten structure of non-converted elements', () => {
- const dispatcher = new ViewConversionDispatcher();
+ dispatcher.on( 'documentFragment', ( evt, data, consumable, conversionApi ) => {
+ spy();
- dispatcher.on( 'text', ( evt, data ) => {
- data.output = data.input.data;
+ expect( conversionApi.convertItem( viewArray ) ).to.equal( null );
+ } );
+
+ dispatcher.convert( new ViewDocumentFragment() );
+
+ expect( spy.calledOnce ).to.be.true;
+ expect( spyArray.calledOnce ).to.be.true;
+ expect( log.warn.calledOnce ).to.be.true;
+
+ log.warn.restore();
} );
+ } );
- dispatcher.on( 'element', ( evt, data, consumable, conversionApi ) => {
- data.output = conversionApi.convertChildren( data.input, consumable );
+ describe( 'convertChildren', () => {
+ it( 'should fire conversion for all children of passed element and return conversion results wrapped in document fragment', () => {
+ silenceWarnings();
+
+ dispatcher.on( 'documentFragment', ( evt, data, consumable, conversionApi ) => {
+ spy();
+
+ const result = conversionApi.convertChildren( data.input, consumableMock, data );
+
+ expect( result ).to.be.instanceof( ModelDocumentFragment );
+ expect( result.childCount ).to.equal( 2 );
+ expect( result.getChild( 0 ) ).to.equal( modelP );
+ expect( result.getChild( 1 ) ).to.equal( modelText );
+ } );
+
+ dispatcher.convert( new ViewDocumentFragment( [ viewP, viewText ] ), { foo: 'bar' } );
+
+ expect( spy.calledOnce ).to.be.true;
+ expect( spyP.calledOnce ).to.be.true;
+ expect( spyText.calledOnce ).to.be.true;
} );
- const viewStructure = new ViewContainerElement( 'div', null, [
- new ViewContainerElement( 'p', null, [
- new ViewContainerElement( 'span', { class: 'nice' }, [
- new ViewAttributeElement( 'a', { href: 'foo.html' }, new ViewText( 'foo' ) ),
- new ViewText( ' bar ' ),
- new ViewAttributeElement( 'i', null, new ViewText( 'xyz' ) )
- ] )
- ] ),
- new ViewContainerElement( 'p', null, [
- new ViewAttributeElement( 'strong', null, [
- new ViewText( 'aaa ' ),
- new ViewAttributeElement( 'span', null, new ViewText( 'bbb' ) ),
- new ViewText( ' ' ),
- new ViewAttributeElement( 'a', { href: 'bar.html' }, new ViewText( 'ccc' ) )
- ] )
- ] )
- ] );
-
- expect( dispatcher.convert( viewStructure ) ).to.deep.equal( [ 'foo', ' bar ', 'xyz', 'aaa ', 'bbb', ' ', 'ccc' ] );
+ it( 'should filter out incorrectly converted elements and log warnings', () => {
+ sinon.spy( log, 'warn' );
+
+ dispatcher.on( 'documentFragment', ( evt, data, consumable, conversionApi ) => {
+ spy();
+
+ const result = conversionApi.convertChildren( data.input, consumableMock, data );
+
+ expect( result ).to.be.instanceof( ModelDocumentFragment );
+ expect( result.childCount ).to.equal( 2 );
+ expect( result.getChild( 0 ) ).to.equal( modelP );
+ expect( result.getChild( 1 ) ).to.equal( modelText );
+ } );
+
+ dispatcher.convert( new ViewDocumentFragment( [ viewArray, viewP, viewDiv, viewText, viewNull ] ), { foo: 'bar' } );
+
+ expect( spy.calledOnce ).to.be.true;
+ expect( spyNull.calledOnce ).to.be.true;
+ expect( spyArray.calledOnce ).to.be.true;
+ expect( log.warn.calledOnce ).to.be.true;
+
+ log.warn.restore();
+ } );
} );
} );
+
+ // Silences warnings that pop up in tests. Use when the test checks a specific functionality and we are not interested in those logs.
+ // No need to restore `log.warn` - it is done in `afterEach()`.
+ function silenceWarnings() {
+ log.warn = () => {};
+ }
} );