tableselection.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  1. /**
  2. * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  4. */
  5. /* globals document */
  6. import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
  7. import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
  8. import {
  9. getData as getModelData,
  10. setData as setModelData,
  11. stringify as stringifyModel
  12. } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
  13. import TableEditing from '../src/tableediting';
  14. import TableSelection from '../src/tableselection';
  15. import { assertSelectedCells, modelTable } from './_utils/utils';
  16. import DocumentFragment from '@ckeditor/ckeditor5-engine/src/model/documentfragment';
  17. import Typing from '@ckeditor/ckeditor5-typing/src/typing';
  18. import { assertEqualMarkup } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
  19. describe( 'TableSelection', () => {
  20. let editorElement, editor, model, tableSelection, modelRoot;
  21. beforeEach( () => {
  22. editorElement = document.createElement( 'div' );
  23. document.body.appendChild( editorElement );
  24. } );
  25. afterEach( async () => {
  26. editorElement.remove();
  27. await editor.destroy();
  28. } );
  29. describe( 'init()', () => {
  30. beforeEach( async () => {
  31. editor = await createEditor();
  32. model = editor.model;
  33. modelRoot = model.document.getRoot();
  34. tableSelection = editor.plugins.get( TableSelection );
  35. setModelData( model, modelTable( [
  36. [ '11[]', '12', '13' ],
  37. [ '21', '22', '23' ],
  38. [ '31', '32', '33' ]
  39. ] ) );
  40. } );
  41. it( 'should have pluginName', () => {
  42. expect( TableSelection.pluginName ).to.equal( 'TableSelection' );
  43. } );
  44. describe( 'plugin disabling support', () => {
  45. it( 'should collapse multi-cell selection when the plugin gets disabled', () => {
  46. const firstCell = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
  47. const lastCell = modelRoot.getNodeByPath( [ 0, 1, 1 ] );
  48. tableSelection.setCellSelection(
  49. firstCell,
  50. lastCell
  51. );
  52. tableSelection.forceDisabled( 'foo' );
  53. const ranges = [ ...model.document.selection.getRanges() ];
  54. expect( ranges ).to.have.length( 1 );
  55. expect( ranges[ 0 ].isCollapsed ).to.be.true;
  56. expect( ranges[ 0 ].start.path ).to.deep.equal( [ 0, 0, 0, 0, 0 ] );
  57. } );
  58. it( 'should reenable table selection when reenabling the plugin', () => {
  59. tableSelection.forceDisabled( 'foo' );
  60. tableSelection.clearForceDisabled( 'foo' );
  61. const firstCell = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
  62. const lastCell = modelRoot.getNodeByPath( [ 0, 1, 1 ] );
  63. tableSelection.setCellSelection(
  64. firstCell,
  65. lastCell
  66. );
  67. assertSelectedCells( model, [
  68. [ 1, 1, 0 ],
  69. [ 1, 1, 0 ],
  70. [ 0, 0, 0 ]
  71. ] );
  72. } );
  73. it( 'should do nothing if there were no multi-cell selections', () => {
  74. tableSelection.forceDisabled( 'foo' );
  75. const ranges = [ ...model.document.selection.getRanges() ];
  76. expect( ranges ).to.have.length( 1 );
  77. expect( ranges[ 0 ].isCollapsed ).to.be.true;
  78. expect( ranges[ 0 ].start.path ).to.deep.equal( [ 0, 0, 0, 0, 2 ] );
  79. } );
  80. } );
  81. } );
  82. describe( 'getSelectedTableCells()', () => {
  83. beforeEach( async () => {
  84. editor = await createEditor();
  85. model = editor.model;
  86. modelRoot = model.document.getRoot();
  87. tableSelection = editor.plugins.get( TableSelection );
  88. setModelData( model, modelTable( [
  89. [ '11[]', '12', '13' ],
  90. [ '21', '22', '23' ],
  91. [ '31', '32', '33' ]
  92. ] ) );
  93. } );
  94. it( 'should return nothing if selection is not started', () => {
  95. expect( tableSelection.getSelectedTableCells() ).to.be.null;
  96. } );
  97. it( 'should return two table cells', () => {
  98. const firstCell = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
  99. const lastCell = modelRoot.getNodeByPath( [ 0, 0, 1 ] );
  100. tableSelection.setCellSelection(
  101. firstCell,
  102. lastCell
  103. );
  104. expect( tableSelection.getSelectedTableCells() ).to.deep.equal( [
  105. firstCell, lastCell
  106. ] );
  107. } );
  108. it( 'should return four table cells for diagonal selection', () => {
  109. const firstCell = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
  110. const lastCell = modelRoot.getNodeByPath( [ 0, 1, 1 ] );
  111. tableSelection.setCellSelection(
  112. firstCell,
  113. lastCell
  114. );
  115. expect( tableSelection.getSelectedTableCells() ).to.deep.equal( [
  116. firstCell, modelRoot.getNodeByPath( [ 0, 0, 1 ] ), modelRoot.getNodeByPath( [ 0, 1, 0 ] ), lastCell
  117. ] );
  118. } );
  119. it( 'should return row table cells', () => {
  120. const firstCell = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
  121. const lastCell = modelRoot.getNodeByPath( [ 0, 0, 2 ] );
  122. tableSelection.setCellSelection(
  123. firstCell,
  124. lastCell
  125. );
  126. expect( tableSelection.getSelectedTableCells() ).to.deep.equal( [
  127. firstCell, modelRoot.getNodeByPath( [ 0, 0, 1 ] ), lastCell
  128. ] );
  129. } );
  130. it( 'should return column table cells', () => {
  131. const firstCell = modelRoot.getNodeByPath( [ 0, 0, 1 ] );
  132. const lastCell = modelRoot.getNodeByPath( [ 0, 2, 1 ] );
  133. tableSelection.setCellSelection( firstCell, lastCell );
  134. expect( tableSelection.getSelectedTableCells() ).to.deep.equal( [
  135. firstCell, modelRoot.getNodeByPath( [ 0, 1, 1 ] ), lastCell
  136. ] );
  137. } );
  138. it( 'should return cells in source order despite backward selection', () => {
  139. const firstCell = modelRoot.getNodeByPath( [ 0, 0, 2 ] );
  140. const lastCell = modelRoot.getNodeByPath( [ 0, 0, 1 ] );
  141. tableSelection.setCellSelection( firstCell, lastCell );
  142. expect( tableSelection.getSelectedTableCells() ).to.deep.equal( [
  143. lastCell, firstCell
  144. ] );
  145. } );
  146. } );
  147. describe( 'getSelectionAsFragment()', () => {
  148. beforeEach( async () => {
  149. editor = await createEditor();
  150. model = editor.model;
  151. modelRoot = model.document.getRoot();
  152. tableSelection = editor.plugins.get( TableSelection );
  153. setModelData( model, modelTable( [
  154. [ '11[]', '12', '13' ],
  155. [ '21', '22', '23' ],
  156. [ '31', '32', '33' ]
  157. ] ) );
  158. } );
  159. it( 'should return undefined if no table cells are selected', () => {
  160. expect( tableSelection.getSelectionAsFragment() ).to.be.null;
  161. } );
  162. it( 'should return document fragment for selected table cells', () => {
  163. tableSelection.setCellSelection(
  164. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  165. modelRoot.getNodeByPath( [ 0, 1, 1 ] )
  166. );
  167. expect( tableSelection.getSelectionAsFragment() ).to.be.instanceOf( DocumentFragment );
  168. } );
  169. it( 'should return cells in the source order in case of forward selection', () => {
  170. tableSelection.setCellSelection(
  171. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  172. modelRoot.getNodeByPath( [ 0, 1, 1 ] )
  173. );
  174. expect( stringifyModel( tableSelection.getSelectionAsFragment() ) ).to.equal( modelTable( [
  175. [ '11', '12' ],
  176. [ '21', '22' ]
  177. ] ) );
  178. } );
  179. it( 'should return cells in the source order in case of backward selection', () => {
  180. tableSelection.setCellSelection(
  181. modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
  182. modelRoot.getNodeByPath( [ 0, 0, 0 ] )
  183. );
  184. expect( editor.model.document.selection.isBackward ).to.be.true;
  185. expect( stringifyModel( tableSelection.getSelectionAsFragment() ) ).to.equal( modelTable( [
  186. [ '11', '12' ],
  187. [ '21', '22' ]
  188. ] ) );
  189. } );
  190. } );
  191. describe( 'delete content', () => {
  192. beforeEach( async () => {
  193. editor = await createEditor();
  194. model = editor.model;
  195. modelRoot = model.document.getRoot();
  196. tableSelection = editor.plugins.get( TableSelection );
  197. setModelData( model, modelTable( [
  198. [ '11[]', '12', '13' ],
  199. [ '21', '22', '23' ],
  200. [ '31', '32', '33' ]
  201. ] ) );
  202. } );
  203. it( 'should put selection in the last selected cell after removing content (backward delete)', () => {
  204. tableSelection.setCellSelection(
  205. modelRoot.getChild( 0 ).getChild( 0 ).getChild( 0 ),
  206. modelRoot.getChild( 0 ).getChild( 1 ).getChild( 1 )
  207. );
  208. editor.execute( 'delete' );
  209. assertEqualMarkup( getModelData( model ), modelTable( [
  210. [ '', '', '13' ],
  211. [ '', '[]', '23' ],
  212. [ '31', '32', '33' ]
  213. ] ) );
  214. } );
  215. it( 'should put selection in the last selected cell after removing content (forward delete)', () => {
  216. tableSelection.setCellSelection(
  217. modelRoot.getChild( 0 ).getChild( 0 ).getChild( 0 ),
  218. modelRoot.getChild( 0 ).getChild( 1 ).getChild( 1 )
  219. );
  220. editor.execute( 'delete' );
  221. assertEqualMarkup( getModelData( model ), modelTable( [
  222. [ '', '', '13' ],
  223. [ '', '[]', '23' ],
  224. [ '31', '32', '33' ]
  225. ] ) );
  226. } );
  227. it( 'should clear single cell if selected', () => {
  228. tableSelection.setCellSelection(
  229. modelRoot.getChild( 0 ).getChild( 0 ).getChild( 0 ),
  230. modelRoot.getChild( 0 ).getChild( 0 ).getChild( 0 )
  231. );
  232. editor.execute( 'forwardDelete' );
  233. assertEqualMarkup( getModelData( model ), modelTable( [
  234. [ '[]', '12', '13' ],
  235. [ '21', '22', '23' ],
  236. [ '31', '32', '33' ]
  237. ] ) );
  238. } );
  239. it( 'should work with document selection passed to Model#deleteContent()', () => {
  240. tableSelection.setCellSelection(
  241. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  242. modelRoot.getNodeByPath( [ 0, 1, 1 ] )
  243. );
  244. model.change( () => {
  245. model.deleteContent( model.document.selection );
  246. } );
  247. assertEqualMarkup( getModelData( model ), modelTable( [
  248. [ '', '', '13' ],
  249. [ '', '[]', '23' ],
  250. [ '31', '32', '33' ]
  251. ] ) );
  252. } );
  253. it( 'should work with any arbitrary selection passed to Model#deleteContent() (delete backwards)', () => {
  254. const selection = model.createSelection( [
  255. model.createRange(
  256. model.createPositionFromPath( modelRoot, [ 0, 0, 0 ] ),
  257. model.createPositionFromPath( modelRoot, [ 0, 0, 1 ] )
  258. ),
  259. model.createRange(
  260. model.createPositionFromPath( modelRoot, [ 0, 0, 1 ] ),
  261. model.createPositionFromPath( modelRoot, [ 0, 0, 2 ] )
  262. )
  263. ] );
  264. model.change( writer => {
  265. model.deleteContent( selection );
  266. writer.setSelection( selection );
  267. } );
  268. assertEqualMarkup( getModelData( model ), modelTable( [
  269. [ '', '[]', '13' ],
  270. [ '21', '22', '23' ],
  271. [ '31', '32', '33' ]
  272. ] ) );
  273. } );
  274. it( 'should work with any arbitrary selection passed to Model#deleteContent() (delete forwards)', () => {
  275. const selection = model.createSelection( [
  276. model.createRange(
  277. model.createPositionFromPath( modelRoot, [ 0, 0, 0 ] ),
  278. model.createPositionFromPath( modelRoot, [ 0, 0, 1 ] )
  279. ),
  280. model.createRange(
  281. model.createPositionFromPath( modelRoot, [ 0, 0, 1 ] ),
  282. model.createPositionFromPath( modelRoot, [ 0, 0, 2 ] )
  283. )
  284. ] );
  285. model.change( writer => {
  286. model.deleteContent( selection, {
  287. direction: 'forward'
  288. } );
  289. writer.setSelection( selection );
  290. } );
  291. assertEqualMarkup( getModelData( model ), modelTable( [
  292. [ '[]', '', '13' ],
  293. [ '21', '22', '23' ],
  294. [ '31', '32', '33' ]
  295. ] ) );
  296. } );
  297. } );
  298. describe( 'getAnchorCell() and getFocusCell()', () => {
  299. beforeEach( async () => {
  300. editor = await createEditor();
  301. model = editor.model;
  302. modelRoot = model.document.getRoot();
  303. tableSelection = editor.plugins.get( TableSelection );
  304. setModelData( model, modelTable( [
  305. [ '[]00', '01', '02' ],
  306. [ '10', '11', '12' ],
  307. [ '20', '21', '22' ]
  308. ] ) );
  309. } );
  310. it( 'should return null if no table cell is selected', () => {
  311. expect( tableSelection.getAnchorCell() ).to.be.null;
  312. expect( tableSelection.getFocusCell() ).to.be.null;
  313. } );
  314. it( 'getAnchorCell() should return the table cell from the first range in the selection', () => {
  315. const anchorCell = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
  316. const focusCell = modelRoot.getNodeByPath( [ 0, 1, 1 ] );
  317. tableSelection.setCellSelection( anchorCell, focusCell );
  318. expect( tableSelection.getAnchorCell() ).to.equal( anchorCell );
  319. } );
  320. it( 'getFocusCell() should return the table cell from the last range in the selection', () => {
  321. const anchorCell = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
  322. const focusCell = modelRoot.getNodeByPath( [ 0, 1, 1 ] );
  323. tableSelection.setCellSelection( anchorCell, focusCell );
  324. expect( tableSelection.getFocusCell() ).to.equal( focusCell );
  325. } );
  326. } );
  327. describe( 'the selection ranges order', () => {
  328. let selection, table;
  329. beforeEach( async () => {
  330. editor = await createEditor();
  331. model = editor.model;
  332. selection = model.document.selection;
  333. modelRoot = model.document.getRoot();
  334. tableSelection = editor.plugins.get( TableSelection );
  335. setModelData( model, modelTable( [
  336. [ '00', '01', '02' ],
  337. [ '10', '11', '12' ],
  338. [ '20', '21', '22' ]
  339. ] ) );
  340. table = modelRoot.getChild( 0 );
  341. } );
  342. it( 'should be to below right', () => {
  343. const anchorCell = table.getChild( 1 ).getChild( 1 );
  344. const focusCell = table.getChild( 2 ).getChild( 2 );
  345. tableSelection.setCellSelection( anchorCell, focusCell );
  346. assertSelection( anchorCell, focusCell, 4 );
  347. expect( tableSelection.getFocusCell() ).to.equal( focusCell );
  348. expect( tableSelection.getAnchorCell() ).to.equal( anchorCell );
  349. expect( selection.isBackward ).to.be.false;
  350. } );
  351. it( 'should be to below left', () => {
  352. const anchorCell = table.getChild( 1 ).getChild( 1 );
  353. const focusCell = table.getChild( 2 ).getChild( 0 );
  354. tableSelection.setCellSelection( anchorCell, focusCell );
  355. assertSelection( anchorCell, focusCell, 4 );
  356. expect( tableSelection.getFocusCell() ).to.equal( focusCell );
  357. expect( tableSelection.getAnchorCell() ).to.equal( anchorCell );
  358. expect( selection.isBackward ).to.be.true;
  359. } );
  360. it( 'should be to above left', () => {
  361. const anchorCell = table.getChild( 1 ).getChild( 1 );
  362. const focusCell = table.getChild( 0 ).getChild( 0 );
  363. tableSelection.setCellSelection( anchorCell, focusCell );
  364. assertSelection( anchorCell, focusCell, 4 );
  365. expect( tableSelection.getFocusCell() ).to.equal( focusCell );
  366. expect( tableSelection.getAnchorCell() ).to.equal( anchorCell );
  367. expect( selection.isBackward ).to.be.true;
  368. } );
  369. it( 'should be to above right', () => {
  370. const anchorCell = table.getChild( 1 ).getChild( 1 );
  371. const focusCell = table.getChild( 0 ).getChild( 2 );
  372. tableSelection.setCellSelection( anchorCell, focusCell );
  373. assertSelection( anchorCell, focusCell, 4 );
  374. expect( tableSelection.getFocusCell() ).to.equal( focusCell );
  375. expect( tableSelection.getAnchorCell() ).to.equal( anchorCell );
  376. expect( selection.isBackward ).to.be.true;
  377. } );
  378. function assertSelection( anchorCell, focusCell, count ) {
  379. const cells = [ ...selection.getRanges() ].map( range => range.getContainedElement() );
  380. expect( selection.rangeCount ).to.equal( count );
  381. expect( cells[ 0 ] ).to.equal( anchorCell );
  382. expect( cells[ cells.length - 1 ] ).to.equal( focusCell );
  383. }
  384. } );
  385. function createEditor() {
  386. return ClassicTestEditor.create( editorElement, {
  387. plugins: [ TableEditing, TableSelection, Paragraph, Typing ]
  388. } );
  389. }
  390. } );