/** * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ /* global document */ import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor'; import RestrictedEditingEditing from './../src/restrictededitingediting'; import RestrictedEditingNavigationCommand from '../src/restrictededitingnavigationcommand'; import { setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view'; import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor'; import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; import BoldEditing from '@ckeditor/ckeditor5-basic-styles/src/bold/boldediting'; describe( 'RestrictedEditingEditing', () => { let editor, element; testUtils.createSinonSandbox(); describe( 'plugin', () => { beforeEach( async () => { element = document.createElement( 'div' ); document.body.appendChild( element ); editor = await ClassicTestEditor.create( element, { plugins: [ RestrictedEditingEditing ] } ); } ); afterEach( () => { element.remove(); return editor.destroy(); } ); it( 'should be named', () => { expect( RestrictedEditingEditing.pluginName ).to.equal( 'RestrictedEditingEditing' ); } ); it( 'should be loaded', () => { expect( editor.plugins.get( RestrictedEditingEditing ) ).to.be.instanceOf( RestrictedEditingEditing ); } ); it( 'adds a "goToPreviousRestrictedEditingRegion" command', () => { expect( editor.commands.get( 'goToPreviousRestrictedEditingRegion' ) ).to.be.instanceOf( RestrictedEditingNavigationCommand ); } ); it( 'adds a "goToNextRestrictedEditingRegion" command', () => { expect( editor.commands.get( 'goToNextRestrictedEditingRegion' ) ).to.be.instanceOf( RestrictedEditingNavigationCommand ); } ); } ); describe( 'conversion', () => { let model; beforeEach( async () => { editor = await VirtualTestEditor.create( { plugins: [ Paragraph, RestrictedEditingEditing ] } ); model = editor.model; } ); afterEach( () => { return editor.destroy(); } ); describe( 'upcast', () => { it( 'should convert to marker', () => { editor.setData( '

foo bar baz

' ); expect( model.markers.has( 'restricted-editing-exception:1' ) ).to.be.true; const marker = model.markers.get( 'restricted-editing-exception:1' ); expect( marker.getStart().path ).to.deep.equal( [ 0, 4 ] ); expect( marker.getEnd().path ).to.deep.equal( [ 0, 7 ] ); } ); it( 'should convert multiple ', () => { editor.setData( '

foo bar baz

' + '

ABCDEFGHIJKLMNOPQRST

' ); expect( model.markers.has( 'restricted-editing-exception:1' ) ).to.be.true; expect( model.markers.has( 'restricted-editing-exception:2' ) ).to.be.true; // Data for the first marker is the same as in previous tests so no need to test it again. const secondMarker = model.markers.get( 'restricted-editing-exception:2' ); expect( secondMarker.getStart().path ).to.deep.equal( [ 1, 6 ] ); expect( secondMarker.getEnd().path ).to.deep.equal( [ 1, 11 ] ); } ); it( 'should not convert other elements', () => { editor.setData( '

foo bar baz

' ); expect( model.markers.has( 'restricted-editing-exception:1' ) ).to.be.false; } ); } ); describe( 'downcast', () => { it( 'should convert model marker to ', () => { setModelData( model, 'foo bar baz' ); const paragraph = model.document.getRoot().getChild( 0 ); model.change( writer => { writer.addMarker( 'restricted-editing-exception:1', { range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ), usingOperation: true, affectsData: true } ); } ); const expectedView = '

foo bar baz

'; expect( editor.getData() ).to.equal( expectedView ); expect( getViewData( editor.editing.view, { withoutSelection: true } ) ).to.equal( expectedView ); } ); it( 'converted should be the outermost attribute element', () => { editor.conversion.for( 'downcast' ).attributeToElement( { model: 'bold', view: 'b' } ); setModelData( model, '<$text bold="true">foo bar baz' ); const paragraph = model.document.getRoot().getChild( 0 ); model.change( writer => { writer.addMarker( 'restricted-editing-exception:1', { range: writer.createRange( writer.createPositionAt( paragraph, 0 ), writer.createPositionAt( paragraph, 'end' ) ), usingOperation: true, affectsData: true } ); } ); const expectedView = '

foo bar baz

'; expect( editor.getData() ).to.equal( expectedView ); expect( getViewData( editor.editing.view, { withoutSelection: true } ) ).to.equal( expectedView ); } ); } ); } ); describe( 'editing behavior', () => { let model; beforeEach( async () => { editor = await VirtualTestEditor.create( { plugins: [ Paragraph, RestrictedEditingEditing ] } ); model = editor.model; } ); afterEach( () => { return editor.destroy(); } ); it( 'should keep markers in the view when editable region is edited', () => { setModelData( model, 'foo bar baz' + 'xxx y[]yy zzz' ); const firstParagraph = model.document.getRoot().getChild( 0 ); const secondParagraph = model.document.getRoot().getChild( 1 ); model.change( writer => { writer.addMarker( 'restricted-editing-exception:1', { range: writer.createRange( writer.createPositionAt( firstParagraph, 4 ), writer.createPositionAt( firstParagraph, 7 ) ), usingOperation: true, affectsData: true } ); writer.addMarker( 'restricted-editing-exception:2', { range: writer.createRange( writer.createPositionAt( secondParagraph, 4 ), writer.createPositionAt( secondParagraph, 7 ) ), usingOperation: true, affectsData: true } ); } ); model.change( writer => { model.insertContent( writer.createText( 'R', model.document.selection.getAttributes() ) ); } ); expect( editor.getData() ).to.equal( '

foo bar baz

' + '

xxx yRyy zzz

' ); expect( getViewData( editor.editing.view, { withoutSelection: true } ) ).to.equal( '

foo bar baz

' + '

xxx yRyy zzz

' ); } ); } ); describe( 'exception highlighting', () => { let model, view; beforeEach( async () => { editor = await VirtualTestEditor.create( { plugins: [ Paragraph, RestrictedEditingEditing, BoldEditing ] } ); model = editor.model; view = editor.editing.view; } ); afterEach( () => { return editor.destroy(); } ); it( 'should convert the highlight to a proper view classes', () => { setModelData( model, 'foo b[a]r baz' ); const paragraph = model.document.getRoot().getChild( 0 ); // foo <$marker>b[a]r baz model.change( writer => { writer.addMarker( 'restricted-editing-exception:1', { range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ), usingOperation: true, affectsData: true } ); } ); expect( getViewData( view ) ).to.equal( '

foo b{a}r baz

' ); } ); it( 'should remove classes when selection is moved away from an exception', () => { setModelData( model, 'foo b[a]r baz' ); const paragraph = model.document.getRoot().getChild( 0 ); // foo <$marker>b[a]r baz model.change( writer => { writer.addMarker( 'restricted-editing-exception:1', { range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ), usingOperation: true, affectsData: true } ); } ); expect( getViewData( view ) ).to.equal( '

foo b{a}r baz

' ); model.change( writer => writer.setSelection( model.document.getRoot().getChild( 0 ), 0 ) ); expect( getViewData( view ) ).to.equal( '

{}foo bar baz

' ); } ); it( 'should work correctly when selection is moved inside an exception', () => { setModelData( model, '[]foo bar baz' ); const paragraph = model.document.getRoot().getChild( 0 ); // []foo <$marker>bar baz model.change( writer => { writer.addMarker( 'restricted-editing-exception:1', { range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ), usingOperation: true, affectsData: true } ); } ); expect( getViewData( view ) ).to.equal( '

{}foo bar baz

' ); model.change( writer => writer.setSelection( model.document.getRoot().getChild( 0 ), 6 ) ); expect( getViewData( view ) ).to.equal( '

foo ba{}r baz

' ); } ); describe( 'editing downcast conversion integration', () => { it( 'works for the #insert event', () => { setModelData( model, 'foo b[a]r baz' ); const paragraph = model.document.getRoot().getChild( 0 ); // foo <$marker>b[a]r baz model.change( writer => { writer.addMarker( 'restricted-editing-exception:1', { range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ), usingOperation: true, affectsData: true } ); } ); model.change( writer => { writer.insertText( 'FOO', { linkHref: 'url' }, model.document.selection.getFirstPosition() ); } ); expect( getViewData( view ) ).to.equal( '

foo bFOO{a}r baz

' ); } ); it( 'works for the #remove event', () => { setModelData( model, 'foo b[a]r baz' ); const paragraph = model.document.getRoot().getChild( 0 ); // foo <$marker>b[a]r baz model.change( writer => { writer.addMarker( 'restricted-editing-exception:1', { range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ), usingOperation: true, affectsData: true } ); } ); model.change( writer => { writer.remove( writer.createRange( writer.createPositionAt( model.document.getRoot().getChild( 0 ), 5 ), writer.createPositionAt( model.document.getRoot().getChild( 0 ), 6 ) ) ); } ); expect( getViewData( view ) ).to.equal( '

foo b{}r baz

' ); } ); it( 'works for the #attribute event', () => { setModelData( model, 'foo b[a]r baz' ); const paragraph = model.document.getRoot().getChild( 0 ); // foo <$marker>b[a]r baz model.change( writer => { writer.addMarker( 'restricted-editing-exception:1', { range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ), usingOperation: true, affectsData: true } ); } ); model.change( writer => { writer.setAttribute( 'bold', true, writer.createRange( model.document.selection.getFirstPosition().getShiftedBy( -1 ), model.document.selection.getFirstPosition().getShiftedBy( 1 ) ) ); } ); expect( getViewData( view ) ).to.equal( '

foo ' + '' + 'b{a' + '}r' + ' baz

' ); } ); it( 'works for the #selection event', () => { setModelData( model, 'foo b[a]r baz' ); const paragraph = model.document.getRoot().getChild( 0 ); // foo <$marker>b[a]r baz model.change( writer => { writer.addMarker( 'restricted-editing-exception:1', { range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ), usingOperation: true, affectsData: true } ); } ); model.change( writer => { writer.setSelection( writer.createRange( model.document.selection.getFirstPosition().getShiftedBy( -1 ), model.document.selection.getFirstPosition().getShiftedBy( 1 ) ) ); } ); expect( getViewData( view ) ).to.equal( '

foo {ba}r baz

' ); } ); it( 'works for the addMarker and removeMarker events', () => { editor.conversion.for( 'editingDowncast' ).markerToHighlight( { model: 'fooMarker', view: {} } ); setModelData( model, 'foo b[a]r baz' ); const paragraph = model.document.getRoot().getChild( 0 ); // foo <$marker>b[a]r baz model.change( writer => { writer.addMarker( 'restricted-editing-exception:1', { range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ), usingOperation: true, affectsData: true } ); } ); model.change( writer => { const range = writer.createRange( writer.createPositionAt( model.document.getRoot().getChild( 0 ), 0 ), writer.createPositionAt( model.document.getRoot().getChild( 0 ), 5 ) ); writer.addMarker( 'fooMarker', { range, usingOperation: true } ); } ); expect( getViewData( view ) ).to.equal( '

' + 'foo ' + '' + 'b{a}r' + '' + ' baz

' ); model.change( writer => writer.removeMarker( 'fooMarker' ) ); expect( getViewData( view ) ).to.equal( '

foo b{a}r baz

' ); } ); } ); } ); } );