/** * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ import ModelTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/modeltesteditor'; import InsertOperation from '@ckeditor/ckeditor5-engine/src/model/operation/insertoperation'; import { getData, setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; import ShiftEnter from '../src/shiftenter'; describe( 'ShiftEnterCommand', () => { let editor, model, doc, schema, command; beforeEach( () => { return ModelTestEditor.create( { plugins: [ ShiftEnter ] } ) .then( newEditor => { editor = newEditor; model = editor.model; doc = model.document; command = editor.commands.get( 'shiftEnter' ); schema = model.schema; // Note: We could use real names like 'paragraph', but that would make test patterns too long. // Plus, this is actually a good test that the algorithm can be used for any model. schema.register( 'img', { allowWhere: '$block' } ); schema.register( 'p', { inheritAllFrom: '$block', allowIn: 'blockLimit' } ); schema.register( 'h', { inheritAllFrom: '$block' } ); schema.register( 'inlineLimit', { allowIn: 'p', isLimit: true } ); schema.register( 'blockLimit', { allowIn: '$root', isLimit: true } ); schema.extend( '$text', { allowIn: [ 'inlineLimit', '$root' ] } ); } ); } ); describe( 'ShiftEnterCommand', () => { it( 'soft breaks a block using parent batch', () => { setData( model, '

foo[]

' ); model.change( writer => { expect( writer.batch.operations ).to.length( 0 ); editor.execute( 'shiftEnter' ); expect( writer.batch.operations ).to.length.above( 0 ); } ); } ); it( 'creates InsertOperation if soft enter is at the beginning of block', () => { setData( model, '

[]foo

' ); editor.execute( 'shiftEnter' ); const operations = Array.from( doc.history.getOperations() ); expect( operations[ operations.length - 1 ] ).to.be.instanceof( InsertOperation ); } ); it( 'creates InsertOperation if soft enter is at the end of block', () => { setData( model, '

foo[]

' ); editor.execute( 'shiftEnter' ); const operations = Array.from( doc.history.getOperations() ); expect( operations[ operations.length - 1 ] ).to.be.instanceof( InsertOperation ); } ); } ); describe( 'execute()', () => { describe( 'collapsed selection', () => { test( 'inserts in the root', 'foo[]bar', 'foo[]bar' ); test( 'inserts inside block', '

x

foo[]bar

y

', '

x

foo[]bar

y

' ); test( 'inserts at the end of block', '

x

foo[]

y

', '

x

foo[]

y

' ); test( 'inserts at the beginning of block', '

x

[]foo

y

', '

x

[]foo

y

' ); } ); describe( 'non-collapsed selection', () => { test( 'deletes the content and inserts the break when directly in the root', 'fo[ob]ar', 'fo[]ar' ); test( 'deletes text and adds break', '

ab[cd]ef

ghi

', '

ab[]ef

ghi

' ); test( 'places selection in the 2nd element', 'ab[c

d]ef

ghi

', 'ab

[]ef

ghi

' ); test( 'does nothing for selection that contains more than one range', '

[abc]

[def]

', '

[abc]

[def]

' ); test( 'inserts break in empty element after it was fully selected', '

x

[abcdef]

y

', '

x

[]

y

' ); // See: comment in softBreakAction(). test( 'leaves one empty element after two were fully selected', '

[abc

def]

', '

[]

' ); test( 'should insert the break in inline limit element - collapsed', '

foo[]bar

', '

foo[]bar

' ); test( 'should insert the break in inline limit elements', '

foo[bar]baz

', '

foo[]baz

' ); test( 'should insert the break at beginning of the inline limit elements', '

foo[bar]baz

', '

foo[]baz

' ); test( 'should insert the break at ending of the inline limit elements', '

foobaz[]

', '

foobaz[]

' ); it( 'should not break inline limit elements - selection partially inside', () => { // Wrap all changes in one block to avoid post-fixing the selection (which is incorret) in the meantime. model.change( () => { setData( model, '

ba[r

f]oo

' ); command.execute(); expect( getData( model ) ).to.equal( '

ba[r

f]oo

' ); } ); } ); test( 'should break paragraph in blockLimit', '

foo[]bar

', '

foo[]bar

' ); it( 'does nothing when break element cannot be inserted in specified context', () => { // Wrap all changes in one block to avoid post-fixing the selection (which is incorret) in the meantime. model.change( () => { setData( model, '[]' ); command.execute(); expect( getData( model ) ).to.equal( '[]' ); } ); } ); it( 'leaves one empty element after two were fully selected (backward)', () => { setData( model, '

[abc

def]

' ); // @TODO: Add option for setting selection direction to model utils. doc.selection._lastRangeBackward = true; command.execute(); expect( getData( model ) ).to.equal( '

[]

' ); } ); it( 'uses DataController.deleteContent', () => { const spy = sinon.spy(); editor.model.on( 'deleteContent', spy ); setData( model, '

[x]

' ); command.execute(); expect( spy.calledOnce ).to.be.true; } ); } ); function test( title, input, output ) { it( title, () => { setData( model, input ); command.execute(); expect( getData( model ) ).to.equal( output ); } ); } } ); describe( '#isEnabled', () => { it( 'should be disabled if cannot be inserted into element', () => { model.change( () => { setData( model, '[]' ); expect( command.isEnabled ).to.equal( false ); } ); } ); it( 'should be enabled for collapsed selection in $root', () => { setData( model, 'Foo.[]' ); expect( command.isEnabled ).to.equal( true ); } ); it( 'should be enabled for collapsed selection in paragraph', () => { setData( model, '

Foo.[]

' ); expect( command.isEnabled ).to.equal( true ); } ); it( 'should be enabled for collapsed selection in heading', () => { setData( model, 'Foo.[]' ); expect( command.isEnabled ).to.equal( true ); } ); it( 'should be enabled for collapsed selection in inline limit element', () => { setData( model, '

Foo.[]

' ); expect( command.isEnabled ).to.equal( true ); } ); it( 'should be enabled for non-collapsed selection in inline limit element', () => { setData( model, '

[Foo.]

' ); expect( command.isEnabled ).to.equal( true ); } ); it( 'should be enabled for collapsed selection in paragraph which is wrapped in a block limit element', () => { setData( model, '

Foo.[]

' ); expect( command.isEnabled ).to.equal( true ); } ); it( 'should be enabled for non-collapsed selection in paragraph which is wrapped in a block limit element', () => { setData( model, '

F[oo.]

' ); expect( command.isEnabled ).to.equal( true ); } ); it( 'should be enabled for non-collapsed selection in paragraphs', () => { setData( model, '

[Foo.

Bar.]

' ); expect( command.isEnabled ).to.equal( true ); } ); it( 'should be enabled for non-collapsed selection in headings', () => { setData( model, '[Foo.Bar.]' ); expect( command.isEnabled ).to.equal( true ); } ); it( 'should be disabled for non-collapsed selection which starts in an inline limit element', () => { // Wrap all changes in one block to avoid post-fixing the selection (which is incorret) in the meantime. model.change( () => { setData( model, '

F[oo.B]ar.

' ); // Refresh it manually because we're in the middle of a change block. command.refresh(); expect( command.isEnabled ).to.equal( false ); } ); } ); it( 'should be disabled for non-collapsed selection which end in an inline limit element', () => { // Wrap all changes in one block to avoid post-fixing the selection (which is incorret) in the meantime. model.change( () => { setData( model, '

F[ooBar].

' ); // Refresh it manually because we're in the middle of a change block. command.refresh(); expect( command.isEnabled ).to.equal( false ); } ); } ); it( 'should be disabled when break element cannot be inserted in specified context', () => { // Wrap all changes in one block to avoid post-fixing the selection (which is incorret) in the meantime. model.change( () => { setData( model, '[]' ); // Refresh it manually because we're in the middle of a change block. command.refresh(); expect( command.isEnabled ).to.equal( false ); } ); } ); it( 'should be disabled for non-collapsed selection which starts in element inside a block limit element', () => { model.change( () => { setData( model, '

F[oo.

B]ar.

' ); // Refresh it manually because we're in the middle of a change block. command.refresh(); expect( command.isEnabled ).to.equal( false ); } ); } ); it( 'should be disabled for non-collapsed selection which ends in element inside a block limit element', () => { model.change( () => { setData( model, '

Fo[o.

Bar].

' ); // Refresh it manually because we're in the middle of a change block. command.refresh(); expect( command.isEnabled ).to.equal( false ); } ); } ); it( 'should be disabled for multi-ranges selection (1)', () => { setData( model, '

[x]

[foo]

' ); expect( command.isEnabled ).to.equal( false ); } ); it( 'should be disabled for multi-ranges selection (2)', () => { setData( model, '

[]x

[]foo

' ); expect( command.isEnabled ).to.equal( false ); } ); } ); } );