/**
* @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md.
*/
import ModelTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/modeltesteditor';
import DeleteCommand from '../src/deletecommand';
import { getData, setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
describe( 'DeleteCommand', () => {
let editor, doc;
testUtils.createSinonSandbox();
beforeEach( () => {
return ModelTestEditor.create()
.then( newEditor => {
editor = newEditor;
doc = editor.document;
const command = new DeleteCommand( editor, 'backward' );
editor.commands.add( 'delete', command );
doc.schema.registerItem( 'paragraph', '$block' );
doc.schema.registerItem( 'heading1', '$block' );
} );
} );
afterEach( () => {
return editor.destroy();
} );
it( 'has direction', () => {
const command = new DeleteCommand( editor, 'forward' );
expect( command ).to.have.property( 'direction', 'forward' );
} );
describe( 'execute()', () => {
it( 'uses enqueueChanges', () => {
setData( doc, 'foo[]bar' );
doc.enqueueChanges( () => {
editor.execute( 'delete' );
// We expect that command is executed in enqueue changes block. Since we are already in
// an enqueued block, the command execution will be postponed. Hence, no changes.
expect( getData( doc ) ).to.equal( '
foo[]bar
' );
} );
// After all enqueued changes are done, the command execution is reflected.
expect( getData( doc ) ).to.equal( 'fo[]bar
' );
} );
it( 'locks buffer when executing', () => {
setData( doc, 'foo[]bar' );
const buffer = editor.commands.get( 'delete' )._buffer;
const lockSpy = testUtils.sinon.spy( buffer, 'lock' );
const unlockSpy = testUtils.sinon.spy( buffer, 'unlock' );
editor.execute( 'delete' );
expect( lockSpy.calledOnce ).to.be.true;
expect( unlockSpy.calledOnce ).to.be.true;
} );
it( 'deletes previous character when selection is collapsed', () => {
setData( doc, 'foo[]bar' );
editor.execute( 'delete' );
expect( getData( doc ) ).to.equal( 'fo[]bar' );
} );
it( 'deletes selection contents', () => {
setData( doc, 'fo[ob]ar' );
editor.execute( 'delete' );
expect( getData( doc ) ).to.equal( 'fo[]ar' );
} );
it( 'merges elements', () => {
setData( doc, 'foo[]bar' );
editor.execute( 'delete' );
expect( getData( doc ) ).to.equal( 'foo[]bar' );
} );
it( 'does not try to delete when selection is at the boundary', () => {
const spy = sinon.spy();
editor.data.on( 'deleteContent', spy );
setData( doc, '[]foo' );
editor.execute( 'delete' );
expect( getData( doc ) ).to.equal( '[]foo' );
expect( spy.callCount ).to.equal( 0 );
} );
it( 'passes options to modifySelection', () => {
const spy = sinon.spy();
editor.data.on( 'modifySelection', spy );
setData( doc, 'foo[]bar' );
editor.commands.get( 'delete' ).direction = 'forward';
editor.execute( 'delete', { unit: 'word' } );
expect( spy.callCount ).to.equal( 1 );
const modifyOpts = spy.args[ 0 ][ 1 ][ 1 ];
expect( modifyOpts ).to.have.property( 'direction', 'forward' );
expect( modifyOpts ).to.have.property( 'unit', 'word' );
} );
it( 'passes options to deleteContent #1', () => {
const spy = sinon.spy();
editor.data.on( 'deleteContent', spy );
setData( doc, 'foo[]bar' );
editor.execute( 'delete' );
expect( spy.callCount ).to.equal( 1 );
const deleteOpts = spy.args[ 0 ][ 1 ][ 2 ];
expect( deleteOpts ).to.have.property( 'doNotResetEntireContent', true );
} );
it( 'passes options to deleteContent #2', () => {
const spy = sinon.spy();
editor.data.on( 'deleteContent', spy );
setData( doc, '[foobar]' );
editor.execute( 'delete' );
expect( spy.callCount ).to.equal( 1 );
const deleteOpts = spy.args[ 0 ][ 1 ][ 2 ];
expect( deleteOpts ).to.have.property( 'doNotResetEntireContent', false );
} );
it( 'leaves an empty paragraph after removing the whole content from editor', () => {
setData( doc, '[Header 1Some text.]' );
editor.execute( 'delete' );
expect( getData( doc ) ).to.equal( '[]' );
} );
it( 'leaves an empty paragraph after removing the whole content inside limit element', () => {
doc.schema.registerItem( 'section', '$root' );
doc.schema.limits.add( 'section' );
doc.schema.allow( { name: 'section', inside: '$root' } );
setData( doc,
'Foo' +
'' +
'[Header 1' +
'Some text.]' +
'' +
'Bar.'
);
editor.execute( 'delete' );
expect( getData( doc ) ).to.equal(
'Foo' +
'' +
'Bar.'
);
} );
it( 'leaves an empty paragraph after removing another paragraph from block element', () => {
doc.schema.registerItem( 'section', '$block' );
doc.schema.registerItem( 'blockQuote', '$block' );
doc.schema.limits.add( 'section' );
doc.schema.allow( { name: 'section', inside: '$root' } );
doc.schema.allow( { name: 'paragraph', inside: 'section' } );
doc.schema.allow( { name: 'blockQuote', inside: 'section' } );
doc.schema.allow( { name: 'paragraph', inside: 'blockQuote' } );
setData( doc, '' );
editor.execute( 'delete' );
expect( getData( doc ) ).to.equal( '' );
} );
it( 'leaves an empty paragraph after removing the whole content when root element was not added as Schema.limits', () => {
doc.schema.limits.delete( '$root' );
setData( doc, '[]' );
editor.execute( 'delete' );
expect( getData( doc ) ).to.equal( '[]' );
} );
it( 'replaces an empty element with paragraph', () => {
setData( doc, '[]' );
editor.execute( 'delete' );
expect( getData( doc ) ).to.equal( '[]' );
} );
it( 'does not replace an element when Backspace or Delete key is held', () => {
setData( doc, 'Bar[]' );
for ( let sequence = 1; sequence < 10; ++sequence ) {
editor.execute( 'delete', { sequence } );
}
expect( getData( doc ) ).to.equal( '[]' );
} );
it( 'does not replace with paragraph in another paragraph already occurs in limit element', () => {
setData( doc, '[]' );
const element = doc.getRoot().getNodeByPath( [ 0 ] );
editor.execute( 'delete' );
expect( element ).is.equal( doc.getRoot().getNodeByPath( [ 0 ] ) );
} );
it( 'does not replace an element if a paragraph is not allowed in current position', () => {
doc.schema.disallow( { name: 'paragraph', inside: '$root' } );
setData( doc, '[]' );
editor.execute( 'delete' );
expect( getData( doc ) ).to.equal( '[]' );
} );
} );
} );