8
0

restrictedediting.js 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. /**
  2. * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  4. */
  5. /* global document */
  6. import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
  7. import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
  8. import RestrictedEditing from './../src/restrictedediting';
  9. import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
  10. import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
  11. import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
  12. import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
  13. import Typing from '@ckeditor/ckeditor5-typing/src/typing';
  14. import { assertEqualMarkup } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
  15. import UndoEditing from '@ckeditor/ckeditor5-undo/src/undoediting';
  16. describe( 'RestrictedEditing', () => {
  17. let editor, element;
  18. testUtils.createSinonSandbox();
  19. describe( 'plugin', () => {
  20. beforeEach( async () => {
  21. element = document.createElement( 'div' );
  22. document.body.appendChild( element );
  23. editor = await ClassicTestEditor.create( element, { plugins: [ RestrictedEditing ] } );
  24. } );
  25. afterEach( () => {
  26. element.remove();
  27. return editor.destroy();
  28. } );
  29. it( 'should be named', () => {
  30. expect( RestrictedEditing.pluginName ).to.equal( 'RestrictedEditing' );
  31. } );
  32. it( 'should be loaded', () => {
  33. expect( editor.plugins.get( RestrictedEditing ) ).to.be.instanceOf( RestrictedEditing );
  34. } );
  35. } );
  36. describe( 'conversion', () => {
  37. let model;
  38. beforeEach( async () => {
  39. editor = await VirtualTestEditor.create( { plugins: [ Paragraph, RestrictedEditing ] } );
  40. model = editor.model;
  41. } );
  42. afterEach( () => {
  43. return editor.destroy();
  44. } );
  45. describe( 'upcast', () => {
  46. it( 'should convert <span class="ck-restricted-editing-exception"> to marker', () => {
  47. editor.setData( '<p>foo <span class="ck-restricted-editing-exception">bar</span> baz</p>' );
  48. expect( model.markers.has( 'restricted-editing-exception:1' ) ).to.be.true;
  49. const marker = model.markers.get( 'restricted-editing-exception:1' );
  50. expect( marker.getStart().path ).to.deep.equal( [ 0, 4 ] );
  51. expect( marker.getEnd().path ).to.deep.equal( [ 0, 7 ] );
  52. } );
  53. it( 'should convert multiple <span class="ck-restricted-editing-exception">', () => {
  54. editor.setData(
  55. '<p>foo <span class="ck-restricted-editing-exception">bar</span> baz</p>' +
  56. '<p>ABCDEF<span class="ck-restricted-editing-exception">GHIJK</span>LMNOPQRST</p>'
  57. );
  58. expect( model.markers.has( 'restricted-editing-exception:1' ) ).to.be.true;
  59. expect( model.markers.has( 'restricted-editing-exception:2' ) ).to.be.true;
  60. // Data for the first marker is the same as in previous tests so no need to test it again.
  61. const secondMarker = model.markers.get( 'restricted-editing-exception:2' );
  62. expect( secondMarker.getStart().path ).to.deep.equal( [ 1, 6 ] );
  63. expect( secondMarker.getEnd().path ).to.deep.equal( [ 1, 11 ] );
  64. } );
  65. it( 'should not convert other <span> elements', () => {
  66. editor.setData( '<p>foo <span class="foo bar">bar</span> baz</p>' );
  67. expect( model.markers.has( 'restricted-editing-exception:1' ) ).to.be.false;
  68. } );
  69. } );
  70. describe( 'downcast', () => {
  71. it( 'should convert model marker to <span>', () => {
  72. setModelData( model, '<paragraph>foo bar baz</paragraph>' );
  73. const paragraph = model.document.getRoot().getChild( 0 );
  74. model.change( writer => {
  75. writer.addMarker( 'restricted-editing-exception:1', {
  76. range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ),
  77. usingOperation: true,
  78. affectsData: true
  79. } );
  80. } );
  81. const expectedView = '<p>foo <span class="ck-restricted-editing-exception">bar</span> baz</p>';
  82. expect( editor.getData() ).to.equal( expectedView );
  83. expect( getViewData( editor.editing.view, { withoutSelection: true } ) ).to.equal( expectedView );
  84. } );
  85. it( 'converted <span> should be the outermost attribute element', () => {
  86. editor.conversion.for( 'downcast' ).attributeToElement( { model: 'bold', view: 'b' } );
  87. setModelData( model, '<paragraph><$text bold="true">foo bar baz</$text></paragraph>' );
  88. const paragraph = model.document.getRoot().getChild( 0 );
  89. model.change( writer => {
  90. writer.addMarker( 'restricted-editing-exception:1', {
  91. range: writer.createRange( writer.createPositionAt( paragraph, 0 ), writer.createPositionAt( paragraph, 'end' ) ),
  92. usingOperation: true,
  93. affectsData: true
  94. } );
  95. } );
  96. const expectedView = '<p><span class="ck-restricted-editing-exception"><b>foo bar baz</b></span></p>';
  97. expect( editor.getData() ).to.equal( expectedView );
  98. expect( getViewData( editor.editing.view, { withoutSelection: true } ) ).to.equal( expectedView );
  99. } );
  100. } );
  101. } );
  102. describe( 'editing behavior', () => {
  103. let model;
  104. beforeEach( async () => {
  105. editor = await VirtualTestEditor.create( { plugins: [ Paragraph, Typing, UndoEditing, RestrictedEditing ] } );
  106. model = editor.model;
  107. } );
  108. afterEach( async () => {
  109. await editor.destroy();
  110. } );
  111. it( 'should keep markers in the view when editable region is edited', () => {
  112. setModelData( model,
  113. '<paragraph>foo bar baz</paragraph>' +
  114. '<paragraph>xxx y[]yy zzz</paragraph>'
  115. );
  116. const firstParagraph = model.document.getRoot().getChild( 0 );
  117. const secondParagraph = model.document.getRoot().getChild( 1 );
  118. model.change( writer => {
  119. writer.addMarker( 'restricted-editing-exception:1', {
  120. range: writer.createRange( writer.createPositionAt( firstParagraph, 4 ), writer.createPositionAt( firstParagraph, 7 ) ),
  121. usingOperation: true,
  122. affectsData: true
  123. } );
  124. writer.addMarker( 'restricted-editing-exception:2', {
  125. range: writer.createRange(
  126. writer.createPositionAt( secondParagraph, 4 ),
  127. writer.createPositionAt( secondParagraph, 7 )
  128. ),
  129. usingOperation: true,
  130. affectsData: true
  131. } );
  132. } );
  133. model.change( writer => {
  134. model.insertContent( writer.createText( 'R', model.document.selection.getAttributes() ) );
  135. } );
  136. const expectedView = '<p>foo <span class="ck-restricted-editing-exception">bar</span> baz</p>' +
  137. '<p>xxx <span class="ck-restricted-editing-exception">yRyy</span> zzz</p>';
  138. expect( editor.getData() ).to.equal( expectedView );
  139. expect( getViewData( editor.editing.view, { withoutSelection: true } ) ).to.equal( expectedView );
  140. } );
  141. it( 'should block user typing outside exception markers', () => {
  142. setModelData( model, '<paragraph>foo []bar baz</paragraph>' );
  143. editor.execute( 'input', { text: 'X' } );
  144. assertEqualMarkup( getModelData( model ), '<paragraph>foo []bar baz</paragraph>' );
  145. } );
  146. it( 'should not block user typing inside exception marker', () => {
  147. setModelData( model, '<paragraph>[]foo bar baz</paragraph>' );
  148. const firstParagraph = model.document.getRoot().getChild( 0 );
  149. model.change( writer => {
  150. writer.addMarker( 'restricted-editing-exception:1', {
  151. range: writer.createRange( writer.createPositionAt( firstParagraph, 4 ), writer.createPositionAt( firstParagraph, 7 ) ),
  152. usingOperation: true,
  153. affectsData: true
  154. } );
  155. } );
  156. model.change( writer => {
  157. writer.setSelection( firstParagraph, 5 );
  158. } );
  159. editor.execute( 'input', { text: 'X' } );
  160. assertEqualMarkup( getModelData( model ), '<paragraph>foo bX[]ar baz</paragraph>' );
  161. } );
  162. } );
  163. describe( 'commands integration', () => {
  164. let model;
  165. beforeEach( async () => {
  166. editor = await VirtualTestEditor.create( { plugins: [ Paragraph, Typing, UndoEditing, RestrictedEditing ] } );
  167. model = editor.model;
  168. } );
  169. afterEach( async () => {
  170. await editor.destroy();
  171. } );
  172. describe( 'undo command', () => {
  173. it( 'should be enabled outside exception marker', () => {
  174. setModelData( model, '<paragraph>[]foo bar baz</paragraph>' );
  175. expect( editor.commands.get( 'undo' ).isEnabled ).to.be.true;
  176. } );
  177. it( 'should be enabled inside exception marker', () => {
  178. setModelData( model, '<paragraph>[]foo bar baz</paragraph>' );
  179. const firstParagraph = model.document.getRoot().getChild( 0 );
  180. model.change( writer => {
  181. writer.addMarker( 'restricted-editing-exception:1', {
  182. range: writer.createRange(
  183. writer.createPositionAt( firstParagraph, 4 ),
  184. writer.createPositionAt( firstParagraph, 7 ) ),
  185. usingOperation: true,
  186. affectsData: true
  187. } );
  188. writer.setSelection( firstParagraph, 5 );
  189. } );
  190. expect( editor.commands.get( 'undo' ).isEnabled ).to.be.true;
  191. } );
  192. } );
  193. describe( 'redo command', () => {
  194. it( 'should be enabled outside exception marker', () => {
  195. setModelData( model, '<paragraph>[]foo bar baz</paragraph>' );
  196. model.change( writer => {
  197. model.insertContent( writer.createText( 'R', model.document.selection.getAttributes() ) );
  198. } );
  199. editor.execute( 'undo' );
  200. expect( editor.commands.get( 'redo' ).isEnabled ).to.be.true;
  201. } );
  202. it( 'should be enabled inside exception marker', () => {
  203. setModelData( model, '<paragraph>[]foo bar baz</paragraph>' );
  204. const firstParagraph = model.document.getRoot().getChild( 0 );
  205. model.change( writer => {
  206. model.insertContent( writer.createText( 'R', model.document.selection.getAttributes() ) );
  207. } );
  208. model.change( writer => {
  209. writer.addMarker( 'restricted-editing-exception:1', {
  210. range: writer.createRange(
  211. writer.createPositionAt( firstParagraph, 4 ),
  212. writer.createPositionAt( firstParagraph, 7 ) ),
  213. usingOperation: true,
  214. affectsData: true
  215. } );
  216. writer.setSelection( firstParagraph, 5 );
  217. } );
  218. editor.execute( 'undo' );
  219. expect( editor.commands.get( 'redo' ).isEnabled ).to.be.true;
  220. } );
  221. } );
  222. } );
  223. } );