/** * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md. */ /* bender-tags: browser-only */ 'use strict'; import Editor from '/ckeditor5/editor.js'; import ModelDocument from '/ckeditor5/engine/model/document.js'; import Range from '/ckeditor5/engine/model/range.js'; import Position from '/ckeditor5/engine/model/position.js'; import Undo from '/ckeditor5/undo/undo.js'; import Creator from '/ckeditor5/creator/creator.js'; import { setData, getData } from '/tests/engine/_utils/model.js'; // import deleteContents from '/ckeditor5/engine/model/composer/deletecontents.js'; let element, editor, doc, root; beforeEach( () => { element = document.createElement( 'div' ); document.body.appendChild( element ); doc = new ModelDocument(); root = doc.createRoot( 'root' ); editor = new Editor( element, { creator: Creator, features: [ Undo ] } ); editor.document = doc; return editor.init(); } ); function setSelection( pathA, pathB ) { doc.selection.setRanges( [ new Range( new Position( root, pathA ), new Position( root, pathB ) ) ] ); } function input( input ) { setData( doc, input, { rootName: 'root' } ); } function output( output ) { expect( getData( doc, { rootName: 'root' } ) ).to.equal( output ); } function undoDisabled() { expect( editor.commands.get( 'undo' ).isEnabled ).to.be.false; } describe( 'undo integration', () => { describe( 'adding and removing content', () => { it( 'add and undo', () => { input( '

foo

bar

' ); doc.batch().insert( doc.selection.getFirstPosition(), 'zzz' ); output( '

fozzzo

bar

' ); editor.execute( 'undo' ); output( '

foo

bar

' ); undoDisabled(); } ); it( 'multiple adding and undo', () => { input( '

foo

bar

' ); doc.batch() .insert( doc.selection.getFirstPosition(), 'zzz' ) .insert( new Position( root, [ 1, 0 ] ), 'xxx' ); output( '

fozzzo

xxxbar

' ); setSelection( [ 1, 0 ], [ 1, 0 ] ); doc.batch().insert( doc.selection.getFirstPosition(), 'yyy' ); output( '

fozzzo

yyyxxxbar

' ); editor.execute( 'undo' ); output( '

fozzzo

xxxbar

' ); editor.execute( 'undo' ); output( '

foo

bar

' ); undoDisabled(); } ); it( 'multiple adding mixed with undo', () => { input( '

foo

bar

' ); doc.batch().insert( doc.selection.getFirstPosition(), 'zzz' ); output( '

fozzzo

bar

' ); setSelection( [ 1, 0 ], [ 1, 0 ] ); doc.batch().insert( doc.selection.getFirstPosition(), 'yyy' ); output( '

fozzzo

yyybar

' ); editor.execute( 'undo' ); output( '

fozzzo

bar

' ); setSelection( [ 0, 0 ], [ 0, 0 ] ); doc.batch().insert( doc.selection.getFirstPosition(), 'xxx' ); output( '

xxxfozzzo

bar

' ); editor.execute( 'undo' ); output( '

fozzzo

bar

' ); editor.execute( 'undo' ); output( '

foo

bar

' ); undoDisabled(); } ); it( 'multiple remove and undo', () => { input( '

foo

bar

' ); doc.batch().remove( Range.createFromPositionAndShift( doc.selection.getFirstPosition(), 2 ) ); output( '

o

bar

' ); setSelection( [ 1, 1 ], [ 1, 1 ] ); doc.batch().remove( Range.createFromPositionAndShift( doc.selection.getFirstPosition(), 2 ) ); output( '

o

b

' ); editor.execute( 'undo' ); // Here is an edge case that selection could be before or after `ar` but selection always ends up after. output( '

o

bar

' ); editor.execute( 'undo' ); // As above. output( '

foo

bar

' ); undoDisabled(); } ); it( 'add and remove different parts and undo', () => { input( '

foo

bar

' ); doc.batch().insert( doc.selection.getFirstPosition(), 'zzz' ); output( '

fozzzo

bar

' ); setSelection( [ 1, 2 ], [ 1, 2 ] ); doc.batch().remove( Range.createFromPositionAndShift( new Position( root, [ 1, 1 ] ) , 1 ) ); output( '

fozzzo

br

' ); editor.execute( 'undo' ); output( '

fozzzo

bar

' ); editor.execute( 'undo' ); output( '

foo

bar

' ); undoDisabled(); } ); //it( 'add and remove same part and undo', () => { // // This test case fails because some operations are transformed to NoOperations incorrectly. // input( '

foo

bar

' ); // // doc.batch().insert( doc.selection.getFirstPosition(), 'zzz' ); // output( '

fozzzo

bar

' ); // // doc.batch().remove( Range.createFromPositionAndShift( new Position( root, [ 0, 2 ] ) , 3 ) ); // output( '

foo

bar

' ); // // editor.execute( 'undo' ); // output( '

fozzzo

bar

' ); // // editor.execute( 'undo' ); // output( '

foo

bar

' ); // // undoDisabled(); //} ); } ); describe( 'moving', () => { //it( 'move same content twice then undo', () => { // // This test case fails because some operations are transformed to NoOperations incorrectly. // input( '

foz

bar

' ); // // doc.batch().move( doc.selection.getFirstRange(), new Position( root, [ 1, 0 ] ) ); // output( '

fz

obar

' ); // // doc.batch().move( doc.selection.getFirstRange(), new Position( root, [ 0, 2 ] ) ); // output( '

fzo

bar

' ); // // editor.execute( 'undo' ); // output( '

fz

obar

' ); // // editor.execute( 'undo' ); // output( '

foz

bar

' ); // // undoDisabled(); //} ); it( 'move content and new parent then undo', () => { input( '

foz

bar

' ); doc.batch().move( doc.selection.getFirstRange(), new Position( root, [ 1, 0 ] ) ); output( '

fz

obar

' ); setSelection( [ 1 ], [ 2 ] ); doc.batch().move( doc.selection.getFirstRange(), new Position( root, [ 0 ] ) ); output( '

obar

fz

' ); editor.execute( 'undo' ); output( '

fz

obar

' ); editor.execute( 'undo' ); output( '

foz

bar

' ); undoDisabled(); } ); } ); describe( 'attributes with other', () => { it( 'attributes then insert inside then undo', () => { input( '

foobar

' ); doc.batch().setAttr( 'bold', true, doc.selection.getFirstRange() ); output( '

fo<$text bold=true>obar

' ); setSelection( [ 0, 3 ], [ 0, 3 ] ); doc.batch().insert( doc.selection.getFirstPosition(), 'zzz' ); output( '

fo<$text bold=true>ozzz<$text bold=true>bar

' ); editor.execute( 'undo' ); output( '

fo<$text bold=true>obar

' ); editor.execute( 'undo' ); output( '

foobar

' ); undoDisabled(); } ); } ); describe( 'wrapping, unwrapping, merging, splitting', () => { it( 'wrap and undo', () => { input( 'fozbar' ); doc.batch().wrap( doc.selection.getFirstRange(), 'p' ); output( 'fo

zb

ar' ); editor.execute( 'undo' ); output( 'fozbar' ); undoDisabled(); } ); //it( 'wrap, move and undo', () => { // input( 'fozbar' ); // // doc.batch().wrap( doc.selection.getFirstRange(), 'p' ); // output( 'fo

zb

ar' ); // // setSelection( [ 2, 0 ], [ 2, 1 ] ); // doc.batch().move( doc.selection.getFirstRange(), new Position( root, [ 0 ] ) ); // output( 'zfo

b

ar' ); // // editor.execute( 'undo' ); // output( 'fo

zb

ar' ); // // // This test case fails here for unknown reason, but "z" letter magically disappears. // // AssertionError: expected 'fobar' to equal 'fozbar' // editor.execute( 'undo' ); // output( 'fozbar' ); // // undoDisabled(); //} ); it( 'unwrap and undo', () => { input( '

foobar

' ); doc.batch().unwrap( doc.selection.getFirstPosition().parent ); output( 'foobar' ); editor.execute( 'undo' ); output( '

foobar

' ); undoDisabled(); } ); //it( 'merge and undo', () => { // input( '

foo

bar

' ); // // doc.batch().merge( new Position( root, [ 1 ] ) ); // // This test fails here because selection is stuck with

element and ends up in graveyard. // // AssertionError: expected '

foobar

' to equal '

foobar

' // output( '

foobar

' ); // // editor.execute( 'undo' ); // // This test fails because when selection is transformed it is first in empty

but when // // "bar" is inserted, it gets moved to the right. // // AssertionError: expected '

foo

bar

' to equal '

foo

bar

' // output( '

foo

bar

' ); // // undoDisabled(); //} ); //it( 'split and undo', () => { // input( '

foobar

' ); // // doc.batch().split( doc.selection.getFirstPosition() ); // // This test fails because selection ends up in wrong node after splitting. // // AssertionError: expected '

foo

bar

' to equal '

foo

bar

' // output( '

foo

bar

' ); // // editor.execute( 'undo' ); // // This test fails because selection after transforming ends up after inserted test. // // AssertionError: expected '

foobar

' to equal '

foobar

' // output( '

foobar

' ); // // undoDisabled(); //} ); } ); describe( 'other edge cases', () => { //it( 'deleteContents between two nodes', () => { // input( '

foo

bar

' ); // // deleteContents( doc.batch(), doc.selection, { merge: true } ); // output( '

foar

' ); // // // This test case fails because of OT problems. // // When the batch is undone, first MergeDelta is reversed to SplitDelta and it is undone. // // Then RemoveOperations are reversed to ReinsertOperation. // // Unfortunately, ReinsertOperation that inserts "o" points to the same position were split happened. // // Then, when ReinsertOperation is transformed by operations of SplitDelta, it ends up in wrong

. // editor.execute( 'undo' ); // output( '

foo

bar

' ); //} ); } ); } );