8
0

modifyselection.js 12 KB

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