restrictededitingediting.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748
  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. import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
  6. import RestrictedEditingEditing from './../src/restrictededitingediting';
  7. import RestrictedEditingNavigationCommand from '../src/restrictededitingnavigationcommand';
  8. import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
  9. import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
  10. import { getCode } from '@ckeditor/ckeditor5-utils/src/keyboard';
  11. import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
  12. import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
  13. import BoldEditing from '@ckeditor/ckeditor5-basic-styles/src/bold/boldediting';
  14. import { assertEqualMarkup } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
  15. import Typing from '@ckeditor/ckeditor5-typing/src/typing';
  16. import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard';
  17. describe( 'RestrictedEditingEditing', () => {
  18. let editor;
  19. testUtils.createSinonSandbox();
  20. describe( 'plugin', () => {
  21. beforeEach( async () => {
  22. editor = await VirtualTestEditor.create( { plugins: [ RestrictedEditingEditing ] } );
  23. } );
  24. afterEach( async () => {
  25. await editor.destroy();
  26. } );
  27. it( 'should be named', () => {
  28. expect( RestrictedEditingEditing.pluginName ).to.equal( 'RestrictedEditingEditing' );
  29. } );
  30. it( 'should be loaded', () => {
  31. expect( editor.plugins.get( RestrictedEditingEditing ) ).to.be.instanceOf( RestrictedEditingEditing );
  32. } );
  33. it( 'adds a "goToPreviousRestrictedEditingRegion" command', () => {
  34. expect( editor.commands.get( 'goToPreviousRestrictedEditingRegion' ) ).to.be.instanceOf( RestrictedEditingNavigationCommand );
  35. } );
  36. it( 'adds a "goToNextRestrictedEditingRegion" command', () => {
  37. expect( editor.commands.get( 'goToNextRestrictedEditingRegion' ) ).to.be.instanceOf( RestrictedEditingNavigationCommand );
  38. } );
  39. } );
  40. describe( 'conversion', () => {
  41. let model;
  42. beforeEach( async () => {
  43. editor = await VirtualTestEditor.create( { plugins: [ Paragraph, RestrictedEditingEditing ] } );
  44. model = editor.model;
  45. } );
  46. afterEach( () => {
  47. return editor.destroy();
  48. } );
  49. describe( 'upcast', () => {
  50. it( 'should convert <span class="ck-restricted-editing-exception"> to marker', () => {
  51. editor.setData( '<p>foo <span class="ck-restricted-editing-exception">bar</span> baz</p>' );
  52. expect( model.markers.has( 'restricted-editing-exception:1' ) ).to.be.true;
  53. const marker = model.markers.get( 'restricted-editing-exception:1' );
  54. expect( marker.getStart().path ).to.deep.equal( [ 0, 4 ] );
  55. expect( marker.getEnd().path ).to.deep.equal( [ 0, 7 ] );
  56. } );
  57. it( 'should convert multiple <span class="ck-restricted-editing-exception">', () => {
  58. editor.setData(
  59. '<p>foo <span class="ck-restricted-editing-exception">bar</span> baz</p>' +
  60. '<p>ABCDEF<span class="ck-restricted-editing-exception">GHIJK</span>LMNOPQRST</p>'
  61. );
  62. expect( model.markers.has( 'restricted-editing-exception:1' ) ).to.be.true;
  63. expect( model.markers.has( 'restricted-editing-exception:2' ) ).to.be.true;
  64. // Data for the first marker is the same as in previous tests so no need to test it again.
  65. const secondMarker = model.markers.get( 'restricted-editing-exception:2' );
  66. expect( secondMarker.getStart().path ).to.deep.equal( [ 1, 6 ] );
  67. expect( secondMarker.getEnd().path ).to.deep.equal( [ 1, 11 ] );
  68. } );
  69. it( 'should not convert other <span> elements', () => {
  70. editor.setData( '<p>foo <span class="foo bar">bar</span> baz</p>' );
  71. expect( model.markers.has( 'restricted-editing-exception:1' ) ).to.be.false;
  72. } );
  73. } );
  74. describe( 'downcast', () => {
  75. it( 'should convert model marker to <span>', () => {
  76. setModelData( model, '<paragraph>foo bar baz</paragraph>' );
  77. const paragraph = model.document.getRoot().getChild( 0 );
  78. model.change( writer => {
  79. writer.addMarker( 'restricted-editing-exception:1', {
  80. range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ),
  81. usingOperation: true,
  82. affectsData: true
  83. } );
  84. } );
  85. const expectedView = '<p>foo <span class="ck-restricted-editing-exception">bar</span> baz</p>';
  86. expect( editor.getData() ).to.equal( expectedView );
  87. expect( getViewData( editor.editing.view, { withoutSelection: true } ) ).to.equal( expectedView );
  88. } );
  89. it( 'converted <span> should be the outermost attribute element', () => {
  90. editor.conversion.for( 'downcast' ).attributeToElement( { model: 'bold', view: 'b' } );
  91. setModelData( model, '<paragraph><$text bold="true">foo bar baz</$text></paragraph>' );
  92. const paragraph = model.document.getRoot().getChild( 0 );
  93. model.change( writer => {
  94. writer.addMarker( 'restricted-editing-exception:1', {
  95. range: writer.createRange( writer.createPositionAt( paragraph, 0 ), writer.createPositionAt( paragraph, 'end' ) ),
  96. usingOperation: true,
  97. affectsData: true
  98. } );
  99. } );
  100. expect( editor.getData() ).to.equal(
  101. '<p><span class="ck-restricted-editing-exception"><b>foo bar baz</b></span></p>'
  102. );
  103. expect( getViewData( editor.editing.view, { withoutSelection: true } ) ).to.equal(
  104. '<p>' +
  105. '<span class="ck-restricted-editing-exception ck-restricted-editing-exception_selected"><b>foo bar baz</b></span>' +
  106. '</p>'
  107. );
  108. } );
  109. } );
  110. } );
  111. describe( 'editing behavior', () => {
  112. let model;
  113. beforeEach( async () => {
  114. editor = await VirtualTestEditor.create( { plugins: [ Paragraph, Typing, RestrictedEditingEditing ] } );
  115. model = editor.model;
  116. } );
  117. afterEach( () => {
  118. return editor.destroy();
  119. } );
  120. it( 'should keep markers in the view when editable region is edited', () => {
  121. setModelData( model,
  122. '<paragraph>foo bar baz</paragraph>' +
  123. '<paragraph>xxx y[]yy zzz</paragraph>'
  124. );
  125. const firstParagraph = model.document.getRoot().getChild( 0 );
  126. const secondParagraph = model.document.getRoot().getChild( 1 );
  127. model.change( writer => {
  128. writer.addMarker( 'restricted-editing-exception:1', {
  129. range: writer.createRange( writer.createPositionAt( firstParagraph, 4 ), writer.createPositionAt( firstParagraph, 7 ) ),
  130. usingOperation: true,
  131. affectsData: true
  132. } );
  133. writer.addMarker( 'restricted-editing-exception:2', {
  134. range: writer.createRange(
  135. writer.createPositionAt( secondParagraph, 4 ),
  136. writer.createPositionAt( secondParagraph, 7 )
  137. ),
  138. usingOperation: true,
  139. affectsData: true
  140. } );
  141. } );
  142. model.change( writer => {
  143. model.insertContent( writer.createText( 'R', model.document.selection.getAttributes() ) );
  144. } );
  145. expect( editor.getData() ).to.equal(
  146. '<p>foo <span class="ck-restricted-editing-exception">bar</span> baz</p>' +
  147. '<p>xxx <span class="ck-restricted-editing-exception">yRyy</span> zzz</p>' );
  148. expect( getViewData( editor.editing.view, { withoutSelection: true } ) ).to.equal(
  149. '<p>foo <span class="ck-restricted-editing-exception">bar</span> baz</p>' +
  150. '<p>xxx <span class="ck-restricted-editing-exception ck-restricted-editing-exception_selected">yRyy</span> zzz</p>' );
  151. } );
  152. it( 'should block user typing outside exception markers', () => {
  153. setModelData( model, '<paragraph>foo []bar baz</paragraph>' );
  154. editor.execute( 'input', { text: 'X' } );
  155. assertEqualMarkup( getModelData( model ), '<paragraph>foo []bar baz</paragraph>' );
  156. } );
  157. it( 'should not block user typing inside exception marker', () => {
  158. setModelData( model, '<paragraph>[]foo bar baz</paragraph>' );
  159. const firstParagraph = model.document.getRoot().getChild( 0 );
  160. model.change( writer => {
  161. writer.addMarker( 'restricted-editing-exception:1', {
  162. range: writer.createRange( writer.createPositionAt( firstParagraph, 4 ), writer.createPositionAt( firstParagraph, 7 ) ),
  163. usingOperation: true,
  164. affectsData: true
  165. } );
  166. } );
  167. model.change( writer => {
  168. writer.setSelection( firstParagraph, 5 );
  169. } );
  170. editor.execute( 'input', { text: 'X' } );
  171. assertEqualMarkup( getModelData( model ), '<paragraph>foo bX[]ar baz</paragraph>' );
  172. } );
  173. it( 'should extend maker when typing on the marker boundary (end)', () => {
  174. setModelData( model, '<paragraph>foo bar[] baz</paragraph>' );
  175. const firstParagraph = model.document.getRoot().getChild( 0 );
  176. model.change( writer => {
  177. writer.addMarker( 'restricted-editing-exception:1', {
  178. range: writer.createRange( writer.createPositionAt( firstParagraph, 4 ), writer.createPositionAt( firstParagraph, 7 ) ),
  179. usingOperation: true,
  180. affectsData: true
  181. } );
  182. } );
  183. editor.execute( 'input', { text: 'X' } );
  184. assertEqualMarkup( getModelData( model ), '<paragraph>foo barX[] baz</paragraph>' );
  185. const markerRange = editor.model.markers.get( 'restricted-editing-exception:1' ).getRange();
  186. const expectedRange = model.createRange(
  187. model.createPositionAt( firstParagraph, 4 ),
  188. model.createPositionAt( firstParagraph, 8 )
  189. );
  190. expect( markerRange.isEqual( expectedRange ) ).to.be.true;
  191. } );
  192. it( 'should extend marker when typing on the marker boundary (start)', () => {
  193. setModelData( model, '<paragraph>foo []bar baz</paragraph>' );
  194. const firstParagraph = model.document.getRoot().getChild( 0 );
  195. model.change( writer => {
  196. writer.addMarker( 'restricted-editing-exception:1', {
  197. range: writer.createRange( writer.createPositionAt( firstParagraph, 4 ), writer.createPositionAt( firstParagraph, 7 ) ),
  198. usingOperation: true,
  199. affectsData: true
  200. } );
  201. } );
  202. editor.execute( 'input', { text: 'X' } );
  203. assertEqualMarkup( getModelData( model ), '<paragraph>foo X[]bar baz</paragraph>' );
  204. const markerRange = editor.model.markers.get( 'restricted-editing-exception:1' ).getRange();
  205. const expectedRange = model.createRange(
  206. model.createPositionAt( firstParagraph, 4 ),
  207. model.createPositionAt( firstParagraph, 8 )
  208. );
  209. expect( markerRange.isEqual( expectedRange ) ).to.be.true;
  210. } );
  211. it( 'should extend marker when typing on the marker boundary (collapsed marker)', () => {
  212. setModelData( model, '<paragraph>[]foo bar baz</paragraph>' );
  213. const firstParagraph = model.document.getRoot().getChild( 0 );
  214. model.change( writer => {
  215. writer.addMarker( 'restricted-editing-exception:1', {
  216. range: writer.createRange( writer.createPositionAt( firstParagraph, 4 ) ),
  217. usingOperation: true,
  218. affectsData: true
  219. } );
  220. } );
  221. model.change( writer => {
  222. writer.setSelection( writer.createPositionAt( firstParagraph, 4 ) );
  223. } );
  224. editor.execute( 'input', { text: 'X' } );
  225. assertEqualMarkup( getModelData( model ), '<paragraph>foo X[]bar baz</paragraph>' );
  226. const markerRange = editor.model.markers.get( 'restricted-editing-exception:1' ).getRange();
  227. const expectedRange = model.createRange(
  228. model.createPositionAt( firstParagraph, 4 ),
  229. model.createPositionAt( firstParagraph, 5 )
  230. );
  231. expect( markerRange.isEqual( expectedRange ) ).to.be.true;
  232. } );
  233. it( 'should not move collapsed marker to $graveyard', () => {
  234. setModelData( model, '<paragraph>foo b[]ar baz</paragraph>' );
  235. const firstParagraph = model.document.getRoot().getChild( 0 );
  236. model.change( writer => {
  237. writer.addMarker( 'restricted-editing-exception:1', {
  238. range: writer.createRange(
  239. writer.createPositionAt( firstParagraph, 4 ),
  240. writer.createPositionAt( firstParagraph, 5 )
  241. ),
  242. usingOperation: true,
  243. affectsData: true
  244. } );
  245. } );
  246. editor.execute( 'delete' );
  247. assertEqualMarkup( getModelData( model ), '<paragraph>foo []ar baz</paragraph>' );
  248. const markerRange = editor.model.markers.get( 'restricted-editing-exception:1' ).getRange();
  249. const expectedRange = model.createRange(
  250. model.createPositionAt( firstParagraph, 4 ),
  251. model.createPositionAt( firstParagraph, 4 )
  252. );
  253. expect( markerRange.isEqual( expectedRange ) ).to.be.true;
  254. } );
  255. } );
  256. describe( 'pasting', () => {
  257. let model, viewDoc;
  258. beforeEach( async () => {
  259. editor = await VirtualTestEditor.create( { plugins: [ Paragraph, Typing, Clipboard, RestrictedEditingEditing ] } );
  260. model = editor.model;
  261. viewDoc = editor.editing.view.document;
  262. } );
  263. afterEach( () => {
  264. return editor.destroy();
  265. } );
  266. it( 'should block pasting outside exception markers', () => {
  267. setModelData( model, '<paragraph>foo []bar baz</paragraph>' );
  268. const spy = sinon.spy();
  269. viewDoc.on( 'clipboardInput', spy, { priority: 'high' } );
  270. viewDoc.fire( 'clipboardInput', {
  271. dataTransfer: {
  272. getData: sinon.spy()
  273. }
  274. } );
  275. sinon.assert.notCalled( spy );
  276. assertEqualMarkup( getModelData( model ), '<paragraph>foo []bar baz</paragraph>' );
  277. } );
  278. it( 'should not block pasting inside exception marker', () => {
  279. setModelData( model, '<paragraph>[]foo bar baz</paragraph>' );
  280. const firstParagraph = model.document.getRoot().getChild( 0 );
  281. const spy = sinon.spy();
  282. viewDoc.on( 'clipboardInput', spy, { priority: 'high' } );
  283. model.change( writer => {
  284. writer.addMarker( 'restricted-editing-exception:1', {
  285. range: writer.createRange( writer.createPositionAt( firstParagraph, 4 ), writer.createPositionAt( firstParagraph, 7 ) ),
  286. usingOperation: true,
  287. affectsData: true
  288. } );
  289. } );
  290. model.change( writer => {
  291. writer.setSelection( firstParagraph, 5 );
  292. } );
  293. viewDoc.fire( 'clipboardInput', {
  294. dataTransfer: {
  295. getData: sinon.spy()
  296. }
  297. } );
  298. sinon.assert.notCalled( spy );
  299. assertEqualMarkup( getModelData( model ), '<paragraph>foo b[]ar baz</paragraph>' );
  300. } );
  301. } );
  302. describe( 'exception highlighting', () => {
  303. let model, view;
  304. beforeEach( async () => {
  305. editor = await VirtualTestEditor.create( {
  306. plugins: [ Paragraph, RestrictedEditingEditing, BoldEditing ]
  307. } );
  308. model = editor.model;
  309. view = editor.editing.view;
  310. } );
  311. afterEach( () => {
  312. return editor.destroy();
  313. } );
  314. it( 'should convert the highlight to a proper view classes', () => {
  315. setModelData( model, '<paragraph>foo b[a]r baz</paragraph>' );
  316. const paragraph = model.document.getRoot().getChild( 0 );
  317. // <paragraph>foo <$marker>b[a]r</$marker> baz</paragraph>
  318. model.change( writer => {
  319. writer.addMarker( 'restricted-editing-exception:1', {
  320. range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ),
  321. usingOperation: true,
  322. affectsData: true
  323. } );
  324. } );
  325. expect( getViewData( view ) ).to.equal(
  326. '<p>foo <span class="ck-restricted-editing-exception ck-restricted-editing-exception_selected">b{a}r</span> baz</p>'
  327. );
  328. } );
  329. it( 'should remove classes when selection is moved away from an exception', () => {
  330. setModelData( model, '<paragraph>foo b[a]r baz</paragraph>' );
  331. const paragraph = model.document.getRoot().getChild( 0 );
  332. // <paragraph>foo <$marker>b[a]r</$marker> baz</paragraph>
  333. model.change( writer => {
  334. writer.addMarker( 'restricted-editing-exception:1', {
  335. range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ),
  336. usingOperation: true,
  337. affectsData: true
  338. } );
  339. } );
  340. expect( getViewData( view ) ).to.equal(
  341. '<p>foo <span class="ck-restricted-editing-exception ck-restricted-editing-exception_selected">b{a}r</span> baz</p>'
  342. );
  343. model.change( writer => writer.setSelection( model.document.getRoot().getChild( 0 ), 0 ) );
  344. expect( getViewData( view ) ).to.equal(
  345. '<p>{}foo <span class="ck-restricted-editing-exception">bar</span> baz</p>'
  346. );
  347. } );
  348. it( 'should work correctly when selection is moved inside an exception', () => {
  349. setModelData( model, '<paragraph>[]foo bar baz</paragraph>' );
  350. const paragraph = model.document.getRoot().getChild( 0 );
  351. // <paragraph>[]foo <$marker>bar</$marker> baz</paragraph>
  352. model.change( writer => {
  353. writer.addMarker( 'restricted-editing-exception:1', {
  354. range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ),
  355. usingOperation: true,
  356. affectsData: true
  357. } );
  358. } );
  359. expect( getViewData( view ) ).to.equal(
  360. '<p>{}foo <span class="ck-restricted-editing-exception">bar</span> baz</p>'
  361. );
  362. model.change( writer => writer.setSelection( model.document.getRoot().getChild( 0 ), 6 ) );
  363. expect( getViewData( view ) ).to.equal(
  364. '<p>foo <span class="ck-restricted-editing-exception ck-restricted-editing-exception_selected">ba{}r</span> baz</p>'
  365. );
  366. } );
  367. describe( 'editing downcast conversion integration', () => {
  368. it( 'works for the #insert event', () => {
  369. setModelData( model, '<paragraph>foo b[a]r baz</paragraph>' );
  370. const paragraph = model.document.getRoot().getChild( 0 );
  371. // <paragraph>foo <$marker>b[a]r</$marker> baz</paragraph>
  372. model.change( writer => {
  373. writer.addMarker( 'restricted-editing-exception:1', {
  374. range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ),
  375. usingOperation: true,
  376. affectsData: true
  377. } );
  378. } );
  379. model.change( writer => {
  380. writer.insertText( 'FOO', { linkHref: 'url' }, model.document.selection.getFirstPosition() );
  381. } );
  382. expect( getViewData( view ) ).to.equal(
  383. '<p>foo <span class="ck-restricted-editing-exception ck-restricted-editing-exception_selected">bFOO{a}r</span> baz</p>'
  384. );
  385. } );
  386. it( 'works for the #remove event', () => {
  387. setModelData( model, '<paragraph>foo b[a]r baz</paragraph>' );
  388. const paragraph = model.document.getRoot().getChild( 0 );
  389. // <paragraph>foo <$marker>b[a]r</$marker> baz</paragraph>
  390. model.change( writer => {
  391. writer.addMarker( 'restricted-editing-exception:1', {
  392. range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ),
  393. usingOperation: true,
  394. affectsData: true
  395. } );
  396. } );
  397. model.change( writer => {
  398. writer.remove( writer.createRange(
  399. writer.createPositionAt( model.document.getRoot().getChild( 0 ), 5 ),
  400. writer.createPositionAt( model.document.getRoot().getChild( 0 ), 6 )
  401. ) );
  402. } );
  403. expect( getViewData( view ) ).to.equal(
  404. '<p>foo <span class="ck-restricted-editing-exception ck-restricted-editing-exception_selected">b{}r</span> baz</p>'
  405. );
  406. } );
  407. it( 'works for the #attribute event', () => {
  408. setModelData( model, '<paragraph>foo b[a]r baz</paragraph>' );
  409. const paragraph = model.document.getRoot().getChild( 0 );
  410. // <paragraph>foo <$marker>b[a]r</$marker> baz</paragraph>
  411. model.change( writer => {
  412. writer.addMarker( 'restricted-editing-exception:1', {
  413. range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ),
  414. usingOperation: true,
  415. affectsData: true
  416. } );
  417. } );
  418. model.change( writer => {
  419. writer.setAttribute( 'bold', true, writer.createRange(
  420. model.document.selection.getFirstPosition().getShiftedBy( -1 ),
  421. model.document.selection.getFirstPosition().getShiftedBy( 1 ) )
  422. );
  423. } );
  424. expect( getViewData( view ) ).to.equal(
  425. '<p>foo ' +
  426. '<span class="ck-restricted-editing-exception ck-restricted-editing-exception_selected">' +
  427. '<strong>b{a</strong>' +
  428. '}r</span>' +
  429. ' baz</p>'
  430. );
  431. } );
  432. it( 'works for the #selection event', () => {
  433. setModelData( model, '<paragraph>foo b[a]r baz</paragraph>' );
  434. const paragraph = model.document.getRoot().getChild( 0 );
  435. // <paragraph>foo <$marker>b[a]r</$marker> baz</paragraph>
  436. model.change( writer => {
  437. writer.addMarker( 'restricted-editing-exception:1', {
  438. range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ),
  439. usingOperation: true,
  440. affectsData: true
  441. } );
  442. } );
  443. model.change( writer => {
  444. writer.setSelection( writer.createRange(
  445. model.document.selection.getFirstPosition().getShiftedBy( -1 ),
  446. model.document.selection.getFirstPosition().getShiftedBy( 1 ) )
  447. );
  448. } );
  449. expect( getViewData( view ) ).to.equal(
  450. '<p>foo {<span class="ck-restricted-editing-exception ck-restricted-editing-exception_selected">ba}r</span> baz</p>'
  451. );
  452. } );
  453. it( 'works for the addMarker and removeMarker events', () => {
  454. editor.conversion.for( 'editingDowncast' ).markerToHighlight( { model: 'fooMarker', view: {} } );
  455. setModelData( model, '<paragraph>foo b[a]r baz</paragraph>' );
  456. const paragraph = model.document.getRoot().getChild( 0 );
  457. // <paragraph>foo <$marker>b[a]r</$marker> baz</paragraph>
  458. model.change( writer => {
  459. writer.addMarker( 'restricted-editing-exception:1', {
  460. range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ),
  461. usingOperation: true,
  462. affectsData: true
  463. } );
  464. } );
  465. model.change( writer => {
  466. const range = writer.createRange(
  467. writer.createPositionAt( model.document.getRoot().getChild( 0 ), 0 ),
  468. writer.createPositionAt( model.document.getRoot().getChild( 0 ), 5 )
  469. );
  470. writer.addMarker( 'fooMarker', { range, usingOperation: true } );
  471. } );
  472. expect( getViewData( view ) ).to.equal(
  473. '<p>' +
  474. '<span>foo </span>' +
  475. '<span class="ck-restricted-editing-exception ck-restricted-editing-exception_selected">' +
  476. '<span>b</span>{a}r' +
  477. '</span>' +
  478. ' baz</p>'
  479. );
  480. model.change( writer => writer.removeMarker( 'fooMarker' ) );
  481. expect( getViewData( view ) ).to.equal(
  482. '<p>foo <span class="ck-restricted-editing-exception ck-restricted-editing-exception_selected">b{a}r</span> baz</p>'
  483. );
  484. } );
  485. } );
  486. } );
  487. describe( 'exception cycling with the keyboard', () => {
  488. let model, view, domEvtDataStub;
  489. beforeEach( async () => {
  490. editor = await VirtualTestEditor.create( {
  491. plugins: [ Paragraph, RestrictedEditingEditing, BoldEditing ]
  492. } );
  493. model = editor.model;
  494. view = editor.editing.view;
  495. domEvtDataStub = {
  496. keyCode: getCode( 'Tab' ),
  497. preventDefault: sinon.spy(),
  498. stopPropagation: sinon.spy()
  499. };
  500. sinon.spy( editor, 'execute' );
  501. } );
  502. afterEach( () => {
  503. return editor.destroy();
  504. } );
  505. it( 'should move to the closest next exception on tab key', () => {
  506. setModelData( model, '<paragraph>[]foo bar baz qux</paragraph>' );
  507. const paragraph = model.document.getRoot().getChild( 0 );
  508. // <paragraph>[]foo <marker>bar</marker> baz qux</paragraph>
  509. model.change( writer => {
  510. writer.addMarker( 'restricted-editing-exception:1', {
  511. range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ),
  512. usingOperation: true,
  513. affectsData: true
  514. } );
  515. } );
  516. // <paragraph>[]foo <marker>bar</marker> <marker>baz</marker≥ qux</paragraph>
  517. model.change( writer => {
  518. writer.addMarker( 'restricted-editing-exception:2', {
  519. range: writer.createRange( writer.createPositionAt( paragraph, 8 ), writer.createPositionAt( paragraph, 11 ) ),
  520. usingOperation: true,
  521. affectsData: true
  522. } );
  523. } );
  524. view.document.fire( 'keydown', domEvtDataStub );
  525. sinon.assert.calledOnce( editor.execute );
  526. sinon.assert.calledWithExactly( editor.execute, 'goToNextRestrictedEditingRegion' );
  527. sinon.assert.calledOnce( domEvtDataStub.preventDefault );
  528. sinon.assert.calledOnce( domEvtDataStub.stopPropagation );
  529. } );
  530. it( 'should not move to the closest next exception on tab key when there is none', () => {
  531. setModelData( model, '<paragraph>foo qux[]</paragraph>' );
  532. const paragraph = model.document.getRoot().getChild( 0 );
  533. // <paragraph><marker>foo</marker> qux[]</paragraph>
  534. model.change( writer => {
  535. writer.addMarker( 'restricted-editing-exception:1', {
  536. range: writer.createRange( writer.createPositionAt( paragraph, 0 ), writer.createPositionAt( paragraph, 3 ) ),
  537. usingOperation: true,
  538. affectsData: true
  539. } );
  540. } );
  541. view.document.fire( 'keydown', domEvtDataStub );
  542. sinon.assert.notCalled( editor.execute );
  543. sinon.assert.calledOnce( domEvtDataStub.preventDefault );
  544. sinon.assert.calledOnce( domEvtDataStub.stopPropagation );
  545. } );
  546. it( 'should move to the closest previous exception on shift+tab key', () => {
  547. setModelData( model, '<paragraph>foo bar baz qux[]</paragraph>' );
  548. const paragraph = model.document.getRoot().getChild( 0 );
  549. // <paragraph>foo <marker>bar</marker> baz qux[]</paragraph>
  550. model.change( writer => {
  551. writer.addMarker( 'restricted-editing-exception:1', {
  552. range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ),
  553. usingOperation: true,
  554. affectsData: true
  555. } );
  556. } );
  557. // <paragraph>foo <marker>bar</marker> <marker>baz</marker≥ qux[]</paragraph>
  558. model.change( writer => {
  559. writer.addMarker( 'restricted-editing-exception:2', {
  560. range: writer.createRange( writer.createPositionAt( paragraph, 8 ), writer.createPositionAt( paragraph, 11 ) ),
  561. usingOperation: true,
  562. affectsData: true
  563. } );
  564. } );
  565. domEvtDataStub.keyCode += getCode( 'Shift' );
  566. view.document.fire( 'keydown', domEvtDataStub );
  567. sinon.assert.calledOnce( editor.execute );
  568. sinon.assert.calledWithExactly( editor.execute, 'goToPreviousRestrictedEditingRegion' );
  569. sinon.assert.calledOnce( domEvtDataStub.preventDefault );
  570. sinon.assert.calledOnce( domEvtDataStub.stopPropagation );
  571. } );
  572. it( 'should not move to the closest previous exception on shift+tab key when there is none', () => {
  573. setModelData( model, '<paragraph>[]foo qux</paragraph>' );
  574. const paragraph = model.document.getRoot().getChild( 0 );
  575. // <paragraph>[]foo <marker>qux</marker></paragraph>
  576. model.change( writer => {
  577. writer.addMarker( 'restricted-editing-exception:1', {
  578. range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ),
  579. usingOperation: true,
  580. affectsData: true
  581. } );
  582. } );
  583. domEvtDataStub.keyCode += getCode( 'Shift' );
  584. view.document.fire( 'keydown', domEvtDataStub );
  585. sinon.assert.notCalled( editor.execute );
  586. sinon.assert.calledOnce( domEvtDataStub.preventDefault );
  587. sinon.assert.calledOnce( domEvtDataStub.stopPropagation );
  588. } );
  589. } );
  590. } );