|
@@ -339,6 +339,194 @@ describe( 'EditingController', () => {
|
|
|
} );
|
|
} );
|
|
|
} );
|
|
} );
|
|
|
|
|
|
|
|
|
|
+ describe( 'marker clearing', () => {
|
|
|
|
|
+ let model, modelRoot, editing, domRoot, mcd, p1;
|
|
|
|
|
+
|
|
|
|
|
+ beforeEach( () => {
|
|
|
|
|
+ model = new Model();
|
|
|
|
|
+ modelRoot = model.document.createRoot();
|
|
|
|
|
+
|
|
|
|
|
+ editing = new EditingController( model );
|
|
|
|
|
+
|
|
|
|
|
+ domRoot = document.createElement( 'div' );
|
|
|
|
|
+ domRoot.contentEditable = true;
|
|
|
|
|
+
|
|
|
|
|
+ document.body.appendChild( domRoot );
|
|
|
|
|
+ editing.createRoot( domRoot );
|
|
|
|
|
+
|
|
|
|
|
+ model.schema.register( 'paragraph', { inheritAllFrom: '$block' } );
|
|
|
|
|
+ model.schema.register( 'div', { inheritAllFrom: '$block' } );
|
|
|
|
|
+ buildModelConverter().for( editing.modelToView ).fromElement( 'paragraph' ).toElement( 'p' );
|
|
|
|
|
+ buildModelConverter().for( editing.modelToView ).fromElement( 'div' ).toElement( 'div' );
|
|
|
|
|
+ buildModelConverter().for( editing.modelToView ).fromMarker( 'marker' ).toHighlight( {} );
|
|
|
|
|
+
|
|
|
|
|
+ const modelData = new ModelDocumentFragment( parse(
|
|
|
|
|
+ '<paragraph>foo</paragraph>' +
|
|
|
|
|
+ '<paragraph>bar</paragraph>',
|
|
|
|
|
+ model.schema
|
|
|
|
|
+ )._children );
|
|
|
|
|
+
|
|
|
|
|
+ model.change( writer => {
|
|
|
|
|
+ writer.insert( modelData, modelRoot );
|
|
|
|
|
+ p1 = modelRoot.getChild( 0 );
|
|
|
|
|
+
|
|
|
|
|
+ model.document.selection.addRange( ModelRange.createFromParentsAndOffsets( p1, 0, p1, 0 ) );
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ mcd = editing.modelToView;
|
|
|
|
|
+ sinon.spy( mcd, 'convertMarkerRemove' );
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ afterEach( () => {
|
|
|
|
|
+ document.body.removeChild( domRoot );
|
|
|
|
|
+ editing.destroy();
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ it( 'should remove marker from view if it will be affected by insert operation', () => {
|
|
|
|
|
+ model.change( writer => {
|
|
|
|
|
+ writer.setMarker( 'marker', ModelRange.createFromParentsAndOffsets( p1, 1, p1, 2 ) );
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ // Adding with 'high' priority, because `applyOperation` is decorated - its default callback is fired with 'normal' priority.
|
|
|
|
|
+ model.on( 'applyOperation', () => {
|
|
|
|
|
+ expect( mcd.convertMarkerRemove.calledOnce ).to.be.true;
|
|
|
|
|
+ expect( getViewData( editing.view, { withoutSelection: true } ) ).to.equal( '<p>foo</p><p>bar</p>' );
|
|
|
|
|
+ }, { priority: 'high' } );
|
|
|
|
|
+
|
|
|
|
|
+ model.change( writer => {
|
|
|
|
|
+ writer.insertText( 'a', p1, 0 );
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ expect( getViewData( editing.view, { withoutSelection: true } ) ).to.equal( '<p>af<span>o</span>o</p><p>bar</p>' );
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ it( 'should remove marker from view if it will be affected by remove operation', () => {
|
|
|
|
|
+ model.change( writer => {
|
|
|
|
|
+ writer.setMarker( 'marker', ModelRange.createFromParentsAndOffsets( p1, 1, p1, 2 ) );
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ // Adding with 'high' priority, because `applyOperation` is decorated - its default callback is fired with 'normal' priority.
|
|
|
|
|
+ model.on( 'applyOperation', () => {
|
|
|
|
|
+ expect( mcd.convertMarkerRemove.calledOnce ).to.be.true;
|
|
|
|
|
+ expect( getViewData( editing.view, { withoutSelection: true } ) ).to.equal( '<p>foo</p><p>bar</p>' );
|
|
|
|
|
+ }, { priority: 'high' } );
|
|
|
|
|
+
|
|
|
|
|
+ model.change( writer => {
|
|
|
|
|
+ writer.remove( ModelRange.createFromParentsAndOffsets( p1, 0, p1, 1 ) );
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ expect( getViewData( editing.view, { withoutSelection: true } ) ).to.equal( '<p><span>o</span>o</p><p>bar</p>' );
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ it( 'should remove marker from view if it will be affected by move operation', () => {
|
|
|
|
|
+ model.change( writer => {
|
|
|
|
|
+ writer.setMarker( 'marker', ModelRange.createFromParentsAndOffsets( p1, 1, p1, 2 ) );
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ // Adding with 'high' priority, because `applyOperation` is decorated - its default callback is fired with 'normal' priority.
|
|
|
|
|
+ model.on( 'applyOperation', () => {
|
|
|
|
|
+ expect( mcd.convertMarkerRemove.calledOnce ).to.be.true;
|
|
|
|
|
+ expect( getViewData( editing.view, { withoutSelection: true } ) ).to.equal( '<p>foo</p><p>bar</p>' );
|
|
|
|
|
+ }, { priority: 'high' } );
|
|
|
|
|
+
|
|
|
|
|
+ model.change( writer => {
|
|
|
|
|
+ const p2 = p1.nextSibling;
|
|
|
|
|
+
|
|
|
|
|
+ writer.move( ModelRange.createFromParentsAndOffsets( p2, 0, p2, 2 ), p1, 0 );
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ expect( getViewData( editing.view, { withoutSelection: true } ) ).to.equal( '<p>baf<span>o</span>o</p><p>r</p>' );
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ it( 'should remove marker from view if it will be affected by rename operation', () => {
|
|
|
|
|
+ model.change( writer => {
|
|
|
|
|
+ writer.setMarker( 'marker', ModelRange.createFromParentsAndOffsets( modelRoot, 0, modelRoot, 1 ) );
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ // Adding with 'high' priority, because `applyOperation` is decorated - its default callback is fired with 'normal' priority.
|
|
|
|
|
+ model.on( 'applyOperation', () => {
|
|
|
|
|
+ expect( mcd.convertMarkerRemove.calledOnce ).to.be.true;
|
|
|
|
|
+ expect( getViewData( editing.view, { withoutSelection: true } ) ).to.equal( '<p>foo</p><p>bar</p>' );
|
|
|
|
|
+ }, { priority: 'high' } );
|
|
|
|
|
+
|
|
|
|
|
+ model.change( writer => {
|
|
|
|
|
+ writer.rename( p1, 'div' );
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ expect( getViewData( editing.view, { withoutSelection: true } ) ).to.equal( '<div><span>foo</span></div><p>bar</p>' );
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ it( 'should remove marker from view if it will be affected by marker operation', () => {
|
|
|
|
|
+ model.change( writer => {
|
|
|
|
|
+ writer.setMarker( 'marker', ModelRange.createFromParentsAndOffsets( p1, 1, p1, 2 ) );
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ // Adding with 'high' priority, because `applyOperation` is decorated - its default callback is fired with 'normal' priority.
|
|
|
|
|
+ model.on( 'applyOperation', () => {
|
|
|
|
|
+ expect( mcd.convertMarkerRemove.calledOnce ).to.be.true;
|
|
|
|
|
+ expect( getViewData( editing.view, { withoutSelection: true } ) ).to.equal( '<p>foo</p><p>bar</p>' );
|
|
|
|
|
+ }, { priority: 'high' } );
|
|
|
|
|
+
|
|
|
|
|
+ model.change( writer => {
|
|
|
|
|
+ const p2 = p1.nextSibling;
|
|
|
|
|
+
|
|
|
|
|
+ writer.setMarker( 'marker', ModelRange.createFromParentsAndOffsets( p2, 1, p2, 2 ) );
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ expect( getViewData( editing.view, { withoutSelection: true } ) ).to.equal( '<p>foo</p><p>b<span>a</span>r</p>' );
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ it( 'should remove marker from view if it is removed through marker collection', () => {
|
|
|
|
|
+ model.change( writer => {
|
|
|
|
|
+ writer.setMarker( 'marker', ModelRange.createFromParentsAndOffsets( p1, 1, p1, 2 ) );
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ // Adding with 'high' priority, because `applyOperation` is decorated - its default callback is fired with 'normal' priority.
|
|
|
|
|
+ model.markers.on( 'remove:marker', () => {
|
|
|
|
|
+ expect( mcd.convertMarkerRemove.calledOnce ).to.be.true;
|
|
|
|
|
+ expect( getViewData( editing.view, { withoutSelection: true } ) ).to.equal( '<p>foo</p><p>bar</p>' );
|
|
|
|
|
+ }, { priority: 'low' } );
|
|
|
|
|
+
|
|
|
|
|
+ model.change( () => {
|
|
|
|
|
+ model.markers.remove( 'marker' );
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ expect( getViewData( editing.view, { withoutSelection: true } ) ).to.equal( '<p>foo</p><p>bar</p>' );
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ it( 'should not remove marker if applied operation is an attribute operation', () => {
|
|
|
|
|
+ model.change( writer => {
|
|
|
|
|
+ writer.setMarker( 'marker', ModelRange.createFromParentsAndOffsets( p1, 1, p1, 2 ) );
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ // Adding with 'high' priority, because `applyOperation` is decorated - its default callback is fired with 'normal' priority.
|
|
|
|
|
+ model.on( 'applyOperation', () => {
|
|
|
|
|
+ expect( mcd.convertMarkerRemove.calledOnce ).to.be.false;
|
|
|
|
|
+ expect( getViewData( editing.view, { withoutSelection: true } ) ).to.equal( '<p>f<span>o</span>o</p><p>bar</p>' );
|
|
|
|
|
+ }, { priority: 'high' } );
|
|
|
|
|
+
|
|
|
|
|
+ model.change( writer => {
|
|
|
|
|
+ writer.setAttribute( 'foo', 'bar', ModelRange.createFromParentsAndOffsets( p1, 0, p1, 2 ) );
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ expect( getViewData( editing.view, { withoutSelection: true } ) ).to.equal( '<p>f<span>o</span>o</p><p>bar</p>' );
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ it( 'should not crash if multiple operations affect a marker', () => {
|
|
|
|
|
+ model.change( writer => {
|
|
|
|
|
+ writer.setMarker( 'marker', ModelRange.createFromParentsAndOffsets( p1, 1, p1, 2 ) );
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ model.change( writer => {
|
|
|
|
|
+ writer.insertText( 'a', p1, 0 );
|
|
|
|
|
+ writer.insertText( 'a', p1, 0 );
|
|
|
|
|
+ writer.insertText( 'a', p1, 0 );
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ expect( getViewData( editing.view, { withoutSelection: true } ) ).to.equal( '<p>aaaf<span>o</span>o</p><p>bar</p>' );
|
|
|
|
|
+ } );
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
describe( 'destroy()', () => {
|
|
describe( 'destroy()', () => {
|
|
|
it( 'should remove listenters', () => {
|
|
it( 'should remove listenters', () => {
|
|
|
const model = new Model();
|
|
const model = new Model();
|