setheaderrowcommand.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672
  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 ModelTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/modeltesteditor';
  6. import { getData, setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
  7. import SetHeaderRowCommand from '../../src/commands/setheaderrowcommand';
  8. import { assertSelectedCells, defaultConversion, defaultSchema, modelTable } from '../_utils/utils';
  9. import TableSelection from '../../src/tableselection';
  10. import TableUtils from '../../src/tableutils';
  11. import { assertEqualMarkup } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
  12. describe( 'SetHeaderRowCommand', () => {
  13. let editor, model, command;
  14. beforeEach( () => {
  15. return ModelTestEditor
  16. .create( {
  17. plugins: [ TableUtils, TableSelection ]
  18. } )
  19. .then( newEditor => {
  20. editor = newEditor;
  21. model = editor.model;
  22. command = new SetHeaderRowCommand( editor );
  23. defaultSchema( model.schema );
  24. defaultConversion( editor.conversion );
  25. } );
  26. } );
  27. afterEach( () => {
  28. return editor.destroy();
  29. } );
  30. describe( 'isEnabled', () => {
  31. it( 'should be false if selection is not in a table', () => {
  32. setData( model, '<paragraph>foo[]</paragraph>' );
  33. expect( command.isEnabled ).to.be.false;
  34. } );
  35. it( 'should be true if selection is in table', () => {
  36. setData( model, '<table><tableRow><tableCell><paragraph>foo[]</paragraph></tableCell></tableRow></table>' );
  37. expect( command.isEnabled ).to.be.true;
  38. } );
  39. it( 'should be true if multiple cells are selected', () => {
  40. setData( model, modelTable( [
  41. [ '01', '02', '03' ]
  42. ] ) );
  43. const tableSelection = editor.plugins.get( TableSelection );
  44. const modelRoot = model.document.getRoot();
  45. tableSelection._setCellSelection(
  46. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  47. modelRoot.getNodeByPath( [ 0, 0, 1 ] )
  48. );
  49. expect( command.isEnabled ).to.be.true;
  50. } );
  51. it( 'should be true if multiple cells in a header row are selected', () => {
  52. setData( model, modelTable( [
  53. [ '01', '02', '03' ]
  54. ], { headingRows: 1 } ) );
  55. const tableSelection = editor.plugins.get( TableSelection );
  56. const modelRoot = model.document.getRoot();
  57. tableSelection._setCellSelection(
  58. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  59. modelRoot.getNodeByPath( [ 0, 0, 1 ] )
  60. );
  61. expect( command.isEnabled ).to.be.true;
  62. } );
  63. } );
  64. describe( 'value', () => {
  65. it( 'should be false if selection is not in a table without heading row', () => {
  66. setData( model, modelTable( [
  67. [ '01[]', '02' ],
  68. [ '11', '12' ]
  69. ] ) );
  70. expect( command.value ).to.be.false;
  71. } );
  72. it( 'should be false if selection is not in a heading row', () => {
  73. setData( model, modelTable( [
  74. [ '01', '02' ],
  75. [ '11', '12[]' ]
  76. ], { headingRows: 1 } ) );
  77. expect( command.value ).to.be.false;
  78. } );
  79. it( 'should be true if selection is in a heading row', () => {
  80. setData( model, modelTable( [
  81. [ '01[]', '02' ],
  82. [ '11', '12' ]
  83. ], { headingRows: 1 } ) );
  84. expect( command.value ).to.be.true;
  85. } );
  86. it( 'should be false if selection is in a heading column', () => {
  87. setData( model, modelTable( [
  88. [ '01', '02' ],
  89. [ '11[]', '12' ]
  90. ], { headingRows: 1, headingColumns: 1 } ) );
  91. expect( command.value ).to.be.false;
  92. } );
  93. it( 'should be true if multiple header rows are selected', () => {
  94. setData( model, modelTable( [
  95. [ '01', '02' ],
  96. [ '11', '12' ]
  97. ], { headingRows: 2 } ) );
  98. const tableSelection = editor.plugins.get( TableSelection );
  99. const modelRoot = model.document.getRoot();
  100. tableSelection._setCellSelection(
  101. modelRoot.getNodeByPath( [ 0, 0, 1 ] ),
  102. modelRoot.getNodeByPath( [ 0, 1, 1 ] )
  103. );
  104. expect( command.value ).to.be.true;
  105. } );
  106. it( 'should be true if multiple header columns are selected in reversed order', () => {
  107. setData( model, modelTable( [
  108. [ '01', '02' ],
  109. [ '11', '12' ]
  110. ], { headingRows: 2 } ) );
  111. const tableSelection = editor.plugins.get( TableSelection );
  112. const modelRoot = model.document.getRoot();
  113. tableSelection._setCellSelection(
  114. modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
  115. modelRoot.getNodeByPath( [ 0, 0, 1 ] )
  116. );
  117. expect( command.value ).to.be.true;
  118. } );
  119. it( 'should be false if only part of selected columns are headers', () => {
  120. setData( model, modelTable( [
  121. [ '01', '02' ],
  122. [ '11', '12' ]
  123. ], { headingRows: 1 } ) );
  124. const tableSelection = editor.plugins.get( TableSelection );
  125. const modelRoot = model.document.getRoot();
  126. tableSelection._setCellSelection(
  127. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  128. modelRoot.getNodeByPath( [ 0, 1, 0 ] )
  129. );
  130. expect( command.value ).to.be.false;
  131. } );
  132. } );
  133. describe( 'execute()', () => {
  134. it( 'should set heading rows attribute that cover row in which is selection', () => {
  135. setData( model, modelTable( [
  136. [ '00' ],
  137. [ '[]10' ],
  138. [ '20' ],
  139. [ '30' ]
  140. ] ) );
  141. command.execute();
  142. assertEqualMarkup( getData( model ), modelTable( [
  143. [ '00' ],
  144. [ '[]10' ],
  145. [ '20' ],
  146. [ '30' ]
  147. ], { headingRows: 2 } ) );
  148. } );
  149. it( 'should toggle heading rows attribute', () => {
  150. setData( model, modelTable( [
  151. [ '00' ],
  152. [ '[]10' ],
  153. [ '20' ],
  154. [ '30' ]
  155. ], { headingRows: 2 } ) );
  156. command.execute();
  157. assertEqualMarkup( getData( model ), modelTable( [
  158. [ '00' ],
  159. [ '[]10' ],
  160. [ '20' ],
  161. [ '30' ]
  162. ], { headingRows: 1 } ) );
  163. command.execute();
  164. setData( model, modelTable( [
  165. [ '00' ],
  166. [ '[]10' ],
  167. [ '20' ],
  168. [ '30' ]
  169. ], { headingRows: 2 } ) );
  170. } );
  171. it( 'should set heading rows attribute if currently selected row is a heading so the heading section is below this row', () => {
  172. setData( model, modelTable( [
  173. [ '00' ],
  174. [ '[]10' ],
  175. [ '20' ],
  176. [ '30' ]
  177. ], { headingRows: 3 } ) );
  178. command.execute();
  179. assertEqualMarkup( getData( model ), modelTable( [
  180. [ '00' ],
  181. [ '[]10' ],
  182. [ '20' ],
  183. [ '30' ]
  184. ], { headingRows: 1 } ) );
  185. } );
  186. it( 'should unsetset heading rows attribute', () => {
  187. setData( model, modelTable( [
  188. [ '[]00' ],
  189. [ '10' ],
  190. [ '20' ],
  191. [ '30' ]
  192. ], { headingRows: 3 } ) );
  193. command.execute();
  194. assertEqualMarkup( getData( model ), modelTable( [
  195. [ '[]00' ],
  196. [ '10' ],
  197. [ '20' ],
  198. [ '30' ]
  199. ] ) );
  200. } );
  201. describe( 'multi-cell selection', () => {
  202. it( 'should set it correctly in a middle of multi-row table', () => {
  203. setData( model, modelTable( [
  204. [ '00' ],
  205. [ '10' ],
  206. [ '20' ],
  207. [ '30' ]
  208. ] ) );
  209. const tableSelection = editor.plugins.get( TableSelection );
  210. const modelRoot = model.document.getRoot();
  211. tableSelection._setCellSelection(
  212. modelRoot.getNodeByPath( [ 0, 1, 0 ] ),
  213. modelRoot.getNodeByPath( [ 0, 2, 0 ] )
  214. );
  215. command.execute();
  216. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  217. [ '00' ],
  218. [ '10' ],
  219. [ '20' ],
  220. [ '30' ]
  221. ], {
  222. headingRows: 3
  223. } ) );
  224. assertSelectedCells( model, [
  225. [ 0 ],
  226. [ 1 ],
  227. [ 1 ],
  228. [ 0 ]
  229. ] );
  230. } );
  231. it( 'should set it correctly in a middle of multi-row table - reversed selection', () => {
  232. setData( model, modelTable( [
  233. [ '00' ],
  234. [ '10' ],
  235. [ '20' ],
  236. [ '30' ]
  237. ] ) );
  238. const tableSelection = editor.plugins.get( TableSelection );
  239. const modelRoot = model.document.getRoot();
  240. tableSelection._setCellSelection(
  241. modelRoot.getNodeByPath( [ 0, 2, 0 ] ),
  242. modelRoot.getNodeByPath( [ 0, 1, 0 ] )
  243. );
  244. command.execute();
  245. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  246. [ '00' ],
  247. [ '10' ],
  248. [ '20' ],
  249. [ '30' ]
  250. ], {
  251. headingRows: 3
  252. } ) );
  253. assertSelectedCells( model, [
  254. [ 0 ],
  255. [ 1 ],
  256. [ 1 ],
  257. [ 0 ]
  258. ] );
  259. } );
  260. it( 'should set it correctly in a middle of multi-row, multiple cell selection', () => {
  261. setData( model, modelTable( [
  262. [ '00', '01', '02', '03' ],
  263. [ '10', '11', '12', '13' ],
  264. [ '20', '21', '22', '23' ],
  265. [ '30', '31', '32', '33' ]
  266. ] ) );
  267. const tableSelection = editor.plugins.get( TableSelection );
  268. const modelRoot = model.document.getRoot();
  269. tableSelection._setCellSelection(
  270. modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
  271. modelRoot.getNodeByPath( [ 0, 2, 2 ] )
  272. );
  273. command.execute();
  274. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  275. [ '00', '01', '02', '03' ],
  276. [ '10', '11', '12', '13' ],
  277. [ '20', '21', '22', '23' ],
  278. [ '30', '31', '32', '33' ]
  279. ], {
  280. headingRows: 3
  281. } ) );
  282. assertSelectedCells( model, [
  283. [ 0, 0, 0, 0 ],
  284. [ 0, 1, 1, 0 ],
  285. [ 0, 1, 1, 0 ],
  286. [ 0, 0, 0, 0 ]
  287. ] );
  288. } );
  289. it( 'should set it correctly in table with more than 10 columns (array sort bug)', () => {
  290. setData( model, modelTable( [
  291. [ '0', 'x' ],
  292. [ '1', 'x' ],
  293. [ '2', 'x' ],
  294. [ '3', 'x' ],
  295. [ '4', 'x' ],
  296. [ '5', 'x' ],
  297. [ '6', 'x' ],
  298. [ '7', 'x' ],
  299. [ '8', 'x' ],
  300. [ '9', 'x' ],
  301. [ '10', 'x' ],
  302. [ '11', 'x' ],
  303. [ '12', 'x' ],
  304. [ '13', 'x' ],
  305. [ '14', 'x' ]
  306. ] ) );
  307. const tableSelection = editor.plugins.get( TableSelection );
  308. const modelRoot = model.document.getRoot();
  309. tableSelection._setCellSelection(
  310. modelRoot.getNodeByPath( [ 0, 13, 0 ] ),
  311. modelRoot.getNodeByPath( [ 0, 2, 0 ] )
  312. );
  313. command.execute();
  314. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  315. [ '0', 'x' ],
  316. [ '1', 'x' ],
  317. [ '2', 'x' ],
  318. [ '3', 'x' ],
  319. [ '4', 'x' ],
  320. [ '5', 'x' ],
  321. [ '6', 'x' ],
  322. [ '7', 'x' ],
  323. [ '8', 'x' ],
  324. [ '9', 'x' ],
  325. [ '10', 'x' ],
  326. [ '11', 'x' ],
  327. [ '12', 'x' ],
  328. [ '13', 'x' ],
  329. [ '14', 'x' ]
  330. ], { headingRows: 14 } ) );
  331. } );
  332. it( 'should set it correctly in table with more than 10 columns (array sort bug, reversed selection)', () => {
  333. setData( model, modelTable( [
  334. [ '0', 'x' ],
  335. [ '1', 'x' ],
  336. [ '2', 'x' ],
  337. [ '3', 'x' ],
  338. [ '4', 'x' ],
  339. [ '5', 'x' ],
  340. [ '6', 'x' ],
  341. [ '7', 'x' ],
  342. [ '8', 'x' ],
  343. [ '9', 'x' ],
  344. [ '10', 'x' ],
  345. [ '11', 'x' ],
  346. [ '12', 'x' ],
  347. [ '13', 'x' ],
  348. [ '14', 'x' ]
  349. ] ) );
  350. const tableSelection = editor.plugins.get( TableSelection );
  351. const modelRoot = model.document.getRoot();
  352. tableSelection._setCellSelection(
  353. modelRoot.getNodeByPath( [ 0, 2, 0 ] ),
  354. modelRoot.getNodeByPath( [ 0, 13, 1 ] )
  355. );
  356. command.execute();
  357. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  358. [ '0', 'x' ],
  359. [ '1', 'x' ],
  360. [ '2', 'x' ],
  361. [ '3', 'x' ],
  362. [ '4', 'x' ],
  363. [ '5', 'x' ],
  364. [ '6', 'x' ],
  365. [ '7', 'x' ],
  366. [ '8', 'x' ],
  367. [ '9', 'x' ],
  368. [ '10', 'x' ],
  369. [ '11', 'x' ],
  370. [ '12', 'x' ],
  371. [ '13', 'x' ],
  372. [ '14', 'x' ]
  373. ], { headingRows: 14 } ) );
  374. } );
  375. it( 'should remove header rows in case of multiple cell selection', () => {
  376. setData( model, modelTable( [
  377. [ '00' ],
  378. [ '10' ],
  379. [ '20' ],
  380. [ '30' ]
  381. ], { headingRows: 4 } ) );
  382. const tableSelection = editor.plugins.get( TableSelection );
  383. const modelRoot = model.document.getRoot();
  384. tableSelection._setCellSelection(
  385. modelRoot.getNodeByPath( [ 0, 1, 0 ] ),
  386. modelRoot.getNodeByPath( [ 0, 2, 0 ] )
  387. );
  388. command.execute();
  389. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  390. [ '00' ],
  391. [ '10' ],
  392. [ '20' ],
  393. [ '30' ]
  394. ], {
  395. headingRows: 1
  396. } ) );
  397. assertSelectedCells( model, [
  398. [ 0 ],
  399. [ 1 ],
  400. [ 1 ],
  401. [ 0 ]
  402. ] );
  403. } );
  404. it( 'should respect forceValue=true in case of multiple row selection', () => {
  405. setData( model, modelTable( [
  406. [ '00' ],
  407. [ '10' ],
  408. [ '20' ],
  409. [ '30' ]
  410. ], {
  411. headingRows: 3
  412. } ) );
  413. const tableSelection = editor.plugins.get( TableSelection );
  414. const modelRoot = model.document.getRoot();
  415. tableSelection._setCellSelection(
  416. modelRoot.getNodeByPath( [ 0, 1, 0 ] ),
  417. modelRoot.getNodeByPath( [ 0, 2, 0 ] )
  418. );
  419. command.execute( { forceValue: true } );
  420. assertEqualMarkup( getData( model, {
  421. withoutSelection: true
  422. } ), modelTable( [
  423. [ '00' ],
  424. [ '10' ],
  425. [ '20' ],
  426. [ '30' ]
  427. ], {
  428. headingRows: 3
  429. } ) );
  430. assertSelectedCells( model, [
  431. [ 0 ],
  432. [ 1 ],
  433. [ 1 ],
  434. [ 0 ]
  435. ] );
  436. } );
  437. it( 'should respect forceValue=false in case of multiple cell selection', () => {
  438. setData( model, modelTable( [
  439. [ '00' ],
  440. [ '10' ],
  441. [ '20' ],
  442. [ '30' ]
  443. ], {
  444. headingRows: 1
  445. } ) );
  446. const tableSelection = editor.plugins.get( TableSelection );
  447. const modelRoot = model.document.getRoot();
  448. tableSelection._setCellSelection(
  449. modelRoot.getNodeByPath( [ 0, 1, 0 ] ),
  450. modelRoot.getNodeByPath( [ 0, 2, 0 ] )
  451. );
  452. command.execute( { forceValue: false } );
  453. assertEqualMarkup( getData( model, {
  454. withoutSelection: true
  455. } ), modelTable( [
  456. [ '00' ],
  457. [ '10' ],
  458. [ '20' ],
  459. [ '30' ]
  460. ], {
  461. headingRows: 1
  462. } ) );
  463. assertSelectedCells( model, [
  464. [ 0 ],
  465. [ 1 ],
  466. [ 1 ],
  467. [ 0 ]
  468. ] );
  469. } );
  470. } );
  471. it( 'should respect forceValue parameter #1', () => {
  472. setData( model, modelTable( [
  473. [ '00' ],
  474. [ '[]10' ],
  475. [ '20' ],
  476. [ '30' ]
  477. ], { headingRows: 3 } ) );
  478. command.execute( { forceValue: true } );
  479. assertEqualMarkup( getData( model ), modelTable( [
  480. [ '00' ],
  481. [ '[]10' ],
  482. [ '20' ],
  483. [ '30' ]
  484. ], { headingRows: 3 } ) );
  485. } );
  486. it( 'should respect forceValue parameter #2', () => {
  487. setData( model, modelTable( [
  488. [ '00' ],
  489. [ '[]10' ],
  490. [ '20' ],
  491. [ '30' ]
  492. ], { headingRows: 1 } ) );
  493. command.execute( { forceValue: false } );
  494. assertEqualMarkup( getData( model ), modelTable( [
  495. [ '00' ],
  496. [ '[]10' ],
  497. [ '20' ],
  498. [ '30' ]
  499. ], { headingRows: 1 } ) );
  500. } );
  501. it( 'should fix rowspaned cells on the edge of an table head section', () => {
  502. setData( model, modelTable( [
  503. [ '00', '01', '02' ],
  504. [ { colspan: 2, rowspan: 2, contents: '10[]' }, '12' ],
  505. [ '22' ]
  506. ], { headingColumns: 2, headingRows: 1 } ) );
  507. command.execute();
  508. assertEqualMarkup( getData( model ), modelTable( [
  509. [ '00', '01', '02' ],
  510. [ { colspan: 2, contents: '10[]' }, '12' ],
  511. [ { colspan: 2, contents: '' }, '22' ]
  512. ], { headingColumns: 2, headingRows: 2 } ) );
  513. } );
  514. it( 'should split to at most 2 table cells when fixing rowspaned cells on the edge of an table head section', () => {
  515. setData( model, modelTable( [
  516. [ '00', '01', '02' ],
  517. [ { colspan: 2, rowspan: 5, contents: '10' }, '12' ],
  518. [ '22[]' ],
  519. [ '32' ],
  520. [ '42' ],
  521. [ '52' ]
  522. ], { headingColumns: 2, headingRows: 1 } ) );
  523. command.execute();
  524. assertEqualMarkup( getData( model ), modelTable( [
  525. [ '00', '01', '02' ],
  526. [ { colspan: 2, rowspan: 2, contents: '10' }, '12' ],
  527. [ '22[]' ],
  528. [ { colspan: 2, rowspan: 3, contents: '' }, '32' ],
  529. [ '42' ],
  530. [ '52' ]
  531. ], { headingColumns: 2, headingRows: 3 } ) );
  532. } );
  533. it( 'should fix rowspaned cells on the edge of an table head section when creating section', () => {
  534. setData( model, modelTable( [
  535. [ { rowspan: 2, contents: '00' }, '01' ],
  536. [ '[]11' ]
  537. ], { headingRows: 2 } ) );
  538. command.execute();
  539. assertEqualMarkup( getData( model ), modelTable( [
  540. [ '00', '01' ],
  541. [ '', '[]11' ]
  542. ], { headingRows: 1 } ) );
  543. } );
  544. it( 'should fix rowspaned cells inside a row', () => {
  545. setData( model, modelTable( [
  546. [ '00', { rowspan: 2, contents: '01' } ],
  547. [ '[]10' ]
  548. ], { headingRows: 2 } ) );
  549. command.execute();
  550. assertEqualMarkup( getData( model ), modelTable( [
  551. [ '00', '01' ],
  552. [ '[]10', '' ]
  553. ], { headingRows: 1 } ) );
  554. } );
  555. it( 'should work properly in the first row of a table', () => {
  556. setData( model, modelTable( [
  557. [ '00', '[]01', '02' ],
  558. [ { colspan: 2, rowspan: 2, contents: '10' }, '12' ],
  559. [ '22' ]
  560. ] ) );
  561. command.execute();
  562. assertEqualMarkup( getData( model ), modelTable( [
  563. [ '00', '[]01', '02' ],
  564. [ { colspan: 2, rowspan: 2, contents: '10' }, '12' ],
  565. [ '22' ]
  566. ], { headingRows: 1 } ) );
  567. } );
  568. } );
  569. } );