8
0

tableselection.js 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953
  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, console */
  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 DomEventData from '@ckeditor/ckeditor5-engine/src/view/observer/domeventdata';
  18. import Typing from '@ckeditor/ckeditor5-typing/src/typing';
  19. import { assertEqualMarkup } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
  20. describe( 'table selection', () => {
  21. let editorElement, editor, model, tableSelection, modelRoot, view, viewDocument;
  22. beforeEach( () => {
  23. editorElement = document.createElement( 'div' );
  24. document.body.appendChild( editorElement );
  25. } );
  26. afterEach( async () => {
  27. editorElement.remove();
  28. await editor.destroy();
  29. } );
  30. describe( 'init()', () => {
  31. beforeEach( async () => {
  32. editor = await createEditor();
  33. model = editor.model;
  34. modelRoot = model.document.getRoot();
  35. tableSelection = editor.plugins.get( TableSelection );
  36. setModelData( model, modelTable( [
  37. [ '11[]', '12', '13' ],
  38. [ '21', '22', '23' ],
  39. [ '31', '32', '33' ]
  40. ] ) );
  41. } );
  42. describe( 'plugin disabling support', () => {
  43. it( 'should collapse multi-cell selection when the plugin gets disabled', () => {
  44. const firstCell = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
  45. const lastCell = modelRoot.getNodeByPath( [ 0, 1, 1 ] );
  46. tableSelection.setCellSelection(
  47. firstCell,
  48. lastCell
  49. );
  50. tableSelection.forceDisabled( 'foo' );
  51. const ranges = [ ...model.document.selection.getRanges() ];
  52. expect( ranges ).to.have.length( 1 );
  53. expect( ranges[ 0 ].isCollapsed ).to.be.true;
  54. expect( ranges[ 0 ].start.path ).to.deep.equal( [ 0, 0, 0, 0, 0 ] );
  55. } );
  56. it( 'should reenable table selection when reenabling the plugin', () => {
  57. tableSelection.forceDisabled( 'foo' );
  58. tableSelection.clearForceDisabled( 'foo' );
  59. const firstCell = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
  60. const lastCell = modelRoot.getNodeByPath( [ 0, 1, 1 ] );
  61. tableSelection.setCellSelection(
  62. firstCell,
  63. lastCell
  64. );
  65. assertSelectedCells( model, [
  66. [ 1, 1, 0 ],
  67. [ 1, 1, 0 ],
  68. [ 0, 0, 0 ]
  69. ] );
  70. } );
  71. it( 'should do nothing if there were no multi-cell selections', () => {
  72. tableSelection.forceDisabled( 'foo' );
  73. const ranges = [ ...model.document.selection.getRanges() ];
  74. expect( ranges ).to.have.length( 1 );
  75. expect( ranges[ 0 ].isCollapsed ).to.be.true;
  76. expect( ranges[ 0 ].start.path ).to.deep.equal( [ 0, 0, 0, 0, 2 ] );
  77. } );
  78. } );
  79. } );
  80. describe( 'selection by Shift+click', () => {
  81. beforeEach( async () => {
  82. editor = await createEditor();
  83. model = editor.model;
  84. modelRoot = model.document.getRoot();
  85. view = editor.editing.view;
  86. viewDocument = view.document;
  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 do nothing if the plugin is disabled', () => {
  95. tableSelection.isEnabled = false;
  96. viewDocument.fire( 'mousedown', new DomEventData( view, {} ) );
  97. assertSelectedCells( model, [
  98. [ 0, 0, 0 ],
  99. [ 0, 0, 0 ],
  100. [ 0, 0, 0 ]
  101. ] );
  102. } );
  103. it( 'should abort if Shift key was not pressed', () => {
  104. viewDocument.fire( 'mousedown', new DomEventData( view, {
  105. shiftKey: false,
  106. target: view.domConverter.mapViewToDom(
  107. // figure > table > tbody > tr > td
  108. viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 2 )
  109. )
  110. } ) );
  111. assertSelectedCells( model, [
  112. [ 0, 0, 0 ],
  113. [ 0, 0, 0 ],
  114. [ 0, 0, 0 ]
  115. ] );
  116. } );
  117. it( 'should abort if Shift+clicked an element outside a table', () => {
  118. const preventDefault = sinon.spy();
  119. model.change( writer => {
  120. const paragraph = writer.createElement( 'paragraph' );
  121. const text = writer.createText( 'foo' );
  122. writer.insert( text, paragraph );
  123. writer.insert( paragraph, model.document.getRoot(), 'end' );
  124. writer.setSelection( paragraph, 'end' );
  125. } );
  126. viewDocument.fire( 'mousedown', new DomEventData( view, {
  127. shiftKey: true,
  128. target: view.domConverter.mapViewToDom(
  129. viewDocument.getRoot().getChild( 1 )
  130. ),
  131. preventDefault
  132. } ) );
  133. assertSelectedCells( model, [
  134. [ 0, 0, 0 ],
  135. [ 0, 0, 0 ],
  136. [ 0, 0, 0 ]
  137. ] );
  138. expect( preventDefault.called ).to.equal( false );
  139. } );
  140. it( 'should abort if clicked a cell that belongs to another table', () => {
  141. const preventDefault = sinon.spy();
  142. setModelData( model, [
  143. modelTable( [
  144. [ '1.11[]', '1.12' ],
  145. [ '1.21', '1.22' ]
  146. ] ),
  147. modelTable( [
  148. [ '2.11', '2.12' ],
  149. [ '2.21', '2.22' ]
  150. ] )
  151. ].join( '' ) );
  152. const domEventDataMock = new DomEventData( view, {
  153. shiftKey: true,
  154. target: view.domConverter.mapViewToDom(
  155. // The second table: figure > table > tbody > tr > td
  156. viewDocument.getRoot().getChild( 1 ).getChild( 1 ).getChild( 0 ).getChild( 1 ).getChild( 1 )
  157. ),
  158. preventDefault
  159. } );
  160. viewDocument.fire( 'mousedown', domEventDataMock );
  161. assertSelectedCells( model, [
  162. [ 0, 0 ],
  163. [ 0, 0 ]
  164. ] );
  165. expect( preventDefault.called ).to.equal( false );
  166. } );
  167. it( 'should select all cells in first row', () => {
  168. const preventDefault = sinon.spy();
  169. const domEventDataMock = new DomEventData( view, {
  170. shiftKey: true,
  171. target: view.domConverter.mapViewToDom(
  172. // figure > table > tbody > tr > td
  173. viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 2 )
  174. ),
  175. preventDefault
  176. } );
  177. viewDocument.fire( 'mousedown', domEventDataMock );
  178. assertSelectedCells( model, [
  179. [ 1, 1, 1 ],
  180. [ 0, 0, 0 ],
  181. [ 0, 0, 0 ]
  182. ] );
  183. expect( preventDefault.called ).to.equal( true );
  184. } );
  185. it( 'should ignore `selectionChange` event when selecting cells', () => {
  186. const consoleLog = sinon.stub( console, 'log' );
  187. const preventDefault = sinon.spy();
  188. const selectionChangeCallback = sinon.spy();
  189. // Adding a new callback to check whether it will be executed (whether `evt.stop()` is being called).
  190. viewDocument.on( 'selectionChange', selectionChangeCallback );
  191. // Shift+click a cell to create a selection. Should disable listening to `selectionChange`.
  192. viewDocument.fire( 'mousedown', new DomEventData( view, {
  193. shiftKey: true,
  194. target: view.domConverter.mapViewToDom(
  195. // figure > table > tbody > tr > td
  196. viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 2 )
  197. ),
  198. preventDefault
  199. } ) );
  200. // Due to browsers "fixing" the selection (e.g. moving it to text nodes), after we set a selection
  201. // the browser fill fire native selectionchange, which triggers our selectionChange. We need to ignore it.
  202. // See a broader explanation in tableselection.js.
  203. viewDocument.fire( 'selectionChange' );
  204. // The callback shouldn't be executed because
  205. // `selectionChange` event should be canceled.
  206. expect( selectionChangeCallback.called ).to.equal( false );
  207. expect( consoleLog.called ).to.equal( true );
  208. expect( consoleLog.firstCall.args[ 0 ] ).to.equal( 'Blocked selectionChange to avoid breaking table cells selection.' );
  209. // Enables listening to `selectionChange` event.
  210. viewDocument.fire( 'mouseup' );
  211. viewDocument.fire( 'selectionChange', {
  212. newSelection: view.document.selection
  213. } );
  214. expect( selectionChangeCallback.called ).to.equal( true );
  215. consoleLog.restore();
  216. } );
  217. } );
  218. describe( 'selection by mouse drag', () => {
  219. let preventDefault;
  220. beforeEach( async () => {
  221. editor = await createEditor();
  222. model = editor.model;
  223. modelRoot = model.document.getRoot();
  224. view = editor.editing.view;
  225. viewDocument = view.document;
  226. tableSelection = editor.plugins.get( TableSelection );
  227. setModelData( model, modelTable( [
  228. [ '11[]', '12', '13' ],
  229. [ '21', '22', '23' ],
  230. [ '31', '32', '33' ]
  231. ] ) );
  232. preventDefault = sinon.spy();
  233. } );
  234. it( 'should do nothing if the plugin is disabled', () => {
  235. tableSelection.isEnabled = false;
  236. const domEventDataMock = new DomEventData( view, {} );
  237. viewDocument.fire( 'mousedown', domEventDataMock );
  238. assertSelectedCells( model, [
  239. [ 0, 0, 0 ],
  240. [ 0, 0, 0 ],
  241. [ 0, 0, 0 ]
  242. ] );
  243. } );
  244. it( 'should abort if Ctrl is pressed', () => {
  245. const domEventDataMock = new DomEventData( view, {
  246. ctrlKey: true
  247. } );
  248. viewDocument.fire( 'mousedown', domEventDataMock );
  249. assertSelectedCells( model, [
  250. [ 0, 0, 0 ],
  251. [ 0, 0, 0 ],
  252. [ 0, 0, 0 ]
  253. ] );
  254. } );
  255. it( 'should abort if Alt is pressed', () => {
  256. const domEventDataMock = new DomEventData( view, {
  257. altKey: true
  258. } );
  259. viewDocument.fire( 'mousedown', domEventDataMock );
  260. assertSelectedCells( model, [
  261. [ 0, 0, 0 ],
  262. [ 0, 0, 0 ],
  263. [ 0, 0, 0 ]
  264. ] );
  265. } );
  266. it( 'should do nothing if any of mouse buttons was not clicked', () => {
  267. viewDocument.fire( 'mousemove', new DomEventData( view, {
  268. buttons: 0
  269. } ) );
  270. assertSelectedCells( model, [
  271. [ 0, 0, 0 ],
  272. [ 0, 0, 0 ],
  273. [ 0, 0, 0 ]
  274. ] );
  275. } );
  276. it( 'should do nothing if started dragging outside of table', () => {
  277. model.change( writer => {
  278. const paragraph = writer.createElement( 'paragraph' );
  279. const text = writer.createText( 'foo' );
  280. writer.insert( text, paragraph );
  281. writer.insert( paragraph, model.document.getRoot(), 'end' );
  282. writer.setSelection( paragraph, 'end' );
  283. } );
  284. viewDocument.fire( 'mousedown', new DomEventData( view, {
  285. target: view.domConverter.mapViewToDom(
  286. viewDocument.getRoot().getChild( 1 )
  287. )
  288. } ) );
  289. viewDocument.fire( 'mousemove', new DomEventData( view, {
  290. buttons: 1
  291. } ) );
  292. assertSelectedCells( model, [
  293. [ 0, 0, 0 ],
  294. [ 0, 0, 0 ],
  295. [ 0, 0, 0 ]
  296. ] );
  297. } );
  298. it( 'should do nothing if ended dragging outside of table', () => {
  299. model.change( writer => {
  300. const paragraph = writer.createElement( 'paragraph' );
  301. const text = writer.createText( 'foo' );
  302. writer.insert( text, paragraph );
  303. writer.insert( paragraph, model.document.getRoot(), 'end' );
  304. writer.setSelection( paragraph, 'end' );
  305. } );
  306. viewDocument.fire( 'mousedown', new DomEventData( view, {
  307. target: view.domConverter.mapViewToDom(
  308. viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 0 )
  309. )
  310. } ) );
  311. viewDocument.fire( 'mousemove', new DomEventData( view, {
  312. target: view.domConverter.mapViewToDom(
  313. viewDocument.getRoot().getChild( 1 )
  314. ),
  315. buttons: 1
  316. } ) );
  317. assertSelectedCells( model, [
  318. [ 0, 0, 0 ],
  319. [ 0, 0, 0 ],
  320. [ 0, 0, 0 ]
  321. ] );
  322. } );
  323. it( 'should do nothing if ended dragging inside another table', () => {
  324. setModelData( model, [
  325. modelTable( [
  326. [ '1.11[]', '1.12' ],
  327. [ '1.21', '1.22' ]
  328. ] ),
  329. modelTable( [
  330. [ '2.11', '2.12' ],
  331. [ '2.21', '2.22' ]
  332. ] )
  333. ].join( '' ) );
  334. viewDocument.fire( 'mousedown', new DomEventData( view, {
  335. target: view.domConverter.mapViewToDom(
  336. viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 0 )
  337. )
  338. } ) );
  339. viewDocument.fire( 'mousemove', new DomEventData( view, {
  340. target: view.domConverter.mapViewToDom(
  341. viewDocument.getRoot().getChild( 1 ).getChild( 1 ).getChild( 0 ).getChild( 1 ).getChild( 1 )
  342. ),
  343. buttons: 1
  344. } ) );
  345. assertSelectedCells( model, [
  346. [ 0, 0 ],
  347. [ 0, 0 ]
  348. ] );
  349. } );
  350. it( 'should do nothing if ended in the same cell', () => {
  351. viewDocument.fire( 'mousedown', new DomEventData( view, {
  352. target: view.domConverter.mapViewToDom(
  353. viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 0 )
  354. )
  355. } ) );
  356. viewDocument.fire( 'mousemove', new DomEventData( view, {
  357. target: view.domConverter.mapViewToDom(
  358. viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 0 )
  359. ),
  360. buttons: 1
  361. } ) );
  362. assertSelectedCells( model, [
  363. [ 0, 0, 0 ],
  364. [ 0, 0, 0 ],
  365. [ 0, 0, 0 ]
  366. ] );
  367. } );
  368. it( 'should select started and ended dragging in the same cell but went over its border', () => {
  369. viewDocument.fire( 'mousedown', new DomEventData( view, {
  370. target: view.domConverter.mapViewToDom(
  371. viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 0 )
  372. )
  373. } ) );
  374. // Select the next one.
  375. viewDocument.fire( 'mousemove', new DomEventData( view, {
  376. target: view.domConverter.mapViewToDom(
  377. viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 1 )
  378. ),
  379. buttons: 1,
  380. preventDefault: sinon.spy()
  381. } ) );
  382. // And back to the "started" cell.
  383. viewDocument.fire( 'mousemove', new DomEventData( view, {
  384. target: view.domConverter.mapViewToDom(
  385. viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 0 )
  386. ),
  387. buttons: 1,
  388. preventDefault: sinon.spy()
  389. } ) );
  390. viewDocument.fire( 'mouseup' );
  391. assertSelectedCells( model, [
  392. [ 1, 0, 0 ],
  393. [ 0, 0, 0 ],
  394. [ 0, 0, 0 ]
  395. ] );
  396. } );
  397. it( 'should select all cells in first row', () => {
  398. viewDocument.fire( 'mousedown', new DomEventData( view, {
  399. target: view.domConverter.mapViewToDom(
  400. // figure > table > tbody > tr > td
  401. viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 0 )
  402. )
  403. } ) );
  404. viewDocument.fire( 'mousemove', new DomEventData( view, {
  405. target: view.domConverter.mapViewToDom(
  406. // figure > table > tbody > tr > td
  407. viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 2 )
  408. ),
  409. buttons: 1,
  410. preventDefault
  411. } ) );
  412. viewDocument.fire( 'mouseup' );
  413. assertSelectedCells( model, [
  414. [ 1, 1, 1 ],
  415. [ 0, 0, 0 ],
  416. [ 0, 0, 0 ]
  417. ] );
  418. expect( preventDefault.called ).to.equal( true );
  419. } );
  420. it( 'should ignore `selectionChange` event when selecting cells ', () => {
  421. const consoleLog = sinon.stub( console, 'log' );
  422. const preventDefault = sinon.spy();
  423. const selectionChangeCallback = sinon.spy();
  424. // Adding a new callback to check whether it will be executed (whether `evt.stop()` is being called).
  425. viewDocument.on( 'selectionChange', selectionChangeCallback );
  426. // Click on a cell.
  427. viewDocument.fire( 'mousedown', new DomEventData( view, {
  428. target: view.domConverter.mapViewToDom(
  429. // figure > table > tbody > tr > td
  430. viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 1 )
  431. )
  432. } ) );
  433. // Then move the mouse to another cell. Disables listening to `selectionChange`.
  434. viewDocument.fire( 'mousemove', new DomEventData( view, {
  435. buttons: 1,
  436. target: view.domConverter.mapViewToDom(
  437. // figure > table > tbody > tr > td
  438. viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 2 )
  439. ),
  440. preventDefault
  441. } ) );
  442. // See explanation why do we fire it in the similar test for Shift+click.
  443. viewDocument.fire( 'selectionChange' );
  444. // `selectionChange` event should be canceled.
  445. expect( selectionChangeCallback.called ).to.equal( false );
  446. expect( consoleLog.called ).to.equal( true );
  447. expect( consoleLog.firstCall.args[ 0 ] ).to.equal( 'Blocked selectionChange to avoid breaking table cells selection.' );
  448. // Enables listening to `selectionChange` event.
  449. viewDocument.fire( 'mouseup' );
  450. viewDocument.fire( 'selectionChange', {
  451. newSelection: view.document.selection
  452. } );
  453. expect( selectionChangeCallback.called ).to.equal( true );
  454. consoleLog.restore();
  455. } );
  456. } );
  457. describe( 'getSelectedTableCells()', () => {
  458. beforeEach( async () => {
  459. editor = await createEditor();
  460. model = editor.model;
  461. modelRoot = model.document.getRoot();
  462. view = editor.editing.view;
  463. viewDocument = view.document;
  464. tableSelection = editor.plugins.get( TableSelection );
  465. setModelData( model, modelTable( [
  466. [ '11[]', '12', '13' ],
  467. [ '21', '22', '23' ],
  468. [ '31', '32', '33' ]
  469. ] ) );
  470. } );
  471. it( 'should return nothing if selection is not started', () => {
  472. expect( tableSelection.getSelectedTableCells() ).to.be.null;
  473. } );
  474. it( 'should return two table cells', () => {
  475. const firstCell = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
  476. const lastCell = modelRoot.getNodeByPath( [ 0, 0, 1 ] );
  477. tableSelection.setCellSelection(
  478. firstCell,
  479. lastCell
  480. );
  481. expect( tableSelection.getSelectedTableCells() ).to.deep.equal( [
  482. firstCell, lastCell
  483. ] );
  484. } );
  485. it( 'should return four table cells for diagonal selection', () => {
  486. const firstCell = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
  487. const lastCell = modelRoot.getNodeByPath( [ 0, 1, 1 ] );
  488. tableSelection.setCellSelection(
  489. firstCell,
  490. lastCell
  491. );
  492. expect( tableSelection.getSelectedTableCells() ).to.deep.equal( [
  493. firstCell, modelRoot.getNodeByPath( [ 0, 0, 1 ] ), modelRoot.getNodeByPath( [ 0, 1, 0 ] ), lastCell
  494. ] );
  495. } );
  496. it( 'should return row table cells', () => {
  497. const firstCell = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
  498. const lastCell = modelRoot.getNodeByPath( [ 0, 0, 2 ] );
  499. tableSelection.setCellSelection(
  500. firstCell,
  501. lastCell
  502. );
  503. expect( tableSelection.getSelectedTableCells() ).to.deep.equal( [
  504. firstCell, modelRoot.getNodeByPath( [ 0, 0, 1 ] ), lastCell
  505. ] );
  506. } );
  507. it( 'should return column table cells', () => {
  508. const firstCell = modelRoot.getNodeByPath( [ 0, 0, 1 ] );
  509. const lastCell = modelRoot.getNodeByPath( [ 0, 2, 1 ] );
  510. tableSelection.setCellSelection( firstCell, lastCell );
  511. expect( tableSelection.getSelectedTableCells() ).to.deep.equal( [
  512. firstCell, modelRoot.getNodeByPath( [ 0, 1, 1 ] ), lastCell
  513. ] );
  514. } );
  515. it( 'should return cells in source order despite backward selection', () => {
  516. const firstCell = modelRoot.getNodeByPath( [ 0, 0, 2 ] );
  517. const lastCell = modelRoot.getNodeByPath( [ 0, 0, 1 ] );
  518. tableSelection.setCellSelection( firstCell, lastCell );
  519. expect( tableSelection.getSelectedTableCells() ).to.deep.equal( [
  520. lastCell, firstCell
  521. ] );
  522. } );
  523. } );
  524. describe( 'getSelectionAsFragment()', () => {
  525. beforeEach( async () => {
  526. editor = await createEditor();
  527. model = editor.model;
  528. modelRoot = model.document.getRoot();
  529. view = editor.editing.view;
  530. viewDocument = view.document;
  531. tableSelection = editor.plugins.get( TableSelection );
  532. setModelData( model, modelTable( [
  533. [ '11[]', '12', '13' ],
  534. [ '21', '22', '23' ],
  535. [ '31', '32', '33' ]
  536. ] ) );
  537. } );
  538. it( 'should return undefined if no table cells are selected', () => {
  539. expect( tableSelection.getSelectionAsFragment() ).to.be.null;
  540. } );
  541. it( 'should return document fragment for selected table cells', () => {
  542. tableSelection.setCellSelection(
  543. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  544. modelRoot.getNodeByPath( [ 0, 1, 1 ] )
  545. );
  546. expect( tableSelection.getSelectionAsFragment() ).to.be.instanceOf( DocumentFragment );
  547. } );
  548. it( 'should return cells in the source order in case of forward selection', () => {
  549. tableSelection.setCellSelection(
  550. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  551. modelRoot.getNodeByPath( [ 0, 1, 1 ] )
  552. );
  553. expect( stringifyModel( tableSelection.getSelectionAsFragment() ) ).to.equal( modelTable( [
  554. [ '11', '12' ],
  555. [ '21', '22' ]
  556. ] ) );
  557. } );
  558. it( 'should return cells in the source order in case of backward selection', () => {
  559. tableSelection.setCellSelection(
  560. modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
  561. modelRoot.getNodeByPath( [ 0, 0, 0 ] )
  562. );
  563. expect( editor.model.document.selection.isBackward ).to.be.true;
  564. expect( stringifyModel( tableSelection.getSelectionAsFragment() ) ).to.equal( modelTable( [
  565. [ '11', '12' ],
  566. [ '21', '22' ]
  567. ] ) );
  568. } );
  569. } );
  570. describe( 'delete content', () => {
  571. beforeEach( async () => {
  572. editor = await createEditor();
  573. model = editor.model;
  574. modelRoot = model.document.getRoot();
  575. view = editor.editing.view;
  576. viewDocument = view.document;
  577. tableSelection = editor.plugins.get( TableSelection );
  578. setModelData( model, modelTable( [
  579. [ '11[]', '12', '13' ],
  580. [ '21', '22', '23' ],
  581. [ '31', '32', '33' ]
  582. ] ) );
  583. } );
  584. it( 'should put selection in the last selected cell after removing content (backward delete)', () => {
  585. tableSelection.setCellSelection(
  586. modelRoot.getChild( 0 ).getChild( 0 ).getChild( 0 ),
  587. modelRoot.getChild( 0 ).getChild( 1 ).getChild( 1 )
  588. );
  589. editor.execute( 'delete' );
  590. assertEqualMarkup( getModelData( model ), modelTable( [
  591. [ '', '', '13' ],
  592. [ '', '[]', '23' ],
  593. [ '31', '32', '33' ]
  594. ] ) );
  595. } );
  596. it( 'should put selection in the last selected cell after removing content (forward delete)', () => {
  597. tableSelection.setCellSelection(
  598. modelRoot.getChild( 0 ).getChild( 0 ).getChild( 0 ),
  599. modelRoot.getChild( 0 ).getChild( 1 ).getChild( 1 )
  600. );
  601. editor.execute( 'delete' );
  602. assertEqualMarkup( getModelData( model ), modelTable( [
  603. [ '', '', '13' ],
  604. [ '', '[]', '23' ],
  605. [ '31', '32', '33' ]
  606. ] ) );
  607. } );
  608. it( 'should clear single cell if selected', () => {
  609. tableSelection.setCellSelection(
  610. modelRoot.getChild( 0 ).getChild( 0 ).getChild( 0 ),
  611. modelRoot.getChild( 0 ).getChild( 0 ).getChild( 0 ),
  612. );
  613. editor.execute( 'forwardDelete' );
  614. assertEqualMarkup( getModelData( model ), modelTable( [
  615. [ '[]', '12', '13' ],
  616. [ '21', '22', '23' ],
  617. [ '31', '32', '33' ]
  618. ] ) );
  619. } );
  620. it( 'should work with document selection passed to Model#deleteContent()', () => {
  621. tableSelection.setCellSelection(
  622. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  623. modelRoot.getNodeByPath( [ 0, 1, 1 ] )
  624. );
  625. model.change( () => {
  626. model.deleteContent( model.document.selection );
  627. } );
  628. assertEqualMarkup( getModelData( model ), modelTable( [
  629. [ '', '', '13' ],
  630. [ '', '[]', '23' ],
  631. [ '31', '32', '33' ]
  632. ] ) );
  633. } );
  634. it( 'should work with any arbitrary selection passed to Model#deleteContent() (delete backwards)', () => {
  635. const selection = model.createSelection( [
  636. model.createRange(
  637. model.createPositionFromPath( modelRoot, [ 0, 0, 0 ] ),
  638. model.createPositionFromPath( modelRoot, [ 0, 0, 1 ] )
  639. ),
  640. model.createRange(
  641. model.createPositionFromPath( modelRoot, [ 0, 0, 1 ] ),
  642. model.createPositionFromPath( modelRoot, [ 0, 0, 2 ] )
  643. )
  644. ] );
  645. model.change( writer => {
  646. model.deleteContent( selection );
  647. writer.setSelection( selection );
  648. } );
  649. assertEqualMarkup( getModelData( model ), modelTable( [
  650. [ '', '[]', '13' ],
  651. [ '21', '22', '23' ],
  652. [ '31', '32', '33' ]
  653. ] ) );
  654. } );
  655. it( 'should work with any arbitrary selection passed to Model#deleteContent() (delete forwards)', () => {
  656. const selection = model.createSelection( [
  657. model.createRange(
  658. model.createPositionFromPath( modelRoot, [ 0, 0, 0 ] ),
  659. model.createPositionFromPath( modelRoot, [ 0, 0, 1 ] )
  660. ),
  661. model.createRange(
  662. model.createPositionFromPath( modelRoot, [ 0, 0, 1 ] ),
  663. model.createPositionFromPath( modelRoot, [ 0, 0, 2 ] )
  664. )
  665. ] );
  666. model.change( writer => {
  667. model.deleteContent( selection, {
  668. direction: 'forward'
  669. } );
  670. writer.setSelection( selection );
  671. } );
  672. assertEqualMarkup( getModelData( model ), modelTable( [
  673. [ '[]', '', '13' ],
  674. [ '21', '22', '23' ],
  675. [ '31', '32', '33' ]
  676. ] ) );
  677. } );
  678. } );
  679. describe( 'getAnchorCell() and getFocusCell()', () => {
  680. beforeEach( async () => {
  681. editor = await createEditor();
  682. model = editor.model;
  683. modelRoot = model.document.getRoot();
  684. tableSelection = editor.plugins.get( TableSelection );
  685. setModelData( model, modelTable( [
  686. [ '[]00', '01', '02' ],
  687. [ '10', '11', '12' ],
  688. [ '20', '21', '22' ]
  689. ] ) );
  690. } );
  691. it( 'should not return element if the table cell is not selected', () => {
  692. expect( tableSelection.getAnchorCell() ).to.be.null;
  693. expect( tableSelection.getFocusCell() ).to.be.null;
  694. } );
  695. it( 'getAnchorCell() should return the table cell from the first range in the selection', () => {
  696. const anchorCell = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
  697. const focusCell = modelRoot.getNodeByPath( [ 0, 1, 1 ] );
  698. tableSelection.setCellSelection( anchorCell, focusCell );
  699. expect( tableSelection.getAnchorCell() ).to.equal( anchorCell );
  700. } );
  701. it( 'getFocusCell() should return the table cell from the last range in the selection', () => {
  702. const anchorCell = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
  703. const focusCell = modelRoot.getNodeByPath( [ 0, 1, 1 ] );
  704. tableSelection.setCellSelection( anchorCell, focusCell );
  705. expect( tableSelection.getFocusCell() ).to.equal( focusCell );
  706. } );
  707. } );
  708. describe( 'the selection ranges order', () => {
  709. let selection, table;
  710. beforeEach( async () => {
  711. editor = await createEditor();
  712. model = editor.model;
  713. selection = model.document.selection;
  714. modelRoot = model.document.getRoot();
  715. tableSelection = editor.plugins.get( TableSelection );
  716. setModelData( model, modelTable( [
  717. [ '00', '01', '02' ],
  718. [ '10', '11', '12' ],
  719. [ '20', '21', '22' ]
  720. ] ) );
  721. table = modelRoot.getChild( 0 );
  722. } );
  723. it( 'should be to below right', () => {
  724. const anchorCell = table.getChild( 1 ).getChild( 1 );
  725. const focusCell = table.getChild( 2 ).getChild( 2 );
  726. tableSelection.setCellSelection( anchorCell, focusCell );
  727. assertSelection( anchorCell, focusCell, 4 );
  728. expect( tableSelection.getFocusCell() ).to.equal( focusCell );
  729. expect( tableSelection.getAnchorCell() ).to.equal( anchorCell );
  730. expect( selection.isBackward ).to.be.false;
  731. } );
  732. it( 'should be to below left', () => {
  733. const anchorCell = table.getChild( 1 ).getChild( 1 );
  734. const focusCell = table.getChild( 2 ).getChild( 0 );
  735. tableSelection.setCellSelection( anchorCell, focusCell );
  736. assertSelection( anchorCell, focusCell, 4 );
  737. expect( tableSelection.getFocusCell() ).to.equal( focusCell );
  738. expect( tableSelection.getAnchorCell() ).to.equal( anchorCell );
  739. expect( selection.isBackward ).to.be.true;
  740. } );
  741. it( 'should be to above left', () => {
  742. const anchorCell = table.getChild( 1 ).getChild( 1 );
  743. const focusCell = table.getChild( 0 ).getChild( 0 );
  744. tableSelection.setCellSelection( anchorCell, focusCell );
  745. assertSelection( anchorCell, focusCell, 4 );
  746. expect( tableSelection.getFocusCell() ).to.equal( focusCell );
  747. expect( tableSelection.getAnchorCell() ).to.equal( anchorCell );
  748. expect( selection.isBackward ).to.be.true;
  749. } );
  750. it( 'should be to above right', () => {
  751. const anchorCell = table.getChild( 1 ).getChild( 1 );
  752. const focusCell = table.getChild( 0 ).getChild( 2 );
  753. tableSelection.setCellSelection( anchorCell, focusCell );
  754. assertSelection( anchorCell, focusCell, 4 );
  755. expect( tableSelection.getFocusCell() ).to.equal( focusCell );
  756. expect( tableSelection.getAnchorCell() ).to.equal( anchorCell );
  757. expect( selection.isBackward ).to.be.true;
  758. } );
  759. function assertSelection( anchorCell, focusCell, count ) {
  760. const cells = [ ...selection.getRanges() ].map( range => range.getContainedElement() );
  761. expect( selection.rangeCount ).to.equal( count );
  762. expect( cells[ 0 ] ).to.equal( anchorCell );
  763. expect( cells[ cells.length - 1 ] ).to.equal( focusCell );
  764. }
  765. } );
  766. function createEditor() {
  767. return ClassicTestEditor.create( editorElement, {
  768. plugins: [ TableEditing, TableSelection, Paragraph, Typing ]
  769. } );
  770. }
  771. } );