modifyselection.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  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 Selection from '/ckeditor5/engine/model/selection.js';
  8. import modifySelection from '/ckeditor5/engine/model/composer/modifyselection.js';
  9. import { setData, stringify } from '/tests/engine/_utils/model.js';
  10. describe( 'Delete utils', () => {
  11. let document;
  12. beforeEach( () => {
  13. document = new Document();
  14. document.schema.registerItem( 'p', '$block' );
  15. document.schema.registerItem( 'x', '$block' );
  16. document.schema.registerItem( 'img', '$inline' );
  17. document.schema.allow( { name: '$text', inside: '$root' } );
  18. document.createRoot();
  19. } );
  20. describe( 'modifySelection', () => {
  21. describe( 'unit=character', () => {
  22. describe( 'within element', () => {
  23. test(
  24. 'does nothing on empty content',
  25. '[]',
  26. '[]'
  27. );
  28. test(
  29. 'does nothing on empty content (with empty element)',
  30. '<p>[]</p>',
  31. '<p>[]</p>'
  32. );
  33. test(
  34. 'does nothing on empty content (backward)',
  35. '[]',
  36. '[]',
  37. { direction: 'backward' }
  38. );
  39. test(
  40. 'does nothing on root boundary',
  41. '<p>foo[]</p>',
  42. '<p>foo[]</p>'
  43. );
  44. test(
  45. 'does nothing on root boundary (backward)',
  46. '<p>[]foo</p>',
  47. '<p>[]foo</p>',
  48. { direction: 'backward' }
  49. );
  50. test(
  51. 'extends one character forward',
  52. '<p>f[]oo</p>',
  53. '<p>f[o]o</p>'
  54. );
  55. it( 'extends one character backward', () => {
  56. setData( document, '<p>fo[]o</p>', { lastRangeBackward: true } );
  57. modifySelection( document.selection, { direction: 'backward' } );
  58. expect( stringify( document.getRoot(), document.selection ) ).to.equal( '<p>f[o]o</p>' );
  59. expect( document.selection.isBackward ).to.true;
  60. } );
  61. test(
  62. 'extends one character forward (non-collapsed)',
  63. '<p>f[o]obar</p>',
  64. '<p>f[oo]bar</p>'
  65. );
  66. it( 'extends one character backward (non-collapsed)', () => {
  67. setData( document, '<p>foob[a]r</p>', { lastRangeBackward: true } );
  68. modifySelection( document.selection, { direction: 'backward' } );
  69. expect( stringify( document.getRoot(), document.selection ) ).to.equal( '<p>foo[ba]r</p>' );
  70. expect( document.selection.isBackward ).to.true;
  71. } );
  72. test(
  73. 'extends to element boundary',
  74. '<p>fo[]o</p>',
  75. '<p>fo[o]</p>'
  76. );
  77. it( 'extends to element boundary (backward)', () => {
  78. setData( document, '<p>f[]oo</p>' );
  79. modifySelection( document.selection, { direction: 'backward' } );
  80. expect( stringify( document.getRoot(), document.selection ) ).to.equal( '<p>[f]oo</p>' );
  81. expect( document.selection.isBackward ).to.true;
  82. } );
  83. test(
  84. 'shrinks forward selection (to collapsed)',
  85. '<p>foo[b]ar</p>',
  86. '<p>foo[]bar</p>',
  87. { direction: 'backward' }
  88. );
  89. it( 'shrinks backward selection (to collapsed)', () => {
  90. setData( document, '<p>foo[b]ar</p>', { lastRangeBackward: true } );
  91. modifySelection( document.selection );
  92. expect( stringify( document.getRoot(), document.selection ) ).to.equal( '<p>foob[]ar</p>' );
  93. expect( document.selection.isBackward ).to.false;
  94. } );
  95. test(
  96. 'extends one element forward',
  97. '<p>f[]<img></img>oo</p>',
  98. '<p>f[<img></img>]oo</p>'
  99. );
  100. test(
  101. 'extends one non-empty element forward',
  102. '<p>f[]<img>x</img>oo</p>',
  103. '<p>f[<img>x</img>]oo</p>'
  104. );
  105. it( 'extends one element backward', () => {
  106. setData( document, '<p>fo<img></img>[]o</p>' );
  107. modifySelection( document.selection, { direction: 'backward' } );
  108. expect( stringify( document.getRoot(), document.selection ) ).to.equal( '<p>fo[<img></img>]o</p>' );
  109. expect( document.selection.isBackward ).to.true;
  110. } );
  111. test(
  112. 'unicode support - combining mark forward',
  113. '<p>foo[]b̂ar</p>',
  114. '<p>foo[b̂]ar</p>'
  115. );
  116. it( 'unicode support - combining mark backward', () => {
  117. setData( document, '<p>foob̂[]ar</p>' );
  118. modifySelection( document.selection, { direction: 'backward' } );
  119. expect( stringify( document.getRoot(), document.selection ) ).to.equal( '<p>foo[b̂]ar</p>' );
  120. expect( document.selection.isBackward ).to.true;
  121. } );
  122. test(
  123. 'unicode support - combining mark multiple',
  124. '<p>fo[]o̻̐ͩbar</p>',
  125. '<p>fo[o̻̐ͩ]bar</p>'
  126. );
  127. it( 'unicode support - combining mark multiple backward', () => {
  128. setData( document, '<p>foo̻̐ͩ[]bar</p>' );
  129. modifySelection( document.selection, { direction: 'backward' } );
  130. expect( stringify( document.getRoot(), document.selection ) ).to.equal( '<p>fo[o̻̐ͩ]bar</p>' );
  131. expect( document.selection.isBackward ).to.true;
  132. } );
  133. test(
  134. 'unicode support - combining mark to the end',
  135. '<p>fo[]o̻̐ͩ</p>',
  136. '<p>fo[o̻̐ͩ]</p>'
  137. );
  138. test(
  139. 'unicode support - surrogate pairs forward',
  140. '<p>[]\uD83D\uDCA9</p>',
  141. '<p>[\uD83D\uDCA9]</p>'
  142. );
  143. it( 'unicode support - surrogate pairs backward', () => {
  144. setData( document, '<p>\uD83D\uDCA9[]</p>' );
  145. modifySelection( document.selection, { direction: 'backward' } );
  146. expect( stringify( document.getRoot(), document.selection ) ).to.equal( '<p>[\uD83D\uDCA9]</p>' );
  147. expect( document.selection.isBackward ).to.true;
  148. } );
  149. } );
  150. describe( 'beyond element', () => {
  151. test(
  152. 'extends over boundary of empty elements',
  153. '<p>[]</p><p></p><p></p>',
  154. '<p>[</p><p>]</p><p></p>'
  155. );
  156. it( 'extends over boundary of empty elements (backward)', () => {
  157. setData( document, '<p></p><p></p><p>[]</p>' );
  158. modifySelection( document.selection, { direction: 'backward' } );
  159. expect( stringify( document.getRoot(), document.selection ) ).to.equal( '<p></p><p>[</p><p>]</p>' );
  160. expect( document.selection.isBackward ).to.true;
  161. } );
  162. test(
  163. 'extends over boundary of non-empty elements',
  164. '<p>a[]</p><p>bcd</p>',
  165. '<p>a[</p><p>]bcd</p>'
  166. );
  167. it( 'extends over boundary of non-empty elements (backward)', () => {
  168. setData( document, '<p>a</p><p>[]bcd</p>' );
  169. modifySelection( document.selection, { direction: 'backward' } );
  170. expect( stringify( document.getRoot(), document.selection ) ).to.equal( '<p>a[</p><p>]bcd</p>' );
  171. expect( document.selection.isBackward ).to.true;
  172. } );
  173. test(
  174. 'extends over character after boundary',
  175. '<p>a[</p><p>]bcd</p>',
  176. '<p>a[</p><p>b]cd</p>'
  177. );
  178. it( 'extends over character after boundary (backward)', () => {
  179. setData( document, '<p>abc[</p><p>]d</p>', { lastRangeBackward: true } );
  180. modifySelection( document.selection, { direction: 'backward' } );
  181. expect( stringify( document.getRoot(), document.selection ) ).to.equal( '<p>ab[c</p><p>]d</p>' );
  182. expect( document.selection.isBackward ).to.true;
  183. } );
  184. test(
  185. 'extends over boundary when next element has nested elements',
  186. '<p>a[]</p><p><x>bcd</x></p>',
  187. '<p>a[</p><p>]<x>bcd</x></p>'
  188. );
  189. test(
  190. 'extends over element when next element has nested elements',
  191. '<p>a[</p><p>]<x>bcd</x>ef</p>',
  192. '<p>a[</p><p><x>bcd</x>]ef</p>'
  193. );
  194. test(
  195. 'extends over element when next node is a text',
  196. '<p>a[]</p>bc',
  197. '<p>a[</p>]bc'
  198. );
  199. it( 'extends over element when next node is a text (backward)', () => {
  200. setData( document, 'ab<p>[]c</p>' );
  201. modifySelection( document.selection, { direction: 'backward' } );
  202. expect( stringify( document.getRoot(), document.selection ) ).to.equal( 'ab[<p>]c</p>' );
  203. expect( document.selection.isBackward ).to.true;
  204. } );
  205. it( 'shrinks over boundary of empty elements', () => {
  206. setData( document, '<p>[</p><p>]</p>', { lastRangeBackward: true } );
  207. modifySelection( document.selection );
  208. expect( stringify( document.getRoot(), document.selection ) ).to.equal( '<p></p><p>[]</p>' );
  209. expect( document.selection.isBackward ).to.false;
  210. } );
  211. it( 'shrinks over boundary of empty elements (backward)', () => {
  212. setData( document, '<p>[</p><p>]</p>' );
  213. modifySelection( document.selection, { direction: 'backward' } );
  214. expect( stringify( document.getRoot(), document.selection ) ).to.equal( '<p>[]</p><p></p>' );
  215. expect( document.selection.isBackward ).to.false;
  216. } );
  217. it( 'shrinks over boundary of non-empty elements', () => {
  218. setData( document, '<p>a[</p><p>]b</p>', { lastRangeBackward: true } );
  219. modifySelection( document.selection );
  220. expect( stringify( document.getRoot(), document.selection ) ).to.equal( '<p>a</p><p>[]b</p>' );
  221. expect( document.selection.isBackward ).to.false;
  222. } );
  223. test(
  224. 'shrinks over boundary of non-empty elements (backward)',
  225. '<p>a[</p><p>]b</p>',
  226. '<p>a[]</p><p>b</p>',
  227. { direction: 'backward' }
  228. );
  229. it( 'updates selection attributes', () => {
  230. setData( document, '<p><$text bold="true">foo</$text>[b]</p>' );
  231. modifySelection( document.selection, { direction: 'backward' } );
  232. expect( stringify( document.getRoot(), document.selection ) ).to.equal( '<p><$text bold="true">foo[]</$text>b</p>' );
  233. expect( document.selection.getAttribute( 'bold' ) ).to.equal( true );
  234. } );
  235. } );
  236. } );
  237. describe( 'unit=codePoint', () => {
  238. test(
  239. 'does nothing on empty content',
  240. '[]',
  241. '[]',
  242. { unit: 'codePoint' }
  243. );
  244. test(
  245. 'does nothing on empty content (with empty element)',
  246. '<p>[]</p>',
  247. '<p>[]</p>',
  248. { unit: 'codePoint' }
  249. );
  250. test(
  251. 'extends one user-perceived character forward - latin letters',
  252. '<p>f[]oo</p>',
  253. '<p>f[o]o</p>',
  254. { unit: 'codePoint' }
  255. );
  256. it( 'extends one user-perceived character backward - latin letters', () => {
  257. setData( document, '<p>fo[]o</p>' );
  258. modifySelection( document.selection, { unit: 'codePoint', direction: 'backward' } );
  259. expect( stringify( document.getRoot(), document.selection ) ).to.equal( '<p>f[o]o</p>' );
  260. expect( document.selection.isBackward ).to.true;
  261. } );
  262. test(
  263. 'unicode support - combining mark forward',
  264. '<p>foo[]b̂ar</p>',
  265. '<p>foo[b]̂ar</p>',
  266. { unit: 'codePoint' }
  267. );
  268. it( 'unicode support - combining mark backward', () => {
  269. setData( document, '<p>foob̂[]ar</p>' );
  270. // Creating new instance of selection instead of operation on engine.model.Document#selection.
  271. // Document's selection will throw errors in some test cases (which are correct cases, but only for
  272. // non-document selections).
  273. const testSelection = Selection.createFromSelection( document.selection );
  274. modifySelection( testSelection, { unit: 'codePoint', direction: 'backward' } );
  275. expect( stringify( document.getRoot(), testSelection ) ).to.equal( '<p>foob[̂]ar</p>' );
  276. expect( testSelection.isBackward ).to.true;
  277. } );
  278. test(
  279. 'unicode support - combining mark multiple',
  280. '<p>fo[]o̻̐ͩbar</p>',
  281. '<p>fo[o]̻̐ͩbar</p>',
  282. { unit: 'codePoint' }
  283. );
  284. test(
  285. 'unicode support - surrogate pairs forward',
  286. '<p>[]\uD83D\uDCA9</p>',
  287. '<p>[\uD83D\uDCA9]</p>',
  288. { unit: 'codePoint' }
  289. );
  290. it( 'unicode support surrogate pairs backward', () => {
  291. setData( document, '<p>\uD83D\uDCA9[]</p>' );
  292. modifySelection( document.selection, { unit: 'codePoint', direction: 'backward' } );
  293. expect( stringify( document.getRoot(), document.selection ) ).to.equal( '<p>[\uD83D\uDCA9]</p>' );
  294. expect( document.selection.isBackward ).to.true;
  295. } );
  296. } );
  297. } );
  298. function test( title, input, output, options ) {
  299. it( title, () => {
  300. input = input.normalize();
  301. output = output.normalize();
  302. setData( document, input );
  303. // Creating new instance of selection instead of operation on engine.model.Document#selection.
  304. // Document's selection will throw errors in some test cases (which are correct cases, but only for
  305. // non-document selections).
  306. const testSelection = Selection.createFromSelection( document.selection );
  307. modifySelection( testSelection, options );
  308. expect( stringify( document.getRoot(), testSelection ) ).to.equal( output );
  309. } );
  310. }
  311. } );