tableutils.js 48 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639
  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 Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
  7. import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
  8. import { getData, setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
  9. import { assertEqualMarkup } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
  10. import TableEditing from '../src/tableediting';
  11. import { modelTable } from './_utils/utils';
  12. import TableUtils from '../src/tableutils';
  13. describe( 'TableUtils', () => {
  14. let editor, model, root, tableUtils;
  15. testUtils.createSinonSandbox();
  16. beforeEach( () => {
  17. return ModelTestEditor.create( {
  18. plugins: [ Paragraph, TableEditing, TableUtils ]
  19. } ).then( newEditor => {
  20. editor = newEditor;
  21. model = editor.model;
  22. root = model.document.getRoot( 'main' );
  23. tableUtils = editor.plugins.get( TableUtils );
  24. } );
  25. } );
  26. afterEach( () => {
  27. return editor.destroy();
  28. } );
  29. describe( '#pluginName', () => {
  30. it( 'should provide plugin name', () => {
  31. expect( TableUtils.pluginName ).to.equal( 'TableUtils' );
  32. } );
  33. } );
  34. describe( 'getCellLocation()', () => {
  35. it( 'should return proper table cell location', () => {
  36. setData( model, modelTable( [
  37. [ { rowspan: 2, colspan: 2, contents: '00[]' }, '02' ],
  38. [ '12' ]
  39. ] ) );
  40. expect( tableUtils.getCellLocation( root.getNodeByPath( [ 0, 0, 0 ] ) ) ).to.deep.equal( { row: 0, column: 0 } );
  41. expect( tableUtils.getCellLocation( root.getNodeByPath( [ 0, 0, 1 ] ) ) ).to.deep.equal( { row: 0, column: 2 } );
  42. expect( tableUtils.getCellLocation( root.getNodeByPath( [ 0, 1, 0 ] ) ) ).to.deep.equal( { row: 1, column: 2 } );
  43. } );
  44. } );
  45. describe( 'insertRows()', () => {
  46. it( 'should be decorated', () => {
  47. const spy = sinon.spy();
  48. setData( model, modelTable( [
  49. [ '11[]', '12' ],
  50. [ '21', '22' ]
  51. ] ) );
  52. tableUtils.on( 'insertRows', spy );
  53. tableUtils.insertRows( root.getNodeByPath( [ 0 ] ), { at: 1 } );
  54. assertEqualMarkup( getData( model ), modelTable( [
  55. [ '11[]', '12' ],
  56. [ '', '' ],
  57. [ '21', '22' ]
  58. ] ) );
  59. expect( spy.calledOnce ).to.be.true;
  60. } );
  61. it( 'should insert row in given table at given index', () => {
  62. setData( model, modelTable( [
  63. [ '11[]', '12' ],
  64. [ '21', '22' ]
  65. ] ) );
  66. tableUtils.insertRows( root.getNodeByPath( [ 0 ] ), { at: 1 } );
  67. assertEqualMarkup( getData( model ), modelTable( [
  68. [ '11[]', '12' ],
  69. [ '', '' ],
  70. [ '21', '22' ]
  71. ] ) );
  72. } );
  73. it( 'should insert row in given table at default index', () => {
  74. setData( model, modelTable( [
  75. [ '11[]', '12' ],
  76. [ '21', '22' ]
  77. ] ) );
  78. tableUtils.insertRows( root.getNodeByPath( [ 0 ] ) );
  79. assertEqualMarkup( getData( model ), modelTable( [
  80. [ '', '' ],
  81. [ '11[]', '12' ],
  82. [ '21', '22' ]
  83. ] ) );
  84. } );
  85. it( 'should update table heading rows attribute when inserting row in headings section', () => {
  86. setData( model, modelTable( [
  87. [ '11[]', '12' ],
  88. [ '21', '22' ],
  89. [ '31', '32' ]
  90. ], { headingRows: 2 } ) );
  91. tableUtils.insertRows( root.getNodeByPath( [ 0 ] ), { at: 1 } );
  92. assertEqualMarkup( getData( model ), modelTable( [
  93. [ '11[]', '12' ],
  94. [ '', '' ],
  95. [ '21', '22' ],
  96. [ '31', '32' ]
  97. ], { headingRows: 3 } ) );
  98. } );
  99. it( 'should not update table heading rows attribute when inserting row after headings section', () => {
  100. setData( model, modelTable( [
  101. [ '11[]', '12' ],
  102. [ '21', '22' ],
  103. [ '31', '32' ]
  104. ], { headingRows: 2 } ) );
  105. tableUtils.insertRows( root.getNodeByPath( [ 0 ] ), { at: 2 } );
  106. assertEqualMarkup( getData( model ), modelTable( [
  107. [ '11[]', '12' ],
  108. [ '21', '22' ],
  109. [ '', '' ],
  110. [ '31', '32' ]
  111. ], { headingRows: 2 } ) );
  112. } );
  113. it( 'should expand rowspan of a cell that overlaps inserted rows', () => {
  114. // +----+----+----+----+
  115. // | 00 | 02 | 03 |
  116. // +----+----+----+----+ <-- heading rows
  117. // | 10 | 12 | 13 |
  118. // + +----+----+
  119. // | | 22 | 23 |
  120. // +----+----+----+----+
  121. // ^-- heading columns
  122. setData( model, modelTable( [
  123. [ { contents: '00', colspan: 2 }, '02', '03' ],
  124. [ { contents: '10[]', colspan: 2, rowspan: 2 }, '12', '13' ],
  125. [ '22', '23' ]
  126. ], { headingColumns: 3, headingRows: 1 } ) );
  127. tableUtils.insertRows( root.getNodeByPath( [ 0 ] ), { at: 2, rows: 3 } );
  128. // +----+----+----+----+
  129. // | 00 | 02 | 03 |
  130. // +----+----+----+----+ <-- heading rows
  131. // | 10 | 12 | 13 |
  132. // + +----+----+
  133. // | | | |
  134. // + +----+----+
  135. // | | | |
  136. // + +----+----+
  137. // | | | |
  138. // + +----+----+
  139. // | | 22 | 23 |
  140. // +----+----+----+----+
  141. // ^-- heading columns
  142. assertEqualMarkup( getData( model ), modelTable( [
  143. [ { contents: '00', colspan: 2 }, '02', '03' ],
  144. [ { contents: '10[]', colspan: 2, rowspan: 5 }, '12', '13' ],
  145. [ '', '' ],
  146. [ '', '' ],
  147. [ '', '' ],
  148. [ '22', '23' ]
  149. ], { headingColumns: 3, headingRows: 1 } ) );
  150. } );
  151. it( 'should not expand rowspan of a cell that does not overlaps inserted rows', () => {
  152. // +----+----+----+
  153. // | 00 | 01 | 02 |
  154. // + +----+----+
  155. // | | 11 | 12 |
  156. // +----+----+----+ <-- heading rows
  157. // | 20 | 21 | 22 |
  158. // +----+----+----+
  159. setData( model, modelTable( [
  160. [ { contents: '00', rowspan: 2 }, '01', '02' ],
  161. [ '11[]', '12' ],
  162. [ '20', '21', '22' ]
  163. ], { headingRows: 2 } ) );
  164. tableUtils.insertRows( root.getNodeByPath( [ 0 ] ), { at: 2, rows: 3 } );
  165. // +----+----+----+
  166. // | 00 | 01 | 02 |
  167. // + +----+----+
  168. // | | 11 | 12 |
  169. // +----+----+----+ <-- heading rows
  170. // | | | |
  171. // +----+----+----+
  172. // | | | |
  173. // +----+----+----+
  174. // | | | |
  175. // +----+----+----+
  176. // | 20 | 21 | 22 |
  177. // +----+----+----+
  178. assertEqualMarkup( getData( model ), modelTable( [
  179. [ { contents: '00', rowspan: 2 }, '01', '02' ],
  180. [ '11[]', '12' ],
  181. [ '', '', '' ],
  182. [ '', '', '' ],
  183. [ '', '', '' ],
  184. [ '20', '21', '22' ]
  185. ], { headingRows: 2 } ) );
  186. } );
  187. it( 'should properly calculate columns if next row has colspans', () => {
  188. // +----+----+----+
  189. // | 00 | 01 | 02 |
  190. // + +----+----+
  191. // | | 11 | 12 |
  192. // +----+----+----+ <-- heading rows
  193. // | 20 |
  194. // +----+----+----+
  195. setData( model, modelTable( [
  196. [ { contents: '00', rowspan: 2 }, '01', '02' ],
  197. [ '11[]', '12' ],
  198. [ { contents: '20', colspan: 3 } ]
  199. ], { headingRows: 2 } ) );
  200. tableUtils.insertRows( root.getNodeByPath( [ 0 ] ), { at: 2, rows: 3 } );
  201. // +----+----+----+
  202. // | 00 | 01 | 02 |
  203. // + +----+----+
  204. // | | 11 | 12 |
  205. // +----+----+----+ <-- heading rows
  206. // | | | |
  207. // +----+----+----+
  208. // | | | |
  209. // +----+----+----+
  210. // | | | |
  211. // +----+----+----+
  212. // | 20 |
  213. // +----+----+----+
  214. assertEqualMarkup( getData( model ), modelTable( [
  215. [ { contents: '00', rowspan: 2 }, '01', '02' ],
  216. [ '11[]', '12' ],
  217. [ '', '', '' ],
  218. [ '', '', '' ],
  219. [ '', '', '' ],
  220. [ { contents: '20', colspan: 3 } ]
  221. ], { headingRows: 2 } ) );
  222. } );
  223. it( 'should insert rows at the end of a table', () => {
  224. setData( model, modelTable( [
  225. [ '11[]', '12' ],
  226. [ '21', '22' ]
  227. ] ) );
  228. tableUtils.insertRows( root.getNodeByPath( [ 0 ] ), { at: 2, rows: 3 } );
  229. assertEqualMarkup( getData( model ), modelTable( [
  230. [ '11[]', '12' ],
  231. [ '21', '22' ],
  232. [ '', '' ],
  233. [ '', '' ],
  234. [ '', '' ]
  235. ] ) );
  236. } );
  237. describe( 'with copyStructureFrom enabled', () => {
  238. beforeEach( () => {
  239. // +----+----+----+----+----+----+
  240. // | 00 | 01 | 03 | 04 | 05 |
  241. // +----+ + +----+----+
  242. // | 10 | | | 14 |
  243. // +----+----+----+----+----+----+
  244. setData( model, modelTable( [
  245. [ '00', { contents: '01', colspan: 2, rowspan: 2 }, { contents: '03', rowspan: 2 }, '04', '05' ],
  246. [ '10', { contents: '14', colspan: 2 } ]
  247. ] ) );
  248. } );
  249. it( 'should copy structure from the first row', () => {
  250. tableUtils.insertRows( root.getNodeByPath( [ 0 ] ), { at: 0, rows: 1, copyStructureFromAbove: false } );
  251. // +----+----+----+----+----+----+
  252. // | | | | | |
  253. // +----+----+----+----+----+----+
  254. // | 00 | 01 | 03 | 04 | 05 |
  255. // +----+ + +----+----+
  256. // | 10 | | | 14 |
  257. // +----+----+----+----+----+----+
  258. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  259. [ '', { contents: '', colspan: 2 }, '', '', '' ],
  260. [ '00', { contents: '01', colspan: 2, rowspan: 2 }, { contents: '03', rowspan: 2 }, '04', '05' ],
  261. [ '10', { contents: '14', colspan: 2 } ]
  262. ] ) );
  263. } );
  264. it( 'should copy structure from the first row and properly handle row-spanned cells', () => {
  265. tableUtils.insertRows( root.getNodeByPath( [ 0 ] ), { at: 1, rows: 1, copyStructureFromAbove: true } );
  266. // +----+----+----+----+----+----+
  267. // | 00 | 01 | 03 | 04 | 05 |
  268. // +----+ + +----+----+
  269. // | | | | | |
  270. // +----+ + +----+----+
  271. // | 10 | | | 14 |
  272. // +----+----+----+----+----+----+
  273. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  274. [ '00', { contents: '01', colspan: 2, rowspan: 3 }, { contents: '03', rowspan: 3 }, '04', '05' ],
  275. [ '', '', '' ],
  276. [ '10', { contents: '14', colspan: 2 } ]
  277. ] ) );
  278. } );
  279. it( 'should copy structure from the last row', () => {
  280. tableUtils.insertRows( root.getNodeByPath( [ 0 ] ), { at: 2, rows: 1, copyStructureFromAbove: true } );
  281. // +----+----+----+----+----+----+
  282. // | 00 | 01 | 03 | 04 | 05 |
  283. // +----+ + +----+----+
  284. // | 10 | | | 14 |
  285. // +----+----+----+----+----+----+
  286. // | | | | |
  287. // +----+----+----+----+----+----+
  288. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  289. [ '00', { contents: '01', colspan: 2, rowspan: 2 }, { contents: '03', rowspan: 2 }, '04', '05' ],
  290. [ '10', { contents: '14', colspan: 2 } ],
  291. [ '', { contents: '', colspan: 2 }, '', { contents: '', colspan: 2 } ]
  292. ] ) );
  293. } );
  294. it( 'should copy structure from the last row and properly handle row-spanned cells', () => {
  295. tableUtils.insertRows( root.getNodeByPath( [ 0 ] ), { at: 1, rows: 1, copyStructureFromAbove: false } );
  296. // +----+----+----+----+----+----+
  297. // | 00 | 01 | 03 | 04 | 05 |
  298. // +----+ + +----+----+
  299. // | | | | |
  300. // +----+ + +----+----+
  301. // | 10 | | | 14 |
  302. // +----+----+----+----+----+----+
  303. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  304. [ '00', { contents: '01', colspan: 2, rowspan: 3 }, { contents: '03', rowspan: 3 }, '04', '05' ],
  305. [ '', { contents: '', colspan: 2 } ],
  306. [ '10', { contents: '14', colspan: 2 } ]
  307. ] ) );
  308. } );
  309. } );
  310. } );
  311. describe( 'insertColumns()', () => {
  312. it( 'should be decorated', () => {
  313. const spy = sinon.spy();
  314. setData( model, modelTable( [
  315. [ '11[]', '12' ],
  316. [ '21', '22' ]
  317. ] ) );
  318. tableUtils.on( 'insertColumns', spy );
  319. tableUtils.insertColumns( root.getNodeByPath( [ 0 ] ), { at: 1 } );
  320. assertEqualMarkup( getData( model ), modelTable( [
  321. [ '11[]', '', '12' ],
  322. [ '21', '', '22' ]
  323. ] ) );
  324. expect( spy.calledOnce ).to.be.true;
  325. } );
  326. it( 'should insert column in given table at given index', () => {
  327. setData( model, modelTable( [
  328. [ '11[]', '12' ],
  329. [ '21', '22' ]
  330. ] ) );
  331. tableUtils.insertColumns( root.getNodeByPath( [ 0 ] ), { at: 1 } );
  332. assertEqualMarkup( getData( model ), modelTable( [
  333. [ '11[]', '', '12' ],
  334. [ '21', '', '22' ]
  335. ] ) );
  336. } );
  337. it( 'should insert column in given table with default values', () => {
  338. setData( model, modelTable( [
  339. [ '11[]', '12' ],
  340. [ '21', '22' ]
  341. ] ) );
  342. tableUtils.insertColumns( root.getNodeByPath( [ 0 ] ) );
  343. assertEqualMarkup( getData( model ), modelTable( [
  344. [ '', '11[]', '12' ],
  345. [ '', '21', '22' ]
  346. ] ) );
  347. } );
  348. it( 'should insert column in given table at default index', () => {
  349. setData( model, modelTable( [
  350. [ '11[]', '12' ],
  351. [ '21', '22' ]
  352. ] ) );
  353. tableUtils.insertColumns( root.getNodeByPath( [ 0 ] ) );
  354. assertEqualMarkup( getData( model ), modelTable( [
  355. [ '', '11[]', '12' ],
  356. [ '', '21', '22' ]
  357. ] ) );
  358. } );
  359. it( 'should insert columns at the end of a row', () => {
  360. setData( model, modelTable( [
  361. [ '00[]', '01' ],
  362. [ { colspan: 2, contents: '10' } ],
  363. [ '20', { rowspan: 2, contents: '21' } ],
  364. [ '30' ]
  365. ] ) );
  366. tableUtils.insertColumns( root.getNodeByPath( [ 0 ] ), { at: 2, columns: 2 } );
  367. assertEqualMarkup( getData( model ), modelTable( [
  368. [ '00[]', '01', '', '' ],
  369. [ { colspan: 2, contents: '10' }, '', '' ],
  370. [ '20', { rowspan: 2, contents: '21' }, '', '' ],
  371. [ '30', '', '' ]
  372. ] ) );
  373. } );
  374. it( 'should insert columns at the beginning of a row', () => {
  375. setData( model, modelTable( [
  376. [ '00[]', '01' ],
  377. [ { colspan: 2, contents: '10' } ],
  378. [ '20', { rowspan: 2, contents: '21' } ],
  379. [ { rowspan: 2, contents: '30' } ],
  380. [ '41' ]
  381. ] ) );
  382. tableUtils.insertColumns( root.getNodeByPath( [ 0 ] ), { at: 0, columns: 2 } );
  383. assertEqualMarkup( getData( model ), modelTable( [
  384. [ '', '', '00[]', '01' ],
  385. [ '', '', { colspan: 2, contents: '10' } ],
  386. [ '', '', '20', { rowspan: 2, contents: '21' } ],
  387. [ '', '', { rowspan: 2, contents: '30' } ],
  388. [ '', '', '41' ]
  389. ] ) );
  390. } );
  391. it( 'should properly insert column at beginning of row-col-spanned cell', () => {
  392. setData( model, modelTable( [
  393. [ '11', '12', '13' ],
  394. [ '21', { colspan: 2, rowspan: 2, contents: '22[]' } ],
  395. [ '31' ],
  396. [ '41', '42', '43' ]
  397. ] ) );
  398. tableUtils.insertColumns( root.getNodeByPath( [ 0 ] ), { at: 1, columns: 1 } );
  399. assertEqualMarkup( getData( model ), modelTable( [
  400. [ '11', '', '12', '13' ],
  401. [ '21', '', { colspan: 2, rowspan: 2, contents: '22[]' } ],
  402. [ '31', '' ],
  403. [ '41', '', '42', '43' ]
  404. ] ) );
  405. } );
  406. it( 'should update table heading columns attribute when inserting column in headings section', () => {
  407. setData( model, modelTable( [
  408. [ '11[]', '12' ],
  409. [ '21', '22' ],
  410. [ '31', '32' ]
  411. ], { headingColumns: 2 } ) );
  412. tableUtils.insertColumns( root.getNodeByPath( [ 0 ] ), { at: 1 } );
  413. assertEqualMarkup( getData( model ), modelTable( [
  414. [ '11[]', '', '12' ],
  415. [ '21', '', '22' ],
  416. [ '31', '', '32' ]
  417. ], { headingColumns: 3 } ) );
  418. } );
  419. it( 'should not update table heading columns attribute when inserting column after headings section', () => {
  420. setData( model, modelTable( [
  421. [ '11[]', '12', '13' ],
  422. [ '21', '22', '23' ],
  423. [ '31', '32', '33' ]
  424. ], { headingColumns: 2 } ) );
  425. tableUtils.insertColumns( root.getNodeByPath( [ 0 ] ), { at: 2 } );
  426. assertEqualMarkup( getData( model ), modelTable( [
  427. [ '11[]', '12', '', '13' ],
  428. [ '21', '22', '', '23' ],
  429. [ '31', '32', '', '33' ]
  430. ], { headingColumns: 2 } ) );
  431. } );
  432. it( 'should expand spanned columns', () => {
  433. setData( model, modelTable( [
  434. [ '00[]', '01' ],
  435. [ { colspan: 2, contents: '10' } ],
  436. [ '20', '21' ]
  437. ], { headingColumns: 2 } ) );
  438. tableUtils.insertColumns( root.getNodeByPath( [ 0 ] ), { at: 1 } );
  439. assertEqualMarkup( getData( model ), modelTable( [
  440. [ '00[]', '', '01' ],
  441. [ { colspan: 3, contents: '10' } ],
  442. [ '20', '', '21' ]
  443. ], { headingColumns: 3 } ) );
  444. } );
  445. it( 'should skip wide spanned columns', () => {
  446. // +----+----+----+----+----+----+
  447. // | 00 | 01 | 02 | 03 | 04 | 05 |
  448. // +----+----+----+----+----+----+
  449. // | 10 | 11 | 12 | 14 | 15 |
  450. // +----+----+----+----+----+----+
  451. // | 20 | 24 |
  452. // +----+----+----+----+----+----+
  453. // ^-- heading columns
  454. setData( model, modelTable( [
  455. [ '00', '01[]', '02', '03', '04', '05' ],
  456. [ '10', '11', { contents: '12', colspan: 2 }, '14', '15' ],
  457. [ { contents: '20', colspan: 4 }, { contents: '24', colspan: 2 } ]
  458. ], { headingColumns: 4 } ) );
  459. tableUtils.insertColumns( root.getNodeByPath( [ 0 ] ), { at: 2, columns: 2 } );
  460. // +----+----+----+----+----+----+----+----+
  461. // | 00 | 01 | | | 02 | 03 | 04 | 05 |
  462. // +----+----+----+----+----+----+----+----+
  463. // | 10 | 11 | | | 12 | 14 | 15 |
  464. // +----+----+----+----+----+----+----+----+
  465. // | 20 | 24 |
  466. // +----+----+----+----+----+----+----+----+
  467. // ^-- heading columns
  468. assertEqualMarkup( getData( model ), modelTable( [
  469. [ '00', '01[]', '', '', '02', '03', '04', '05' ],
  470. [ '10', '11', '', '', { contents: '12', colspan: 2 }, '14', '15' ],
  471. [ { contents: '20', colspan: 6 }, { contents: '24', colspan: 2 } ]
  472. ], { headingColumns: 6 } ) );
  473. } );
  474. it( 'should skip row & column spanned cells', () => {
  475. // +----+----+----+
  476. // | 00 | 02 |
  477. // + +----+
  478. // | | 12 |
  479. // +----+----+----+
  480. // | 20 | 21 | 22 |
  481. // +----+----+----+
  482. setData( model, modelTable( [
  483. [ { colspan: 2, rowspan: 2, contents: '00[]' }, '02' ],
  484. [ '12' ],
  485. [ '20', '21', '22' ]
  486. ], { headingColumns: 2 } ) );
  487. tableUtils.insertColumns( root.getNodeByPath( [ 0 ] ), { at: 1, columns: 2 } );
  488. assertEqualMarkup( getData( model ), modelTable( [
  489. [ { colspan: 4, rowspan: 2, contents: '00[]' }, '02' ],
  490. [ '12' ],
  491. [ '20', '', '', '21', '22' ]
  492. ], { headingColumns: 4 } ) );
  493. } );
  494. it( 'should properly insert column while table has row-spanned cells', () => {
  495. setData( model, modelTable( [
  496. [ { rowspan: 4, contents: '00[]' }, { rowspan: 2, contents: '01' }, '02' ],
  497. [ '12' ],
  498. [ { rowspan: 2, contents: '21' }, '22' ],
  499. [ '32' ]
  500. ], { headingColumns: 2 } ) );
  501. tableUtils.insertColumns( root.getNodeByPath( [ 0 ] ), { at: 1, columns: 1 } );
  502. assertEqualMarkup( getData( model ), modelTable( [
  503. [ { rowspan: 4, contents: '00[]' }, '', { rowspan: 2, contents: '01' }, '02' ],
  504. [ '', '12' ],
  505. [ '', { rowspan: 2, contents: '21' }, '22' ],
  506. [ '', '32' ]
  507. ], { headingColumns: 3 } ) );
  508. } );
  509. } );
  510. describe( 'splitCellVertically()', () => {
  511. it( 'should split table cell to given table cells number', () => {
  512. setData( model, modelTable( [
  513. [ '00', '01', '02' ],
  514. [ '10', '[]11', '12' ],
  515. [ '20', { colspan: 2, contents: '21' } ],
  516. [ { colspan: 2, contents: '30' }, '32' ]
  517. ] ) );
  518. tableUtils.splitCellVertically( root.getNodeByPath( [ 0, 1, 1 ] ), 3 );
  519. assertEqualMarkup( getData( model ), modelTable( [
  520. [ '00', { colspan: 3, contents: '01' }, '02' ],
  521. [ '10', '[]11', '', '', '12' ],
  522. [ '20', { colspan: 4, contents: '21' } ],
  523. [ { colspan: 4, contents: '30' }, '32' ]
  524. ] ) );
  525. } );
  526. it( 'should split table cell for two table cells as a default', () => {
  527. setData( model, modelTable( [
  528. [ '00', '01', '02' ],
  529. [ '10', '[]11', '12' ],
  530. [ '20', { colspan: 2, contents: '21' } ],
  531. [ { colspan: 2, contents: '30' }, '32' ]
  532. ] ) );
  533. tableUtils.splitCellVertically( root.getNodeByPath( [ 0, 1, 1 ] ) );
  534. assertEqualMarkup( getData( model ), modelTable( [
  535. [ '00', { colspan: 2, contents: '01' }, '02' ],
  536. [ '10', '[]11', '', '12' ],
  537. [ '20', { colspan: 3, contents: '21' } ],
  538. [ { colspan: 3, contents: '30' }, '32' ]
  539. ] ) );
  540. } );
  541. it( 'should split table cell if split is equal to colspan', () => {
  542. setData( model, modelTable( [
  543. [ '00', '01', '02' ],
  544. [ '10', '11', '12' ],
  545. [ '20', { colspan: 2, contents: '21[]' } ],
  546. [ { colspan: 2, contents: '30' }, '32' ]
  547. ] ) );
  548. tableUtils.splitCellVertically( root.getNodeByPath( [ 0, 2, 1 ] ), 2 );
  549. assertEqualMarkup( getData( model ), modelTable( [
  550. [ '00', '01', '02' ],
  551. [ '10', '11', '12' ],
  552. [ '20', '21[]', '' ],
  553. [ { colspan: 2, contents: '30' }, '32' ]
  554. ] ) );
  555. } );
  556. it( 'should properly split table cell if split is uneven', () => {
  557. setData( model, modelTable( [
  558. [ '00', '01', '02' ],
  559. [ { colspan: 3, contents: '10[]' } ]
  560. ] ) );
  561. tableUtils.splitCellVertically( root.getNodeByPath( [ 0, 1, 0 ] ), 2 );
  562. assertEqualMarkup( getData( model ), modelTable( [
  563. [ '00', '01', '02' ],
  564. [ { colspan: 2, contents: '10[]' }, '' ]
  565. ] ) );
  566. } );
  567. it( 'should properly set colspan of inserted cells', () => {
  568. setData( model, modelTable( [
  569. [ '00', '01', '02', '03' ],
  570. [ { colspan: 4, contents: '10[]' } ]
  571. ] ) );
  572. tableUtils.splitCellVertically( root.getNodeByPath( [ 0, 1, 0 ] ), 2 );
  573. assertEqualMarkup( getData( model ), modelTable( [
  574. [ '00', '01', '02', '03' ],
  575. [ { colspan: 2, contents: '10[]' }, { colspan: 2, contents: '' } ]
  576. ] ) );
  577. } );
  578. it( 'should keep rowspan attribute for newly inserted cells', () => {
  579. setData( model, modelTable( [
  580. [ '00', '01', '02', '03', '04', '05' ],
  581. [ { colspan: 5, rowspan: 2, contents: '10[]' }, '15' ],
  582. [ '25' ]
  583. ] ) );
  584. tableUtils.splitCellVertically( root.getNodeByPath( [ 0, 1, 0 ] ), 2 );
  585. assertEqualMarkup( getData( model ), modelTable( [
  586. [ '00', '01', '02', '03', '04', '05' ],
  587. [ { colspan: 3, rowspan: 2, contents: '10[]' }, { colspan: 2, rowspan: 2, contents: '' }, '15' ],
  588. [ '25' ]
  589. ] ) );
  590. } );
  591. it( 'should keep rowspan attribute of for newly inserted cells if number of cells is bigger then curren colspan', () => {
  592. setData( model, modelTable( [
  593. [ '00', '01', '02' ],
  594. [ { colspan: 2, rowspan: 2, contents: '10[]' }, '12' ],
  595. [ '22' ]
  596. ] ) );
  597. tableUtils.splitCellVertically( root.getNodeByPath( [ 0, 1, 0 ] ), 3 );
  598. assertEqualMarkup( getData( model ), modelTable( [
  599. [ { colspan: 2, contents: '00' }, '01', '02' ],
  600. [ { rowspan: 2, contents: '10[]' }, { rowspan: 2, contents: '' }, { rowspan: 2, contents: '' }, '12' ],
  601. [ '22' ]
  602. ] ) );
  603. } );
  604. it( 'should properly break a cell if it has colspan and number of created cells is bigger then colspan', () => {
  605. setData( model, modelTable( [
  606. [ '00', '01', '02', '03' ],
  607. [ { colspan: 4, contents: '10[]' } ]
  608. ] ) );
  609. tableUtils.splitCellVertically( root.getNodeByPath( [ 0, 1, 0 ] ), 6 );
  610. assertEqualMarkup( getData( model ), modelTable( [
  611. [ { colspan: 3, contents: '00' }, '01', '02', '03' ],
  612. [ '10[]', '', '', '', '', '' ]
  613. ] ) );
  614. } );
  615. it( 'should update heading columns is split cell is in heading section', () => {
  616. setData( model, modelTable( [
  617. [ '00', '01' ],
  618. [ '10[]', '11' ]
  619. ], { headingColumns: 1 } ) );
  620. tableUtils.splitCellVertically( root.getNodeByPath( [ 0, 1, 0 ] ), 3 );
  621. assertEqualMarkup( getData( model ), modelTable( [
  622. [ { colspan: 3, contents: '00' }, '01' ],
  623. [ '10[]', '', '', '11' ]
  624. ], { headingColumns: 3 } ) );
  625. } );
  626. } );
  627. describe( 'splitCellHorizontally()', () => {
  628. it( 'should split table cell to default table cells number', () => {
  629. setData( model, modelTable( [
  630. [ '00', '01', '02' ],
  631. [ '10', '[]11', '12' ],
  632. [ '20', '21', '22' ]
  633. ] ) );
  634. tableUtils.splitCellHorizontally( root.getNodeByPath( [ 0, 1, 1 ] ) );
  635. assertEqualMarkup( getData( model ), modelTable( [
  636. [ '00', '01', '02' ],
  637. [ { rowspan: 2, contents: '10' }, '[]11', { rowspan: 2, contents: '12' } ],
  638. [ '' ],
  639. [ '20', '21', '22' ]
  640. ] ) );
  641. } );
  642. it( 'should split table cell to given table cells number', () => {
  643. setData( model, modelTable( [
  644. [ '00', '01', '02' ],
  645. [ '10', '[]11', '12' ],
  646. [ '20', '21', '22' ]
  647. ] ) );
  648. tableUtils.splitCellHorizontally( root.getNodeByPath( [ 0, 1, 1 ] ), 4 );
  649. assertEqualMarkup( getData( model ), modelTable( [
  650. [ '00', '01', '02' ],
  651. [ { rowspan: 4, contents: '10' }, '[]11', { rowspan: 4, contents: '12' } ],
  652. [ '' ],
  653. [ '' ],
  654. [ '' ],
  655. [ '20', '21', '22' ]
  656. ] ) );
  657. } );
  658. it( 'should properly update row-spanned cells overlapping selected cell', () => {
  659. setData( model, modelTable( [
  660. [ { rowspan: 2, contents: '00' }, '01', { rowspan: 3, contents: '02' } ],
  661. [ '[]11' ],
  662. [ '20', '21' ]
  663. ] ) );
  664. tableUtils.splitCellHorizontally( root.getNodeByPath( [ 0, 1, 0 ] ), 3 );
  665. assertEqualMarkup( getData( model ), modelTable( [
  666. [ { rowspan: 4, contents: '00' }, '01', { rowspan: 5, contents: '02' } ],
  667. [ '[]11' ],
  668. [ '' ],
  669. [ '' ],
  670. [ '20', '21' ]
  671. ] ) );
  672. } );
  673. it( 'should split row-spanned cell', () => {
  674. setData( model, modelTable( [
  675. [ '00', { rowspan: 2, contents: '01[]' } ],
  676. [ '10' ],
  677. [ '20', '21' ]
  678. ] ) );
  679. const tableCell = root.getNodeByPath( [ 0, 0, 1 ] );
  680. tableUtils.splitCellHorizontally( tableCell, 2 );
  681. assertEqualMarkup( getData( model ), modelTable( [
  682. [ '00', '01[]' ],
  683. [ '10', '' ],
  684. [ '20', '21' ]
  685. ] ) );
  686. } );
  687. it( 'should copy colspan while splitting row-spanned cell', () => {
  688. setData( model, modelTable( [
  689. [ '00', { rowspan: 2, colspan: 2, contents: '01[]' } ],
  690. [ '10' ],
  691. [ '20', '21', '22' ]
  692. ] ) );
  693. const tableCell = root.getNodeByPath( [ 0, 0, 1 ] );
  694. tableUtils.splitCellHorizontally( tableCell, 2 );
  695. assertEqualMarkup( getData( model ), modelTable( [
  696. [ '00', { colspan: 2, contents: '01[]' } ],
  697. [ '10', { colspan: 2, contents: '' } ],
  698. [ '20', '21', '22' ]
  699. ] ) );
  700. } );
  701. it( 'should evenly distribute rowspan attribute', () => {
  702. setData( model, modelTable( [
  703. [ '00', { rowspan: 7, contents: '01[]' } ],
  704. [ '10' ],
  705. [ '20' ],
  706. [ '30' ],
  707. [ '40' ],
  708. [ '50' ],
  709. [ '60' ],
  710. [ '70', '71' ]
  711. ] ) );
  712. const tableCell = root.getNodeByPath( [ 0, 0, 1 ] );
  713. tableUtils.splitCellHorizontally( tableCell, 3 );
  714. assertEqualMarkup( getData( model ), modelTable( [
  715. [ '00', { rowspan: 3, contents: '01[]' } ],
  716. [ '10' ],
  717. [ '20' ],
  718. [ '30', { rowspan: 2, contents: '' } ],
  719. [ '40' ],
  720. [ '50', { rowspan: 2, contents: '' } ],
  721. [ '60' ],
  722. [ '70', '71' ]
  723. ] ) );
  724. } );
  725. it( 'should split row-spanned cell and updated other cells rowspan when splitting to bigger number of cells', () => {
  726. setData( model, modelTable( [
  727. [ '00', { rowspan: 2, contents: '01[]' } ],
  728. [ '10' ],
  729. [ '20', '21' ]
  730. ] ) );
  731. const tableCell = root.getNodeByPath( [ 0, 0, 1 ] );
  732. tableUtils.splitCellHorizontally( tableCell, 3 );
  733. assertEqualMarkup( getData( model ), modelTable( [
  734. [ { rowspan: 2, contents: '00' }, '01[]' ],
  735. [ '' ],
  736. [ '10', '' ],
  737. [ '20', '21' ]
  738. ] ) );
  739. } );
  740. it( 'should split row-spanned & col-spanned cell', () => {
  741. setData( model, modelTable( [
  742. [ '00', { colspan: 2, contents: '01[]' } ],
  743. [ '10', '11', '12' ]
  744. ] ) );
  745. const tableCell = root.getNodeByPath( [ 0, 0, 1 ] );
  746. tableUtils.splitCellHorizontally( tableCell, 3 );
  747. assertEqualMarkup( getData( model ), modelTable( [
  748. [ { rowspan: 3, contents: '00' }, { colspan: 2, contents: '01[]' } ],
  749. [ { colspan: 2, contents: '' } ],
  750. [ { colspan: 2, contents: '' } ],
  751. [ '10', '11', '12' ]
  752. ] ) );
  753. } );
  754. it( 'should split table cell from a heading section', () => {
  755. setData( model, modelTable( [
  756. [ '00[]', '01', '02' ],
  757. [ '10', '11', '12' ],
  758. [ '20', '21', '22' ]
  759. ], { headingRows: 1 } ) );
  760. tableUtils.splitCellHorizontally( root.getNodeByPath( [ 0, 0, 0 ] ), 3 );
  761. assertEqualMarkup( getData( model ), modelTable( [
  762. [ '00[]', { rowspan: 3, contents: '01' }, { rowspan: 3, contents: '02' } ],
  763. [ '' ],
  764. [ '' ],
  765. [ '10', '11', '12' ],
  766. [ '20', '21', '22' ]
  767. ], { headingRows: 3 } ) );
  768. } );
  769. } );
  770. describe( 'getColumns()', () => {
  771. it( 'should return proper number of columns', () => {
  772. setData( model, modelTable( [
  773. [ '00', { colspan: 3, contents: '01' }, '04' ]
  774. ] ) );
  775. expect( tableUtils.getColumns( root.getNodeByPath( [ 0 ] ) ) ).to.equal( 5 );
  776. } );
  777. } );
  778. describe( 'getRows()', () => {
  779. it( 'should return proper number of columns for simple table', () => {
  780. setData( model, modelTable( [
  781. [ '00', '01' ],
  782. [ '10', '11' ]
  783. ] ) );
  784. expect( tableUtils.getRows( root.getNodeByPath( [ 0 ] ) ) ).to.equal( 2 );
  785. } );
  786. it( 'should return proper number of columns for a table with header', () => {
  787. setData( model, modelTable( [
  788. [ '00', '01' ],
  789. [ '10', '11' ]
  790. ], { headingRows: 1 } ) );
  791. expect( tableUtils.getRows( root.getNodeByPath( [ 0 ] ) ) ).to.equal( 2 );
  792. } );
  793. it( 'should return proper number of columns for rowspan table', () => {
  794. setData( model, modelTable( [
  795. [ '00', '01' ],
  796. [ { rowspan: 2, contents: '10' }, '11' ],
  797. [ '21' ]
  798. ] ) );
  799. expect( tableUtils.getRows( root.getNodeByPath( [ 0 ] ) ) ).to.equal( 3 );
  800. } );
  801. } );
  802. describe( 'removeRows()', () => {
  803. describe( 'single row', () => {
  804. it( 'should remove a given row from a table start', () => {
  805. setData( model, modelTable( [
  806. [ '00', '01' ],
  807. [ '10', '11' ],
  808. [ '20', '21' ]
  809. ] ) );
  810. tableUtils.removeRows( root.getChild( 0 ), { at: 0 } );
  811. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  812. [ '10', '11' ],
  813. [ '20', '21' ]
  814. ] ) );
  815. } );
  816. it( 'should remove last row', () => {
  817. setData( model, modelTable( [
  818. [ '00', '01' ],
  819. [ '10', '11' ]
  820. ] ) );
  821. tableUtils.removeRows( root.getChild( 0 ), { at: 1 } );
  822. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  823. [ '00', '01' ]
  824. ] ) );
  825. } );
  826. it( 'should change heading rows if removing a heading row', () => {
  827. setData( model, modelTable( [
  828. [ '00', '01' ],
  829. [ '10', '11' ],
  830. [ '20', '21' ]
  831. ], { headingRows: 2 } ) );
  832. tableUtils.removeRows( root.getChild( 0 ), { at: 1 } );
  833. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  834. [ '00', '01' ],
  835. [ '20', '21' ]
  836. ], { headingRows: 1 } ) );
  837. } );
  838. it( 'should change heading rows if removing a heading row (and cell below is row-spanned)', () => {
  839. setData( model, modelTable( [
  840. [ '00', '01' ],
  841. [ '10', { contents: '11', rowspan: 2 } ],
  842. [ '20' ]
  843. ], { headingRows: 1 } ) );
  844. tableUtils.removeRows( root.getChild( 0 ), { at: 0 } );
  845. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  846. [ '10', { contents: '11', rowspan: 2 } ],
  847. [ '20' ]
  848. ] ) );
  849. } );
  850. it( 'should decrease rowspan of table cells from previous rows', () => {
  851. // +----+----+----+----+----+
  852. // | 00 | 01 | 02 | 03 | 04 |
  853. // +----+ + + + +
  854. // | 10 | | | | |
  855. // +----+----+ + + +
  856. // | 20 | 21 | | | |
  857. // +----+----+----+ + +
  858. // | 30 | 31 | 32 | | |
  859. // +----+----+----+----+ +
  860. // | 40 | 41 | 42 | 43 | |
  861. // +----+----+----+----+----+
  862. // | 50 | 51 | 52 | 53 | 54 |
  863. // +----+----+----+----+----+
  864. setData( model, modelTable( [
  865. [ '00', { contents: '01', rowspan: 2 }, { contents: '02', rowspan: 3 }, { contents: '03', rowspan: 4 },
  866. { contents: '04', rowspan: 5 } ],
  867. [ '10' ],
  868. [ '20', '21' ],
  869. [ '30', '31', '32' ],
  870. [ '40', '41', '42', '43' ],
  871. [ '50', '51', '52', '53', '54' ]
  872. ] ) );
  873. tableUtils.removeRows( root.getChild( 0 ), { at: 1, rows: 1 } );
  874. // +----+----+----+----+----+
  875. // | 00 | 01 | 02 | 03 | 04 |
  876. // +----+----+ + + +
  877. // | 20 | 21 | | | |
  878. // +----+----+----+ + +
  879. // | 30 | 31 | 32 | | |
  880. // +----+----+----+----+ +
  881. // | 40 | 41 | 42 | 43 | |
  882. // +----+----+----+----+----+
  883. // | 50 | 51 | 52 | 53 | 54 |
  884. // +----+----+----+----+----+
  885. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  886. [ '00', '01', { contents: '02', rowspan: 2 }, { contents: '03', rowspan: 3 }, { contents: '04', rowspan: 4 } ],
  887. [ '20', '21' ],
  888. [ '30', '31', '32' ],
  889. [ '40', '41', '42', '43' ],
  890. [ '50', '51', '52', '53', '54' ]
  891. ] ) );
  892. } );
  893. it( 'should decrease rowspan of table cells from previous rows (row-spanned cells on different rows)', () => {
  894. setData( model, modelTable( [
  895. [ { rowspan: 4, contents: '00' }, { rowspan: 3, contents: '01' }, { rowspan: 2, contents: '02' }, '03', '04' ],
  896. [ { rowspan: 2, contents: '13' }, '14' ],
  897. [ '22', '24' ],
  898. [ '31', '32', '33', '34' ]
  899. ] ) );
  900. tableUtils.removeRows( root.getChild( 0 ), { at: 2 } );
  901. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  902. [ { rowspan: 3, contents: '00' }, { rowspan: 2, contents: '01' }, { rowspan: 2, contents: '02' }, '03', '04' ],
  903. [ '13', '14' ],
  904. [ '31', '32', '33', '34' ]
  905. ] ) );
  906. } );
  907. it( 'should move row-spanned cells to a row below removing it\'s row', () => {
  908. setData( model, modelTable( [
  909. [ { rowspan: 3, contents: '00' }, { rowspan: 2, contents: '01' }, '02' ],
  910. [ '12' ],
  911. [ '21', '22' ],
  912. [ '30', '31', '32' ]
  913. ] ) );
  914. tableUtils.removeRows( root.getChild( 0 ), { at: 0 } );
  915. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  916. [ { rowspan: 2, contents: '00' }, '01', '12' ],
  917. [ '21', '22' ],
  918. [ '30', '31', '32' ]
  919. ] ) );
  920. } );
  921. it( 'should move row-spanned cells to a row below removing it\'s row (other cell is overlapping removed row)', () => {
  922. setData( model, modelTable( [
  923. [ '00', { rowspan: 3, contents: '01' }, '02', '03', '04' ],
  924. [ '10', { rowspan: 2, contents: '12' }, '13', '14' ],
  925. [ '20', '23', '24' ]
  926. ] ) );
  927. tableUtils.removeRows( root.getChild( 0 ), { at: 1 } );
  928. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  929. [ '00', { rowspan: 2, contents: '01' }, '02', '03', '04' ],
  930. [ '20', '12', '23', '24' ]
  931. ] ) );
  932. } );
  933. } );
  934. describe( 'many rows', () => {
  935. it( 'should properly remove middle rows', () => {
  936. setData( model, modelTable( [
  937. [ '00', '01' ],
  938. [ '10', '11' ],
  939. [ '20', '21' ],
  940. [ '30', '31' ]
  941. ] ) );
  942. tableUtils.removeRows( root.getChild( 0 ), { at: 1, rows: 2 } );
  943. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  944. [ '00', '01' ],
  945. [ '30', '31' ]
  946. ] ) );
  947. } );
  948. it( 'should properly remove tailing rows', () => {
  949. setData( model, modelTable( [
  950. [ '00', '01' ],
  951. [ '10', '11' ],
  952. [ '20', '21' ],
  953. [ '30', '31' ]
  954. ] ) );
  955. tableUtils.removeRows( root.getChild( 0 ), { at: 2, rows: 2 } );
  956. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  957. [ '00', '01' ],
  958. [ '10', '11' ]
  959. ] ) );
  960. } );
  961. it( 'should properly remove beginning rows', () => {
  962. setData( model, modelTable( [
  963. [ '00', '01' ],
  964. [ '10', '11' ],
  965. [ '20', '21' ],
  966. [ '30', '31' ]
  967. ] ) );
  968. tableUtils.removeRows( root.getChild( 0 ), { at: 0, rows: 2 } );
  969. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  970. [ '20', '21' ],
  971. [ '30', '31' ]
  972. ] ) );
  973. } );
  974. it( 'should support removing multiple headings (removed rows in heading section)', () => {
  975. setData( model, modelTable( [
  976. [ '00', '01' ],
  977. [ '10', '11' ],
  978. [ '20', '21' ],
  979. [ '30', '31' ]
  980. ], { headingRows: 3 } ) );
  981. tableUtils.removeRows( root.getChild( 0 ), { at: 0, rows: 2 } );
  982. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  983. [ '20', '21' ],
  984. [ '30', '31' ]
  985. ], { headingRows: 1 } ) );
  986. } );
  987. it( 'should support removing multiple headings (removed rows in heading and body section)', () => {
  988. setData( model, modelTable( [
  989. [ '00', '01' ],
  990. [ '10', '11' ],
  991. [ '20', '21' ],
  992. [ '30', '31' ],
  993. [ '40', '41' ]
  994. ], { headingRows: 3 } ) );
  995. tableUtils.removeRows( root.getChild( 0 ), { at: 1, rows: 3 } );
  996. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  997. [ '00', '01' ],
  998. [ '40', '41' ]
  999. ], { headingRows: 1 } ) );
  1000. } );
  1001. it( 'should support removing mixed heading and cell rows', () => {
  1002. setData( model, modelTable( [
  1003. [ '00', '01' ],
  1004. [ '10', '11' ],
  1005. [ '20', '21' ]
  1006. ], { headingRows: 1 } ) );
  1007. tableUtils.removeRows( root.getChild( 0 ), { at: 0, rows: 2 } );
  1008. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  1009. [ '20', '21' ]
  1010. ] ) );
  1011. } );
  1012. it( 'should move row-spanned cells to a row after removed rows section', () => {
  1013. setData( model, modelTable( [
  1014. [ '00', '01', '02', '03' ],
  1015. [ { rowspan: 4, contents: '10' }, { rowspan: 3, contents: '11' }, { rowspan: 2, contents: '12' }, '13' ],
  1016. [ { rowspan: 3, contents: '23' } ],
  1017. [ '32' ],
  1018. [ '41', '42' ]
  1019. ] ) );
  1020. tableUtils.removeRows( root.getChild( 0 ), { at: 1, rows: 2 } );
  1021. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  1022. [ '00', '01', '02', '03' ],
  1023. [ { rowspan: 2, contents: '10' }, '11', '32', { rowspan: 2, contents: '23' } ],
  1024. [ '41', '42' ]
  1025. ] ) );
  1026. } );
  1027. it( 'should decrease rowspan of table cells from rows before removed rows section', () => {
  1028. setData( model, modelTable( [
  1029. [ { rowspan: 4, contents: '00' }, { rowspan: 3, contents: '01' }, { rowspan: 2, contents: '02' }, '03' ],
  1030. [ '13' ],
  1031. [ '22', '23' ],
  1032. [ '31', '32', '33' ]
  1033. ] ) );
  1034. tableUtils.removeRows( root.getChild( 0 ), { at: 1, rows: 2 } );
  1035. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  1036. [ { rowspan: 2, contents: '00' }, '01', '02', '03' ],
  1037. [ '31', '32', '33' ]
  1038. ] ) );
  1039. } );
  1040. it( 'should decrease rowspan of table cells from previous rows', () => {
  1041. // +----+----+----+----+----+
  1042. // | 00 | 01 | 02 | 03 | 04 |
  1043. // +----+ + + + +
  1044. // | 10 | | | | |
  1045. // +----+----+ + + +
  1046. // | 20 | 21 | | | |
  1047. // +----+----+----+ + +
  1048. // | 30 | 31 | 32 | | |
  1049. // +----+----+----+----+ +
  1050. // | 40 | 41 | 42 | 43 | |
  1051. // +----+----+----+----+----+
  1052. // | 50 | 51 | 52 | 53 | 54 |
  1053. // +----+----+----+----+----+
  1054. setData( model, modelTable( [
  1055. [ '00', { contents: '01', rowspan: 2 }, { contents: '02', rowspan: 3 }, { contents: '03', rowspan: 4 },
  1056. { contents: '04', rowspan: 5 } ],
  1057. [ '10' ],
  1058. [ '20', '21' ],
  1059. [ '30', '31', '32' ],
  1060. [ '40', '41', '42', '43' ],
  1061. [ '50', '51', '52', '53', '54' ]
  1062. ] ) );
  1063. tableUtils.removeRows( root.getChild( 0 ), { at: 2, rows: 2 } );
  1064. // +----+----+----+----+----+
  1065. // | 00 | 01 | 02 | 03 | 04 |
  1066. // +----+ + + + +
  1067. // | 10 | | | | |
  1068. // +----+----+----+----+ +
  1069. // | 40 | 41 | 42 | 43 | |
  1070. // +----+----+----+----+----+
  1071. // | 50 | 51 | 52 | 53 | 54 |
  1072. // +----+----+----+----+----+
  1073. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  1074. [ '00', { contents: '01', rowspan: 2 }, { contents: '02', rowspan: 2 }, { contents: '03', rowspan: 2 },
  1075. { contents: '04', rowspan: 3 } ],
  1076. [ '10' ],
  1077. [ '40', '41', '42', '43' ],
  1078. [ '50', '51', '52', '53', '54' ]
  1079. ] ) );
  1080. } );
  1081. it( 'should re-use batch to create one undo step', () => {
  1082. setData( model, modelTable( [
  1083. [ '00', '01' ],
  1084. [ '10', '11' ],
  1085. [ '20', '21' ],
  1086. [ '30', '31' ]
  1087. ], { headingRows: 3 } ) );
  1088. const createdBatches = new Set();
  1089. model.on( 'applyOperation', ( evt, args ) => {
  1090. const operation = args[ 0 ];
  1091. createdBatches.add( operation.batch );
  1092. } );
  1093. const batch = model.createBatch();
  1094. tableUtils.removeRows( root.getChild( 0 ), { at: 0, rows: 2, batch } );
  1095. expect( createdBatches.size ).to.equal( 1 );
  1096. } );
  1097. } );
  1098. } );
  1099. describe( 'removeColumns()', () => {
  1100. describe( 'single row', () => {
  1101. it( 'should remove a given column', () => {
  1102. setData( model, modelTable( [
  1103. [ '00', '01', '02' ],
  1104. [ '10', '11', '12' ],
  1105. [ '20', '21', '22' ]
  1106. ] ) );
  1107. tableUtils.removeColumns( root.getNodeByPath( [ 0 ] ), { at: 1 } );
  1108. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  1109. [ '00', '02' ],
  1110. [ '10', '12' ],
  1111. [ '20', '22' ]
  1112. ] ) );
  1113. } );
  1114. it( 'should remove a given column from a table start', () => {
  1115. setData( model, modelTable( [
  1116. [ '00', '01' ],
  1117. [ '10', '11' ],
  1118. [ '20', '21' ]
  1119. ] ) );
  1120. tableUtils.removeColumns( root.getNodeByPath( [ 0 ] ), { at: 0 } );
  1121. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  1122. [ '01' ],
  1123. [ '11' ],
  1124. [ '21' ]
  1125. ] ) );
  1126. } );
  1127. it( 'should change heading columns if removing a heading column', () => {
  1128. setData( model, modelTable( [
  1129. [ '00', '01' ],
  1130. [ '10', '11' ],
  1131. [ '20', '21' ]
  1132. ], { headingColumns: 2 } ) );
  1133. tableUtils.removeColumns( root.getNodeByPath( [ 0 ] ), { at: 0 } );
  1134. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  1135. [ '01' ],
  1136. [ '11' ],
  1137. [ '21' ]
  1138. ], { headingColumns: 1 } ) );
  1139. } );
  1140. it( 'should decrease colspan of table cells from previous column', () => {
  1141. setData( model, modelTable( [
  1142. [ { colspan: 4, contents: '00' }, '04' ],
  1143. [ { colspan: 3, contents: '10' }, '13', '14' ],
  1144. [ { colspan: 2, contents: '20' }, '22', '23', '24' ],
  1145. [ '30', { colspan: 2, contents: '31' }, '33', '34' ],
  1146. [ '40', '41', '42', '43', '44' ]
  1147. ] ) );
  1148. tableUtils.removeColumns( root.getNodeByPath( [ 0 ] ), { at: 2 } );
  1149. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  1150. [ { colspan: 3, contents: '00' }, '04' ],
  1151. [ { colspan: 2, contents: '10' }, '13', '14' ],
  1152. [ { colspan: 2, contents: '20' }, '23', '24' ],
  1153. [ '30', '31', '33', '34' ],
  1154. [ '40', '41', '43', '44' ]
  1155. ] ) );
  1156. } );
  1157. it( 'should decrease colspan of cells that are on removed column', () => {
  1158. setData( model, modelTable( [
  1159. [ { colspan: 3, contents: '00' }, '03' ],
  1160. [ { colspan: 2, contents: '10' }, '12', '13' ],
  1161. [ '20', '21', '22', '23' ]
  1162. ] ) );
  1163. tableUtils.removeColumns( root.getNodeByPath( [ 0 ] ), { at: 0 } );
  1164. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  1165. [ { colspan: 2, contents: '00' }, '03' ],
  1166. [ '10', '12', '13' ],
  1167. [ '21', '22', '23' ]
  1168. ] ) );
  1169. } );
  1170. it( 'should remove column with rowspan (first column)', () => {
  1171. setData( model, modelTable( [
  1172. [ { rowspan: 2, contents: '00' }, '01' ],
  1173. [ '11' ]
  1174. ] ) );
  1175. tableUtils.removeColumns( root.getNodeByPath( [ 0 ] ), { at: 0 } );
  1176. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  1177. [ '01' ],
  1178. [ '11' ]
  1179. ] ) );
  1180. } );
  1181. it( 'should remove column with rowspan (last column)', () => {
  1182. setData( model, modelTable( [
  1183. [ '00', { rowspan: 2, contents: '01' } ],
  1184. [ '10' ]
  1185. ] ) );
  1186. tableUtils.removeColumns( root.getNodeByPath( [ 0 ] ), { at: 1 } );
  1187. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  1188. [ '00' ],
  1189. [ '10' ]
  1190. ] ) );
  1191. } );
  1192. it( 'should remove column if other column is row-spanned (last column)', () => {
  1193. setData( model, modelTable( [
  1194. [ '00', { rowspan: 2, contents: '01' } ],
  1195. [ '10' ]
  1196. ] ) );
  1197. tableUtils.removeColumns( root.getNodeByPath( [ 0 ] ), { at: 0 } );
  1198. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  1199. [ '01' ]
  1200. ] ) );
  1201. } );
  1202. it( 'should remove column if other column is row-spanned (first column)', () => {
  1203. setData( model, modelTable( [
  1204. [ { rowspan: 2, contents: '00' }, '01' ],
  1205. [ '11' ]
  1206. ] ) );
  1207. tableUtils.removeColumns( root.getNodeByPath( [ 0 ] ), { at: 1 } );
  1208. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  1209. [ '00' ]
  1210. ] ) );
  1211. } );
  1212. it( 'should remove column if removing row with one column - other columns are spanned', () => {
  1213. setData( model, modelTable( [
  1214. [ '00', { rowspan: 2, contents: '01' }, { rowspan: 2, contents: '02' } ],
  1215. [ '10' ],
  1216. [ '20', '21', '22' ]
  1217. ] ) );
  1218. tableUtils.removeColumns( root.getNodeByPath( [ 0 ] ), { at: 0 } );
  1219. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  1220. [ '01', '02' ],
  1221. [ '21', '22' ]
  1222. ] ) );
  1223. } );
  1224. it( 'should remove the column properly when multiple rows should be removed (because of to row-spans)', () => {
  1225. setData( model, modelTable( [
  1226. [ '00', { contents: '01', rowspan: 3 }, { contents: '02', rowspan: 3 } ],
  1227. [ '10' ],
  1228. [ '20' ]
  1229. ] ) );
  1230. tableUtils.removeColumns( root.getNodeByPath( [ 0 ] ), { at: 0 } );
  1231. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  1232. [ '01', '02' ]
  1233. ] ) );
  1234. } );
  1235. } );
  1236. describe( 'multiple columns', () => {
  1237. it( 'should properly remove two first columns', () => {
  1238. setData( model, modelTable( [
  1239. [ '00', '01', '02' ],
  1240. [ '10', '11', '12' ],
  1241. [ '20', '21', '22' ],
  1242. [ '30', '31', '32' ]
  1243. ] ) );
  1244. tableUtils.removeColumns( root.getNodeByPath( [ 0 ] ), { at: 0, columns: 2 } );
  1245. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  1246. [ '02' ],
  1247. [ '12' ],
  1248. [ '22' ],
  1249. [ '32' ]
  1250. ] ) );
  1251. } );
  1252. it( 'should properly remove two middle columns', () => {
  1253. setData( model, modelTable( [
  1254. [ '00', '01', '02', '03' ],
  1255. [ '10', '11', '12', '13' ],
  1256. [ '20', '21', '22', '23' ],
  1257. [ '30', '31', '32', '33' ]
  1258. ] ) );
  1259. tableUtils.removeColumns( root.getNodeByPath( [ 0 ] ), { at: 1, columns: 2 } );
  1260. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  1261. [ '00', '03' ],
  1262. [ '10', '13' ],
  1263. [ '20', '23' ],
  1264. [ '30', '33' ]
  1265. ] ) );
  1266. } );
  1267. it( 'should properly remove two last columns', () => {
  1268. setData( model, modelTable( [
  1269. [ '00', '01', '02' ],
  1270. [ '10', '11', '12' ],
  1271. [ '20', '21', '22' ],
  1272. [ '30', '31', '32' ]
  1273. ] ) );
  1274. tableUtils.removeColumns( root.getNodeByPath( [ 0 ] ), { at: 1, columns: 2 } );
  1275. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  1276. [ '00' ],
  1277. [ '10' ],
  1278. [ '20' ],
  1279. [ '30' ]
  1280. ] ) );
  1281. } );
  1282. it( 'should properly remove multiple heading columns', () => {
  1283. setData( model, modelTable( [
  1284. [ '00', '01', '02', '03', '04' ],
  1285. [ '10', '11', '12', '13', '14' ]
  1286. ], { headingColumns: 3 } ) );
  1287. tableUtils.removeColumns( root.getNodeByPath( [ 0 ] ), { at: 1, columns: 3 } );
  1288. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  1289. [ '00', '04' ],
  1290. [ '10', '14' ]
  1291. ], { headingColumns: 1 } ) );
  1292. } );
  1293. it( 'should properly calculate truncated colspans', () => {
  1294. setData( model, modelTable( [
  1295. [ { contents: '00', colspan: 3 } ],
  1296. [ '10', '11', '12' ],
  1297. [ '20', '21', '22' ]
  1298. ] ) );
  1299. tableUtils.removeColumns( root.getNodeByPath( [ 0 ] ), { at: 0, columns: 2 } );
  1300. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  1301. [ '00' ],
  1302. [ '12' ],
  1303. [ '22' ]
  1304. ] ) );
  1305. } );
  1306. } );
  1307. } );
  1308. describe( 'createTable()', () => {
  1309. it( 'should create table', () => {
  1310. setData( model, '[]' );
  1311. model.change( writer => {
  1312. const table = tableUtils.createTable( writer, { rows: 3, columns: 2 } );
  1313. model.insertContent( table, model.document.selection.focus );
  1314. } );
  1315. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  1316. [ '', '' ],
  1317. [ '', '' ],
  1318. [ '', '' ]
  1319. ] ) );
  1320. } );
  1321. it( 'should create table with heading rows', () => {
  1322. setData( model, '[]' );
  1323. model.change( writer => {
  1324. const table = tableUtils.createTable( writer, { rows: 3, columns: 2, headingRows: 1 } );
  1325. model.insertContent( table, model.document.selection.focus );
  1326. } );
  1327. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  1328. [ '', '' ],
  1329. [ '', '' ],
  1330. [ '', '' ]
  1331. ], { headingRows: 1 } ) );
  1332. } );
  1333. it( 'should create table with heading columns', () => {
  1334. setData( model, '[]' );
  1335. model.change( writer => {
  1336. const table = tableUtils.createTable( writer, { rows: 3, columns: 2, headingColumns: 1 } );
  1337. model.insertContent( table, model.document.selection.focus );
  1338. } );
  1339. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  1340. [ '', '' ],
  1341. [ '', '' ],
  1342. [ '', '' ]
  1343. ], { headingColumns: 1 } ) );
  1344. } );
  1345. it( 'should create table with heading rows and columns', () => {
  1346. setData( model, '[]' );
  1347. model.change( writer => {
  1348. const table = tableUtils.createTable( writer, { rows: 3, columns: 2, headingRows: 2, headingColumns: 1 } );
  1349. model.insertContent( table, model.document.selection.focus );
  1350. } );
  1351. assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
  1352. [ '', '' ],
  1353. [ '', '' ],
  1354. [ '', '' ]
  1355. ], { headingRows: 2, headingColumns: 1 } ) );
  1356. } );
  1357. } );
  1358. } );