documentselection.js 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135
  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 DocumentSelection from '../../src/view/documentselection';
  6. import Selection from '../../src/view/selection';
  7. import Range from '../../src/view/range';
  8. import Document from '../../src/view/document';
  9. import Element from '../../src/view/element';
  10. import Text from '../../src/view/text';
  11. import Position from '../../src/view/position';
  12. import count from '@ckeditor/ckeditor5-utils/src/count';
  13. import createViewRoot from './_utils/createroot';
  14. import { parse } from '../../src/dev-utils/view';
  15. import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
  16. import { expectToThrowCKEditorError } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
  17. describe( 'DocumentSelection', () => {
  18. let documentSelection, el, range1, range2, range3;
  19. testUtils.createSinonSandbox();
  20. beforeEach( () => {
  21. const text = new Text( 'xxxxxxxxxxxxxxxxxxxx' );
  22. el = new Element( 'p', null, text );
  23. documentSelection = new DocumentSelection();
  24. range1 = Range._createFromParentsAndOffsets( text, 5, text, 10 );
  25. range2 = Range._createFromParentsAndOffsets( text, 1, text, 2 );
  26. range3 = Range._createFromParentsAndOffsets( text, 12, text, 14 );
  27. } );
  28. describe( 'constructor()', () => {
  29. it( 'should be able to create an empty selection', () => {
  30. const selection = new DocumentSelection();
  31. expect( Array.from( selection.getRanges() ) ).to.deep.equal( [] );
  32. } );
  33. it( 'should be able to create a selection from the given ranges', () => {
  34. const ranges = [ range1, range2, range3 ];
  35. const selection = new DocumentSelection( ranges );
  36. expect( Array.from( selection.getRanges() ) ).to.deep.equal( ranges );
  37. } );
  38. it( 'should be able to create a selection from the given ranges and isLastBackward flag', () => {
  39. const ranges = [ range1, range2, range3 ];
  40. const selection = new DocumentSelection( ranges, { backward: true } );
  41. expect( selection.isBackward ).to.be.true;
  42. } );
  43. it( 'should be able to create a selection from the given range and isLastBackward flag', () => {
  44. const selection = new DocumentSelection( range1, { backward: true } );
  45. expect( Array.from( selection.getRanges() ) ).to.deep.equal( [ range1 ] );
  46. expect( selection.isBackward ).to.be.true;
  47. } );
  48. it( 'should be able to create a selection from the given iterable of ranges and isLastBackward flag', () => {
  49. const ranges = new Set( [ range1, range2, range3 ] );
  50. const selection = new DocumentSelection( ranges, { backward: false } );
  51. expect( Array.from( selection.getRanges() ) ).to.deep.equal( [ range1, range2, range3 ] );
  52. expect( selection.isBackward ).to.be.false;
  53. } );
  54. it( 'should be able to create a collapsed selection at the given position', () => {
  55. const position = range1.start;
  56. const selection = new DocumentSelection( position );
  57. expect( Array.from( selection.getRanges() ).length ).to.equal( 1 );
  58. expect( selection.getFirstRange().start ).to.deep.equal( position );
  59. expect( selection.getFirstRange().end ).to.deep.equal( position );
  60. expect( selection.isBackward ).to.be.false;
  61. } );
  62. it( 'should be able to create a collapsed selection at the given position', () => {
  63. const position = range1.start;
  64. const selection = new DocumentSelection( position );
  65. expect( Array.from( selection.getRanges() ).length ).to.equal( 1 );
  66. expect( selection.getFirstRange().start ).to.deep.equal( position );
  67. expect( selection.getFirstRange().end ).to.deep.equal( position );
  68. expect( selection.isBackward ).to.be.false;
  69. } );
  70. it( 'should be able to create a selection from the other document selection', () => {
  71. const otherSelection = new DocumentSelection( [ range2, range3 ], { backward: true } );
  72. const selection = new DocumentSelection( otherSelection );
  73. expect( Array.from( selection.getRanges() ) ).to.deep.equal( [ range2, range3 ] );
  74. expect( selection.isBackward ).to.be.true;
  75. } );
  76. it( 'should be able to create a selection from the other selection', () => {
  77. const otherSelection = new Selection( [ range2, range3 ], { backward: true } );
  78. const selection = new DocumentSelection( otherSelection );
  79. expect( Array.from( selection.getRanges() ) ).to.deep.equal( [ range2, range3 ] );
  80. expect( selection.isBackward ).to.be.true;
  81. } );
  82. it( 'should be able to create a fake selection from the other fake selection', () => {
  83. const otherSelection = new DocumentSelection( [ range2, range3 ], { fake: true, label: 'foo bar baz' } );
  84. const selection = new DocumentSelection( otherSelection );
  85. expect( selection.isFake ).to.be.true;
  86. expect( selection.fakeSelectionLabel ).to.equal( 'foo bar baz' );
  87. } );
  88. it( 'should throw an error when range is invalid', () => {
  89. expectToThrowCKEditorError( () => {
  90. // eslint-disable-next-line no-new
  91. new DocumentSelection( [ { invalid: 'range' } ] );
  92. }, /view-selection-add-range-not-range/ );
  93. } );
  94. it( 'should throw an error when ranges intersects', () => {
  95. const text = el.getChild( 0 );
  96. const range2 = Range._createFromParentsAndOffsets( text, 7, text, 15 );
  97. expectToThrowCKEditorError( () => {
  98. // eslint-disable-next-line no-new
  99. new DocumentSelection( [ range1, range2 ] );
  100. }, 'view-selection-range-intersects' );
  101. } );
  102. it( 'should throw an error when trying to set to not selectable', () => {
  103. expectToThrowCKEditorError( () => {
  104. // eslint-disable-next-line no-new
  105. new DocumentSelection( {} );
  106. }, /view-selection-setTo-not-selectable/ );
  107. } );
  108. } );
  109. describe( 'anchor', () => {
  110. it( 'should return null if no ranges in selection', () => {
  111. expect( documentSelection.anchor ).to.be.null;
  112. } );
  113. it( 'should return start of single range in selection', () => {
  114. documentSelection._setTo( range1 );
  115. const anchor = documentSelection.anchor;
  116. expect( anchor.isEqual( range1.start ) ).to.be.true;
  117. expect( anchor ).to.not.equal( range1.start );
  118. } );
  119. it( 'should return end of single range in selection when added as backward', () => {
  120. documentSelection._setTo( range1, { backward: true } );
  121. const anchor = documentSelection.anchor;
  122. expect( anchor.isEqual( range1.end ) ).to.be.true;
  123. expect( anchor ).to.not.equal( range1.end );
  124. } );
  125. it( 'should get anchor from last inserted range', () => {
  126. documentSelection._setTo( [ range1, range2 ] );
  127. expect( documentSelection.anchor.isEqual( range2.start ) ).to.be.true;
  128. } );
  129. } );
  130. describe( 'focus', () => {
  131. it( 'should return null if no ranges in selection', () => {
  132. expect( documentSelection.focus ).to.be.null;
  133. } );
  134. it( 'should return end of single range in selection', () => {
  135. documentSelection._setTo( range1 );
  136. const focus = documentSelection.focus;
  137. expect( focus.isEqual( range1.end ) ).to.be.true;
  138. } );
  139. it( 'should return start of single range in selection when added as backward', () => {
  140. documentSelection._setTo( range1, { backward: true } );
  141. const focus = documentSelection.focus;
  142. expect( focus.isEqual( range1.start ) ).to.be.true;
  143. expect( focus ).to.not.equal( range1.start );
  144. } );
  145. it( 'should get focus from last inserted range', () => {
  146. documentSelection._setTo( [ range1, range2 ] );
  147. expect( documentSelection.focus.isEqual( range2.end ) ).to.be.true;
  148. } );
  149. } );
  150. describe( '_setFocus()', () => {
  151. it( 'keeps all existing ranges when no modifications needed', () => {
  152. documentSelection._setTo( range1 );
  153. documentSelection._setFocus( documentSelection.focus );
  154. expect( count( documentSelection.getRanges() ) ).to.equal( 1 );
  155. } );
  156. it( 'throws if there are no ranges in selection', () => {
  157. const endPos = Position._createAt( el, 'end' );
  158. expectToThrowCKEditorError( () => {
  159. documentSelection._setFocus( endPos );
  160. }, /view-selection-setFocus-no-ranges/, documentSelection );
  161. } );
  162. it( 'modifies existing collapsed selection', () => {
  163. const startPos = Position._createAt( el, 1 );
  164. const endPos = Position._createAt( el, 2 );
  165. documentSelection._setTo( startPos );
  166. documentSelection._setFocus( endPos );
  167. expect( documentSelection.anchor.compareWith( startPos ) ).to.equal( 'same' );
  168. expect( documentSelection.focus.compareWith( endPos ) ).to.equal( 'same' );
  169. } );
  170. it( 'makes existing collapsed selection a backward selection', () => {
  171. const startPos = Position._createAt( el, 1 );
  172. const endPos = Position._createAt( el, 0 );
  173. documentSelection._setTo( startPos );
  174. documentSelection._setFocus( endPos );
  175. expect( documentSelection.anchor.compareWith( startPos ) ).to.equal( 'same' );
  176. expect( documentSelection.focus.compareWith( endPos ) ).to.equal( 'same' );
  177. expect( documentSelection.isBackward ).to.be.true;
  178. } );
  179. it( 'modifies existing non-collapsed selection', () => {
  180. const startPos = Position._createAt( el, 1 );
  181. const endPos = Position._createAt( el, 2 );
  182. const newEndPos = Position._createAt( el, 3 );
  183. documentSelection._setTo( new Range( startPos, endPos ) );
  184. documentSelection._setFocus( newEndPos );
  185. expect( documentSelection.anchor.compareWith( startPos ) ).to.equal( 'same' );
  186. expect( documentSelection.focus.compareWith( newEndPos ) ).to.equal( 'same' );
  187. } );
  188. it( 'makes existing non-collapsed selection a backward selection', () => {
  189. const startPos = Position._createAt( el, 1 );
  190. const endPos = Position._createAt( el, 2 );
  191. const newEndPos = Position._createAt( el, 0 );
  192. documentSelection._setTo( new Range( startPos, endPos ) );
  193. documentSelection._setFocus( newEndPos );
  194. expect( documentSelection.anchor.compareWith( startPos ) ).to.equal( 'same' );
  195. expect( documentSelection.focus.compareWith( newEndPos ) ).to.equal( 'same' );
  196. expect( documentSelection.isBackward ).to.be.true;
  197. } );
  198. it( 'makes existing backward selection a forward selection', () => {
  199. const startPos = Position._createAt( el, 1 );
  200. const endPos = Position._createAt( el, 2 );
  201. const newEndPos = Position._createAt( el, 3 );
  202. documentSelection._setTo( new Range( startPos, endPos ), { backward: true } );
  203. documentSelection._setFocus( newEndPos );
  204. expect( documentSelection.anchor.compareWith( endPos ) ).to.equal( 'same' );
  205. expect( documentSelection.focus.compareWith( newEndPos ) ).to.equal( 'same' );
  206. expect( documentSelection.isBackward ).to.be.false;
  207. } );
  208. it( 'modifies existing backward selection', () => {
  209. const startPos = Position._createAt( el, 1 );
  210. const endPos = Position._createAt( el, 2 );
  211. const newEndPos = Position._createAt( el, 0 );
  212. documentSelection._setTo( new Range( startPos, endPos ), { backward: true } );
  213. documentSelection._setFocus( newEndPos );
  214. expect( documentSelection.anchor.compareWith( endPos ) ).to.equal( 'same' );
  215. expect( documentSelection.focus.compareWith( newEndPos ) ).to.equal( 'same' );
  216. expect( documentSelection.isBackward ).to.be.true;
  217. } );
  218. it( 'modifies only the last range', () => {
  219. // Offsets are chosen in this way that the order of adding ranges must count, not their document order.
  220. const startPos1 = Position._createAt( el, 4 );
  221. const endPos1 = Position._createAt( el, 5 );
  222. const startPos2 = Position._createAt( el, 1 );
  223. const endPos2 = Position._createAt( el, 2 );
  224. const newEndPos = Position._createAt( el, 0 );
  225. documentSelection._setTo( [
  226. new Range( startPos1, endPos1 ),
  227. new Range( startPos2, endPos2 )
  228. ] );
  229. documentSelection._setFocus( newEndPos );
  230. const ranges = Array.from( documentSelection.getRanges() );
  231. expect( ranges ).to.have.lengthOf( 2 );
  232. expect( ranges[ 0 ].start.compareWith( startPos1 ) ).to.equal( 'same' );
  233. expect( ranges[ 0 ].end.compareWith( endPos1 ) ).to.equal( 'same' );
  234. expect( documentSelection.anchor.compareWith( startPos2 ) ).to.equal( 'same' );
  235. expect( documentSelection.focus.compareWith( newEndPos ) ).to.equal( 'same' );
  236. expect( documentSelection.isBackward ).to.be.true;
  237. } );
  238. it( 'collapses the selection when extending to the anchor', () => {
  239. const startPos = Position._createAt( el, 1 );
  240. const endPos = Position._createAt( el, 2 );
  241. documentSelection._setTo( new Range( startPos, endPos ) );
  242. documentSelection._setFocus( startPos );
  243. expect( documentSelection.focus.compareWith( startPos ) ).to.equal( 'same' );
  244. expect( documentSelection.isCollapsed ).to.be.true;
  245. } );
  246. } );
  247. describe( 'isCollapsed', () => {
  248. it( 'should return true when there is single collapsed range', () => {
  249. const range = Range._createFromParentsAndOffsets( el, 5, el, 5 );
  250. documentSelection._setTo( range );
  251. expect( documentSelection.isCollapsed ).to.be.true;
  252. } );
  253. it( 'should return false when there are multiple ranges', () => {
  254. const range1 = Range._createFromParentsAndOffsets( el, 5, el, 5 );
  255. const range2 = Range._createFromParentsAndOffsets( el, 15, el, 15 );
  256. documentSelection._setTo( [ range1, range2 ] );
  257. expect( documentSelection.isCollapsed ).to.be.false;
  258. } );
  259. it( 'should return false when there is not collapsed range', () => {
  260. const range = Range._createFromParentsAndOffsets( el, 15, el, 16 );
  261. documentSelection._setTo( range );
  262. expect( documentSelection.isCollapsed ).to.be.false;
  263. } );
  264. } );
  265. describe( 'rangeCount', () => {
  266. it( 'should return proper range count', () => {
  267. expect( documentSelection.rangeCount ).to.equal( 0 );
  268. documentSelection._setTo( range1 );
  269. expect( documentSelection.rangeCount ).to.equal( 1 );
  270. documentSelection._setTo( [ range1, range2 ] );
  271. expect( documentSelection.rangeCount ).to.equal( 2 );
  272. } );
  273. } );
  274. describe( 'isBackward', () => {
  275. it( 'is defined by the last added range', () => {
  276. const range1 = Range._createFromParentsAndOffsets( el, 5, el, 10 );
  277. const range2 = Range._createFromParentsAndOffsets( el, 15, el, 16 );
  278. documentSelection._setTo( range1, { backward: true } );
  279. expect( documentSelection ).to.have.property( 'isBackward', true );
  280. documentSelection._setTo( [ range1, range2 ] );
  281. expect( documentSelection ).to.have.property( 'isBackward', false );
  282. } );
  283. it( 'is false when last range is collapsed', () => {
  284. const range = Range._createFromParentsAndOffsets( el, 5, el, 5 );
  285. documentSelection._setTo( range, { backward: true } );
  286. expect( documentSelection.isBackward ).to.be.false;
  287. } );
  288. } );
  289. describe( 'getRanges', () => {
  290. it( 'should return iterator with copies of all ranges', () => {
  291. documentSelection._setTo( [ range1, range2 ] );
  292. const iterable = documentSelection.getRanges();
  293. const ranges = Array.from( iterable );
  294. expect( ranges.length ).to.equal( 2 );
  295. expect( ranges[ 0 ].isEqual( range1 ) ).to.be.true;
  296. expect( ranges[ 0 ] ).to.not.equal( range1 );
  297. expect( ranges[ 1 ].isEqual( range2 ) ).to.be.true;
  298. expect( ranges[ 1 ] ).to.not.equal( range2 );
  299. } );
  300. } );
  301. describe( 'getFirstRange', () => {
  302. it( 'should return copy of range with first position', () => {
  303. documentSelection._setTo( [ range1, range2, range3 ] );
  304. const range = documentSelection.getFirstRange();
  305. expect( range.isEqual( range2 ) ).to.be.true;
  306. expect( range ).to.not.equal( range2 );
  307. } );
  308. it( 'should return null if no ranges are present', () => {
  309. expect( documentSelection.getFirstRange() ).to.be.null;
  310. } );
  311. } );
  312. describe( 'getLastRange', () => {
  313. it( 'should return copy of range with last position', () => {
  314. documentSelection._setTo( [ range1, range2, range3 ] );
  315. const range = documentSelection.getLastRange();
  316. expect( range.isEqual( range3 ) ).to.be.true;
  317. expect( range ).to.not.equal( range3 );
  318. } );
  319. it( 'should return null if no ranges are present', () => {
  320. expect( documentSelection.getLastRange() ).to.be.null;
  321. } );
  322. } );
  323. describe( 'getFirstPosition', () => {
  324. it( 'should return copy of first position', () => {
  325. documentSelection._setTo( [ range1, range2, range3 ] );
  326. const position = documentSelection.getFirstPosition();
  327. expect( position.isEqual( range2.start ) ).to.be.true;
  328. expect( position ).to.not.equal( range2.start );
  329. } );
  330. it( 'should return null if no ranges are present', () => {
  331. expect( documentSelection.getFirstPosition() ).to.be.null;
  332. } );
  333. } );
  334. describe( 'getLastPosition', () => {
  335. it( 'should return copy of range with last position', () => {
  336. documentSelection._setTo( [ range1, range2, range3 ] );
  337. const position = documentSelection.getLastPosition();
  338. expect( position.isEqual( range3.end ) ).to.be.true;
  339. expect( position ).to.not.equal( range3.end );
  340. } );
  341. it( 'should return null if no ranges are present', () => {
  342. expect( documentSelection.getLastPosition() ).to.be.null;
  343. } );
  344. } );
  345. describe( 'isEqual', () => {
  346. it( 'should return true if selections equal', () => {
  347. documentSelection._setTo( [ range1, range2 ] );
  348. const otherSelection = new DocumentSelection();
  349. otherSelection._setTo( [ range1, range2 ] );
  350. expect( documentSelection.isEqual( otherSelection ) ).to.be.true;
  351. } );
  352. it( 'should return true if selections equal - DocumentSelection and Selection', () => {
  353. documentSelection._setTo( [ range1, range2 ] );
  354. const otherSelection = new Selection();
  355. otherSelection.setTo( [ range1, range2 ] );
  356. expect( documentSelection.isEqual( otherSelection ) ).to.be.true;
  357. } );
  358. it( 'should return true if backward selections equal', () => {
  359. documentSelection._setTo( range1, { backward: true } );
  360. const otherSelection = new DocumentSelection( [ range1 ], { backward: true } );
  361. expect( documentSelection.isEqual( otherSelection ) ).to.be.true;
  362. } );
  363. it( 'should return true if backward selections equal - DocumentSelection and Selection', () => {
  364. documentSelection._setTo( range1, { backward: true } );
  365. const otherSelection = new Selection( [ range1 ], { backward: true } );
  366. expect( documentSelection.isEqual( otherSelection ) ).to.be.true;
  367. } );
  368. it( 'should return false if ranges count does not equal', () => {
  369. documentSelection._setTo( [ range1, range2 ] );
  370. const otherSelection = new DocumentSelection( [ range1 ] );
  371. expect( documentSelection.isEqual( otherSelection ) ).to.be.false;
  372. } );
  373. it( 'should return false if ranges count does not equal - DocumentSelection and Selection', () => {
  374. documentSelection._setTo( [ range1, range2 ] );
  375. const otherSelection = new Selection( [ range1 ] );
  376. expect( documentSelection.isEqual( otherSelection ) ).to.be.false;
  377. } );
  378. it( 'should return false if ranges (other than the last added one) do not equal', () => {
  379. documentSelection._setTo( [ range1, range3 ] );
  380. const otherSelection = new DocumentSelection( [ range2, range3 ] );
  381. expect( documentSelection.isEqual( otherSelection ) ).to.be.false;
  382. } );
  383. it( 'should return false if ranges (other than the last added one) do not equal - DocumentSelection and Selection', () => {
  384. documentSelection._setTo( [ range1, range3 ] );
  385. const otherSelection = new Selection( [ range2, range3 ] );
  386. expect( documentSelection.isEqual( otherSelection ) ).to.be.false;
  387. } );
  388. it( 'should return false if directions do not equal', () => {
  389. documentSelection._setTo( range1 );
  390. const otherSelection = new DocumentSelection( [ range1 ], { backward: true } );
  391. expect( documentSelection.isEqual( otherSelection ) ).to.be.false;
  392. } );
  393. it( 'should return false if directions do not equal - DocumentSelection and Selection', () => {
  394. documentSelection._setTo( range1 );
  395. const otherSelection = new Selection( [ range1 ], { backward: true } );
  396. expect( documentSelection.isEqual( otherSelection ) ).to.be.false;
  397. } );
  398. it( 'should return false if one selection is fake', () => {
  399. const otherSelection = new DocumentSelection( null, { fake: true } );
  400. expect( documentSelection.isEqual( otherSelection ) ).to.be.false;
  401. } );
  402. it( 'should return false if one selection is fake', () => {
  403. const otherSelection = new DocumentSelection( null, { fake: true } );
  404. expect( documentSelection.isEqual( otherSelection ) ).to.be.false;
  405. } );
  406. it( 'should return true if both selection are fake - DocumentSelection and Selection', () => {
  407. const otherSelection = new Selection( range1, { fake: true } );
  408. documentSelection._setTo( range1, { fake: true } );
  409. expect( documentSelection.isEqual( otherSelection ) ).to.be.true;
  410. } );
  411. it( 'should return false if both selection are fake but have different label', () => {
  412. const otherSelection = new DocumentSelection( [ range1 ], { fake: true, label: 'foo bar baz' } );
  413. documentSelection._setTo( range1, { fake: true, label: 'foo' } );
  414. expect( documentSelection.isEqual( otherSelection ) ).to.be.false;
  415. } );
  416. it( 'should return false if both selection are fake but have different label - DocumentSelection and Selection', () => {
  417. const otherSelection = new Selection( [ range1 ], { fake: true, label: 'foo bar baz' } );
  418. documentSelection._setTo( range1, { fake: true, label: 'foo' } );
  419. expect( documentSelection.isEqual( otherSelection ) ).to.be.false;
  420. } );
  421. it( 'should return true if both selections are empty', () => {
  422. const otherSelection = new DocumentSelection();
  423. expect( documentSelection.isEqual( otherSelection ) ).to.be.true;
  424. } );
  425. it( 'should return true if both selections are empty - DocumentSelection and Selection', () => {
  426. const otherSelection = new Selection();
  427. expect( documentSelection.isEqual( otherSelection ) ).to.be.true;
  428. } );
  429. } );
  430. describe( 'isSimilar', () => {
  431. it( 'should return true if selections equal', () => {
  432. documentSelection._setTo( [ range1, range2 ] );
  433. const otherSelection = new DocumentSelection( [ range1, range2 ] );
  434. expect( documentSelection.isSimilar( otherSelection ) ).to.be.true;
  435. } );
  436. it( 'should return true if selections equal - DocumentSelection and Selection', () => {
  437. documentSelection._setTo( [ range1, range2 ] );
  438. const otherSelection = new Selection( [ range1, range2 ] );
  439. expect( documentSelection.isSimilar( otherSelection ) ).to.be.true;
  440. } );
  441. it( 'should return false if ranges count does not equal', () => {
  442. documentSelection._setTo( [ range1, range2 ] );
  443. const otherSelection = new DocumentSelection( [ range1 ] );
  444. expect( documentSelection.isSimilar( otherSelection ) ).to.be.false;
  445. } );
  446. it( 'should return false if ranges count does not equal - DocumentSelection and Selection', () => {
  447. documentSelection._setTo( [ range1, range2 ] );
  448. const otherSelection = new Selection( [ range1 ] );
  449. expect( documentSelection.isSimilar( otherSelection ) ).to.be.false;
  450. } );
  451. it( 'should return false if trimmed ranges (other than the last added one) are not equal', () => {
  452. documentSelection._setTo( [ range1, range3 ] );
  453. const otherSelection = new DocumentSelection( [ range2, range3 ] );
  454. expect( documentSelection.isSimilar( otherSelection ) ).to.be.false;
  455. } );
  456. it( 'should return false if trimmed ranges (other than the last added one) are not equal - with Selection', () => {
  457. documentSelection._setTo( [ range1, range3 ] );
  458. const otherSelection = new Selection( [ range2, range3 ] );
  459. expect( documentSelection.isSimilar( otherSelection ) ).to.be.false;
  460. } );
  461. it( 'should return false if directions are not equal', () => {
  462. documentSelection._setTo( range1 );
  463. const otherSelection = new DocumentSelection( [ range1 ], { backward: true } );
  464. expect( documentSelection.isSimilar( otherSelection ) ).to.be.false;
  465. } );
  466. it( 'should return false if directions are not equal - DocumentSelection and Selection', () => {
  467. documentSelection._setTo( range1 );
  468. const otherSelection = new Selection( [ range1 ], { backward: true } );
  469. expect( documentSelection.isSimilar( otherSelection ) ).to.be.false;
  470. } );
  471. it( 'should return true if both selections are empty', () => {
  472. const otherSelection = new DocumentSelection();
  473. expect( documentSelection.isSimilar( otherSelection ) ).to.be.true;
  474. } );
  475. it( 'should return true if both selections are empty - DocumentSelection and Selection', () => {
  476. const otherSelection = new Selection();
  477. expect( documentSelection.isSimilar( otherSelection ) ).to.be.true;
  478. } );
  479. it( 'should return true if all ranges trimmed from both selections are equal', () => {
  480. const view = parse(
  481. '<container:p><attribute:span></attribute:span></container:p>' +
  482. '<container:p><attribute:span>xx</attribute:span></container:p>'
  483. );
  484. const p1 = view.getChild( 0 );
  485. const p2 = view.getChild( 1 );
  486. const span1 = p1.getChild( 0 );
  487. const span2 = p2.getChild( 0 );
  488. // <p>[<span>{]</span>}</p><p>[<span>{xx}</span>]</p>
  489. const rangeA1 = Range._createFromParentsAndOffsets( p1, 0, span1, 0 );
  490. const rangeB1 = Range._createFromParentsAndOffsets( span1, 0, p1, 1 );
  491. const rangeA2 = Range._createFromParentsAndOffsets( p2, 0, p2, 1 );
  492. const rangeB2 = Range._createFromParentsAndOffsets( span2, 0, span2, 1 );
  493. documentSelection._setTo( [ rangeA1, rangeA2 ] );
  494. const otherSelection = new DocumentSelection( [ rangeB2, rangeB1 ] );
  495. expect( documentSelection.isSimilar( otherSelection ) ).to.be.true;
  496. expect( otherSelection.isSimilar( documentSelection ) ).to.be.true;
  497. expect( documentSelection.isEqual( otherSelection ) ).to.be.false;
  498. expect( otherSelection.isEqual( documentSelection ) ).to.be.false;
  499. } );
  500. it( 'should return true if all ranges trimmed from both selections are equal - DocumentSelection and Selection', () => {
  501. const view = parse(
  502. '<container:p><attribute:span></attribute:span></container:p>' +
  503. '<container:p><attribute:span>xx</attribute:span></container:p>'
  504. );
  505. const p1 = view.getChild( 0 );
  506. const p2 = view.getChild( 1 );
  507. const span1 = p1.getChild( 0 );
  508. const span2 = p2.getChild( 0 );
  509. // <p>[<span>{]</span>}</p><p>[<span>{xx}</span>]</p>
  510. const rangeA1 = Range._createFromParentsAndOffsets( p1, 0, span1, 0 );
  511. const rangeB1 = Range._createFromParentsAndOffsets( span1, 0, p1, 1 );
  512. const rangeA2 = Range._createFromParentsAndOffsets( p2, 0, p2, 1 );
  513. const rangeB2 = Range._createFromParentsAndOffsets( span2, 0, span2, 1 );
  514. documentSelection._setTo( [ rangeA1, rangeA2 ] );
  515. const otherSelection = new Selection( [ rangeB2, rangeB1 ] );
  516. expect( documentSelection.isSimilar( otherSelection ) ).to.be.true;
  517. expect( otherSelection.isSimilar( documentSelection ) ).to.be.true;
  518. expect( documentSelection.isEqual( otherSelection ) ).to.be.false;
  519. expect( otherSelection.isEqual( documentSelection ) ).to.be.false;
  520. } );
  521. } );
  522. describe( 'is()', () => {
  523. it( 'should return true for selection', () => {
  524. expect( documentSelection.is( 'selection' ) ).to.be.true;
  525. expect( documentSelection.is( 'view:selection' ) ).to.be.true;
  526. } );
  527. it( 'should return true for documentSelection', () => {
  528. expect( documentSelection.is( 'documentSelection' ) ).to.be.true;
  529. expect( documentSelection.is( 'view:documentSelection' ) ).to.be.true;
  530. } );
  531. it( 'should return false for other values', () => {
  532. expect( documentSelection.is( 'node' ) ).to.be.false;
  533. expect( documentSelection.is( 'view:node' ) ).to.be.false;
  534. expect( documentSelection.is( 'text' ) ).to.be.false;
  535. expect( documentSelection.is( 'view:text' ) ).to.be.false;
  536. expect( documentSelection.is( 'textProxy' ) ).to.be.false;
  537. expect( documentSelection.is( 'element' ) ).to.be.false;
  538. expect( documentSelection.is( 'rootElement' ) ).to.be.false;
  539. expect( documentSelection.is( 'model:selection' ) ).to.be.false;
  540. expect( documentSelection.is( 'model:documentSelection' ) ).to.be.false;
  541. } );
  542. } );
  543. describe( '_setTo()', () => {
  544. describe( 'simple scenarios', () => {
  545. it( 'should set selection ranges from the given selection', () => {
  546. documentSelection._setTo( range1 );
  547. const otherSelection = new DocumentSelection( [ range2, range3 ], { backward: true } );
  548. documentSelection._setTo( otherSelection );
  549. expect( documentSelection.rangeCount ).to.equal( 2 );
  550. expect( documentSelection._ranges[ 0 ].isEqual( range2 ) ).to.be.true;
  551. expect( documentSelection._ranges[ 0 ] ).is.not.equal( range2 );
  552. expect( documentSelection._ranges[ 1 ].isEqual( range3 ) ).to.be.true;
  553. expect( documentSelection._ranges[ 1 ] ).is.not.equal( range3 );
  554. expect( documentSelection.anchor.isEqual( range3.end ) ).to.be.true;
  555. } );
  556. it( 'should set selection on the given Range', () => {
  557. documentSelection._setTo( range1 );
  558. expect( Array.from( documentSelection.getRanges() ) ).to.deep.equal( [ range1 ] );
  559. expect( documentSelection.isBackward ).to.be.false;
  560. } );
  561. it( 'should set selection on the given iterable of Ranges', () => {
  562. documentSelection._setTo( new Set( [ range1, range2 ] ) );
  563. expect( Array.from( documentSelection.getRanges() ) ).to.deep.equal( [ range1, range2 ] );
  564. expect( documentSelection.isBackward ).to.be.false;
  565. } );
  566. it( 'should set collapsed selection on the given Position', () => {
  567. documentSelection._setTo( range1.start );
  568. expect( Array.from( documentSelection.getRanges() ).length ).to.equal( 1 );
  569. expect( Array.from( documentSelection.getRanges() )[ 0 ].start ).to.deep.equal( range1.start );
  570. expect( documentSelection.isBackward ).to.be.false;
  571. expect( documentSelection.isCollapsed ).to.be.true;
  572. } );
  573. it( 'should fire change event', done => {
  574. documentSelection.on( 'change', () => {
  575. expect( documentSelection.rangeCount ).to.equal( 1 );
  576. expect( documentSelection.getFirstRange().isEqual( range1 ) ).to.be.true;
  577. done();
  578. } );
  579. const otherSelection = new DocumentSelection( [ range1 ] );
  580. documentSelection._setTo( otherSelection );
  581. } );
  582. it( 'should set fake state and label', () => {
  583. const label = 'foo bar baz';
  584. const otherSelection = new DocumentSelection( null, { fake: true, label } );
  585. documentSelection._setTo( otherSelection );
  586. expect( documentSelection.isFake ).to.be.true;
  587. expect( documentSelection.fakeSelectionLabel ).to.equal( label );
  588. } );
  589. it( 'should throw an error when trying to set to not selectable', () => {
  590. const otherSelection = new DocumentSelection();
  591. expectToThrowCKEditorError( () => {
  592. otherSelection._setTo( {} );
  593. }, /view-selection-setTo-not-selectable/ );
  594. } );
  595. it( 'should throw an error when trying to set to not selectable #2', () => {
  596. const otherSelection = new DocumentSelection();
  597. expectToThrowCKEditorError( () => {
  598. otherSelection._setTo();
  599. }, /view-selection-setTo-not-selectable/ );
  600. } );
  601. } );
  602. describe( 'setting collapsed selection', () => {
  603. beforeEach( () => {
  604. documentSelection._setTo( [ range1, range2 ] );
  605. } );
  606. it( 'should collapse selection at position', () => {
  607. const position = new Position( el, 4 );
  608. documentSelection._setTo( position );
  609. const range = documentSelection.getFirstRange();
  610. expect( range.start.parent ).to.equal( el );
  611. expect( range.start.offset ).to.equal( 4 );
  612. expect( range.start.isEqual( range.end ) ).to.be.true;
  613. } );
  614. it( 'should collapse selection at node and offset', () => {
  615. const foo = new Text( 'foo' );
  616. const p = new Element( 'p', null, foo );
  617. documentSelection._setTo( foo, 0 );
  618. let range = documentSelection.getFirstRange();
  619. expect( range.start.parent ).to.equal( foo );
  620. expect( range.start.offset ).to.equal( 0 );
  621. expect( range.start.isEqual( range.end ) ).to.be.true;
  622. documentSelection._setTo( p, 1 );
  623. range = documentSelection.getFirstRange();
  624. expect( range.start.parent ).to.equal( p );
  625. expect( range.start.offset ).to.equal( 1 );
  626. expect( range.start.isEqual( range.end ) ).to.be.true;
  627. } );
  628. it( 'should throw an error when the second parameter is not passed and first is an item', () => {
  629. const foo = new Text( 'foo' );
  630. expectToThrowCKEditorError( () => {
  631. documentSelection._setTo( foo );
  632. }, /view-selection-setTo-required-second-parameter/, documentSelection );
  633. } );
  634. it( 'should collapse selection at node and flag', () => {
  635. const foo = new Text( 'foo' );
  636. const p = new Element( 'p', null, foo );
  637. documentSelection._setTo( foo, 'end' );
  638. let range = documentSelection.getFirstRange();
  639. expect( range.start.parent ).to.equal( foo );
  640. expect( range.start.offset ).to.equal( 3 );
  641. expect( range.start.isEqual( range.end ) ).to.be.true;
  642. documentSelection._setTo( foo, 'before' );
  643. range = documentSelection.getFirstRange();
  644. expect( range.start.parent ).to.equal( p );
  645. expect( range.start.offset ).to.equal( 0 );
  646. expect( range.start.isEqual( range.end ) ).to.be.true;
  647. documentSelection._setTo( foo, 'after' );
  648. range = documentSelection.getFirstRange();
  649. expect( range.start.parent ).to.equal( p );
  650. expect( range.start.offset ).to.equal( 1 );
  651. expect( range.start.isEqual( range.end ) ).to.be.true;
  652. } );
  653. } );
  654. describe( 'setting collapsed selection at start', () => {
  655. it( 'should collapse to start position and fire change event', done => {
  656. documentSelection._setTo( [ range1, range2, range3 ] );
  657. documentSelection.once( 'change', () => {
  658. expect( documentSelection.rangeCount ).to.equal( 1 );
  659. expect( documentSelection.isCollapsed ).to.be.true;
  660. expect( documentSelection._ranges[ 0 ].start.isEqual( range2.start ) ).to.be.true;
  661. done();
  662. } );
  663. documentSelection._setTo( documentSelection.getFirstPosition() );
  664. } );
  665. } );
  666. describe( 'setting collapsed selection to end', () => {
  667. it( 'should collapse to end position and fire change event', done => {
  668. documentSelection._setTo( [ range1, range2, range3 ] );
  669. documentSelection.once( 'change', () => {
  670. expect( documentSelection.rangeCount ).to.equal( 1 );
  671. expect( documentSelection.isCollapsed ).to.be.true;
  672. expect( documentSelection._ranges[ 0 ].end.isEqual( range3.end ) ).to.be.true;
  673. done();
  674. } );
  675. documentSelection._setTo( documentSelection.getLastPosition() );
  676. } );
  677. } );
  678. describe( 'removing all ranges', () => {
  679. it( 'should remove all ranges and fire change event', done => {
  680. documentSelection._setTo( [ range1, range2 ] );
  681. documentSelection.once( 'change', () => {
  682. expect( documentSelection.rangeCount ).to.equal( 0 );
  683. done();
  684. } );
  685. documentSelection._setTo( null );
  686. } );
  687. } );
  688. describe( 'setting fake selection', () => {
  689. it( 'should allow to set selection to fake', () => {
  690. documentSelection._setTo( range1, { fake: true } );
  691. expect( documentSelection.isFake ).to.be.true;
  692. } );
  693. it( 'should allow to set fake selection label', () => {
  694. const label = 'foo bar baz';
  695. documentSelection._setTo( range1, { fake: true, label } );
  696. expect( documentSelection.fakeSelectionLabel ).to.equal( label );
  697. } );
  698. it( 'should not set label when set to false', () => {
  699. const label = 'foo bar baz';
  700. documentSelection._setTo( range1, { fake: false, label } );
  701. expect( documentSelection.fakeSelectionLabel ).to.equal( '' );
  702. } );
  703. it( 'should reset label when set to false', () => {
  704. const label = 'foo bar baz';
  705. documentSelection._setTo( range1, { fake: true, label } );
  706. documentSelection._setTo( range1 );
  707. expect( documentSelection.fakeSelectionLabel ).to.equal( '' );
  708. } );
  709. it( 'should fire change event', done => {
  710. documentSelection.once( 'change', () => {
  711. expect( documentSelection.isFake ).to.be.true;
  712. expect( documentSelection.fakeSelectionLabel ).to.equal( 'foo bar baz' );
  713. done();
  714. } );
  715. documentSelection._setTo( range1, { fake: true, label: 'foo bar baz' } );
  716. } );
  717. it( 'should be possible to create an empty fake selection', () => {
  718. documentSelection._setTo( null, { fake: true, label: 'foo bar baz' } );
  719. expect( documentSelection.fakeSelectionLabel ).to.equal( 'foo bar baz' );
  720. expect( documentSelection.isFake ).to.be.true;
  721. } );
  722. } );
  723. describe( 'setting selection to itself', () => {
  724. it( 'should correctly set ranges when setting to the same selection', () => {
  725. documentSelection._setTo( [ range1, range2 ] );
  726. documentSelection._setTo( documentSelection );
  727. const ranges = Array.from( documentSelection.getRanges() );
  728. expect( ranges.length ).to.equal( 2 );
  729. expect( ranges[ 0 ].isEqual( range1 ) ).to.be.true;
  730. expect( ranges[ 1 ].isEqual( range2 ) ).to.be.true;
  731. } );
  732. it( 'should correctly set ranges when setting to the same selection\'s ranges', () => {
  733. documentSelection._setTo( [ range1, range2 ] );
  734. documentSelection._setTo( documentSelection.getRanges() );
  735. const ranges = Array.from( documentSelection.getRanges() );
  736. expect( ranges.length ).to.equal( 2 );
  737. expect( ranges[ 0 ].isEqual( range1 ) ).to.be.true;
  738. expect( ranges[ 1 ].isEqual( range2 ) ).to.be.true;
  739. } );
  740. } );
  741. describe( 'throwing errors', () => {
  742. it( 'should throw an error when range is invalid', () => {
  743. expectToThrowCKEditorError( () => {
  744. documentSelection._setTo( [ { invalid: 'range' } ] );
  745. }, /view-selection-add-range-not-range/, documentSelection );
  746. } );
  747. it( 'should throw when range is intersecting with already added range', () => {
  748. const text = el.getChild( 0 );
  749. const range2 = Range._createFromParentsAndOffsets( text, 7, text, 15 );
  750. expectToThrowCKEditorError( () => {
  751. documentSelection._setTo( [ range1, range2 ] );
  752. }, 'view-selection-range-intersects', documentSelection );
  753. } );
  754. } );
  755. it( 'should allow setting selection on an item', () => {
  756. const textNode1 = new Text( 'foo' );
  757. const textNode2 = new Text( 'bar' );
  758. const textNode3 = new Text( 'baz' );
  759. const element = new Element( 'p', null, [ textNode1, textNode2, textNode3 ] );
  760. documentSelection._setTo( textNode2, 'on' );
  761. const ranges = Array.from( documentSelection.getRanges() );
  762. expect( ranges.length ).to.equal( 1 );
  763. expect( ranges[ 0 ].start.parent ).to.equal( element );
  764. expect( ranges[ 0 ].start.offset ).to.deep.equal( 1 );
  765. expect( ranges[ 0 ].end.parent ).to.equal( element );
  766. expect( ranges[ 0 ].end.offset ).to.deep.equal( 2 );
  767. } );
  768. it( 'should allow setting selection inside an element', () => {
  769. const element = new Element( 'p', null, [ new Text( 'foo' ), new Text( 'bar' ) ] );
  770. documentSelection._setTo( element, 'in' );
  771. const ranges = Array.from( documentSelection.getRanges() );
  772. expect( ranges.length ).to.equal( 1 );
  773. expect( ranges[ 0 ].start.parent ).to.equal( element );
  774. expect( ranges[ 0 ].start.offset ).to.deep.equal( 0 );
  775. expect( ranges[ 0 ].end.parent ).to.equal( element );
  776. expect( ranges[ 0 ].end.offset ).to.deep.equal( 2 );
  777. } );
  778. it( 'should allow setting backward selection inside an element', () => {
  779. const element = new Element( 'p', null, [ new Text( 'foo' ), new Text( 'bar' ) ] );
  780. documentSelection._setTo( element, 'in', { backward: true } );
  781. const ranges = Array.from( documentSelection.getRanges() );
  782. expect( ranges.length ).to.equal( 1 );
  783. expect( ranges[ 0 ].start.parent ).to.equal( element );
  784. expect( ranges[ 0 ].start.offset ).to.deep.equal( 0 );
  785. expect( ranges[ 0 ].end.parent ).to.equal( element );
  786. expect( ranges[ 0 ].end.offset ).to.deep.equal( 2 );
  787. expect( documentSelection.isBackward ).to.be.true;
  788. } );
  789. } );
  790. describe( 'getEditableElement()', () => {
  791. it( 'should return null if no ranges in selection', () => {
  792. expect( documentSelection.editableElement ).to.be.null;
  793. } );
  794. it( 'should return null if selection is placed in container that is not EditableElement', () => {
  795. documentSelection._setTo( range1 );
  796. expect( documentSelection.editableElement ).to.be.null;
  797. } );
  798. it( 'should return EditableElement when selection is placed inside', () => {
  799. const viewDocument = new Document();
  800. documentSelection._setTo( viewDocument.selection );
  801. const root = createViewRoot( viewDocument, 'div', 'main' );
  802. const element = new Element( 'p' );
  803. root._appendChild( element );
  804. documentSelection._setTo( Range._createFromParentsAndOffsets( element, 0, element, 0 ) );
  805. expect( documentSelection.editableElement ).to.equal( root );
  806. } );
  807. } );
  808. describe( 'isFake', () => {
  809. it( 'should be false for newly created instance', () => {
  810. expect( documentSelection.isFake ).to.be.false;
  811. } );
  812. } );
  813. describe( 'getSelectedElement()', () => {
  814. it( 'should return selected element', () => {
  815. const { selection: documentSelection, view } = parse( 'foo [<b>bar</b>] baz' );
  816. const b = view.getChild( 1 );
  817. expect( documentSelection.getSelectedElement() ).to.equal( b );
  818. } );
  819. it( 'should return null if there is more than one range', () => {
  820. const { selection: documentSelection } = parse( 'foo [<b>bar</b>] [<i>baz</i>]' );
  821. expect( documentSelection.getSelectedElement() ).to.be.null;
  822. } );
  823. it( 'should return null if there is no selection', () => {
  824. expect( documentSelection.getSelectedElement() ).to.be.null;
  825. } );
  826. it( 'should return null if selection is not over single element #1', () => {
  827. const { selection: documentSelection } = parse( 'foo [<b>bar</b> ba}z' );
  828. expect( documentSelection.getSelectedElement() ).to.be.null;
  829. } );
  830. it( 'should return null if selection is not over single element #2', () => {
  831. const { selection: documentSelection } = parse( 'foo <b>{bar}</b> baz' );
  832. expect( documentSelection.getSelectedElement() ).to.be.null;
  833. } );
  834. } );
  835. } );