removerowcommand.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  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. import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
  6. import { getData, setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
  7. import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
  8. import RemoveRowCommand from '../../src/commands/removerowcommand';
  9. import TableSelection from '../../src/tableselection';
  10. import { modelTable, viewTable } from '../_utils/utils';
  11. import { assertEqualMarkup } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
  12. import TableEditing from '../../src/tableediting';
  13. import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
  14. describe( 'RemoveRowCommand', () => {
  15. let editor, model, command;
  16. beforeEach( async () => {
  17. editor = await VirtualTestEditor.create( { plugins: [ Paragraph, TableEditing, TableSelection ] } );
  18. model = editor.model;
  19. command = new RemoveRowCommand( editor );
  20. } );
  21. afterEach( () => {
  22. return editor.destroy();
  23. } );
  24. describe( 'isEnabled', () => {
  25. it( 'should be true if selection is inside table cell', () => {
  26. setData( model, modelTable( [
  27. [ '00[]', '01' ],
  28. [ '10', '11' ]
  29. ] ) );
  30. expect( command.isEnabled ).to.be.true;
  31. } );
  32. it( 'should be true if selection contains multiple cells', () => {
  33. setData( model, modelTable( [
  34. [ '00', '01' ],
  35. [ '10', '11' ],
  36. [ '20', '21' ]
  37. ] ) );
  38. const tableSelection = editor.plugins.get( TableSelection );
  39. const modelRoot = model.document.getRoot();
  40. tableSelection.setCellSelection(
  41. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  42. modelRoot.getNodeByPath( [ 0, 0, 1 ] )
  43. );
  44. expect( command.isEnabled ).to.be.true;
  45. } );
  46. it( 'should be false if selection is inside table with one row only', () => {
  47. setData( model, modelTable( [
  48. [ '00[]', '01' ]
  49. ] ) );
  50. expect( command.isEnabled ).to.be.false;
  51. } );
  52. it( 'should be false if all the rows are selected', () => {
  53. setData( model, modelTable( [
  54. [ '00', '01' ],
  55. [ '10', '11' ]
  56. ] ) );
  57. const tableSelection = editor.plugins.get( TableSelection );
  58. const modelRoot = model.document.getRoot();
  59. tableSelection.setCellSelection(
  60. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  61. modelRoot.getNodeByPath( [ 0, 1, 0 ] )
  62. );
  63. expect( command.isEnabled ).to.be.false;
  64. } );
  65. it( 'should be false if selection is outside a table', () => {
  66. setData( model, '<paragraph>11[]</paragraph>' );
  67. expect( command.isEnabled ).to.be.false;
  68. } );
  69. it( 'should be false when the first column with rowspan is selected', () => {
  70. // (#6427)
  71. setData( model, modelTable( [
  72. [ { rowspan: 2, contents: '00' }, '01' ],
  73. [ '11' ],
  74. [ '20', '21' ]
  75. ] ) );
  76. const tableSelection = editor.plugins.get( TableSelection );
  77. const modelRoot = model.document.getRoot();
  78. tableSelection.setCellSelection(
  79. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  80. modelRoot.getNodeByPath( [ 0, 2, 0 ] )
  81. );
  82. expect( command.isEnabled ).to.be.false;
  83. } );
  84. it( 'should be false if all the rows are selected - table with more than 10 rows (array sort bug)', () => {
  85. setData( model, modelTable( [
  86. [ '0' ],
  87. [ '1' ],
  88. [ '2' ],
  89. [ '3' ],
  90. [ '4' ],
  91. [ '5' ],
  92. [ '6' ],
  93. [ '7' ],
  94. [ '8' ],
  95. [ '9' ],
  96. [ '10' ],
  97. [ '11' ],
  98. [ '12' ]
  99. ] ) );
  100. const tableSelection = editor.plugins.get( TableSelection );
  101. const modelRoot = model.document.getRoot();
  102. tableSelection.setCellSelection(
  103. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  104. modelRoot.getNodeByPath( [ 0, 12, 0 ] )
  105. );
  106. expect( command.isEnabled ).to.be.false;
  107. } );
  108. } );
  109. describe( 'execute()', () => {
  110. it( 'should remove a given row', () => {
  111. setData( model, modelTable( [
  112. [ '00', '01' ],
  113. [ '[]10', '11' ],
  114. [ '20', '21' ]
  115. ] ) );
  116. command.execute();
  117. assertEqualMarkup( getData( model ), modelTable( [
  118. [ '00', '01' ],
  119. [ '[]20', '21' ]
  120. ] ) );
  121. } );
  122. describe( 'with multiple rows selected', () => {
  123. it( 'should properly remove middle rows', () => {
  124. setData( model, modelTable( [
  125. [ '00', '01' ],
  126. [ '10', '11' ],
  127. [ '20', '21' ],
  128. [ '30', '31' ]
  129. ] ) );
  130. const tableSelection = editor.plugins.get( TableSelection );
  131. const modelRoot = model.document.getRoot();
  132. tableSelection.setCellSelection(
  133. modelRoot.getNodeByPath( [ 0, 1, 0 ] ),
  134. modelRoot.getNodeByPath( [ 0, 2, 0 ] )
  135. );
  136. command.execute();
  137. assertEqualMarkup( getData( model ), modelTable( [
  138. [ '00', '01' ],
  139. [ '[]30', '31' ]
  140. ] ) );
  141. } );
  142. it( 'should properly remove middle rows in reversed order', () => {
  143. setData( model, modelTable( [
  144. [ '00', '01' ],
  145. [ '10', '11' ],
  146. [ '20', '21' ],
  147. [ '30', '31' ]
  148. ] ) );
  149. const tableSelection = editor.plugins.get( TableSelection );
  150. const modelRoot = model.document.getRoot();
  151. tableSelection.setCellSelection(
  152. modelRoot.getNodeByPath( [ 0, 2, 0 ] ),
  153. modelRoot.getNodeByPath( [ 0, 1, 0 ] )
  154. );
  155. command.execute();
  156. assertEqualMarkup( getData( model ), modelTable( [
  157. [ '00', '01' ],
  158. [ '[]30', '31' ]
  159. ] ) );
  160. } );
  161. it( 'should properly remove tailing rows', () => {
  162. setData( model, modelTable( [
  163. [ '00', '01' ],
  164. [ '10', '11' ],
  165. [ '20', '21' ],
  166. [ '30', '31' ]
  167. ] ) );
  168. const tableSelection = editor.plugins.get( TableSelection );
  169. const modelRoot = model.document.getRoot();
  170. tableSelection.setCellSelection(
  171. modelRoot.getNodeByPath( [ 0, 2, 0 ] ),
  172. modelRoot.getNodeByPath( [ 0, 3, 0 ] )
  173. );
  174. command.execute();
  175. assertEqualMarkup( getData( model ), modelTable( [
  176. [ '00', '01' ],
  177. [ '[]10', '11' ]
  178. ] ) );
  179. } );
  180. it( 'should properly remove beginning rows', () => {
  181. setData( model, modelTable( [
  182. [ '00', '01' ],
  183. [ '10', '11' ],
  184. [ '20', '21' ],
  185. [ '30', '31' ]
  186. ] ) );
  187. const tableSelection = editor.plugins.get( TableSelection );
  188. const modelRoot = model.document.getRoot();
  189. tableSelection.setCellSelection(
  190. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  191. modelRoot.getNodeByPath( [ 0, 1, 0 ] )
  192. );
  193. command.execute();
  194. assertEqualMarkup( getData( model ), modelTable( [
  195. [ '[]20', '21' ],
  196. [ '30', '31' ]
  197. ] ) );
  198. } );
  199. it( 'should support removing multiple headings (removed rows in heading section)', () => {
  200. setData( model, modelTable( [
  201. [ '00', '01' ],
  202. [ '10', '11' ],
  203. [ '20', '21' ],
  204. [ '30', '31' ]
  205. ], { headingRows: 3 } ) );
  206. const tableSelection = editor.plugins.get( TableSelection );
  207. const modelRoot = model.document.getRoot();
  208. tableSelection.setCellSelection(
  209. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  210. modelRoot.getNodeByPath( [ 0, 1, 0 ] )
  211. );
  212. command.execute();
  213. assertEqualMarkup( getData( model ), modelTable( [
  214. [ '[]20', '21' ],
  215. [ '30', '31' ]
  216. ], { headingRows: 1 } ) );
  217. } );
  218. it( 'should support removing multiple headings (removed rows in heading and body section)', () => {
  219. setData( model, modelTable( [
  220. [ '00', '01' ],
  221. [ '10', '11' ],
  222. [ '20', '21' ],
  223. [ '30', '31' ],
  224. [ '40', '41' ]
  225. ], { headingRows: 3 } ) );
  226. const tableSelection = editor.plugins.get( TableSelection );
  227. const modelRoot = model.document.getRoot();
  228. tableSelection.setCellSelection(
  229. modelRoot.getNodeByPath( [ 0, 1, 0 ] ),
  230. modelRoot.getNodeByPath( [ 0, 3, 0 ] )
  231. );
  232. command.execute();
  233. assertEqualMarkup( getData( model ), modelTable( [
  234. [ '00', '01' ],
  235. [ '[]40', '41' ]
  236. ], { headingRows: 1 } ) );
  237. // The editing view should also be properly downcasted.
  238. assertEqualMarkup( getViewData( editor.editing.view, { withoutSelection: true } ), viewTable( [
  239. [ '00', '01' ],
  240. [ '40', '41' ]
  241. ], { headingRows: 1, asWidget: true } ) );
  242. } );
  243. it( 'should support removing mixed heading and cell rows', () => {
  244. setData( model, modelTable( [
  245. [ '00', '01' ],
  246. [ '10', '11' ],
  247. [ '20', '21' ]
  248. ], { headingRows: 1 } ) );
  249. const tableSelection = editor.plugins.get( TableSelection );
  250. const modelRoot = model.document.getRoot();
  251. tableSelection.setCellSelection(
  252. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  253. modelRoot.getNodeByPath( [ 0, 1, 0 ] )
  254. );
  255. command.execute();
  256. assertEqualMarkup( getData( model ), modelTable( [
  257. [ '[]20', '21' ]
  258. ] ) );
  259. } );
  260. it( 'should properly calculate truncated rowspans', () => {
  261. setData( model, modelTable( [
  262. [ '00', { contents: '01', rowspan: 3 } ],
  263. [ '10' ],
  264. [ '20' ]
  265. ] ) );
  266. const tableSelection = editor.plugins.get( TableSelection );
  267. const modelRoot = model.document.getRoot();
  268. tableSelection.setCellSelection(
  269. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  270. modelRoot.getNodeByPath( [ 0, 1, 0 ] )
  271. );
  272. command.execute();
  273. assertEqualMarkup( getData( model ), modelTable( [
  274. [ '[]20', '01' ]
  275. ] ) );
  276. } );
  277. it( 'should create one undo step (1 batch)', () => {
  278. setData( model, modelTable( [
  279. [ '00', '01' ],
  280. [ '10', '11' ],
  281. [ '20', '21' ],
  282. [ '30', '31' ]
  283. ], { headingRows: 3 } ) );
  284. const createdBatches = new Set();
  285. model.on( 'applyOperation', ( evt, args ) => {
  286. const operation = args[ 0 ];
  287. createdBatches.add( operation.batch );
  288. } );
  289. const tableSelection = editor.plugins.get( TableSelection );
  290. const modelRoot = model.document.getRoot();
  291. tableSelection.setCellSelection(
  292. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  293. modelRoot.getNodeByPath( [ 0, 1, 0 ] )
  294. );
  295. command.execute();
  296. expect( createdBatches.size ).to.equal( 1 );
  297. } );
  298. it( 'should properly remove more than 10 rows selected (array sort bug)', () => {
  299. setData( model, modelTable( [
  300. [ '0' ],
  301. [ '1' ],
  302. [ '2' ],
  303. [ '3' ],
  304. [ '4' ],
  305. [ '5' ],
  306. [ '6' ],
  307. [ '7' ],
  308. [ '8' ],
  309. [ '9' ],
  310. [ '10' ],
  311. [ '11' ],
  312. [ '12' ],
  313. [ '13' ],
  314. [ '14' ]
  315. ] ) );
  316. const tableSelection = editor.plugins.get( TableSelection );
  317. const modelRoot = model.document.getRoot();
  318. tableSelection.setCellSelection(
  319. modelRoot.getNodeByPath( [ 0, 1, 0 ] ),
  320. modelRoot.getNodeByPath( [ 0, 12, 0 ] )
  321. );
  322. command.execute();
  323. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  324. [ '0' ],
  325. [ '13' ],
  326. [ '14' ]
  327. ] ) );
  328. } );
  329. } );
  330. describe( 'with entire row selected', () => {
  331. it( 'should remove a row if all its cells are selected', () => {
  332. setData( model, modelTable( [
  333. [ '00', '01' ],
  334. [ '10', '11' ],
  335. [ '20', '21' ]
  336. ] ) );
  337. const tableSelection = editor.plugins.get( TableSelection );
  338. const modelRoot = model.document.getRoot();
  339. tableSelection.setCellSelection(
  340. modelRoot.getNodeByPath( [ 0, 1, 0 ] ),
  341. modelRoot.getNodeByPath( [ 0, 1, 1 ] )
  342. );
  343. command.execute();
  344. assertEqualMarkup( getData( model ), modelTable( [
  345. [ '00', '01' ],
  346. [ '[]20', '21' ]
  347. ] ) );
  348. } );
  349. it( 'should properly remove row if reversed selection is made', () => {
  350. setData( model, modelTable( [
  351. [ '00', '01' ],
  352. [ '10', '11' ]
  353. ] ) );
  354. const tableSelection = editor.plugins.get( TableSelection );
  355. const modelRoot = model.document.getRoot();
  356. tableSelection.setCellSelection(
  357. modelRoot.getNodeByPath( [ 0, 0, 1 ] ),
  358. modelRoot.getNodeByPath( [ 0, 0, 0 ] )
  359. );
  360. command.execute();
  361. assertEqualMarkup( getData( model ), modelTable( [
  362. [ '[]10', '11' ]
  363. ] ) );
  364. } );
  365. } );
  366. it( 'should remove a given row from a table start', () => {
  367. setData( model, modelTable( [
  368. [ '[]00', '01' ],
  369. [ '10', '11' ],
  370. [ '20', '21' ]
  371. ] ) );
  372. command.execute();
  373. assertEqualMarkup( getData( model ), modelTable( [
  374. [ '[]10', '11' ],
  375. [ '20', '21' ]
  376. ] ) );
  377. } );
  378. it( 'should remove a given row from a table start when selection is at the end', () => {
  379. setData( model, modelTable( [
  380. [ '00', '01[]' ],
  381. [ '10', '11' ],
  382. [ '20', '21' ]
  383. ] ) );
  384. command.execute();
  385. assertEqualMarkup( getData( model ), modelTable( [
  386. [ '10', '[]11' ],
  387. [ '20', '21' ]
  388. ] ) );
  389. } );
  390. it( 'should remove last row', () => {
  391. setData( model, modelTable( [
  392. [ '00', '01' ],
  393. [ '[]10', '11' ]
  394. ] ) );
  395. command.execute();
  396. assertEqualMarkup( getData( model ), modelTable( [
  397. [ '[]00', '01' ]
  398. ] ) );
  399. } );
  400. it( 'should change heading rows if removing a heading row', () => {
  401. setData( model, modelTable( [
  402. [ '00', '01' ],
  403. [ '[]10', '11' ],
  404. [ '20', '21' ]
  405. ], { headingRows: 2 } ) );
  406. command.execute();
  407. assertEqualMarkup( getData( model ), modelTable( [
  408. [ '00', '01' ],
  409. [ '[]20', '21' ]
  410. ], { headingRows: 1 } ) );
  411. } );
  412. it( 'should decrease rowspan of table cells from previous rows', () => {
  413. setData( model, modelTable( [
  414. [ { rowspan: 4, contents: '00' }, { rowspan: 3, contents: '01' }, { rowspan: 2, contents: '02' }, '03', '04' ],
  415. [ { rowspan: 2, contents: '13' }, '14' ],
  416. [ '22[]', '24' ],
  417. [ '31', '32', '33', '34' ]
  418. ] ) );
  419. command.execute();
  420. assertEqualMarkup( getData( model ), modelTable( [
  421. [ { rowspan: 3, contents: '00' }, { rowspan: 2, contents: '01' }, { rowspan: 2, contents: '02' }, '03', '04' ],
  422. [ '13', '14' ],
  423. [ '31', '32', '[]33', '34' ]
  424. ] ) );
  425. } );
  426. it( 'should move rowspaned cells to row below removing it\'s row', () => {
  427. setData( model, modelTable( [
  428. [ { rowspan: 3, contents: '[]00' }, { rowspan: 2, contents: '01' }, '02' ],
  429. [ '12' ],
  430. [ '21', '22' ],
  431. [ '30', '31', '32' ]
  432. ] ) );
  433. command.execute();
  434. assertEqualMarkup( getData( model ), modelTable( [
  435. [ { rowspan: 2, contents: '[]00' }, '01', '12' ],
  436. [ '21', '22' ],
  437. [ '30', '31', '32' ]
  438. ] ) );
  439. } );
  440. it( 'should remove empty columns after removing row', () => {
  441. setData( model, modelTable( [
  442. [ '00', { contents: '01', colspan: 2 } ],
  443. [ '[]10', '11', '12' ]
  444. ] ) );
  445. command.execute();
  446. assertEqualMarkup( getData( model ), modelTable( [
  447. [ '[]00', '01' ]
  448. ] ) );
  449. } );
  450. } );
  451. } );