8
0

tableutils.js 48 KB

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