/**
* @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
import Typing from '@ckeditor/ckeditor5-typing/src/typing';
import Command from '@ckeditor/ckeditor5-core/src/command';
import { setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
import RestrictedEditingModeEditing from './../src/restrictededitingmodeediting';
import UndoEditing from '@ckeditor/ckeditor5-undo/src/undoediting';
describe( 'RestrictedEditingEditing - commands', () => {
let editor;
testUtils.createSinonSandbox();
describe( 'commands toggling', () => {
let model, firstParagraph;
beforeEach( async () => {
editor = await VirtualTestEditor.create( { plugins: [ Paragraph, Typing, RestrictedEditingModeEditing ] } );
model = editor.model;
setModelData( model, '[]foo bar baz' );
firstParagraph = model.document.getRoot().getChild( 0 );
model.change( writer => {
writer.addMarker( 'restrictedEditingException:1', {
range: writer.createRange(
writer.createPositionAt( firstParagraph, 4 ),
writer.createPositionAt( firstParagraph, 7 ) ),
usingOperation: true,
affectsData: true
} );
} );
} );
afterEach( async () => {
await editor.destroy();
} );
describe( 'always enabled commands', () => {
describe( 'undo', () => {
beforeEach( () => {
editor.commands.add( 'undo', buildFakeCommand( editor ) );
} );
it( 'should be enabled outside exception marker', () => {
model.change( writer => {
writer.setSelection( firstParagraph, 1 );
} );
expect( editor.commands.get( 'undo' ).isEnabled ).to.be.true;
} );
it( 'should be enabled inside exception marker', () => {
model.change( writer => {
writer.setSelection( firstParagraph, 5 );
} );
expect( editor.commands.get( 'undo' ).isEnabled ).to.be.true;
} );
} );
describe( 'redo', () => {
beforeEach( () => {
editor.commands.add( 'redo', buildFakeCommand( editor ) );
} );
it( 'should be enabled outside exception marker', () => {
model.change( writer => {
writer.setSelection( firstParagraph, 1 );
} );
expect( editor.commands.get( 'redo' ).isEnabled ).to.be.true;
} );
it( 'should be enabled inside exception marker', () => {
model.change( writer => {
writer.setSelection( firstParagraph, 5 );
} );
expect( editor.commands.get( 'redo' ).isEnabled ).to.be.true;
} );
} );
} );
describe( 'commands enabled in exception marker', () => {
describe( 'input', () => {
beforeEach( () => {
editor.commands.add( 'input', buildFakeCommand( editor ) );
model.change( writer => {
writer.setSelection( firstParagraph, 'end' );
} );
} );
it( 'should be disabled when caret is outside exception marker', () => {
model.change( writer => {
writer.setSelection( firstParagraph, 1 );
} );
expect( editor.commands.get( 'input' ).isEnabled ).to.be.false;
} );
it( 'should be enabled when caret is inside exception marker (not touching boundaries)', () => {
model.change( writer => {
writer.setSelection( firstParagraph, 5 );
} );
expect( editor.commands.get( 'input' ).isEnabled ).to.be.true;
} );
it( 'should be disabled when caret is inside other marker', () => {
model.change( writer => {
writer.addMarker( 'foo-bar:1', {
range: writer.createRange(
writer.createPositionAt( firstParagraph, 0 ),
writer.createPositionAt( firstParagraph, 3 ) ),
usingOperation: true,
affectsData: true
} );
writer.setSelection( firstParagraph, 1 );
} );
expect( editor.commands.get( 'input' ).isEnabled ).to.be.false;
} );
it( 'should be enabled when caret is inside exception marker (start boundary)', () => {
model.change( writer => {
writer.setSelection( firstParagraph, 4 );
} );
expect( editor.commands.get( 'input' ).isEnabled ).to.be.true;
} );
it( 'should be enabled when caret is inside exception marker (end boundary)', () => {
model.change( writer => {
writer.setSelection( firstParagraph, 7 );
} );
expect( editor.commands.get( 'input' ).isEnabled ).to.be.true;
} );
it( 'should be disabled for multi-range selection (collapsed ranges)', () => {
model.change( writer => {
writer.setSelection( [
writer.createRange(
writer.createPositionAt( firstParagraph, 5 )
),
writer.createRange(
writer.createPositionAt( firstParagraph, 9 )
)
] );
} );
expect( editor.commands.get( 'input' ).isEnabled ).to.be.false;
} );
it( 'should be disabled for non-collapsed selection that expands over exception marker', () => {
model.change( writer => {
writer.setSelection( writer.createRange(
writer.createPositionAt( firstParagraph, 0 ),
writer.createPositionAt( firstParagraph, 5 )
) );
} );
expect( editor.commands.get( 'input' ).isEnabled ).to.be.false;
} );
it( 'should be enabled for non-collapsed selection that is fully contained inside exception marker', () => {
model.change( writer => {
writer.setSelection( writer.createRange(
writer.createPositionAt( firstParagraph, 5 ),
writer.createPositionAt( firstParagraph, 6 )
) );
} );
expect( editor.commands.get( 'input' ).isEnabled ).to.be.true;
} );
it( 'should be enabled for non-collapsed selection inside exception marker (start position on marker boundary)', () => {
model.change( writer => {
writer.setSelection( writer.createRange(
writer.createPositionAt( firstParagraph, 4 ),
writer.createPositionAt( firstParagraph, 6 )
) );
} );
expect( editor.commands.get( 'input' ).isEnabled ).to.be.true;
} );
it( 'should be enabled for non-collapsed selection inside exception marker (end position on marker boundary)', () => {
model.change( writer => {
writer.setSelection( writer.createRange(
writer.createPositionAt( firstParagraph, 5 ),
writer.createPositionAt( firstParagraph, 7 )
) );
} );
expect( editor.commands.get( 'input' ).isEnabled ).to.be.true;
} );
it( 'should be enabled for non-collapsed selection is equal to exception marker', () => {
model.change( writer => {
writer.setSelection( writer.createRange(
writer.createPositionAt( firstParagraph, 4 ),
writer.createPositionAt( firstParagraph, 7 )
) );
} );
expect( editor.commands.get( 'input' ).isEnabled ).to.be.true;
} );
it( 'should be disabled for non-collapsed selection with more then one range', () => {
model.change( writer => {
writer.setSelection( [
writer.createRange(
writer.createPositionAt( firstParagraph, 5 ),
writer.createPositionAt( firstParagraph, 6 )
),
writer.createRange(
writer.createPositionAt( firstParagraph, 8 ),
writer.createPositionAt( firstParagraph, 9 )
)
] );
} );
expect( editor.commands.get( 'input' ).isEnabled ).to.be.false;
} );
} );
describe( 'delete', () => {
beforeEach( () => {
editor.commands.add( 'delete', buildFakeCommand( editor ) );
model.change( writer => {
writer.setSelection( firstParagraph, 'end' );
} );
} );
it( 'should be disabled when caret is outside exception marker', () => {
model.change( writer => {
writer.setSelection( firstParagraph, 1 );
} );
expect( editor.commands.get( 'delete' ).isEnabled ).to.be.false;
} );
it( 'should be enabled when caret is inside exception marker (not touching boundaries)', () => {
model.change( writer => {
writer.setSelection( firstParagraph, 5 );
} );
expect( editor.commands.get( 'delete' ).isEnabled ).to.be.true;
} );
it( 'should be disabled when caret is inside exception marker (start boundary)', () => {
model.change( writer => {
writer.setSelection( firstParagraph, 4 );
} );
expect( editor.commands.get( 'delete' ).isEnabled ).to.be.false;
} );
it( 'should be disabled when caret moves to start boundary and it was enabled previously', () => {
model.change( writer => {
writer.setSelection( firstParagraph, 5 );
} );
expect( editor.commands.get( 'delete' ).isEnabled ).to.be.true;
model.change( writer => {
writer.setSelection( firstParagraph, 4 );
} );
expect( editor.commands.get( 'delete' ).isEnabled ).to.be.false;
} );
it( 'should be enabled when caret is inside exception marker (end boundary)', () => {
model.change( writer => {
writer.setSelection( firstParagraph, 7 );
} );
expect( editor.commands.get( 'delete' ).isEnabled ).to.be.true;
} );
it( 'should be disabled for non-collapsed selection that expands over exception marker', () => {
model.change( writer => {
writer.setSelection( writer.createRange(
writer.createPositionAt( firstParagraph, 0 ),
writer.createPositionAt( firstParagraph, 5 )
) );
} );
expect( editor.commands.get( 'delete' ).isEnabled ).to.be.false;
} );
it( 'should be enabled for non-collapsed selection that is fully contained inside exception marker', () => {
model.change( writer => {
writer.setSelection( writer.createRange(
writer.createPositionAt( firstParagraph, 5 ),
writer.createPositionAt( firstParagraph, 6 )
) );
} );
expect( editor.commands.get( 'delete' ).isEnabled ).to.be.true;
} );
it( 'should be enabled for non-collapsed selection inside exception marker (start position on marker boundary)', () => {
model.change( writer => {
writer.setSelection( writer.createRange(
writer.createPositionAt( firstParagraph, 4 ),
writer.createPositionAt( firstParagraph, 6 )
) );
} );
expect( editor.commands.get( 'delete' ).isEnabled ).to.be.true;
} );
it( 'should be enabled for non-collapsed selection inside exception marker (end position on marker boundary)', () => {
model.change( writer => {
writer.setSelection( writer.createRange(
writer.createPositionAt( firstParagraph, 5 ),
writer.createPositionAt( firstParagraph, 7 )
) );
} );
expect( editor.commands.get( 'delete' ).isEnabled ).to.be.true;
} );
it( 'should be enabled for non-collapsed selection is equal to exception marker', () => {
model.change( writer => {
writer.setSelection( writer.createRange(
writer.createPositionAt( firstParagraph, 4 ),
writer.createPositionAt( firstParagraph, 7 )
) );
} );
expect( editor.commands.get( 'delete' ).isEnabled ).to.be.true;
} );
} );
describe( 'forwardDelete', () => {
beforeEach( () => {
editor.commands.add( 'forwardDelete', buildFakeCommand( editor ) );
model.change( writer => {
writer.setSelection( firstParagraph, 'end' );
} );
} );
it( 'should be disabled when caret is outside exception marker', () => {
model.change( writer => {
writer.setSelection( firstParagraph, 1 );
} );
expect( editor.commands.get( 'forwardDelete' ).isEnabled ).to.be.false;
} );
it( 'should be enabled when caret is inside exception marker (not touching boundaries)', () => {
model.change( writer => {
writer.setSelection( firstParagraph, 5 );
} );
expect( editor.commands.get( 'forwardDelete' ).isEnabled ).to.be.true;
} );
it( 'should be enabled when caret is inside exception marker (start boundary)', () => {
model.change( writer => {
writer.setSelection( firstParagraph, 4 );
} );
expect( editor.commands.get( 'forwardDelete' ).isEnabled ).to.be.true;
} );
it( 'should be disabled when caret is inside exception marker (end boundary)', () => {
model.change( writer => {
writer.setSelection( firstParagraph, 7 );
} );
expect( editor.commands.get( 'forwardDelete' ).isEnabled ).to.be.false;
} );
it( 'should be disabled when caret moves to end boundary and it was enabled previously', () => {
model.change( writer => {
writer.setSelection( firstParagraph, 5 );
} );
expect( editor.commands.get( 'forwardDelete' ).isEnabled ).to.be.true;
model.change( writer => {
writer.setSelection( firstParagraph, 7 );
} );
expect( editor.commands.get( 'forwardDelete' ).isEnabled ).to.be.false;
} );
it( 'should be disabled for non-collapsed selection that expands over exception marker', () => {
model.change( writer => {
writer.setSelection( writer.createRange(
writer.createPositionAt( firstParagraph, 0 ),
writer.createPositionAt( firstParagraph, 5 )
) );
} );
expect( editor.commands.get( 'forwardDelete' ).isEnabled ).to.be.false;
} );
it( 'should be enabled for non-collapsed selection that is fully contained inside exception marker', () => {
model.change( writer => {
writer.setSelection( writer.createRange(
writer.createPositionAt( firstParagraph, 5 ),
writer.createPositionAt( firstParagraph, 6 )
) );
} );
expect( editor.commands.get( 'forwardDelete' ).isEnabled ).to.be.true;
} );
it( 'should be enabled for non-collapsed selection inside exception marker (start position on marker boundary)', () => {
model.change( writer => {
writer.setSelection( writer.createRange(
writer.createPositionAt( firstParagraph, 4 ),
writer.createPositionAt( firstParagraph, 6 )
) );
} );
expect( editor.commands.get( 'forwardDelete' ).isEnabled ).to.be.true;
} );
it( 'should be enabled for non-collapsed selection inside exception marker (end position on marker boundary)', () => {
model.change( writer => {
writer.setSelection( writer.createRange(
writer.createPositionAt( firstParagraph, 5 ),
writer.createPositionAt( firstParagraph, 7 )
) );
} );
expect( editor.commands.get( 'forwardDelete' ).isEnabled ).to.be.true;
} );
it( 'should be enabled for non-collapsed selection is equal to exception marker', () => {
model.change( writer => {
writer.setSelection( writer.createRange(
writer.createPositionAt( firstParagraph, 4 ),
writer.createPositionAt( firstParagraph, 7 )
) );
} );
expect( editor.commands.get( 'forwardDelete' ).isEnabled ).to.be.true;
} );
} );
} );
describe( 'non-enabled commands', () => {
beforeEach( () => {
editor.commands.add( 'other', buildFakeCommand( editor ) );
model.change( writer => {
writer.setSelection( firstParagraph, 'end' );
} );
} );
it( 'should be disabled outside exception marker', () => {
model.change( writer => {
writer.setSelection( firstParagraph, 1 );
} );
expect( editor.commands.get( 'other' ).isEnabled ).to.be.false;
} );
it( 'should be disabled inside exception marker', () => {
model.change( writer => {
writer.setSelection( firstParagraph, 1 );
} );
model.change( writer => {
writer.setSelection( firstParagraph, 5 );
} );
expect( editor.commands.get( 'other' ).isEnabled ).to.be.false;
} );
it( 'should be disabled when caret is inside exception marker (not touching boundaries)', () => {
model.change( writer => {
writer.setSelection( firstParagraph, 5 );
} );
expect( editor.commands.get( 'other' ).isEnabled ).to.be.false;
} );
it( 'should be disabled when caret is inside exception marker (start boundary)', () => {
model.change( writer => {
writer.setSelection( firstParagraph, 4 );
} );
expect( editor.commands.get( 'other' ).isEnabled ).to.be.false;
} );
it( 'should be disabled when caret is inside exception marker (end boundary)', () => {
model.change( writer => {
writer.setSelection( firstParagraph, 7 );
} );
expect( editor.commands.get( 'other' ).isEnabled ).to.be.false;
} );
it( 'should be disabled for non-collapsed selection that expands over exception marker', () => {
model.change( writer => {
writer.setSelection( writer.createRange(
writer.createPositionAt( firstParagraph, 0 ),
writer.createPositionAt( firstParagraph, 5 )
) );
} );
expect( editor.commands.get( 'other' ).isEnabled ).to.be.false;
} );
it( 'should be disabled for non-collapsed selection that is fully contained inside exception marker', () => {
model.change( writer => {
writer.setSelection( writer.createRange(
writer.createPositionAt( firstParagraph, 5 ),
writer.createPositionAt( firstParagraph, 6 )
) );
} );
expect( editor.commands.get( 'other' ).isEnabled ).to.be.false;
} );
it( 'should be disabled for non-collapsed selection inside exception marker (start position on marker boundary)', () => {
model.change( writer => {
writer.setSelection( writer.createRange(
writer.createPositionAt( firstParagraph, 4 ),
writer.createPositionAt( firstParagraph, 6 )
) );
} );
expect( editor.commands.get( 'other' ).isEnabled ).to.be.false;
} );
it( 'should be disabled for non-collapsed selection inside exception marker (end position on marker boundary)', () => {
model.change( writer => {
writer.setSelection( writer.createRange(
writer.createPositionAt( firstParagraph, 5 ),
writer.createPositionAt( firstParagraph, 7 )
) );
} );
expect( editor.commands.get( 'other' ).isEnabled ).to.be.false;
} );
it( 'should be disabled for non-collapsed selection is equal to exception marker', () => {
model.change( writer => {
writer.setSelection( writer.createRange(
writer.createPositionAt( firstParagraph, 4 ),
writer.createPositionAt( firstParagraph, 7 )
) );
} );
expect( editor.commands.get( 'other' ).isEnabled ).to.be.false;
} );
} );
} );
describe( 'commands enabled in exception marker by configuration', () => {
let model, firstParagraph;
beforeEach( async () => {
editor = await VirtualTestEditor.create( {
plugins: [ Paragraph, Typing, RestrictedEditingModeEditing ],
restrictedEditing: {
allowedCommands: [ 'allowed' ]
}
} );
model = editor.model;
editor.commands.add( 'allowed', buildFakeCommand( editor ) );
setModelData( model, '[]foo bar baz' );
firstParagraph = model.document.getRoot().getChild( 0 );
model.change( writer => {
writer.addMarker( 'restrictedEditingException:1', {
range: writer.createRange(
writer.createPositionAt( firstParagraph, 4 ),
writer.createPositionAt( firstParagraph, 7 ) ),
usingOperation: true,
affectsData: true
} );
writer.setSelection( firstParagraph, 'end' );
} );
} );
afterEach( async () => {
await editor.destroy();
} );
it( 'should be disabled when caret is outside exception marker', () => {
model.change( writer => {
writer.setSelection( firstParagraph, 1 );
} );
expect( editor.commands.get( 'allowed' ).isEnabled ).to.be.false;
} );
it( 'should be enabled when caret is inside exception marker (not touching boundaries)', () => {
model.change( writer => {
writer.setSelection( firstParagraph, 5 );
} );
expect( editor.commands.get( 'allowed' ).isEnabled ).to.be.true;
} );
it( 'should be disabled when caret is inside other marker', () => {
model.change( writer => {
writer.addMarker( 'foo-bar:1', {
range: writer.createRange(
writer.createPositionAt( firstParagraph, 0 ),
writer.createPositionAt( firstParagraph, 3 ) ),
usingOperation: true,
affectsData: true
} );
writer.setSelection( firstParagraph, 1 );
} );
expect( editor.commands.get( 'allowed' ).isEnabled ).to.be.false;
} );
it( 'should be enabled when caret is inside exception marker (start boundary)', () => {
model.change( writer => {
writer.setSelection( firstParagraph, 4 );
} );
expect( editor.commands.get( 'allowed' ).isEnabled ).to.be.true;
} );
it( 'should be enabled when caret is inside exception marker (end boundary)', () => {
model.change( writer => {
writer.setSelection( firstParagraph, 7 );
} );
expect( editor.commands.get( 'allowed' ).isEnabled ).to.be.true;
} );
it( 'should be disabled for multi-range selection (collapsed ranges)', () => {
model.change( writer => {
writer.setSelection( [
writer.createRange(
writer.createPositionAt( firstParagraph, 5 )
),
writer.createRange(
writer.createPositionAt( firstParagraph, 9 )
)
] );
} );
expect( editor.commands.get( 'allowed' ).isEnabled ).to.be.false;
} );
it( 'should be disabled for non-collapsed selection that expands over exception marker', () => {
model.change( writer => {
writer.setSelection( writer.createRange(
writer.createPositionAt( firstParagraph, 0 ),
writer.createPositionAt( firstParagraph, 5 )
) );
} );
expect( editor.commands.get( 'allowed' ).isEnabled ).to.be.false;
} );
it( 'should be enabled for non-collapsed selection that is fully contained inside exception marker', () => {
model.change( writer => {
writer.setSelection( writer.createRange(
writer.createPositionAt( firstParagraph, 5 ),
writer.createPositionAt( firstParagraph, 6 )
) );
} );
expect( editor.commands.get( 'allowed' ).isEnabled ).to.be.true;
} );
it( 'should be enabled for non-collapsed selection inside exception marker (start position on marker boundary)', () => {
model.change( writer => {
writer.setSelection( writer.createRange(
writer.createPositionAt( firstParagraph, 4 ),
writer.createPositionAt( firstParagraph, 6 )
) );
} );
expect( editor.commands.get( 'allowed' ).isEnabled ).to.be.true;
} );
it( 'should be enabled for non-collapsed selection inside exception marker (end position on marker boundary)', () => {
model.change( writer => {
writer.setSelection( writer.createRange(
writer.createPositionAt( firstParagraph, 5 ),
writer.createPositionAt( firstParagraph, 7 )
) );
} );
expect( editor.commands.get( 'allowed' ).isEnabled ).to.be.true;
} );
it( 'should be enabled for non-collapsed selection is equal to exception marker', () => {
model.change( writer => {
writer.setSelection( writer.createRange(
writer.createPositionAt( firstParagraph, 4 ),
writer.createPositionAt( firstParagraph, 7 )
) );
} );
expect( editor.commands.get( 'allowed' ).isEnabled ).to.be.true;
} );
it( 'should be disabled for non-collapsed selection with more then one range', () => {
model.change( writer => {
writer.setSelection( [
writer.createRange(
writer.createPositionAt( firstParagraph, 5 ),
writer.createPositionAt( firstParagraph, 6 )
),
writer.createRange(
writer.createPositionAt( firstParagraph, 8 ),
writer.createPositionAt( firstParagraph, 9 )
)
] );
} );
expect( editor.commands.get( 'allowed' ).isEnabled ).to.be.false;
} );
} );
describe( 'integration', () => {
let model, firstParagraph;
beforeEach( async () => {
editor = await VirtualTestEditor.create( { plugins: [ Paragraph, Typing, UndoEditing, RestrictedEditingModeEditing ] } );
model = editor.model;
setModelData( model, '[]foo bar baz' );
firstParagraph = model.document.getRoot().getChild( 0 );
model.change( writer => {
writer.addMarker( 'restrictedEditingException:1', {
range: writer.createRange(
writer.createPositionAt( firstParagraph, 4 ),
writer.createPositionAt( firstParagraph, 7 ) ),
usingOperation: true,
affectsData: true
} );
} );
} );
afterEach( async () => {
await editor.destroy();
} );
describe( 'delete + undo', () => {
it( 'should be enabled after data change (no selection change event on undo)', () => {
model.change( writer => {
writer.setSelection( firstParagraph, 7 );
} );
editor.execute( 'delete' );
editor.execute( 'undo' );
expect( editor.commands.get( 'delete' ).isEnabled ).to.be.true;
} );
} );
} );
class FakeCommand extends Command {
refresh() {
this.isEnabled = true;
}
}
function buildFakeCommand( editor ) {
return new FakeCommand( editor );
}
} );