/** * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md. */ import AttributeCommand from '../src/attributecommand'; import ModelTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/modeltesteditor'; import Range from '@ckeditor/ckeditor5-engine/src/model/range'; import Position from '@ckeditor/ckeditor5-engine/src/model/position'; import { setData, getData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; describe( 'AttributeCommand', () => { const attrKey = 'bold'; let editor, command, model, doc, root; beforeEach( () => { return ModelTestEditor .create() .then( newEditor => { editor = newEditor; model = editor.model; doc = model.document; root = doc.getRoot(); command = new AttributeCommand( editor, attrKey ); model.schema.register( 'p', { inheritAllFrom: '$block' } ); model.schema.register( 'h1', { inheritAllFrom: '$block' } ); model.schema.register( 'img', { allowWhere: [ '$block', '$text' ], isObject: true } ); model.schema.on( 'checkAttribute', ( evt, args ) => { const ctx = args[ 0 ]; const attributeName = args[ 1 ]; // Allow 'bold' on p>$text. if ( ctx.endsWith( 'p $text' ) && attributeName == 'bold' ) { evt.stop(); evt.return = true; } }, { priority: 'high' } ); } ); } ); afterEach( () => { command.destroy(); return editor.destroy(); } ); describe( 'value', () => { it( 'is true when selection has the attribute', () => { model.change( () => { doc.selection.setAttribute( attrKey, true ); } ); expect( command.value ).to.be.true; } ); it( 'is false when selection does not have the attribute', () => { model.change( () => { doc.selection.removeAttribute( attrKey ); } ); expect( command.value ).to.be.false; } ); // See https://github.com/ckeditor/ckeditor5-core/issues/73#issuecomment-311572827. it( 'is false when selection contains object with nested editable', () => { model.schema.register( 'caption', { allowContentOf: '$block', allowIn: 'img', isLimit: true } ); model.schema.extend( '$text', { allowIn: 'caption', allowAttributes: 'bold' } ); setData( model, '
[
[
f[]oo
' ); expect( command.isEnabled ).to.be.true; } ); it( 'should return false if characters with the attribute cannot be placed at caret position', () => { setData( model, '[foo]
' ); expect( command.isEnabled ).to.be.true; } ); it( 'should return false if there are no nodes in selection that can have the attribute', () => { setData( model, 'fo[ob]ar
' ); command.isEnabled = false; command.execute(); expect( getData( model ) ).to.equal( 'fo[ob]ar
' ); } ); it( 'should add attribute on selected nodes if the command value was false', () => { setData( model, 'a[bc<$text bold="true">fo]obar$text>xyz
' ); expect( command.value ).to.be.false; command.execute(); expect( command.value ).to.be.true; expect( getData( model ) ).to.equal( 'a[<$text bold="true">bcfo]obar$text>xyz
' ); } ); it( 'should remove attribute from selected nodes if the command value was true', () => { setData( model, 'abc[<$text bold="true">foo]bar$text>xyz
' ); expect( command.value ).to.be.true; command.execute(); expect( getData( model ) ).to.equal( 'abc[foo]<$text bold="true">bar$text>xyz
' ); expect( command.value ).to.be.false; } ); it( 'should add attribute on selected nodes if execute parameter was set to true', () => { setData( model, 'abc<$text bold="true">foob[ar$text>x]yz
' ); expect( command.value ).to.be.true; command.execute( { forceValue: true } ); expect( command.value ).to.be.true; expect( getData( model ) ).to.equal( 'abc<$text bold="true">foob[arx$text>]yz
' ); } ); it( 'should remove attribute on selected nodes if execute parameter was set to false', () => { setData( model, 'a[bc<$text bold="true">fo]obar$text>xyz
' ); command.execute( { forceValue: false } ); expect( command.value ).to.be.false; expect( getData( model ) ).to.equal( 'a[bcfo]<$text bold="true">obar$text>xyz
' ); } ); it( 'should change selection attribute if selection is collapsed in non-empty parent', () => { setData( model, 'a[]bc<$text bold="true">foobar$text>xyz
' ); expect( command.value ).to.be.false; command.execute(); expect( command.value ).to.be.true; expect( doc.selection.hasAttribute( 'bold' ) ).to.be.true; command.execute(); expect( command.value ).to.be.false; expect( doc.selection.hasAttribute( 'bold' ) ).to.be.false; } ); it( 'should not store attribute change on selection if selection is collapsed in non-empty parent', () => { setData( model, 'a[]bc<$text bold="true">foobar$text>xyz
' ); command.execute(); // It should not save that bold was executed at position ( root, [ 0, 1 ] ). model.change( () => { // Simulate clicking right arrow key by changing selection ranges. doc.selection.setRanges( [ new Range( new Position( root, [ 0, 2 ] ), new Position( root, [ 0, 2 ] ) ) ] ); // Get back to previous selection. doc.selection.setRanges( [ new Range( new Position( root, [ 0, 1 ] ), new Position( root, [ 0, 1 ] ) ) ] ); } ); expect( command.value ).to.be.false; } ); it( 'should change selection attribute and store it if selection is collapsed in empty parent', () => { setData( model, 'abc<$text bold="true">foobar$text>xyz
[]
' ); expect( command.value ).to.be.false; command.execute(); expect( command.value ).to.be.true; expect( doc.selection.hasAttribute( 'bold' ) ).to.be.true; // Attribute should be stored. // Simulate clicking somewhere else in the editor. model.change( () => { doc.selection.setRanges( [ new Range( new Position( root, [ 0, 2 ] ), new Position( root, [ 0, 2 ] ) ) ] ); } ); expect( command.value ).to.be.false; // Go back to where attribute was stored. model.change( () => { doc.selection.setRanges( [ new Range( new Position( root, [ 1, 0 ] ), new Position( root, [ 1, 0 ] ) ) ] ); } ); // Attribute should be restored. expect( command.value ).to.be.true; command.execute(); expect( command.value ).to.be.false; expect( doc.selection.hasAttribute( 'bold' ) ).to.be.false; } ); it( 'should not apply attribute change where it would invalid schema', () => { model.schema.register( 'image', { inheritAllFrom: '$block' } ); setData( model, 'ab[c<$text bold="true">foobar$text>xy
]z
ab[<$text bold="true">c$text><$text bold="true">foobarxy$text>
]z
a[bc<$text bold="true">fo]obar$text>xyz
' ); model.change( writer => { expect( writer.batch.deltas.length ).to.equal( 0 ); command.execute(); expect( writer.batch.deltas.length ).to.equal( 1 ); } ); expect( getData( model ) ).to.equal( 'a[<$text bold="true">bcfo]obar$text>xyz
' ); } ); describe( 'should cause firing model document changesDone event', () => { let spy; beforeEach( () => { spy = sinon.spy(); } ); it( 'collapsed selection in non-empty parent', () => { setData( model, 'x[]y
' ); doc.on( 'changesDone', spy ); command.execute(); expect( spy.calledOnce ).to.be.true; } ); it( 'non-collapsed selection', () => { setData( model, '[xy]
' ); doc.on( 'changesDone', spy ); command.execute(); expect( spy.calledOnce ).to.be.true; } ); it( 'in empty parent', () => { setData( model, '[]
' ); doc.on( 'changesDone', spy ); command.execute(); expect( spy.calledOnce ).to.be.true; } ); } ); } ); } );