/**
* @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/* global document */
import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
import UndoEditing from '../src/undoediting';
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
import HeadingEditing from '@ckeditor/ckeditor5-heading/src/headingediting';
import Typing from '@ckeditor/ckeditor5-typing/src/typing';
import Enter from '@ckeditor/ckeditor5-enter/src/enter';
import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard';
import BoldEditing from '@ckeditor/ckeditor5-basic-styles/src/bold/boldediting';
import TableEditing from '@ckeditor/ckeditor5-table/src/tableediting';
import { setData, getData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
describe( 'UndoEditing integration', () => {
let editor, model, doc, root, div;
beforeEach( () => {
div = document.createElement( 'div' );
document.body.appendChild( div );
return ClassicEditor.create( div, {
plugins: [ Paragraph, HeadingEditing, Typing, Enter, Clipboard, BoldEditing, UndoEditing, TableEditing ]
} ).then( newEditor => {
editor = newEditor;
model = editor.model;
doc = model.document;
root = doc.getRoot();
} );
} );
afterEach( () => {
div.remove();
return editor.destroy();
} );
function setSelection( pathA, pathB ) {
model.change( writer => {
writer.setSelection( writer.createRange(
writer.createPositionFromPath( root, pathA ), writer.createPositionFromPath( root, pathB )
) );
} );
}
function input( input ) {
model.enqueueChange( 'transparent', () => {
setData( model, input );
} );
}
function output( output ) {
expect( getData( model ) ).to.equal( output );
}
function undoDisabled() {
expect( editor.commands.get( 'undo' ).isEnabled ).to.be.false;
}
function redoDisabled() {
expect( editor.commands.get( 'redo' ).isEnabled ).to.be.false;
}
describe( 'adding and removing content', () => {
it( 'add and undo', () => {
input( 'fo[]obar' );
model.change( writer => {
writer.insertText( 'zzz', doc.selection.getFirstPosition() );
} );
output( 'fozzz[]obar' );
editor.execute( 'undo' );
output( 'fo[]obar' );
undoDisabled();
} );
it( 'multiple adding and undo', () => {
input( 'fo[]obar' );
model.change( writer => {
writer.insertText( 'zzz', doc.selection.getFirstPosition() );
writer.insertText( 'xxx', writer.createPositionFromPath( root, [ 1, 0 ] ) );
} );
output( 'fozzz[]oxxxbar' );
model.change( writer => {
setSelection( [ 1, 0 ], [ 1, 0 ] );
writer.insertText( 'yyy', doc.selection.getFirstPosition() );
} );
output( 'fozzzoyyy[]xxxbar' );
editor.execute( 'undo' );
output( 'fozzzo[]xxxbar' );
editor.execute( 'undo' );
output( 'fo[]obar' );
undoDisabled();
} );
it( 'multiple adding mixed with undo', () => {
input( 'fo[]obar' );
model.change( writer => {
writer.insertText( 'zzz', doc.selection.getFirstPosition() );
} );
output( 'fozzz[]obar' );
model.change( writer => {
setSelection( [ 1, 0 ], [ 1, 0 ] );
writer.insertText( 'yyy', doc.selection.getFirstPosition() );
} );
output( 'fozzzoyyy[]bar' );
editor.execute( 'undo' );
output( 'fozzzo[]bar' );
model.change( writer => {
setSelection( [ 0, 0 ], [ 0, 0 ] );
writer.insertText( 'xxx', doc.selection.getFirstPosition() );
} );
output( 'xxx[]fozzzobar' );
editor.execute( 'undo' );
output( '[]fozzzobar' );
editor.execute( 'undo' );
output( 'fo[]obar' );
undoDisabled();
} );
it( 'multiple remove and undo', () => {
input( '[]foobar' );
model.change( writer => {
const start = doc.selection.getFirstPosition();
writer.remove( writer.createRange( start, start.getShiftedBy( 2 ) ) );
} );
output( '[]obar' );
model.change( writer => {
setSelection( [ 1, 1 ], [ 1, 1 ] );
const start = doc.selection.getFirstPosition();
writer.remove( writer.createRange( start, start.getShiftedBy( 2 ) ) );
} );
output( 'ob[]' );
editor.execute( 'undo' );
// Here is an edge case that selection could be before or after `ar`.
output( 'obar[]' );
editor.execute( 'undo' );
// As above.
output( 'fo[]obar' );
undoDisabled();
} );
it( 'add and remove different parts and undo', () => {
input( 'fo[]obar' );
model.change( writer => {
writer.insertText( 'zzz', doc.selection.getFirstPosition() );
} );
output( 'fozzz[]obar' );
model.change( writer => {
setSelection( [ 1, 2 ], [ 1, 2 ] );
const start = writer.createPositionFromPath( root, [ 1, 1 ] );
writer.remove( writer.createRange( start, start.getShiftedBy( 1 ) ) );
} );
output( 'fozzzob[]r' );
editor.execute( 'undo' );
output( 'fozzzoba[]r' );
editor.execute( 'undo' );
output( 'fo[]obar' );
undoDisabled();
} );
it( 'add and remove same part and undo', () => {
input( 'fo[]obar' );
model.change( writer => {
writer.insertText( 'zzz', doc.selection.getFirstPosition() );
} );
output( 'fozzz[]obar' );
model.change( writer => {
const start = writer.createPositionFromPath( root, [ 0, 2 ] );
writer.remove( writer.createRange( start, start.getShiftedBy( 3 ) ) );
} );
output( 'fo[]obar' );
editor.execute( 'undo' );
output( 'fozzz[]obar' );
editor.execute( 'undo' );
output( 'fo[]obar' );
undoDisabled();
} );
it( 'undo remove all content', () => {
input( 'foo[]' );
model.change( writer => {
writer.remove( writer.createRangeIn( root ) );
} );
output( '[]' ); // All hail our king and savior, autoparagraphing!
editor.execute( 'undo' );
output( 'foo[]' );
undoDisabled();
} );
it( 'undo insert first content', () => {
input( '' );
output( '[]' ); // All hail our king and savior, autoparagraphing!
model.change( writer => {
writer.remove( writer.createRangeIn( root ) );
writer.insertElement( 'heading1', doc.selection.getFirstPosition() );
} );
output( '[]' );
editor.execute( 'undo' );
output( '[]' );
undoDisabled();
} );
// #72.
it( 'undo paste multiple elements simulation', () => {
input( '' );
const p = root.getChild( 0 );
const pos = model.createPositionFromPath( root, [ 0 ] );
model.change( writer => {
writer.remove( p );
writer.insertElement( 'heading1', pos );
writer.insertElement( 'heading2', pos.getShiftedBy( 1 ) );
} );
output( '[]' );
editor.execute( 'undo' );
output( '[]' );
undoDisabled();
} );
} );
describe( 'moving', () => {
it( 'move same content twice then undo', () => {
input( 'f[o]zbar' );
model.change( writer => {
writer.move( doc.selection.getFirstRange(), writer.createPositionFromPath( root, [ 1, 0 ] ) );
} );
output( 'fz[o]bar' );
model.change( writer => {
writer.move( doc.selection.getFirstRange(), writer.createPositionFromPath( root, [ 0, 2 ] ) );
} );
output( 'fz[o]bar' );
editor.execute( 'undo' );
output( 'fz[o]bar' );
editor.execute( 'undo' );
output( 'f[o]zbar' );
undoDisabled();
} );
it( 'move content and new parent then undo', () => {
input( 'f[o]zbar' );
model.change( writer => {
writer.move( doc.selection.getFirstRange(), writer.createPositionFromPath( root, [ 1, 0 ] ) );
} );
output( 'fz[o]bar' );
model.change( writer => {
setSelection( [ 1 ], [ 2 ] );
writer.move( doc.selection.getFirstRange(), writer.createPositionFromPath( root, [ 0 ] ) );
} );
output( '[obar]fz' );
editor.execute( 'undo' );
output( 'fz[obar]' );
editor.execute( 'undo' );
output( 'f[o]zbar' );
undoDisabled();
} );
} );
describe( 'attributes with other', () => {
it( 'attributes then insert inside then undo', () => {
input( 'fo[ob]ar' );
model.change( writer => {
writer.setAttribute( 'bold', true, doc.selection.getFirstRange() );
} );
output( 'fo[<$text bold="true">ob$text>]ar' );
expect( doc.selection.getAttribute( 'bold' ) ).to.be.true;
model.change( writer => {
setSelection( [ 0, 3 ], [ 0, 3 ] );
writer.insertText( 'zzz', doc.selection.getFirstPosition() );
} );
output( 'fo<$text bold="true">o$text>zzz[]<$text bold="true">b$text>ar' );
expect( doc.selection.getAttribute( 'bold' ) ).to.be.undefined;
editor.execute( 'undo' );
output( 'fo<$text bold="true">o[]b$text>ar' );
expect( doc.selection.getAttribute( 'bold' ) ).to.be.true;
editor.execute( 'undo' );
output( 'fo[ob]ar' );
expect( doc.selection.getAttribute( 'bold' ) ).to.be.undefined;
undoDisabled();
} );
} );
describe( 'wrapping, unwrapping, merging, splitting', () => {
it( 'wrap and undo', () => {
model.schema.extend( '$text', { allowIn: '$root' } );
input( 'fo[zb]ar' );
model.change( writer => {
writer.wrap( doc.selection.getFirstRange(), 'paragraph' );
} );
output( 'fo[zb]ar' );
editor.execute( 'undo' );
output( 'fo[zb]ar' );
undoDisabled();
} );
it( 'wrap, move and undo', () => {
model.schema.extend( '$text', { allowIn: '$root' } );
input( 'fo[zb]ar' );
model.change( writer => {
writer.wrap( doc.selection.getFirstRange(), 'paragraph' );
} );
// Would be better if selection was inside P.
output( 'fo[zb]ar' );
model.change( writer => {
setSelection( [ 2, 0 ], [ 2, 1 ] );
writer.move( doc.selection.getFirstRange(), writer.createPositionFromPath( root, [ 0 ] ) );
} );
output( '[z]fobar' );
editor.execute( 'undo' );
output( 'fo[z]bar' );
editor.execute( 'undo' );
output( 'fo[zb]ar' );
undoDisabled();
} );
it( 'unwrap and undo', () => {
input( 'foo[]bar' );
model.change( writer => {
writer.unwrap( doc.selection.getFirstPosition().parent );
} );
output( 'foo[]bar' );
editor.execute( 'undo' );
output( 'foo[]bar' );
undoDisabled();
} );
it( 'merge and undo', () => {
input( 'foo[]bar' );
model.change( writer => {
writer.merge( writer.createPositionFromPath( root, [ 1 ] ) );
} );
output( 'foo[]bar' );
editor.execute( 'undo' );
output( 'foo[]bar' );
undoDisabled();
} );
it( 'split and undo', () => {
input( 'foo[]bar' );
model.change( writer => {
writer.split( doc.selection.getFirstPosition() );
} );
output( 'foo[]bar' );
editor.execute( 'undo' );
output( 'foo[]bar' );
undoDisabled();
} );
it.skip( 'move, merge and undo', () => {
// Add a "div feature".
model.schema.register( 'div', {
allowWhere: '$block',
allowContentOf: '$root'
} );
editor.conversion.for( 'downcast' ).elementToElement( { model: 'div', view: 'div' } );
editor.conversion.for( 'upcast' ).elementToElement( { model: 'div', view: 'div' } );
input(
'
' +
''
);
const elements = [
root.getNodeByPath( [ 0, 1 ] ),
root.getNodeByPath( [ 1, 0 ] ),
root.getNodeByPath( [ 1, 1 ] )
];
model.change( writer => {
for ( let i = elements.length - 1; i > 0; i-- ) {
merge( elements[ i ], elements[ i - 1 ], writer );
}
} );
output(
'' +
''
);
editor.execute( 'undo' );
output(
'' +
''
);
function merge( element, targetElement, writer ) {
const positionAfterTarget = writer.createPositionAfter( targetElement );
const positionBeforeElement = writer.createPositionBefore( element );
if ( !positionAfterTarget.isEqual( positionBeforeElement ) ) {
writer.move( writer.createRangeOn( element ), positionAfterTarget );
}
writer.merge( positionAfterTarget );
}
} );
} );
// Restoring selection in those examples may be completely off.
describe( 'multiple enters, deletes and typing', () => {
it( 'split, split, split', () => {
input( '12345678' );
split( [ 0, 3 ] );
output( '123[]45678' );
split( [ 1, 4 ] );
output( '1234567[]8' );
split( [ 1, 2 ] );
output( '12345[]678' );
editor.execute( 'undo' );
output( '12345[]678' );
editor.execute( 'undo' );
output( '1234567[]8' );
editor.execute( 'undo' );
output( '123[]45678' );
undoDisabled();
editor.execute( 'redo' );
output( '123[]45678' );
editor.execute( 'redo' );
output( '1234567[]8' );
editor.execute( 'redo' );
output( '12345[]678' );
redoDisabled();
} );
it( 'merge, merge, merge', () => {
input( '12345678' );
merge( [ 1 ] );
output( '123[]45678' );
merge( [ 2 ] );
output( '1234567[]8' );
merge( [ 1 ] );
output( '12345[]678' );
editor.execute( 'undo' );
output( '12345[]678' );
editor.execute( 'undo' );
output( '1234567[]8' );
editor.execute( 'undo' );
output( '123[]45678' );
undoDisabled();
editor.execute( 'redo' );
output( '123[]45678' );
editor.execute( 'redo' );
output( '1234567[]8' );
editor.execute( 'redo' );
output( '12345[]678' );
redoDisabled();
} );
it( 'split, merge, split, merge (same position)', () => {
input( '12345678' );
split( [ 0, 3 ] );
output( '123[]45678' );
merge( [ 1 ] );
output( '123[]45678' );
split( [ 0, 3 ] );
output( '123[]45678' );
merge( [ 1 ] );
output( '123[]45678' );
editor.execute( 'undo' );
output( '123[]45678' );
editor.execute( 'undo' );
output( '123[]45678' );
editor.execute( 'undo' );
output( '123[]45678' );
editor.execute( 'undo' );
output( '123[]45678' );
undoDisabled();
editor.execute( 'redo' );
output( '123[]45678' );
editor.execute( 'redo' );
output( '123[]45678' );
editor.execute( 'redo' );
output( '123[]45678' );
editor.execute( 'redo' );
output( '123[]45678' );
redoDisabled();
} );
it( 'split, split, split, merge, merge, merge', () => {
input( '12345678' );
split( [ 0, 3 ] );
output( '123[]45678' );
split( [ 1, 4 ] );
output( '1234567[]8' );
split( [ 1, 2 ] );
output( '12345[]678' );
merge( [ 1 ] );
output( '123[]45678' );
merge( [ 2 ] );
output( '1234567[]8' );
merge( [ 1 ] );
output( '12345[]678' );
editor.execute( 'undo' );
output( '12345[]678' );
editor.execute( 'undo' );
output( '1234567[]8' );
editor.execute( 'undo' );
output( '123[]45678' );
editor.execute( 'undo' );
output( '12345[]678' );
editor.execute( 'undo' );
output( '1234567[]8' );
editor.execute( 'undo' );
output( '123[]45678' );
undoDisabled();
editor.execute( 'redo' );
output( '123[]45678' );
editor.execute( 'redo' );
output( '1234567[]8' );
editor.execute( 'redo' );
output( '12345[]678' );
editor.execute( 'redo' );
output( '123[]45678' );
editor.execute( 'redo' );
output( '1234567[]8' );
editor.execute( 'redo' );
output( '12345[]678' );
redoDisabled();
} );
it( 'split, split, merge, split, merge (different order)', () => {
input( '12345678' );
split( [ 0, 3 ] );
output( '123[]45678' );
split( [ 1, 2 ] );
output( '12345[]678' );
merge( [ 1 ] );
output( '123[]45678' );
split( [ 1, 1 ] );
output( '123456[]78' );
merge( [ 1 ] );
output( '12345[]678' );
editor.execute( 'undo' );
output( '12345[]678' );
editor.execute( 'undo' );
output( '123456[]78' );
editor.execute( 'undo' );
output( '123[]45678' );
editor.execute( 'undo' );
output( '12345[]678' );
editor.execute( 'undo' );
output( '123[]45678' );
undoDisabled();
editor.execute( 'redo' );
output( '123[]45678' );
editor.execute( 'redo' );
output( '12345[]678' );
editor.execute( 'redo' );
output( '123[]45678' );
editor.execute( 'redo' );
output( '123456[]78' );
editor.execute( 'redo' );
output( '12345[]678' );
redoDisabled();
} );
it( 'split, remove, split, merge, merge', () => {
input( '12345678' );
split( [ 0, 3 ] );
output( '123[]45678' );
remove( [ 1, 4 ] );
remove( [ 1, 3 ] );
output( '12345[]8' );
split( [ 1, 1 ] );
output( '1234[]58' );
merge( [ 1 ] );
output( '123[]458' );
merge( [ 1 ] );
output( '1234[]58' );
editor.execute( 'undo' );
output( '1234[]58' );
editor.execute( 'undo' );
output( '123[]458' );
editor.execute( 'undo' );
output( '1234[]58' );
editor.execute( 'undo' );
output( '1234567[]8' );
editor.execute( 'undo' );
output( '123[]45678' );
undoDisabled();
editor.execute( 'redo' );
output( '123[]45678' );
editor.execute( 'redo' );
output( '12345[]8' );
editor.execute( 'redo' );
output( '1234[]58' );
editor.execute( 'redo' );
output( '123[]458' );
editor.execute( 'redo' );
output( '1234[]58' );
redoDisabled();
} );
it( 'split, typing, split, merge, merge', () => {
input( '12345678' );
split( [ 0, 3 ] );
output( '123[]45678' );
type( [ 1, 4 ], 'x' );
type( [ 1, 5 ], 'y' );
output( '1234567xy[]8' );
split( [ 1, 2 ] );
output( '12345[]67xy8' );
merge( [ 1 ] );
output( '123[]4567xy8' );
merge( [ 1 ] );
output( '12345[]67xy8' );
editor.execute( 'undo' );
output( '12345[]67xy8' );
editor.execute( 'undo' );
output( '123[]4567xy8' );
editor.execute( 'undo' );
output( '12345[]67xy8' );
editor.execute( 'undo' );
output( '1234567[]8' );
editor.execute( 'undo' );
output( '123[]45678' );
undoDisabled();
editor.execute( 'redo' );
output( '123[]45678' );
editor.execute( 'redo' );
output( '1234567xy[]8' );
editor.execute( 'redo' );
output( '12345[]67xy8' );
editor.execute( 'redo' );
output( '123[]4567xy8' );
editor.execute( 'redo' );
output( '12345[]67xy8' );
redoDisabled();
} );
} );
describe( 'other reported cases', () => {
// ckeditor5-engine#t/1051
it( 'rename leaks to other elements on undo #1', () => {
input( '[]FooBar' );
model.change( writer => {
writer.rename( root.getChild( 0 ), 'paragraph' );
} );
output( '[]FooBar' );
model.change( writer => {
writer.split( writer.createPositionAt( root.getChild( 0 ), 1 ) );
} );
output( '[]FooBar' );
model.change( writer => {
writer.merge( writer.createPositionAt( root, 2 ) );
} );
output( '[]FooBar' );
editor.execute( 'undo' );
output( '[]FooBar' );
editor.execute( 'undo' );
output( '[]FooBar' );
editor.execute( 'undo' );
output( '[]FooBar' );
} );
// Similar issue that bases on the same error as above, however here we first merge (above we first split).
it( 'rename leaks to other elements on undo #2', () => {
input( '[]FooBar' );
model.change( writer => {
writer.rename( root.getChild( 0 ), 'heading2' );
} );
output( '[]FooBar' );
model.change( writer => {
writer.merge( writer.createPositionAt( root, 1 ) );
} );
output( '[]FooBar' );
editor.execute( 'undo' );
output( '[]FooBar' );
editor.execute( 'undo' );
output( '[]FooBar' );
} );
// Reverse issue, this time first operation is merge and then rename.
it( 'merge, rename, undo, undo is correct', () => {
input( '[]FooBar' );
model.change( writer => {
writer.merge( writer.createPositionAt( root, 1 ) );
} );
output( '[]FooBar' );
model.change( writer => {
writer.rename( root.getChild( 0 ), 'heading2' );
} );
output( '[]FooBar' );
editor.execute( 'undo' );
output( '[]FooBar' );
editor.execute( 'undo' );
output( '[]FooBar' );
} );
// ckeditor5-engine#t/1053
it( 'wrap, split, undo, undo is correct', () => {
// Add a "div feature".
model.schema.register( 'div', {
allowWhere: '$block',
allowContentOf: '$root'
} );
editor.conversion.for( 'downcast' ).elementToElement( { model: 'div', view: 'div' } );
editor.conversion.for( 'upcast' ).elementToElement( { model: 'div', view: 'div' } );
input( '[]FooBar' );
model.change( writer => {
writer.wrap( writer.createRangeIn( root ), 'div' );
} );
output( '' );
model.change( writer => {
writer.split( writer.createPositionFromPath( root, [ 0, 0, 1 ] ) );
} );
output( '' );
editor.execute( 'undo' );
output( '' );
editor.execute( 'undo' );
output( '[]FooBar' );
} );
// ckeditor5-engine#t/1055
it( 'selection attribute setting: split, bold, merge, undo, undo, undo', () => {
input( 'Foo[]Bar' );
editor.execute( 'enter' );
output( 'Foo[]Bar' );
editor.execute( 'bold' );
output(
'Foo' +
'<$text bold="true">[]$text>' +
'Bar'
);
editor.execute( 'forwardDelete' );
output( 'Foo[]Bar' );
editor.execute( 'undo' );
output(
'Foo' +
'<$text bold="true">[]$text>' +
'Bar'
);
editor.execute( 'undo' );
output( 'Foo[]Bar' );
editor.execute( 'undo' );
output( 'Foo[]Bar' );
} );
// https://github.com/ckeditor/ckeditor5-undo/issues/65#issuecomment-323682195
it( 'undoing split after the element created by split has been removed', () => {
input( 'Foo[]bar' );
editor.execute( 'enter' );
model.change( writer => {
const range = writer.createRange(
writer.createPositionFromPath( root, [ 0, 3 ] ),
writer.createPositionFromPath( root, [ 1, 3 ] )
);
writer.setSelection( range );
editor.execute( 'delete' );
} );
output( 'Foo[]' );
editor.execute( 'undo' );
output( 'Foo[bar]' );
editor.execute( 'undo' );
output( 'Foo[]bar' );
} );
} );
describe( 'clipboard', () => {
function pasteHtml( editor, html ) {
editor.editing.view.document.fire( 'paste', {
dataTransfer: createDataTransfer( { 'text/html': html } ),
stopPropagation() {},
preventDefault() {}
} );
}
function createDataTransfer( data ) {
return {
getData( type ) {
return data[ type ];
},
setData() {}
};
}
// ckeditor5-engine#t/1065
it( 'undo paste into non empty element should not throw and be correct', () => {
model.change( () => {
input( 'Foo[]' );
} );
model.change( () => {
pasteHtml( editor, 'a
b
' );
} );
output( 'Fooab[]' );
model.change( () => {
pasteHtml( editor, 'c
d
' );
} );
output( 'Fooabcd[]' );
editor.execute( 'undo' );
output( 'Fooab[]' );
editor.execute( 'undo' );
output( 'Foo[]' );
} );
// ckeditor5#781
it( 'cutting should not create empty undo step', () => {
input( 'Fo[oba]r' );
editor.editing.view.document.fire( 'cut', {
dataTransfer: createDataTransfer(),
preventDefault() {},
method: 'cut'
} );
editor.execute( 'undo' );
output( 'Fo[oba]r' );
undoDisabled();
} );
} );
describe( 'other edge cases', () => {
it( 'deleteContent between two nodes', () => {
input( 'fo[ob]ar' );
editor.model.deleteContent( doc.selection );
output( 'fo[]ar' );
editor.execute( 'undo' );
output( 'fo[ob]ar' );
} );
// Related to ckeditor5-engine#891 and ckeditor5-list#51.
it( 'change attribute of removed node then undo and redo', () => {
input( '' );
const gy = doc.graveyard;
const p = doc.getRoot().getChild( 0 );
model.change( writer => {
writer.remove( p );
} );
model.change( writer => {
writer.setAttribute( 'bold', true, p );
} );
editor.execute( 'undo' );
editor.execute( 'redo' );
expect( p.root ).to.equal( gy );
expect( p.getAttribute( 'bold' ) ).to.be.true;
} );
it( 'undo table cells merge', () => {
input(
'' +
'' +
'00' +
'[01]' +
'[02]' +
'03' +
'' +
'' +
'10' +
'[11]' +
'[12]' +
'13' +
'' +
'' +
'20' +
'21' +
'22' +
'23' +
'' +
'
'
);
editor.execute( 'mergeTableCells' );
output(
'' +
'' +
'00' +
'' +
'[01' +
'02' +
'11' +
'12]' +
'' +
'03' +
'' +
'' +
'10' +
'13' +
'' +
'' +
'20' +
'21' +
'22' +
'23' +
'' +
'
'
);
editor.execute( 'undo' );
output(
'' +
'' +
'00' +
'[01]' +
'[02]' +
'03' +
'' +
'' +
'10' +
'[11]' +
'[12]' +
'13' +
'' +
'' +
'20' +
'21' +
'22' +
'23' +
'' +
'
'
);
} );
} );
it( 'postfixers should not add another undo step when fixing undo changes', () => {
input( '[]' );
const paragraph = root.getChild( 0 );
// 1. Step - add text and marker to the paragraph.
model.change( writer => {
writer.appendText( 'foo', paragraph );
writer.addMarker( 'marker', { range: writer.createRangeIn( paragraph ), usingOperation: true } );
} );
// 2. Step - remove text from paragraph.
model.change( writer => {
writer.remove( writer.createRangeIn( paragraph ) );
} );
// Register post fixer to remove marker from non-empty paragraph.
model.document.registerPostFixer( writer => {
const hasMarker = model.markers.has( 'marker' );
const isEmpty = paragraph.childCount === 0;
// Remove marker when paragraph is empty.
if ( hasMarker && !isEmpty ) {
writer.removeMarker( 'marker' );
return true;
}
return false;
} );
editor.execute( 'undo' );
// Check if postfixer changes are executed together with undo and there is no additional undo steps.
expect( model.markers.has( 'marker' ) ).to.be.false;
expect( editor.commands.get( 'undo' )._stack.length ).to.equal( 1 );
output( 'foo[]' );
} );
function split( path ) {
setSelection( path.slice(), path.slice() );
editor.execute( 'enter' );
}
function merge( path ) {
const selPath = path.slice();
selPath.push( 0 );
setSelection( selPath, selPath.slice() );
editor.execute( 'delete' );
}
function type( path, text ) {
setSelection( path.slice(), path.slice() );
editor.execute( 'input', { text } );
}
function remove( path ) {
setSelection( path.slice(), path.slice() );
editor.execute( 'delete' );
}
} );