/** * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md. */ import ModelTestEditor from 'tests/core/_utils/modeltesteditor.js'; import Range from 'ckeditor5/engine/model/range.js'; import Position from 'ckeditor5/engine/model/position.js'; import UndoEngine from 'ckeditor5/undo/undoengine.js'; import { setData, getData } from 'ckeditor5/engine/dev-utils/model.js'; let editor, doc, root; beforeEach( () => { return ModelTestEditor.create( { plugins: [ UndoEngine ] } ) .then( newEditor => { editor = newEditor; doc = editor.document; doc.schema.registerItem( 'p', '$block' ); root = doc.getRoot(); } ); } ); function setSelection( pathA, pathB ) { doc.selection.setRanges( [ new Range( new Position( root, pathA ), new Position( root, pathB ) ) ] ); } function input( input ) { setData( doc, input ); } function output( output ) { expect( getData( doc ) ).to.equal( output ); } function undoDisabled() { expect( editor.commands.get( 'undo' ).isEnabled ).to.be.false; } describe( 'UndoEngine integration', () => { describe( 'adding and removing content', () => { it( 'add and undo', () => { input( '
fo[]o
bar
' ); doc.batch().insert( doc.selection.getFirstPosition(), 'zzz' ); output( 'fozzz[]o
bar
' ); editor.execute( 'undo' ); output( 'fo[]o
bar
' ); undoDisabled(); } ); it( 'multiple adding and undo', () => { input( 'fo[]o
bar
' ); doc.batch() .insert( doc.selection.getFirstPosition(), 'zzz' ) .insert( new Position( root, [ 1, 0 ] ), 'xxx' ); output( 'fozzz[]o
xxxbar
' ); setSelection( [ 1, 0 ], [ 1, 0 ] ); doc.batch().insert( doc.selection.getFirstPosition(), 'yyy' ); output( 'fozzzo
yyy[]xxxbar
' ); editor.execute( 'undo' ); output( 'fozzzo
[]xxxbar
' ); editor.execute( 'undo' ); output( 'fo[]o
bar
' ); undoDisabled(); } ); it( 'multiple adding mixed with undo', () => { input( 'fo[]o
bar
' ); doc.batch().insert( doc.selection.getFirstPosition(), 'zzz' ); output( 'fozzz[]o
bar
' ); setSelection( [ 1, 0 ], [ 1, 0 ] ); doc.batch().insert( doc.selection.getFirstPosition(), 'yyy' ); output( 'fozzzo
yyy[]bar
' ); editor.execute( 'undo' ); output( 'fozzzo
[]bar
' ); setSelection( [ 0, 0 ], [ 0, 0 ] ); doc.batch().insert( doc.selection.getFirstPosition(), 'xxx' ); output( 'xxx[]fozzzo
bar
' ); editor.execute( 'undo' ); output( '[]fozzzo
bar
' ); editor.execute( 'undo' ); output( 'fo[]o
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`. output( 'o
b[]ar
' ); editor.execute( 'undo' ); // As above. output( '[]foo
bar
' ); undoDisabled(); } ); it( 'add and remove different parts and undo', () => { input( 'fo[]o
bar
' ); doc.batch().insert( doc.selection.getFirstPosition(), 'zzz' ); output( 'fozzz[]o
bar
' ); setSelection( [ 1, 2 ], [ 1, 2 ] ); doc.batch().remove( Range.createFromPositionAndShift( new Position( root, [ 1, 1 ] ) , 1 ) ); output( 'fozzzo
b[]r
' ); editor.execute( 'undo' ); output( 'fozzzo
ba[]r
' ); editor.execute( 'undo' ); output( 'fo[]o
bar
' ); undoDisabled(); } ); it( 'add and remove same part and undo', () => { input( 'fo[]o
bar
' ); doc.batch().insert( doc.selection.getFirstPosition(), 'zzz' ); output( 'fozzz[]o
bar
' ); doc.batch().remove( Range.createFromPositionAndShift( new Position( root, [ 0, 2 ] ) , 3 ) ); output( 'fo[]o
bar
' ); editor.execute( 'undo' ); output( 'fozzz[]o
bar
' ); editor.execute( 'undo' ); output( 'fo[]o
bar
' ); undoDisabled(); } ); } ); describe( 'moving', () => { it( 'move same content twice then undo', () => { input( 'f[o]z
bar
' ); doc.batch().move( doc.selection.getFirstRange(), new Position( root, [ 1, 0 ] ) ); output( 'fz
[o]bar
' ); doc.batch().move( doc.selection.getFirstRange(), new Position( root, [ 0, 2 ] ) ); output( 'fz[o]
bar
' ); editor.execute( 'undo' ); output( 'fz
[o]bar
' ); editor.execute( 'undo' ); output( 'f[o]z
bar
' ); undoDisabled(); } ); it( 'move content and new parent then undo', () => { input( 'f[o]z
bar
' ); doc.batch().move( doc.selection.getFirstRange(), new Position( root, [ 1, 0 ] ) ); output( 'fz
[o]bar
' ); 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( 'f[o]z
bar
' ); undoDisabled(); } ); } ); describe( 'attributes with other', () => { it( 'attributes then insert inside then undo', () => { input( 'fo[ob]ar
' ); doc.batch().setAttribute( doc.selection.getFirstRange(), 'bold', true ); output( 'fo[<$text bold="true">ob$text>]ar
' ); setSelection( [ 0, 3 ], [ 0, 3 ] ); doc.batch().insert( doc.selection.getFirstPosition(), 'zzz' ); output( 'fo<$text bold="true">o$text>zzz<$text bold="true">[]b$text>ar
' ); expect( doc.selection.getAttribute( 'bold' ) ).to.true; editor.execute( 'undo' ); output( 'fo<$text bold="true">o[]b$text>ar
' ); expect( doc.selection.getAttribute( 'bold' ) ).to.true; editor.execute( 'undo' ); output( 'fo[ob]ar
' ); undoDisabled(); } ); } ); describe( 'wrapping, unwrapping, merging, splitting', () => { it( 'wrap and undo', () => { doc.schema.allow( { name: '$text', inside: '$root' } ); input( 'fo[zb]ar' ); doc.batch().wrap( doc.selection.getFirstRange(), 'p' ); output( 'fo[zb
]ar' ); editor.execute( 'undo' ); output( 'fo[zb]ar' ); undoDisabled(); } ); it( 'wrap, move and undo', () => { doc.schema.allow( { name: '$text', inside: '$root' } ); input( 'fo[zb]ar' ); doc.batch().wrap( doc.selection.getFirstRange(), 'p' ); // Would be better if selection was inside P. output( 'fo[zb
]ar' ); setSelection( [ 2, 0 ], [ 2, 1 ] ); doc.batch().move( doc.selection.getFirstRange(), new Position( root, [ 0 ] ) ); output( '[z]fob
ar' ); editor.execute( 'undo' ); output( 'fo[z]b
ar' ); editor.execute( 'undo' ); output( 'fo[zb]ar' ); undoDisabled(); } ); it( 'unwrap and undo', () => { input( 'foo[]bar
' ); doc.batch().unwrap( doc.selection.getFirstPosition().parent ); output( 'foo[]bar' ); editor.execute( 'undo' ); output( 'foo[]bar
' ); undoDisabled(); } ); it( 'merge and undo', () => { input( 'foo
[]bar
' ); doc.batch().merge( new Position( root, [ 1 ] ) ); // Because selection is stuck withit ends up in graveyard. We have to manually move it to correct node. setSelection( [ 0, 3 ], [ 0, 3 ] ); output( '
foo[]bar
' ); editor.execute( 'undo' ); output( 'foo
[]bar
' ); undoDisabled(); } ); it( 'split and undo', () => { input( 'foo[]bar
' ); doc.batch().split( doc.selection.getFirstPosition() ); // Because selection is stuck withit ends up in wrong node. We have to manually move it to correct node. setSelection( [ 1, 0 ], [ 1, 0 ] ); output( '
foo
[]bar
' ); editor.execute( 'undo' ); output( 'foo[]bar
' ); undoDisabled(); } ); } ); describe( 'other edge cases', () => { it( 'deleteContent between two nodes', () => { input( 'fo[o
b]ar
' ); editor.data.deleteContent( doc.selection, doc.batch(), { merge: true } ); output( 'fo[]ar
' ); editor.execute( 'undo' ); output( 'fo[o
b]ar
' ); } ); } ); } );