|
@@ -13,12 +13,15 @@ import ModelDocument from '/ckeditor5/engine/treemodel/document.js';
|
|
|
import ModelElement from '/ckeditor5/engine/treemodel/element.js';
|
|
import ModelElement from '/ckeditor5/engine/treemodel/element.js';
|
|
|
import ModelText from '/ckeditor5/engine/treemodel/text.js';
|
|
import ModelText from '/ckeditor5/engine/treemodel/text.js';
|
|
|
import ModelRange from '/ckeditor5/engine/treemodel/range.js';
|
|
import ModelRange from '/ckeditor5/engine/treemodel/range.js';
|
|
|
|
|
+import ModelPosition from '/ckeditor5/engine/treemodel/position.js';
|
|
|
|
|
|
|
|
import ViewElement from '/ckeditor5/engine/treeview/element.js';
|
|
import ViewElement from '/ckeditor5/engine/treeview/element.js';
|
|
|
import ViewContainerElement from '/ckeditor5/engine/treeview/containerelement.js';
|
|
import ViewContainerElement from '/ckeditor5/engine/treeview/containerelement.js';
|
|
|
import ViewAttributeElement from '/ckeditor5/engine/treeview/attributeelement.js';
|
|
import ViewAttributeElement from '/ckeditor5/engine/treeview/attributeelement.js';
|
|
|
import ViewText from '/ckeditor5/engine/treeview/text.js';
|
|
import ViewText from '/ckeditor5/engine/treeview/text.js';
|
|
|
import ViewWriter from '/ckeditor5/engine/treeview/writer.js';
|
|
import ViewWriter from '/ckeditor5/engine/treeview/writer.js';
|
|
|
|
|
+import ViewSelection from '/ckeditor5/engine/treeview/selection.js';
|
|
|
|
|
+import ViewRange from '/ckeditor5/engine/treeview/range.js';
|
|
|
|
|
|
|
|
import Mapper from '/ckeditor5/engine/treecontroller/mapper.js';
|
|
import Mapper from '/ckeditor5/engine/treecontroller/mapper.js';
|
|
|
import ModelConversionDispatcher from '/ckeditor5/engine/treecontroller/modelconversiondispatcher.js';
|
|
import ModelConversionDispatcher from '/ckeditor5/engine/treecontroller/modelconversiondispatcher.js';
|
|
@@ -63,7 +66,7 @@ function viewToString( item ) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
describe( 'Model converter builder', () => {
|
|
describe( 'Model converter builder', () => {
|
|
|
- let dispatcher, modelDoc, modelRoot, viewRoot, mapper, writer;
|
|
|
|
|
|
|
+ let dispatcher, modelDoc, modelRoot, viewRoot, mapper, writer, viewSelection;
|
|
|
|
|
|
|
|
beforeEach( () => {
|
|
beforeEach( () => {
|
|
|
modelDoc = new ModelDocument();
|
|
modelDoc = new ModelDocument();
|
|
@@ -75,8 +78,9 @@ describe( 'Model converter builder', () => {
|
|
|
mapper.bindElements( modelRoot, viewRoot );
|
|
mapper.bindElements( modelRoot, viewRoot );
|
|
|
|
|
|
|
|
writer = new ViewWriter();
|
|
writer = new ViewWriter();
|
|
|
|
|
+ viewSelection = new ViewSelection();
|
|
|
|
|
|
|
|
- dispatcher = new ModelConversionDispatcher( { writer, mapper } );
|
|
|
|
|
|
|
+ dispatcher = new ModelConversionDispatcher( { writer, mapper, viewSelection } );
|
|
|
|
|
|
|
|
dispatcher.on( 'insert:$text', insertText() );
|
|
dispatcher.on( 'insert:$text', insertText() );
|
|
|
dispatcher.on( 'move', move() );
|
|
dispatcher.on( 'move', move() );
|
|
@@ -189,6 +193,85 @@ describe( 'Model converter builder', () => {
|
|
|
|
|
|
|
|
expect( viewToString( viewRoot ) ).to.equal( '<div>foo</div>' );
|
|
expect( viewToString( viewRoot ) ).to.equal( '<div>foo</div>' );
|
|
|
} );
|
|
} );
|
|
|
|
|
+
|
|
|
|
|
+ it( 'selection conversion', () => {
|
|
|
|
|
+ // This test requires collapsed range selection converter (breaking attributes), clearing view selection
|
|
|
|
|
+ // and clearing "artifacts" (empty nodes) before new conversion happens.
|
|
|
|
|
+ dispatcher.on( 'selection', ( evt, selection, consumable, conversionApi ) => {
|
|
|
|
|
+ // Clear artifacts.
|
|
|
|
|
+ for ( let range of conversionApi.viewSelection.getRanges() ) {
|
|
|
|
|
+ const parentNode = range.start.parent;
|
|
|
|
|
+
|
|
|
|
|
+ if ( parentNode instanceof ViewElement && parentNode.getChildCount() === 0 ) {
|
|
|
|
|
+ parentNode.parent.removeChildren( parentNode.getIndex() );
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Break attributes.
|
|
|
|
|
+ const viewPosition = conversionApi.mapper.toViewPosition( selection.getFirstPosition() );
|
|
|
|
|
+ const brokenPosition = conversionApi.writer.breakAttributes( viewPosition );
|
|
|
|
|
+
|
|
|
|
|
+ // Set the new range as the only range of selection (clear old ranges).
|
|
|
|
|
+ conversionApi.viewSelection.setRanges( [ new ViewRange( brokenPosition, brokenPosition ) ], selection.isBackward );
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ // Model converter builder should add selection converter.
|
|
|
|
|
+ BuildModelConverterFor( dispatcher ).fromAttribute( 'italic' ).toElement( ( value ) => new ViewAttributeElement( value ) );
|
|
|
|
|
+
|
|
|
|
|
+ modelRoot.appendChildren( new ModelText( 'foo', { italic: 'em' } ) );
|
|
|
|
|
+
|
|
|
|
|
+ // Set collapsed selection after "f".
|
|
|
|
|
+ const position = new ModelPosition( modelRoot, [ 1 ] );
|
|
|
|
|
+ modelDoc.selection.setRanges( [ new ModelRange( position, position ) ] );
|
|
|
|
|
+ modelDoc.selection._updateAttributes();
|
|
|
|
|
+
|
|
|
|
|
+ // Convert stuff.
|
|
|
|
|
+ dispatcher.convertInsert( ModelRange.createFromElement( modelRoot ) );
|
|
|
|
|
+ dispatcher.convertSelection( modelDoc.selection );
|
|
|
|
|
+
|
|
|
|
|
+ // Check if view structure is ok.
|
|
|
|
|
+ expect( viewToString( viewRoot ) ).to.equal( '<div><em>foo</em></div>' );
|
|
|
|
|
+
|
|
|
|
|
+ // Check if view selection is collapsed after "f" letter.
|
|
|
|
|
+ let ranges = Array.from( viewSelection.getRanges() );
|
|
|
|
|
+ expect( ranges.length ).to.equal( 1 );
|
|
|
|
|
+ expect( ranges[ 0 ].start.isEqual( ranges[ 0 ].end ) ).to.be.true;
|
|
|
|
|
+ expect( ranges[ 0 ].start.parent ).to.be.instanceof( ViewText ); // "foo".
|
|
|
|
|
+ expect( ranges[ 0 ].start.offset ).to.equal( 1 );
|
|
|
|
|
+
|
|
|
|
|
+ // Change selection attribute, convert it.
|
|
|
|
|
+ modelDoc.selection.setAttribute( 'italic', 'i' );
|
|
|
|
|
+ dispatcher.convertSelection( modelDoc.selection );
|
|
|
|
|
+
|
|
|
|
|
+ // Check if view structure has changed.
|
|
|
|
|
+ expect( viewToString( viewRoot ) ).to.equal( '<div><em>f</em><i></i><em>oo</em></div>' );
|
|
|
|
|
+
|
|
|
|
|
+ // Check if view selection is inside new <em> element.
|
|
|
|
|
+ ranges = Array.from( viewSelection.getRanges() );
|
|
|
|
|
+ expect( ranges.length ).to.equal( 1 );
|
|
|
|
|
+ expect( ranges[ 0 ].start.isEqual( ranges[ 0 ].end ) ).to.be.true;
|
|
|
|
|
+ expect( ranges[ 0 ].start.parent.name ).to.equal( 'i' );
|
|
|
|
|
+ expect( ranges[ 0 ].start.offset ).to.equal( 0 );
|
|
|
|
|
+
|
|
|
|
|
+ // Some more tests checking how selection attributes changes are converted:
|
|
|
|
|
+ modelDoc.selection.removeAttribute( 'italic' );
|
|
|
|
|
+ dispatcher.convertSelection( modelDoc.selection );
|
|
|
|
|
+
|
|
|
|
|
+ expect( viewToString( viewRoot ) ).to.equal( '<div><em>f</em><em>oo</em></div>' );
|
|
|
|
|
+ ranges = Array.from( viewSelection.getRanges() );
|
|
|
|
|
+ expect( ranges[ 0 ].start.parent.name ).to.equal( 'div' );
|
|
|
|
|
+ expect( ranges[ 0 ].start.offset ).to.equal( 1 );
|
|
|
|
|
+
|
|
|
|
|
+ modelDoc.selection.setAttribute( 'italic', 'em' );
|
|
|
|
|
+ dispatcher.convertSelection( modelDoc.selection );
|
|
|
|
|
+
|
|
|
|
|
+ expect( viewToString( viewRoot ) ).to.equal( '<div><em>foo</em></div>' );
|
|
|
|
|
+ ranges = Array.from( viewSelection.getRanges() );
|
|
|
|
|
+ expect( ranges.length ).to.equal( 1 );
|
|
|
|
|
+ expect( ranges[ 0 ].start.isEqual( ranges[ 0 ].end ) ).to.be.true;
|
|
|
|
|
+ expect( ranges[ 0 ].start.parent ).to.be.instanceof( ViewText ); // "foo".
|
|
|
|
|
+ expect( ranges[ 0 ].start.offset ).to.equal( 1 );
|
|
|
|
|
+ } );
|
|
|
} );
|
|
} );
|
|
|
|
|
|
|
|
describe( 'model attribute to view attribute conversion', () => {
|
|
describe( 'model attribute to view attribute conversion', () => {
|