deletecontents.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. /**
  2. * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /* bender-tags: model, composer */
  6. import Document from '/ckeditor5/engine/model/document.js';
  7. import deleteContents from '/ckeditor5/engine/model/composer/deletecontents.js';
  8. import { setData, getData } from '/tests/engine/_utils/model.js';
  9. describe( 'Delete utils', () => {
  10. let document;
  11. beforeEach( () => {
  12. document = new Document();
  13. document.createRoot();
  14. const schema = document.schema;
  15. // Note: We used short names instead of "image", "paragraph", etc. to make the tests shorter.
  16. // We could use any random names in fact, but using HTML tags may explain the tests a bit better.
  17. schema.registerItem( 'img', '$inline' );
  18. schema.registerItem( 'p', '$block' );
  19. schema.registerItem( 'h1', '$block' );
  20. schema.registerItem( 'pchild' );
  21. schema.allow( { name: 'pchild', inside: 'p' } );
  22. schema.allow( { name: '$text', inside: '$root' } );
  23. schema.allow( { name: 'img', inside: '$root' } );
  24. schema.allow( { name: '$text', attributes: [ 'bold', 'italic' ] } );
  25. schema.allow( { name: 'p', attributes: [ 'align' ] } );
  26. } );
  27. describe( 'deleteContents', () => {
  28. describe( 'in simple scenarios', () => {
  29. test(
  30. 'does nothing on collapsed selection',
  31. 'f[]oo',
  32. 'f[]oo'
  33. );
  34. test(
  35. 'deletes single character',
  36. 'f[o]o',
  37. 'f[]o'
  38. );
  39. it( 'deletes single character (backward selection)' , () => {
  40. setData( document, 'f[o]o', { lastRangeBackward: true } );
  41. deleteContents( document.batch(), document.selection );
  42. expect( getData( document ) ).to.equal( 'f[]o' );
  43. } );
  44. test(
  45. 'deletes whole text',
  46. '[foo]',
  47. '[]'
  48. );
  49. test(
  50. 'deletes whole text between nodes',
  51. '<img></img>[foo]<img></img>',
  52. '<img></img>[]<img></img>'
  53. );
  54. test(
  55. 'deletes an element',
  56. 'x[<img></img>]y',
  57. 'x[]y'
  58. );
  59. test(
  60. 'deletes a bunch of nodes',
  61. 'w[x<img></img>y]z',
  62. 'w[]z'
  63. );
  64. test(
  65. 'does not break things when option.merge passed',
  66. 'w[x<img></img>y]z',
  67. 'w[]z',
  68. { merge: true }
  69. );
  70. } );
  71. describe( 'with text attributes', () => {
  72. it( 'deletes characters (first half has attrs)', () => {
  73. setData( document, '<$text bold="true">fo[o</$text>b]ar', { selectionAttributes: {
  74. bold: true
  75. } } );
  76. deleteContents( document.batch(), document.selection );
  77. expect( getData( document ) ).to.equal( '<$text bold="true">fo[]</$text>ar' );
  78. expect( document.selection.getAttribute( 'bold' ) ).to.equal( 'true' );
  79. } );
  80. it( 'deletes characters (2nd half has attrs)', () => {
  81. setData( document, 'fo[o<$text bold="true">b]ar</$text>', { selectionAttributes: {
  82. bold: true
  83. } } );
  84. deleteContents( document.batch(), document.selection );
  85. expect( getData( document ) ).to.equal( 'fo[]<$text bold="true">ar</$text>' );
  86. expect( document.selection.getAttribute( 'bold' ) ).to.undefined;
  87. } );
  88. it( 'clears selection attrs when emptied content', () => {
  89. setData( document, '<p>x</p><p>[<$text bold="true">foo</$text>]</p><p>y</p>', { selectionAttributes: {
  90. bold: true
  91. } } );
  92. deleteContents( document.batch(), document.selection );
  93. expect( getData( document ) ).to.equal( '<p>x</p><p>[]</p><p>y</p>' );
  94. expect( document.selection.getAttribute( 'bold' ) ).to.undefined;
  95. } );
  96. it( 'leaves selection attributes when text contains them', () => {
  97. setData( document, '<p>x<$text bold="true">a[foo]b</$text>y</p>', { selectionAttributes: {
  98. bold: true
  99. } } );
  100. deleteContents( document.batch(), document.selection );
  101. expect( getData( document ) ).to.equal( '<p>x<$text bold="true">a[]b</$text>y</p>' );
  102. expect( document.selection.getAttribute( 'bold' ) ).to.equal( 'true' );
  103. } );
  104. } );
  105. // Note: The algorithm does not care what kind of it's merging as it knows nothing useful about these elements.
  106. // In most cases it handles all elements like you'd expect to handle block elements in HTML. However,
  107. // in some scenarios where the tree depth is bigger results may be hard to justify. In fact, such cases
  108. // should not happen unless we're talking about lists or tables, but these features will need to cover
  109. // their scenarios themselves. In all generic scenarios elements are never nested.
  110. //
  111. // You may also be thinking – but I don't want my elements to be merged. It means that there are some special rules,
  112. // like – multiple editing hosts (cE=true/false in use) or block limit elements like <td>.
  113. // Those case should, again, be handled by their specific implementations.
  114. describe( 'in multi-element scenarios', () => {
  115. test(
  116. 'do not merge when no need to',
  117. '<p>x</p><p>[foo]</p><p>y</p>',
  118. '<p>x</p><p>[]</p><p>y</p>',
  119. { merge: true }
  120. );
  121. test(
  122. 'merges second element into the first one (same name)',
  123. '<p>x</p><p>fo[o</p><p>b]ar</p><p>y</p>',
  124. '<p>x</p><p>fo[]ar</p><p>y</p>',
  125. { merge: true }
  126. );
  127. test(
  128. 'does not merge second element into the first one (same name, !option.merge)',
  129. '<p>x</p><p>fo[o</p><p>b]ar</p><p>y</p>',
  130. '<p>x</p><p>fo[]</p><p>ar</p><p>y</p>'
  131. );
  132. test(
  133. 'merges second element into the first one (same name)',
  134. '<p>x</p><p>fo[o</p><p>b]ar</p><p>y</p>',
  135. '<p>x</p><p>fo[]ar</p><p>y</p>',
  136. { merge: true }
  137. );
  138. test(
  139. 'merges second element into the first one (different name)',
  140. '<p>x</p><h1>fo[o</h1><p>b]ar</p><p>y</p>',
  141. '<p>x</p><h1>fo[]ar</h1><p>y</p>',
  142. { merge: true }
  143. );
  144. it( 'merges second element into the first one (different name, backward selection)', () => {
  145. setData( document, '<p>x</p><h1>fo[o</h1><p>b]ar</p><p>y</p>', { lastRangeBackward: true } );
  146. deleteContents( document.batch(), document.selection, { merge: true } );
  147. expect( getData( document ) ).to.equal( '<p>x</p><h1>fo[]ar</h1><p>y</p>' );
  148. } );
  149. test(
  150. 'merges second element into the first one (different attrs)',
  151. '<p>x</p><p align="l">fo[o</p><p>b]ar</p><p>y</p>',
  152. '<p>x</p><p align="l">fo[]ar</p><p>y</p>',
  153. { merge: true }
  154. );
  155. test(
  156. 'merges second element to an empty first element',
  157. '<p>x</p><h1>[</h1><p>fo]o</p><p>y</p>',
  158. '<p>x</p><h1>[]o</h1><p>y</p>',
  159. { merge: true }
  160. );
  161. test(
  162. 'merges elements when deep nested',
  163. '<p>x<pchild>fo[o</pchild></p><p><pchild>b]ar</pchild>y</p>',
  164. '<p>x<pchild>fo[]ar</pchild>y</p>',
  165. { merge: true }
  166. );
  167. // For code coverage reasons.
  168. test(
  169. 'merges element when selection is in two consecutive nodes even when it is empty',
  170. '<p>foo[</p><p>]bar</p>',
  171. '<p>foo[]bar</p>',
  172. { merge: true }
  173. );
  174. // If you disagree with this case please read the notes before this section.
  175. test(
  176. 'merges elements when left end deep nested',
  177. '<p>x<pchild>fo[o</pchild></p><p>b]ary</p>',
  178. '<p>x<pchild>fo[]</pchild>ary</p>',
  179. { merge: true }
  180. );
  181. // If you disagree with this case please read the notes before this section.
  182. test(
  183. 'merges elements when right end deep nested',
  184. '<p>xfo[o</p><p><pchild>b]ar</pchild>y<img></img></p>',
  185. '<p>xfo[]<pchild>ar</pchild>y<img></img></p>',
  186. { merge: true }
  187. );
  188. test(
  189. 'merges elements when more content in the right branch',
  190. '<p>xfo[o</p><p>b]a<pchild>r</pchild>y</p>',
  191. '<p>xfo[]a<pchild>r</pchild>y</p>',
  192. { merge: true }
  193. );
  194. test(
  195. 'leaves just one element when all selected',
  196. '<h1>[x</h1><p>foo</p><p>y]</p>',
  197. '<h1>[]</h1>',
  198. { merge: true }
  199. );
  200. } );
  201. function test( title, input, output, options ) {
  202. it( title, () => {
  203. setData( document, input );
  204. deleteContents( document.batch(), document.selection, options );
  205. expect( getData( document ) ).to.equal( output );
  206. } );
  207. }
  208. } );
  209. } );