8
0

mergecellcommand.js 30 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109
  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 MergeCellCommand from '../../src/commands/mergecellcommand';
  12. describe( 'MergeCellCommand', () => {
  13. let editor, model, command, root;
  14. beforeEach( () => {
  15. return ModelTestEditor
  16. .create( {
  17. plugins: [ Paragraph, TableEditing ]
  18. } )
  19. .then( newEditor => {
  20. editor = newEditor;
  21. model = editor.model;
  22. root = model.document.getRoot( 'main' );
  23. } );
  24. } );
  25. afterEach( () => {
  26. return editor.destroy();
  27. } );
  28. describe( 'direction=right', () => {
  29. beforeEach( () => {
  30. command = new MergeCellCommand( editor, { direction: 'right' } );
  31. } );
  32. describe( 'isEnabled', () => {
  33. it( 'should be true if in cell that has sibling on the right', () => {
  34. setData( model, modelTable( [
  35. [ '00[]', '01' ]
  36. ] ) );
  37. expect( command.isEnabled ).to.be.true;
  38. } );
  39. it( 'should be false if last cell of a row', () => {
  40. setData( model, modelTable( [
  41. [ '00', '01[]' ]
  42. ] ) );
  43. expect( command.isEnabled ).to.be.false;
  44. } );
  45. it( 'should be true if in a cell that has sibling on the right with the same rowspan', () => {
  46. setData( model, modelTable( [
  47. [ { rowspan: 2, contents: '00[]' }, { rowspan: 2, contents: '01' } ]
  48. ] ) );
  49. expect( command.isEnabled ).to.be.true;
  50. } );
  51. it( 'should be false if in a cell that has sibling but with different rowspan', () => {
  52. setData( model, modelTable( [
  53. [ { rowspan: 2, contents: '00[]' }, { rowspan: 3, contents: '01' }, '02' ],
  54. [ '12' ],
  55. [ '20', '22' ]
  56. ] ) );
  57. expect( command.isEnabled ).to.be.false;
  58. } );
  59. it( 'should be false when next cell is rowspanned', () => {
  60. setData( model, modelTable( [
  61. [ '00', { rowspan: 3, contents: '01' }, '02' ],
  62. [ '10[]', '12' ],
  63. [ '20', '22' ]
  64. ] ) );
  65. expect( command.isEnabled ).to.be.false;
  66. } );
  67. it( 'should be true when current cell is colspanned', () => {
  68. setData( model, modelTable( [
  69. [ { colspan: 2, contents: '00[]' }, '02' ]
  70. ] ) );
  71. expect( command.isEnabled ).to.be.true;
  72. } );
  73. it( 'should be false if not in a cell', () => {
  74. setData( model, '<paragraph>11[]</paragraph>' );
  75. expect( command.isEnabled ).to.be.false;
  76. } );
  77. describe( 'when the heading section is in the table', () => {
  78. it( 'should be false if mergeable cell is in other table section then current cell', () => {
  79. setData( model, modelTable( [
  80. [ '00[]', '01' ]
  81. ], { headingColumns: 1 } ) );
  82. expect( command.isEnabled ).to.be.false;
  83. } );
  84. it( 'should be true if merged cell would not cross heading section (mergeable cell with colspan)', () => {
  85. setData( model, modelTable( [
  86. [ '00[]', { colspan: 2, contents: '01' }, '02', '03' ]
  87. ], { headingColumns: 3 } ) );
  88. expect( command.isEnabled ).to.be.true;
  89. } );
  90. it( 'should be false if merged cell would cross heading section (current cell with colspan)', () => {
  91. setData( model, modelTable( [
  92. [ { colspan: 2, contents: '00[]' }, '01', '02', '03' ]
  93. ], { headingColumns: 2 } ) );
  94. expect( command.isEnabled ).to.be.false;
  95. } );
  96. it( 'should be true if merged cell would not cross heading section (current cell with colspan)', () => {
  97. setData( model, modelTable( [
  98. [ { colspan: 2, contents: '00[]' }, '01', '02', '03' ]
  99. ], { headingColumns: 3 } ) );
  100. expect( command.isEnabled ).to.be.true;
  101. } );
  102. it( 'should be true if merged cell would not cross the section boundary (regular section)', () => {
  103. setData( model, modelTable( [
  104. [ '00', '01', '02[]', '03' ]
  105. ], { headingColumns: 1 } ) );
  106. expect( command.isEnabled ).to.be.true;
  107. } );
  108. } );
  109. } );
  110. describe( 'value', () => {
  111. it( 'should be set to mergeable sibling if in cell that has sibling on the right', () => {
  112. setData( model, modelTable( [
  113. [ '00[]', '01' ]
  114. ] ) );
  115. expect( command.value ).to.equal( root.getNodeByPath( [ 0, 0, 1 ] ) );
  116. } );
  117. it( 'should be set to mergeable sibling if in cell that has sibling on the right (selection in block content)', () => {
  118. setData( model, modelTable( [
  119. [ '00', '<paragraph>[]01</paragraph>', '02' ]
  120. ] ) );
  121. expect( command.value ).to.equal( root.getNodeByPath( [ 0, 0, 2 ] ) );
  122. } );
  123. it( 'should be undefined if last cell of a row', () => {
  124. setData( model, modelTable( [
  125. [ '00', '01[]' ]
  126. ] ) );
  127. expect( command.value ).to.be.undefined;
  128. } );
  129. it( 'should be set to mergeable sibling if in a cell that has sibling on the right with the same rowspan', () => {
  130. setData( model, modelTable( [
  131. [ { rowspan: 2, contents: '00[]' }, { rowspan: 2, contents: '01' } ]
  132. ] ) );
  133. expect( command.value ).to.equal( root.getNodeByPath( [ 0, 0, 1 ] ) );
  134. } );
  135. it( 'should be undefined if in a cell that has sibling but with different rowspan', () => {
  136. setData( model, modelTable( [
  137. [ { rowspan: 2, contents: '00[]' }, { rowspan: 3, contents: '01' }, '02' ],
  138. [ '12' ],
  139. [ '20', '22' ]
  140. ] ) );
  141. expect( command.value ).to.be.undefined;
  142. } );
  143. it( 'should be undefined if not in a cell', () => {
  144. setData( model, '<paragraph>11[]</paragraph>' );
  145. expect( command.value ).to.be.undefined;
  146. } );
  147. } );
  148. describe( 'execute()', () => {
  149. it( 'should merge table cells', () => {
  150. setData( model, modelTable( [
  151. [ '[]00', '01' ],
  152. [ '10', '11' ]
  153. ] ) );
  154. command.execute();
  155. assertEqualMarkup( getData( model ), modelTable( [
  156. [ { colspan: 2, contents: '<paragraph>[00</paragraph><paragraph>01]</paragraph>' } ],
  157. [ '10', '11' ]
  158. ] ) );
  159. } );
  160. it( 'should merge table cells and remove empty columns', () => {
  161. setData( model, modelTable( [
  162. [ '[]00', '01' ]
  163. ] ) );
  164. command.execute();
  165. assertEqualMarkup( getData( model ), modelTable( [
  166. [ '<paragraph>[00</paragraph><paragraph>01]</paragraph>' ]
  167. ] ) );
  168. } );
  169. it( 'should result in single empty paragraph if both cells are empty', () => {
  170. setData( model, modelTable( [
  171. [ '[]', '' ],
  172. [ '10', '11' ]
  173. ] ) );
  174. command.execute();
  175. assertEqualMarkup( getData( model ), modelTable( [
  176. [ { colspan: 2, contents: '<paragraph>[]</paragraph>' } ],
  177. [ '10', '11' ]
  178. ] ) );
  179. } );
  180. it( 'should result in single paragraph (other cell is empty)', () => {
  181. setData( model, modelTable( [
  182. [ 'foo[]', '' ],
  183. [ '10', '11' ]
  184. ] ) );
  185. command.execute();
  186. assertEqualMarkup( getData( model ), modelTable( [
  187. [ { colspan: 2, contents: '<paragraph>[foo]</paragraph>' } ],
  188. [ '10', '11' ]
  189. ] ) );
  190. } );
  191. it( 'should result in single paragraph (selection cell is empty)', () => {
  192. setData( model, modelTable( [
  193. [ '[]', 'foo' ],
  194. [ '10', '11' ]
  195. ] ) );
  196. command.execute();
  197. assertEqualMarkup( getData( model ), modelTable( [
  198. [ { colspan: 2, contents: '<paragraph>[foo]</paragraph>' } ],
  199. [ '10', '11' ]
  200. ] ) );
  201. } );
  202. it( 'should not merge other empty blocks to single block', () => {
  203. model.schema.register( 'block', {
  204. allowWhere: '$block',
  205. allowContentOf: '$block',
  206. isBlock: true
  207. } );
  208. setData( model, modelTable( [
  209. [ '<block>[]</block>', '<block></block>' ],
  210. [ '10', '11' ]
  211. ] ) );
  212. command.execute();
  213. assertEqualMarkup( getData( model ), modelTable( [
  214. [ { colspan: 2, contents: '<block>[</block><block>]</block>' } ],
  215. [ '10', '11' ]
  216. ] ) );
  217. } );
  218. } );
  219. } );
  220. describe( 'direction=left', () => {
  221. beforeEach( () => {
  222. command = new MergeCellCommand( editor, { direction: 'left' } );
  223. } );
  224. describe( 'isEnabled', () => {
  225. it( 'should be true if in cell that has sibling on the left', () => {
  226. setData( model, modelTable( [
  227. [ '00', '01[]' ]
  228. ] ) );
  229. expect( command.isEnabled ).to.be.true;
  230. } );
  231. it( 'should be false if first cell of a row', () => {
  232. setData( model, modelTable( [
  233. [ '00[]', '01' ]
  234. ] ) );
  235. expect( command.isEnabled ).to.be.false;
  236. } );
  237. it( 'should be true if in a cell that has sibling on the left with the same rowspan', () => {
  238. setData( model, modelTable( [
  239. [ { rowspan: 2, contents: '00' }, { rowspan: 2, contents: '01[]' } ]
  240. ] ) );
  241. expect( command.isEnabled ).to.be.true;
  242. } );
  243. it( 'should be false if in a cell that has sibling but with different rowspan', () => {
  244. setData( model, modelTable( [
  245. [ { rowspan: 2, contents: '00' }, { rowspan: 3, contents: '01[]' } ],
  246. [ '12' ],
  247. [ '20', '22' ]
  248. ] ) );
  249. expect( command.isEnabled ).to.be.false;
  250. } );
  251. it( 'should be false when next cell is rowspanned', () => {
  252. setData( model, modelTable( [
  253. [ '00', { rowspan: 3, contents: '01' }, '02' ],
  254. [ '10', '12[]' ],
  255. [ '20', '22' ]
  256. ] ) );
  257. expect( command.isEnabled ).to.be.false;
  258. } );
  259. it( 'should be true when mergeable cell is colspanned', () => {
  260. setData( model, modelTable( [
  261. [ { colspan: 2, contents: '00' }, '02[]' ]
  262. ] ) );
  263. expect( command.isEnabled ).to.be.true;
  264. } );
  265. it( 'should be false if not in a cell', () => {
  266. setData( model, '<paragraph>11[]</paragraph>' );
  267. expect( command.isEnabled ).to.be.false;
  268. } );
  269. describe( 'when the heading section is in the table', () => {
  270. it( 'should be false if mergeable cell is in other table section then current cell', () => {
  271. setData( model, modelTable( [
  272. [ '00', '01[]' ]
  273. ], { headingColumns: 1 } ) );
  274. expect( command.isEnabled ).to.be.false;
  275. } );
  276. it( 'should be false if merged cell would cross heading section (mergeable cell with colspan)', () => {
  277. setData( model, modelTable( [
  278. [ { colspan: 2, contents: '00' }, '02[]', '03' ]
  279. ], { headingColumns: 2 } ) );
  280. expect( command.isEnabled ).to.be.false;
  281. } );
  282. it( 'should be true if merged cell would not cross the section boundary (in regular section)', () => {
  283. setData( model, modelTable( [
  284. [ '00', '01', '02[]', '03' ]
  285. ], { headingColumns: 1 } ) );
  286. expect( command.isEnabled ).to.be.true;
  287. } );
  288. it( 'should be true if merged cell would not cross the section boundary (in heading section)', () => {
  289. setData( model, modelTable( [
  290. [ '00', '01[]', '02', '03' ]
  291. ], { headingColumns: 2 } ) );
  292. expect( command.isEnabled ).to.be.true;
  293. } );
  294. } );
  295. } );
  296. describe( 'value', () => {
  297. it( 'should be set to mergeable sibling if in cell that has sibling on the left', () => {
  298. setData( model, modelTable( [
  299. [ '00', '01[]' ]
  300. ] ) );
  301. expect( command.value ).to.equal( root.getNodeByPath( [ 0, 0, 0 ] ) );
  302. } );
  303. it( 'should be set to mergeable sibling if in cell that has sibling on the left (selection in block content)', () => {
  304. setData( model, modelTable( [
  305. [ '00', '<paragraph>01[]</paragraph>', '02' ]
  306. ] ) );
  307. expect( command.value ).to.equal( root.getNodeByPath( [ 0, 0, 0 ] ) );
  308. } );
  309. it( 'should be undefined if first cell of a row', () => {
  310. setData( model, modelTable( [
  311. [ '00[]', '01' ]
  312. ] ) );
  313. expect( command.value ).to.be.undefined;
  314. } );
  315. it( 'should be set to mergeable sibling if in a cell that has sibling on the left with the same rowspan', () => {
  316. setData( model, modelTable( [
  317. [ { rowspan: 2, contents: '00' }, { rowspan: 2, contents: '01[]' } ]
  318. ] ) );
  319. expect( command.value ).to.equal( root.getNodeByPath( [ 0, 0, 0 ] ) );
  320. } );
  321. it( 'should be undefined if in a cell that has sibling but with different rowspan', () => {
  322. setData( model, modelTable( [
  323. [ { rowspan: 2, contents: '00' }, { rowspan: 3, contents: '01[]' } ],
  324. [ '12' ],
  325. [ '20', '22' ]
  326. ] ) );
  327. expect( command.value ).to.be.undefined;
  328. } );
  329. it( 'should be undefined if not in a cell', () => {
  330. setData( model, '<paragraph>11[]</paragraph>' );
  331. expect( command.value ).to.be.undefined;
  332. } );
  333. } );
  334. describe( 'execute()', () => {
  335. it( 'should merge table cells', () => {
  336. setData( model, modelTable( [
  337. [ '00', '[]01' ],
  338. [ '10', '11' ]
  339. ] ) );
  340. command.execute();
  341. assertEqualMarkup( getData( model ), modelTable( [
  342. [ { colspan: 2, contents: '<paragraph>[00</paragraph><paragraph>01]</paragraph>' } ],
  343. [ '10', '11' ]
  344. ] ) );
  345. } );
  346. it( 'should merge table cells and remove empty columns', () => {
  347. setData( model, modelTable( [
  348. [ '00', '[]01' ]
  349. ] ) );
  350. command.execute();
  351. assertEqualMarkup( getData( model ), modelTable( [
  352. [ '<paragraph>[00</paragraph><paragraph>01]</paragraph>' ]
  353. ] ) );
  354. } );
  355. it( 'should result in single empty paragraph if both cells are empty', () => {
  356. setData( model, modelTable( [
  357. [ '', '[]' ],
  358. [ '10', '11' ]
  359. ] ) );
  360. command.execute();
  361. assertEqualMarkup( getData( model ), modelTable( [
  362. [ { colspan: 2, contents: '<paragraph>[]</paragraph>' } ],
  363. [ '10', '11' ]
  364. ] ) );
  365. } );
  366. it( 'should result in single paragraph (other cell is empty)', () => {
  367. setData( model, modelTable( [
  368. [ '', 'foo[]' ],
  369. [ '10', '11' ]
  370. ] ) );
  371. command.execute();
  372. assertEqualMarkup( getData( model ), modelTable( [
  373. [ { colspan: 2, contents: '<paragraph>[foo]</paragraph>' } ],
  374. [ '10', '11' ]
  375. ] ) );
  376. } );
  377. it( 'should result in single paragraph (selection cell is empty)', () => {
  378. setData( model, modelTable( [
  379. [ 'foo', '[]' ],
  380. [ '10', '11' ]
  381. ] ) );
  382. command.execute();
  383. assertEqualMarkup( getData( model ), modelTable( [
  384. [ { colspan: 2, contents: '<paragraph>[foo]</paragraph>' } ],
  385. [ '10', '11' ]
  386. ] ) );
  387. } );
  388. it( 'should not merge other empty blocks to single block', () => {
  389. model.schema.register( 'block', {
  390. allowWhere: '$block',
  391. allowContentOf: '$block',
  392. isBlock: true
  393. } );
  394. setData( model, modelTable( [
  395. [ '<block></block>', '<block>[]</block>' ],
  396. [ '10', '11' ]
  397. ] ) );
  398. command.execute();
  399. assertEqualMarkup( getData( model ), modelTable( [
  400. [ { colspan: 2, contents: '<block>[</block><block>]</block>' } ],
  401. [ '10', '11' ]
  402. ] ) );
  403. } );
  404. } );
  405. } );
  406. describe( 'direction=down', () => {
  407. beforeEach( () => {
  408. command = new MergeCellCommand( editor, { direction: 'down' } );
  409. } );
  410. describe( 'isEnabled', () => {
  411. it( 'should be true if in cell that has mergeable cell in next row', () => {
  412. setData( model, modelTable( [
  413. [ '00', '01[]' ],
  414. [ '10', '11' ]
  415. ] ) );
  416. expect( command.isEnabled ).to.be.true;
  417. } );
  418. it( 'should be false if in last row', () => {
  419. setData( model, modelTable( [
  420. [ '00', '01' ],
  421. [ '10[]', '11' ]
  422. ] ) );
  423. expect( command.isEnabled ).to.be.false;
  424. } );
  425. it( 'should be true if in a cell that has mergeable cell with the same colspan', () => {
  426. setData( model, modelTable( [
  427. [ { colspan: 2, contents: '00[]' }, '02' ],
  428. [ { colspan: 2, contents: '01' }, '12' ]
  429. ] ) );
  430. expect( command.isEnabled ).to.be.true;
  431. } );
  432. it( 'should be false if in a cell that potential mergeable cell has different colspan', () => {
  433. setData( model, modelTable( [
  434. [ { colspan: 2, contents: '00[]' }, '02' ],
  435. [ { colspan: 3, contents: '01' } ]
  436. ] ) );
  437. expect( command.isEnabled ).to.be.false;
  438. } );
  439. it( 'should be false if not in a cell', () => {
  440. setData( model, '<paragraph>11[]</paragraph>' );
  441. expect( command.isEnabled ).to.be.false;
  442. } );
  443. it( 'should be false if mergeable cell is in other table section then current cell', () => {
  444. setData( model, modelTable( [
  445. [ '00[]', '01' ],
  446. [ '10', '11' ]
  447. ], { headingRows: 1 } ) );
  448. expect( command.isEnabled ).to.be.false;
  449. } );
  450. } );
  451. describe( 'value', () => {
  452. it( 'should be set to mergeable cell', () => {
  453. setData( model, modelTable( [
  454. [ '00', '01[]' ],
  455. [ '10', '11' ]
  456. ] ) );
  457. expect( command.value ).to.equal( root.getNodeByPath( [ 0, 1, 1 ] ) );
  458. } );
  459. it( 'should be set to mergeable cell (selection in block content)', () => {
  460. setData( model, modelTable( [
  461. [ '00' ],
  462. [ '<paragraph>10[]</paragraph>' ],
  463. [ '20' ]
  464. ] ) );
  465. expect( command.value ).to.equal( root.getNodeByPath( [ 0, 2, 0 ] ) );
  466. } );
  467. it( 'should be undefined if in last row', () => {
  468. setData( model, modelTable( [
  469. [ '00', '01' ],
  470. [ '10[]', '11' ]
  471. ] ) );
  472. expect( command.value ).to.be.undefined;
  473. } );
  474. it( 'should be set to mergeable cell with the same rowspan', () => {
  475. setData( model, modelTable( [
  476. [ { colspan: 2, contents: '00[]' }, '02' ],
  477. [ { colspan: 2, contents: '01' }, '12' ]
  478. ] ) );
  479. expect( command.value ).to.equal( root.getNodeByPath( [ 0, 1, 0 ] ) );
  480. } );
  481. it( 'should be undefined if in a cell that potential mergeable cell has different rowspan', () => {
  482. setData( model, modelTable( [
  483. [ { colspan: 2, contents: '00[]' }, '02' ],
  484. [ { colspan: 3, contents: '01' } ]
  485. ] ) );
  486. expect( command.value ).to.be.undefined;
  487. } );
  488. it( 'should be undefined if mergable cell is in other table section', () => {
  489. setData( model, modelTable( [
  490. [ { rowspan: 2, contents: '00[]' }, '02' ],
  491. [ '12' ],
  492. [ '21', '22' ]
  493. ], { headingRows: 2 } ) );
  494. expect( command.value ).to.be.undefined;
  495. } );
  496. it( 'should be undefined if not in a cell', () => {
  497. setData( model, '<paragraph>11[]</paragraph>' );
  498. expect( command.value ).to.be.undefined;
  499. } );
  500. } );
  501. describe( 'execute()', () => {
  502. it( 'should merge table cells', () => {
  503. setData( model, modelTable( [
  504. [ '00', '01[]' ],
  505. [ '10', '11' ]
  506. ] ) );
  507. command.execute();
  508. assertEqualMarkup( getData( model ), modelTable( [
  509. [ '00', { rowspan: 2, contents: '<paragraph>[01</paragraph><paragraph>11]</paragraph>' } ],
  510. [ '10' ]
  511. ] ) );
  512. } );
  513. it( 'should result in single empty paragraph if both cells are empty', () => {
  514. setData( model, modelTable( [
  515. [ '[]', '' ],
  516. [ '', '' ]
  517. ] ) );
  518. command.execute();
  519. assertEqualMarkup( getData( model ), modelTable( [
  520. [ { rowspan: 2, contents: '[]' }, '' ],
  521. [ '' ]
  522. ] ) );
  523. } );
  524. it( 'should result in single paragraph (other cell is empty)', () => {
  525. setData( model, modelTable( [
  526. [ 'foo[]', '' ],
  527. [ '', '' ]
  528. ] ) );
  529. command.execute();
  530. assertEqualMarkup( getData( model ), modelTable( [
  531. [ { rowspan: 2, contents: '[foo]' }, '' ],
  532. [ '' ]
  533. ] ) );
  534. } );
  535. it( 'should result in single paragraph (selection cell is empty)', () => {
  536. setData( model, modelTable( [
  537. [ '[]', '' ],
  538. [ 'foo', '' ]
  539. ] ) );
  540. command.execute();
  541. assertEqualMarkup( getData( model ), modelTable( [
  542. [ { rowspan: 2, contents: '[foo]' }, '' ],
  543. [ '' ]
  544. ] ) );
  545. } );
  546. it( 'should not merge other empty blocks to single block', () => {
  547. model.schema.register( 'block', {
  548. allowWhere: '$block',
  549. allowContentOf: '$block',
  550. isBlock: true
  551. } );
  552. setData( model, modelTable( [
  553. [ '<block>[]</block>', '' ],
  554. [ '<block></block>', '' ]
  555. ] ) );
  556. command.execute();
  557. assertEqualMarkup( getData( model ), modelTable( [
  558. [ { rowspan: 2, contents: '<block>[</block><block>]</block>' }, '' ],
  559. [ '' ]
  560. ] ) );
  561. } );
  562. it( 'should remove empty row if merging table cells ', () => {
  563. setData( model, modelTable( [
  564. [ { rowspan: 2, contents: '00' }, '01[]', { rowspan: 3, contents: '02' } ],
  565. [ '11' ],
  566. [ '20', '21' ]
  567. ] ) );
  568. command.execute();
  569. assertEqualMarkup( getData( model ), modelTable( [
  570. [ '00', '<paragraph>[01</paragraph><paragraph>11]</paragraph>', { rowspan: 2, contents: '02' } ],
  571. [ '20', '21' ]
  572. ] ) );
  573. } );
  574. it( 'should not reduce rowspan on cells above removed empty row when merging table cells ', () => {
  575. setData( model, modelTable( [
  576. [ { rowspan: 2, contents: '00' }, '01', '02' ],
  577. [ '11', '12' ],
  578. [ { rowspan: 2, contents: '20' }, '21[]', { rowspan: 3, contents: '22' } ],
  579. [ '31' ],
  580. [ '40', '41' ]
  581. ] ) );
  582. command.execute();
  583. assertEqualMarkup( getData( model ), modelTable( [
  584. [ { rowspan: 2, contents: '00' }, '01', '02' ],
  585. [ '11', '12' ],
  586. [ '20', '<paragraph>[21</paragraph><paragraph>31]</paragraph>', { rowspan: 2, contents: '22' } ],
  587. [ '40', '41' ]
  588. ] ) );
  589. } );
  590. it( 'should adjust heading rows if empty row was removed ', () => {
  591. // +----+----+
  592. // | 00 | 01 |
  593. // + +----+
  594. // | | 11 |
  595. // +----+----+ <-- heading rows
  596. // | 20 | 21 |
  597. // +----+----+
  598. setData( model, modelTable( [
  599. [ { contents: '00', rowspan: 2 }, '[]01' ],
  600. [ '11' ],
  601. [ '20', '21' ]
  602. ], { headingRows: 2 } ) );
  603. command.execute();
  604. assertEqualMarkup( getData( model ), modelTable( [
  605. [ '00', '<paragraph>[01</paragraph><paragraph>11]</paragraph>' ],
  606. [ '20', '21' ]
  607. ], { headingRows: 1 } ) );
  608. } );
  609. it( 'should create one undo step (1 batch)', () => {
  610. // +----+----+
  611. // | 00 | 01 |
  612. // + +----+
  613. // | | 11 |
  614. // +----+----+ <-- heading rows
  615. // | 20 | 21 |
  616. // +----+----+
  617. setData( model, modelTable( [
  618. [ { contents: '00', rowspan: 2 }, '[]01' ],
  619. [ '11' ],
  620. [ '20', '21' ]
  621. ], { headingRows: 2 } ) );
  622. const createdBatches = new Set();
  623. model.on( 'applyOperation', ( evt, [ operation ] ) => {
  624. createdBatches.add( operation.batch );
  625. } );
  626. command.execute();
  627. expect( createdBatches.size ).to.equal( 1 );
  628. } );
  629. } );
  630. } );
  631. describe( 'direction=up', () => {
  632. beforeEach( () => {
  633. command = new MergeCellCommand( editor, { direction: 'up' } );
  634. } );
  635. describe( 'isEnabled', () => {
  636. it( 'should be true if in cell that has mergeable cell in previous row', () => {
  637. setData( model, modelTable( [
  638. [ '00', '01' ],
  639. [ '10', '11[]' ]
  640. ] ) );
  641. expect( command.isEnabled ).to.be.true;
  642. } );
  643. it( 'should be false if in first row', () => {
  644. setData( model, modelTable( [
  645. [ '00[]', '01' ],
  646. [ '10', '11' ]
  647. ] ) );
  648. expect( command.isEnabled ).to.be.false;
  649. } );
  650. it( 'should be true if in a cell that has mergeable cell with the same colspan', () => {
  651. setData( model, modelTable( [
  652. [ { colspan: 2, contents: '00' }, '02' ],
  653. [ { colspan: 2, contents: '01[]' }, '12' ]
  654. ] ) );
  655. expect( command.isEnabled ).to.be.true;
  656. } );
  657. it( 'should be false if in a cell that potential mergeable cell has different colspan', () => {
  658. setData( model, modelTable( [
  659. [ { colspan: 2, contents: '00' }, '02' ],
  660. [ { colspan: 3, contents: '01[]' } ]
  661. ] ) );
  662. expect( command.isEnabled ).to.be.false;
  663. } );
  664. it( 'should be false if not in a cell', () => {
  665. setData( model, '<paragraph>11[]</paragraph>' );
  666. expect( command.isEnabled ).to.be.false;
  667. } );
  668. it( 'should be false if mergeable cell is in other table section then current cell', () => {
  669. setData( model, modelTable( [
  670. [ '00', '01' ],
  671. [ '10[]', '11' ]
  672. ], { headingRows: 1 } ) );
  673. expect( command.isEnabled ).to.be.false;
  674. } );
  675. } );
  676. describe( 'value', () => {
  677. it( 'should be set to mergeable cell', () => {
  678. setData( model, modelTable( [
  679. [ '00', '01' ],
  680. [ '10', '11[]' ]
  681. ] ) );
  682. expect( command.value ).to.equal( root.getNodeByPath( [ 0, 0, 1 ] ) );
  683. } );
  684. it( 'should be set to mergeable cell (selection in block content)', () => {
  685. setData( model, modelTable( [
  686. [ '00' ],
  687. [ '<paragraph>10[]</paragraph>' ],
  688. [ '20' ]
  689. ] ) );
  690. expect( command.value ).to.equal( root.getNodeByPath( [ 0, 0, 0 ] ) );
  691. } );
  692. it( 'should be undefined if in first row', () => {
  693. setData( model, modelTable( [
  694. [ '00[]', '01' ],
  695. [ '10', '11' ]
  696. ] ) );
  697. expect( command.value ).to.be.undefined;
  698. } );
  699. it( 'should be set to mergeable cell with the same rowspan', () => {
  700. setData( model, modelTable( [
  701. [ { colspan: 2, contents: '00' }, '02' ],
  702. [ { colspan: 2, contents: '01[]' }, '12' ]
  703. ] ) );
  704. expect( command.value ).to.equal( root.getNodeByPath( [ 0, 0, 0 ] ) );
  705. } );
  706. it( 'should be set to mergeable cell in rows with spanned cells', () => {
  707. setData( model, modelTable( [
  708. [ { rowspan: 3, contents: '00' }, '11', '12', '13' ],
  709. [ { rowspan: 2, contents: '21' }, '22', '23' ],
  710. [ '32', { rowspan: 2, contents: '33[]' } ],
  711. [ { colspan: 2, contents: '40' }, '42' ]
  712. ] ) );
  713. expect( command.value ).to.equal( root.getNodeByPath( [ 0, 1, 2 ] ) );
  714. } );
  715. it( 'should be undefined if in a cell that potential mergeable cell has different rowspan', () => {
  716. setData( model, modelTable( [
  717. [ { colspan: 2, contents: '00' }, '02' ],
  718. [ { colspan: 3, contents: '01[]' } ]
  719. ] ) );
  720. expect( command.value ).to.be.undefined;
  721. } );
  722. it( 'should be undefined if not in a cell', () => {
  723. setData( model, '<paragraph>11[]</paragraph>' );
  724. expect( command.value ).to.be.undefined;
  725. } );
  726. } );
  727. describe( 'execute()', () => {
  728. it( 'should merge table cells', () => {
  729. setData( model, modelTable( [
  730. [ '00', '01' ],
  731. [ '10', '[]11' ]
  732. ] ) );
  733. command.execute();
  734. assertEqualMarkup( getData( model ), modelTable( [
  735. [ '00', { rowspan: 2, contents: '<paragraph>[01</paragraph><paragraph>11]</paragraph>' } ],
  736. [ '10' ]
  737. ] ) );
  738. } );
  739. it( 'should result in single empty paragraph if both cells are empty', () => {
  740. setData( model, modelTable( [
  741. [ '', '' ],
  742. [ '[]', '' ]
  743. ] ) );
  744. command.execute();
  745. assertEqualMarkup( getData( model ), modelTable( [
  746. [ { rowspan: 2, contents: '[]' }, '' ],
  747. [ '' ]
  748. ] ) );
  749. } );
  750. it( 'should result in single paragraph (other cell is empty)', () => {
  751. setData( model, modelTable( [
  752. [ '', '' ],
  753. [ 'foo[]', '' ]
  754. ] ) );
  755. command.execute();
  756. assertEqualMarkup( getData( model ), modelTable( [
  757. [ { rowspan: 2, contents: '[foo]' }, '' ],
  758. [ '' ]
  759. ] ) );
  760. } );
  761. it( 'should result in single paragraph (selection cell is empty)', () => {
  762. setData( model, modelTable( [
  763. [ 'foo', '' ],
  764. [ '[]', '' ]
  765. ] ) );
  766. command.execute();
  767. assertEqualMarkup( getData( model ), modelTable( [
  768. [ { rowspan: 2, contents: '[foo]' }, '' ],
  769. [ '' ]
  770. ] ) );
  771. } );
  772. it( 'should not merge other empty blocks to single block', () => {
  773. model.schema.register( 'block', {
  774. allowWhere: '$block',
  775. allowContentOf: '$block',
  776. isBlock: true
  777. } );
  778. setData( model, modelTable( [
  779. [ '<block></block>', '' ],
  780. [ '<block>[]</block>', '' ]
  781. ] ) );
  782. command.execute();
  783. assertEqualMarkup( getData( model ), modelTable( [
  784. [ { rowspan: 2, contents: '<block>[</block><block>]</block>' }, '' ],
  785. [ '' ]
  786. ] ) );
  787. } );
  788. it( 'should properly merge cells in rows with spaned cells', () => {
  789. setData( model, modelTable( [
  790. [ { rowspan: 3, contents: '00' }, '11', '12', '13' ],
  791. [ { rowspan: 2, contents: '21' }, '22', '23' ],
  792. [ '32', { rowspan: 2, contents: '33[]' } ],
  793. [ { colspan: 2, contents: '40' }, '42' ]
  794. ] ) );
  795. command.execute();
  796. assertEqualMarkup( getData( model ), modelTable( [
  797. [ { rowspan: 3, contents: '00' }, '11', '12', '13' ],
  798. [
  799. { rowspan: 2, contents: '21' },
  800. '22',
  801. { rowspan: 3, contents: '<paragraph>[23</paragraph><paragraph>33]</paragraph>' }
  802. ],
  803. [ '32' ],
  804. [ { colspan: 2, contents: '40' }, '42' ]
  805. ] ) );
  806. } );
  807. it( 'should remove empty row if merging table cells ', () => {
  808. setData( model, modelTable( [
  809. [ { rowspan: 2, contents: '00' }, '01', { rowspan: 3, contents: '02' } ],
  810. [ '11[]' ],
  811. [ '20', '21' ]
  812. ] ) );
  813. command.execute();
  814. assertEqualMarkup( getData( model ), modelTable( [
  815. [ '00', '<paragraph>[01</paragraph><paragraph>11]</paragraph>', { rowspan: 2, contents: '02' } ],
  816. [ '20', '21' ]
  817. ] ) );
  818. } );
  819. it( 'should not reduce rowspan on cells above removed empty row when merging table cells ', () => {
  820. setData( model, modelTable( [
  821. [ { rowspan: 2, contents: '00' }, '01', '02' ],
  822. [ '11', '12' ],
  823. [ { rowspan: 2, contents: '20' }, '21', { rowspan: 3, contents: '22' } ],
  824. [ '31[]' ],
  825. [ '40', '41' ]
  826. ] ) );
  827. command.execute();
  828. assertEqualMarkup( getData( model ), modelTable( [
  829. [ { rowspan: 2, contents: '00' }, '01', '02' ],
  830. [ '11', '12' ],
  831. [ '20', '<paragraph>[21</paragraph><paragraph>31]</paragraph>', { rowspan: 2, contents: '22' } ],
  832. [ '40', '41' ]
  833. ] ) );
  834. } );
  835. it( 'should adjust heading rows if empty row was removed ', () => {
  836. // +----+----+
  837. // | 00 | 01 |
  838. // + +----+
  839. // | | 11 |
  840. // +----+----+ <-- heading rows
  841. // | 20 | 21 |
  842. // +----+----+
  843. setData( model, modelTable( [
  844. [ { contents: '00', rowspan: 2 }, '01' ],
  845. [ '[]11' ],
  846. [ '20', '21' ]
  847. ], { headingRows: 2 } ) );
  848. command.execute();
  849. assertEqualMarkup( getData( model ), modelTable( [
  850. [ '00', '<paragraph>[01</paragraph><paragraph>11]</paragraph>' ],
  851. [ '20', '21' ]
  852. ], { headingRows: 1 } ) );
  853. } );
  854. it( 'should create one undo step (1 batch)', () => {
  855. // +----+----+
  856. // | 00 | 01 |
  857. // + +----+
  858. // | | 11 |
  859. // +----+----+ <-- heading rows
  860. // | 20 | 21 |
  861. // +----+----+
  862. setData( model, modelTable( [
  863. [ { contents: '00', rowspan: 2 }, '01' ],
  864. [ '[]11' ],
  865. [ '20', '21' ]
  866. ], { headingRows: 2 } ) );
  867. const createdBatches = new Set();
  868. model.on( 'applyOperation', ( evt, [ operation ] ) => {
  869. createdBatches.add( operation.batch );
  870. } );
  871. command.execute();
  872. expect( createdBatches.size ).to.equal( 1 );
  873. } );
  874. } );
  875. } );
  876. } );