tableclipboard-paste.js 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318
  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. /* globals document */
  6. import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
  7. import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
  8. import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard';
  9. import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
  10. import TableEditing from '../src/tableediting';
  11. import { assertSelectedCells, modelTable, viewTable } from './_utils/utils';
  12. import { assertEqualMarkup } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
  13. import TableClipboard from '../src/tableclipboard';
  14. import ImageEditing from '@ckeditor/ckeditor5-image/src/image/imageediting';
  15. import ImageCaptionEditing from '@ckeditor/ckeditor5-image/src/imagecaption/imagecaptionediting';
  16. describe( 'table clipboard', () => {
  17. let editor, model, modelRoot, tableSelection, viewDocument, element;
  18. beforeEach( () => {
  19. element = document.createElement( 'div' );
  20. document.body.appendChild( element );
  21. } );
  22. afterEach( async () => {
  23. await editor.destroy();
  24. element.remove();
  25. } );
  26. describe( 'Clipboard integration - paste (selection scenarios)', () => {
  27. beforeEach( async () => {
  28. await createEditor();
  29. setModelData( model, modelTable( [
  30. [ '00[]', '01', '02', '03' ],
  31. [ '10', '11', '12', '13' ],
  32. [ '20', '21', '22', '23' ],
  33. [ '30', '31', '32', '33' ]
  34. ] ) );
  35. } );
  36. it( 'should be disabled in a readonly mode', () => {
  37. editor.isReadOnly = true;
  38. tableSelection.setCellSelection(
  39. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  40. modelRoot.getNodeByPath( [ 0, 1, 1 ] )
  41. );
  42. const data = pasteTable( [
  43. [ 'aa', 'ab' ],
  44. [ 'ba', 'bb' ]
  45. ] );
  46. editor.isReadOnly = false;
  47. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  48. [ '00', '01', '02', '03' ],
  49. [ '10', '11', '12', '13' ],
  50. [ '20', '21', '22', '23' ],
  51. [ '30', '31', '32', '33' ]
  52. ] ) );
  53. sinon.assert.calledOnce( data.preventDefault );
  54. } );
  55. it( 'should allow normal paste if no table cells are selected', () => {
  56. const data = {
  57. dataTransfer: createDataTransfer(),
  58. preventDefault: sinon.spy(),
  59. stopPropagation: sinon.spy()
  60. };
  61. data.dataTransfer.setData( 'text/html', '<p>foo</p>' );
  62. viewDocument.fire( 'paste', data );
  63. editor.isReadOnly = false;
  64. assertEqualMarkup( getModelData( model ), modelTable( [
  65. [ '00foo[]', '01', '02', '03' ],
  66. [ '10', '11', '12', '13' ],
  67. [ '20', '21', '22', '23' ],
  68. [ '30', '31', '32', '33' ]
  69. ] ) );
  70. } );
  71. it( 'should block non-rectangular selection', () => {
  72. setModelData( model, modelTable( [
  73. [ { contents: '00', colspan: 3 } ],
  74. [ '10', '11', '12' ],
  75. [ '20', '21', '22' ]
  76. ] ) );
  77. tableSelection.setCellSelection(
  78. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  79. modelRoot.getNodeByPath( [ 0, 1, 1 ] )
  80. );
  81. pasteTable( [
  82. [ 'aa', 'ab' ],
  83. [ 'ba', 'bb' ]
  84. ] );
  85. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  86. [ { contents: '00', colspan: 3 } ],
  87. [ '10', '11', '12' ],
  88. [ '20', '21', '22' ]
  89. ] ) );
  90. } );
  91. describe( 'single cell selected', () => {
  92. it( 'blocks this case', () => {
  93. setModelData( model, modelTable( [
  94. [ '00', '01', '02' ],
  95. [ '10', '11', '12' ],
  96. [ '20', '21', '22' ]
  97. ] ) );
  98. tableSelection.setCellSelection(
  99. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  100. modelRoot.getNodeByPath( [ 0, 0, 0 ] )
  101. );
  102. pasteTable( [
  103. [ 'aa', 'ab' ],
  104. [ 'ba', 'bb' ]
  105. ] );
  106. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  107. [ '00', '01', '02' ],
  108. [ '10', '11', '12' ],
  109. [ '20', '21', '22' ]
  110. ] ) );
  111. } );
  112. } );
  113. describe( 'pasted table is equal to the selected area', () => {
  114. describe( 'no spans', () => {
  115. it( 'handles simple table paste to a simple table fragment - at the beginning of a table', () => {
  116. tableSelection.setCellSelection(
  117. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  118. modelRoot.getNodeByPath( [ 0, 1, 1 ] )
  119. );
  120. pasteTable( [
  121. [ 'aa', 'ab' ],
  122. [ 'ba', 'bb' ]
  123. ] );
  124. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  125. [ 'aa', 'ab', '02', '03' ],
  126. [ 'ba', 'bb', '12', '13' ],
  127. [ '20', '21', '22', '23' ],
  128. [ '30', '31', '32', '33' ]
  129. ] ) );
  130. assertSelectedCells( model, [
  131. [ 1, 1, 0, 0 ],
  132. [ 1, 1, 0, 0 ],
  133. [ 0, 0, 0, 0 ],
  134. [ 0, 0, 0, 0 ]
  135. ] );
  136. } );
  137. it( 'handles simple table paste to a simple table fragment - at the end of a table', () => {
  138. tableSelection.setCellSelection(
  139. modelRoot.getNodeByPath( [ 0, 2, 2 ] ),
  140. modelRoot.getNodeByPath( [ 0, 3, 3 ] )
  141. );
  142. pasteTable( [
  143. [ 'aa', 'ab' ],
  144. [ 'ba', 'bb' ]
  145. ] );
  146. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  147. [ '00', '01', '02', '03' ],
  148. [ '10', '11', '12', '13' ],
  149. [ '20', '21', 'aa', 'ab' ],
  150. [ '30', '31', 'ba', 'bb' ]
  151. ] ) );
  152. assertSelectedCells( model, [
  153. [ 0, 0, 0, 0 ],
  154. [ 0, 0, 0, 0 ],
  155. [ 0, 0, 1, 1 ],
  156. [ 0, 0, 1, 1 ]
  157. ] );
  158. } );
  159. it( 'handles simple table paste to a simple table fragment - in the middle of a table', () => {
  160. tableSelection.setCellSelection(
  161. modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
  162. modelRoot.getNodeByPath( [ 0, 2, 2 ] )
  163. );
  164. pasteTable( [
  165. [ 'aa', 'ab' ],
  166. [ 'ba', 'bb' ]
  167. ] );
  168. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  169. [ '00', '01', '02', '03' ],
  170. [ '10', 'aa', 'ab', '13' ],
  171. [ '20', 'ba', 'bb', '23' ],
  172. [ '30', '31', '32', '33' ]
  173. ] ) );
  174. assertSelectedCells( model, [
  175. [ 0, 0, 0, 0 ],
  176. [ 0, 1, 1, 0 ],
  177. [ 0, 1, 1, 0 ],
  178. [ 0, 0, 0, 0 ]
  179. ] );
  180. } );
  181. it( 'handles simple row paste to a simple row fragment - in the middle of a table', () => {
  182. tableSelection.setCellSelection(
  183. modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
  184. modelRoot.getNodeByPath( [ 0, 1, 2 ] )
  185. );
  186. pasteTable( [
  187. [ 'aa', 'ab' ]
  188. ] );
  189. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  190. [ '00', '01', '02', '03' ],
  191. [ '10', 'aa', 'ab', '13' ],
  192. [ '20', '21', '22', '23' ],
  193. [ '30', '31', '32', '33' ]
  194. ] ) );
  195. assertSelectedCells( model, [
  196. [ 0, 0, 0, 0 ],
  197. [ 0, 1, 1, 0 ],
  198. [ 0, 0, 0, 0 ],
  199. [ 0, 0, 0, 0 ]
  200. ] );
  201. } );
  202. it( 'handles simple column paste to a simple column fragment - in the middle of a table', () => {
  203. tableSelection.setCellSelection(
  204. modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
  205. modelRoot.getNodeByPath( [ 0, 2, 1 ] )
  206. );
  207. pasteTable( [
  208. [ 'aa' ],
  209. [ 'ba' ]
  210. ] );
  211. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  212. [ '00', '01', '02', '03' ],
  213. [ '10', 'aa', '12', '13' ],
  214. [ '20', 'ba', '22', '23' ],
  215. [ '30', '31', '32', '33' ]
  216. ] ) );
  217. assertSelectedCells( model, [
  218. [ 0, 0, 0, 0 ],
  219. [ 0, 1, 0, 0 ],
  220. [ 0, 1, 0, 0 ],
  221. [ 0, 0, 0, 0 ]
  222. ] );
  223. } );
  224. it( 'handles simple table paste to a simple table fragment - whole table selected', () => {
  225. tableSelection.setCellSelection(
  226. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  227. modelRoot.getNodeByPath( [ 0, 3, 3 ] )
  228. );
  229. pasteTable( [
  230. [ 'aa', 'ab', 'ac', 'ad' ],
  231. [ 'ba', 'bb', 'bc', 'bd' ],
  232. [ 'ca', 'cb', 'cc', 'cd' ],
  233. [ 'da', 'db', 'dc', 'dd' ]
  234. ] );
  235. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  236. [ 'aa', 'ab', 'ac', 'ad' ],
  237. [ 'ba', 'bb', 'bc', 'bd' ],
  238. [ 'ca', 'cb', 'cc', 'cd' ],
  239. [ 'da', 'db', 'dc', 'dd' ]
  240. ] ) );
  241. assertSelectedCells( model, [
  242. [ 1, 1, 1, 1 ],
  243. [ 1, 1, 1, 1 ],
  244. [ 1, 1, 1, 1 ],
  245. [ 1, 1, 1, 1 ]
  246. ] );
  247. } );
  248. } );
  249. describe( 'pasted table has spans', () => {
  250. it( 'handles pasting table that has cell with colspan', () => {
  251. tableSelection.setCellSelection(
  252. modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
  253. modelRoot.getNodeByPath( [ 0, 2, 2 ] )
  254. );
  255. pasteTable( [
  256. [ { colspan: 2, contents: 'aa' } ],
  257. [ 'ba', 'bb' ]
  258. ] );
  259. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  260. [ '00', '01', '02', '03' ],
  261. [ '10', { colspan: 2, contents: 'aa' }, '13' ],
  262. [ '20', 'ba', 'bb', '23' ],
  263. [ '30', '31', '32', '33' ]
  264. ] ) );
  265. /* eslint-disable no-multi-spaces */
  266. assertSelectedCells( model, [
  267. [ 0, 0, 0, 0 ],
  268. [ 0, 1, 0 ],
  269. [ 0, 1, 1, 0 ],
  270. [ 0, 0, 0, 0 ]
  271. ] );
  272. /* eslint-enable no-multi-spaces */
  273. } );
  274. it( 'handles pasting table that has many cells with various colspan', () => {
  275. tableSelection.setCellSelection(
  276. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  277. modelRoot.getNodeByPath( [ 0, 3, 2 ] )
  278. );
  279. pasteTable( [
  280. [ 'aa', { colspan: 2, contents: 'ab' } ],
  281. [ { colspan: 3, contents: 'ba' } ],
  282. [ 'ca', 'cb', 'cc' ],
  283. [ { colspan: 2, contents: 'da' }, 'dc' ]
  284. ] );
  285. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  286. [ 'aa', { colspan: 2, contents: 'ab' }, '03' ],
  287. [ { colspan: 3, contents: 'ba' }, '13' ],
  288. [ 'ca', 'cb', 'cc', '23' ],
  289. [ { colspan: 2, contents: 'da' }, 'dc', '33' ]
  290. ] ) );
  291. /* eslint-disable no-multi-spaces */
  292. assertSelectedCells( model, [
  293. [ 1, 1, 0 ],
  294. [ 1, 0 ],
  295. [ 1, 1, 1, 0 ],
  296. [ 1, 1, 0 ]
  297. ] );
  298. /* eslint-enable no-multi-spaces */
  299. } );
  300. it( 'handles pasting table that has cell with rowspan', () => {
  301. tableSelection.setCellSelection(
  302. modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
  303. modelRoot.getNodeByPath( [ 0, 2, 2 ] )
  304. );
  305. pasteTable( [
  306. [ { rowspan: 2, contents: 'aa' }, 'ab' ],
  307. [ 'bb' ]
  308. ] );
  309. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  310. [ '00', '01', '02', '03' ],
  311. [ '10', { rowspan: 2, contents: 'aa' }, 'ab', '13' ],
  312. [ '20', 'bb', '23' ],
  313. [ '30', '31', '32', '33' ]
  314. ] ) );
  315. /* eslint-disable no-multi-spaces */
  316. assertSelectedCells( model, [
  317. [ 0, 0, 0, 0 ],
  318. [ 0, 1, 1, 0 ],
  319. [ 0, 1, 0 ],
  320. [ 0, 0, 0, 0 ]
  321. ] );
  322. /* eslint-enable no-multi-spaces */
  323. } );
  324. it( 'handles pasting table that has many cells with various rowspan', () => {
  325. tableSelection.setCellSelection(
  326. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  327. modelRoot.getNodeByPath( [ 0, 2, 3 ] )
  328. );
  329. pasteTable( [
  330. [ 'aa', { rowspan: 3, contents: 'ab' }, { rowspan: 2, contents: 'ac' }, 'ad' ],
  331. [ { rowspan: 2, contents: 'ba' }, 'bd' ],
  332. [ 'cc', 'cd' ]
  333. ] );
  334. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  335. [ 'aa', { rowspan: 3, contents: 'ab' }, { rowspan: 2, contents: 'ac' }, 'ad' ],
  336. [ { rowspan: 2, contents: 'ba' }, 'bd' ],
  337. [ 'cc', 'cd' ],
  338. [ '30', '31', '32', '33' ]
  339. ] ) );
  340. /* eslint-disable no-multi-spaces */
  341. assertSelectedCells( model, [
  342. [ 1, 1, 1, 1 ],
  343. [ 1, 1 ],
  344. [ 1, 1 ],
  345. [ 0, 0, 0 ]
  346. ] );
  347. /* eslint-enable no-multi-spaces */
  348. } );
  349. it( 'handles pasting multi-spanned table', () => {
  350. setModelData( model, modelTable( [
  351. [ '00', '01', '02', '03', '04', '05' ],
  352. [ '10', '11', '12', '13', '14', '15' ],
  353. [ '20', '21', '22', '23', '24', '25' ],
  354. [ '30', '31', '32', '33', '34', '35' ],
  355. [ '40', '41', '42', '43', '44', '45' ]
  356. ] ) );
  357. tableSelection.setCellSelection(
  358. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  359. modelRoot.getNodeByPath( [ 0, 3, 4 ] )
  360. );
  361. // +----+----+----+----+----+
  362. // | aa | ac | ad | ae |
  363. // +----+----+----+----+ +
  364. // | ba | bb | |
  365. // +----+ +----+
  366. // | ca | | ce |
  367. // + +----+----+----+----+
  368. // | | db | dc | dd |
  369. // +----+----+----+----+----+
  370. pasteTable( [
  371. [ { contents: 'aa', colspan: 2 }, 'ac', 'ad', { contents: 'ae', rowspan: 2 } ],
  372. [ 'ba', { contents: 'bb', colspan: 3, rowspan: 2 } ],
  373. [ { contents: 'ca', rowspan: 2 }, 'ce' ],
  374. [ 'db', 'dc', { contents: 'dd', colspan: 2 } ]
  375. ] );
  376. // +----+----+----+----+----+----+
  377. // | aa | ac | ad | ae | 05 |
  378. // +----+----+----+----+ +----+
  379. // | ba | bb | | 15 |
  380. // +----+ +----+----+
  381. // | ca | | ce | 25 |
  382. // + +----+----+----+----+----+
  383. // | | db | dc | dd | 35 |
  384. // +----+----+----+----+----+----+
  385. // | 40 | 41 | 42 | 43 | 44 | 45 |
  386. // +----+----+----+----+----+----+
  387. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  388. [ { contents: 'aa', colspan: 2 }, 'ac', 'ad', { contents: 'ae', rowspan: 2 }, '05' ],
  389. [ 'ba', { contents: 'bb', colspan: 3, rowspan: 2 }, '15' ],
  390. [ { contents: 'ca', rowspan: 2 }, 'ce', '25' ],
  391. [ 'db', 'dc', { contents: 'dd', colspan: 2 }, '35' ],
  392. [ '40', '41', '42', '43', '44', '45' ]
  393. ] ) );
  394. /* eslint-disable no-multi-spaces */
  395. assertSelectedCells( model, [
  396. [ 1, 1, 1, 1, 0 ],
  397. [ 1, 1, 0 ],
  398. [ 1, 1, 0 ],
  399. [ 1, 1, 1, 0 ],
  400. [ 0, 0, 0, 0, 0, 0 ]
  401. ] );
  402. /* eslint-enable no-multi-spaces */
  403. } );
  404. } );
  405. describe( 'content table has spans', () => {
  406. it( 'handles pasting simple table over a table with colspans (no colspan exceeds selection)', () => {
  407. setModelData( model, modelTable( [
  408. [ '00[]', '01', '02', '03' ],
  409. [ { colspan: 3, contents: '10' }, '13' ],
  410. [ { colspan: 2, contents: '20' }, '22', '23' ],
  411. [ '30', '31', { colspan: 2, contents: '31' } ]
  412. ] ) );
  413. tableSelection.setCellSelection(
  414. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  415. modelRoot.getNodeByPath( [ 0, 2, 1 ] )
  416. );
  417. pasteTable( [
  418. [ 'aa', 'ab', 'ac' ],
  419. [ 'ba', 'bb', 'bc' ],
  420. [ 'ca', 'cb', 'cc' ]
  421. ] );
  422. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  423. [ 'aa', 'ab', 'ac', '03' ],
  424. [ 'ba', 'bb', 'bc', '13' ],
  425. [ 'ca', 'cb', 'cc', '23' ],
  426. [ '30', '31', { colspan: 2, contents: '31' } ]
  427. ] ) );
  428. /* eslint-disable no-multi-spaces */
  429. assertSelectedCells( model, [
  430. [ 1, 1, 1, 0 ],
  431. [ 1, 1, 1, 0 ],
  432. [ 1, 1, 1, 0 ],
  433. [ 0, 0, 0 ]
  434. ] );
  435. /* eslint-enable no-multi-spaces */
  436. } );
  437. it( 'handles pasting simple table over a table with rowspans (no rowspan exceeds selection)', () => {
  438. setModelData( model, modelTable( [
  439. [ '00', { rowspan: 3, contents: '01' }, { rowspan: 2, contents: '02' }, '03' ],
  440. [ { rowspan: 2, contents: '10' }, '13' ],
  441. [ '22', '23' ],
  442. [ '30', '31', '32', '33' ]
  443. ] ) );
  444. tableSelection.setCellSelection(
  445. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  446. modelRoot.getNodeByPath( [ 0, 2, 0 ] )
  447. );
  448. pasteTable( [
  449. [ 'aa', 'ab', 'ac' ],
  450. [ 'ba', 'bb', 'bc' ],
  451. [ 'ca', 'cb', 'cc' ]
  452. ] );
  453. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  454. [ 'aa', 'ab', 'ac', '03' ],
  455. [ 'ba', 'bb', 'bc', '13' ],
  456. [ 'ca', 'cb', 'cc', '23' ],
  457. [ '30', '31', '32', '33' ]
  458. ] ) );
  459. assertSelectedCells( model, [
  460. [ 1, 1, 1, 0 ],
  461. [ 1, 1, 1, 0 ],
  462. [ 1, 1, 1, 0 ],
  463. [ 0, 0, 0, 0 ]
  464. ] );
  465. } );
  466. it( 'handles pasting simple table over table with multi-spans (no span exceeds selection)', () => {
  467. // +----+----+----+----+----+----+
  468. // | 00 | 02 | 03 | 05 |
  469. // + + + +----+
  470. // | | | | 15 |
  471. // +----+----+----+ +----+
  472. // | 20 | 21 | | 25 |
  473. // + +----+----+----+----+----+
  474. // | | 31 | 32 | 34 | 35 |
  475. // +----+----+----+----+----+----+
  476. // | 40 | 41 | 42 | 43 | 44 | 45 |
  477. // +----+----+----+----+----+----+
  478. setModelData( model, modelTable( [
  479. [
  480. { contents: '00', colspan: 2, rowspan: 2 },
  481. { contents: '02', rowspan: 2 },
  482. { contents: '03', colspan: 2, rowspan: 3 },
  483. '05'
  484. ],
  485. [ '15' ],
  486. [ { contents: '20', rowspan: 2 }, { contents: '21', colspan: 2 }, '25' ],
  487. [ '31', { contents: '32', colspan: 2 }, '34', '35' ],
  488. [ '40', '41', '42', '43', '44', '45' ]
  489. ] ) );
  490. tableSelection.setCellSelection(
  491. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  492. modelRoot.getNodeByPath( [ 0, 3, 2 ] )
  493. );
  494. pasteTable( [
  495. [ 'aa', 'ab', 'ac', 'ad', 'ae' ],
  496. [ 'ba', 'bb', 'bc', 'bd', 'be' ],
  497. [ 'ca', 'cb', 'cc', 'cd', 'ce' ],
  498. [ 'da', 'db', 'dc', 'dd', 'de' ]
  499. ] );
  500. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  501. [ 'aa', 'ab', 'ac', 'ad', 'ae', '05' ],
  502. [ 'ba', 'bb', 'bc', 'bd', 'be', '15' ],
  503. [ 'ca', 'cb', 'cc', 'cd', 'ce', '25' ],
  504. [ 'da', 'db', 'dc', 'dd', 'de', '35' ],
  505. [ '40', '41', '42', '43', '44', '45' ]
  506. ] ) );
  507. assertSelectedCells( model, [
  508. [ 1, 1, 1, 1, 1, 0 ],
  509. [ 1, 1, 1, 1, 1, 0 ],
  510. [ 1, 1, 1, 1, 1, 0 ],
  511. [ 1, 1, 1, 1, 1, 0 ],
  512. [ 0, 0, 0, 0, 0, 0 ]
  513. ] );
  514. } );
  515. // TODO: Skipped case - should allow pasting but no tools to compare areas (like in MergeCellsCommand).
  516. it.skip( 'handles pasting table that has cell with colspan (last row in selection is spanned)', () => {
  517. // +----+----+----+----+
  518. // | 00 | 01 | 02 | 03 |
  519. // +----+----+----+----+
  520. // | 10 | 11 | 13 |
  521. // + + +----+
  522. // | | | 23 |
  523. // +----+----+----+----+
  524. // | 30 | 31 | 32 | 33 |
  525. // +----+----+----+----+
  526. setModelData( model, modelTable( [
  527. [ '00', '01', '02', '03' ],
  528. [ { contents: '10', rowspan: 2 }, { contents: '11', colspan: 2, rowspan: 2 }, '13' ],
  529. [ '23' ],
  530. [ '30', '31', '32', '33' ]
  531. ] ) );
  532. tableSelection.setCellSelection(
  533. modelRoot.getNodeByPath( [ 0, 0, 2 ] ),
  534. modelRoot.getNodeByPath( [ 0, 1, 0 ] )
  535. );
  536. pasteTable( [
  537. [ 'aa', 'ab', 'ac' ],
  538. [ 'ba', 'bb', 'bc' ],
  539. [ 'ca', 'cb', 'cc' ]
  540. ] );
  541. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  542. [ 'aa', 'ab', 'ac', '03' ],
  543. [ 'ba', 'bb', 'bc', '13' ],
  544. [ 'ca', 'cb', 'cc', '23' ],
  545. [ '30', '31', '32', '33' ]
  546. ] ) );
  547. assertSelectedCells( model, [
  548. [ 1, 1, 1, 0 ],
  549. [ 1, 1, 1, 0 ],
  550. [ 1, 1, 1, 0 ],
  551. [ 0, 0, 0, 0 ]
  552. ] );
  553. } );
  554. } );
  555. describe( 'content and paste tables have spans', () => {
  556. it( 'handles pasting colspanned table over table with colspans (no colspan exceeds selection)', () => {
  557. setModelData( model, modelTable( [
  558. [ '00[]', '01', '02', '03' ],
  559. [ { colspan: 3, contents: '10' }, '13' ],
  560. [ { colspan: 2, contents: '20' }, '22', '23' ],
  561. [ '30', '31', { colspan: 2, contents: '31' } ]
  562. ] ) );
  563. tableSelection.setCellSelection(
  564. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  565. modelRoot.getNodeByPath( [ 0, 2, 1 ] )
  566. );
  567. pasteTable( [
  568. [ 'aa', { colspan: 2, contents: 'ab' } ],
  569. [ { colspan: 3, contents: 'ba' } ],
  570. [ 'ca', 'cb', 'cc' ]
  571. ] );
  572. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  573. [ 'aa', { colspan: 2, contents: 'ab' }, '03' ],
  574. [ { colspan: 3, contents: 'ba' }, '13' ],
  575. [ 'ca', 'cb', 'cc', '23' ],
  576. [ '30', '31', { colspan: 2, contents: '31' } ]
  577. ] ) );
  578. /* eslint-disable no-multi-spaces */
  579. assertSelectedCells( model, [
  580. [ 1, 1, 0 ],
  581. [ 1, 0 ],
  582. [ 1, 1, 1, 0 ],
  583. [ 0, 0, 0 ]
  584. ] );
  585. /* eslint-enable no-multi-spaces */
  586. } );
  587. it( 'handles pasting rowspanned table over table with rowspans (no rowspan exceeds selection)', () => {
  588. setModelData( model, modelTable( [
  589. [ { rowspan: 3, contents: '00' }, { rowspan: 2, contents: '01' }, '02', '03' ],
  590. [ { rowspan: 2, contents: '12' }, '13' ],
  591. [ '21', '23' ],
  592. [ '30', '31', '32', '33' ]
  593. ] ) );
  594. tableSelection.setCellSelection(
  595. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  596. modelRoot.getNodeByPath( [ 0, 2, 1 ] )
  597. );
  598. pasteTable( [
  599. [ 'aa', { rowspan: 3, contents: 'ab' }, { rowspan: 2, contents: 'ac' }, 'ad' ],
  600. [ { rowspan: 2, contents: 'ba' }, 'bd' ],
  601. [ 'cc', 'cd' ]
  602. ] );
  603. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  604. [ 'aa', { rowspan: 3, contents: 'ab' }, { rowspan: 2, contents: 'ac' }, 'ad' ],
  605. [ { rowspan: 2, contents: 'ba' }, 'bd' ],
  606. [ 'cc', 'cd' ],
  607. [ '30', '31', '32', '33' ]
  608. ] ) );
  609. /* eslint-disable no-multi-spaces */
  610. assertSelectedCells( model, [
  611. [ 1, 1, 1, 1 ],
  612. [ 1, 1 ],
  613. [ 1, 1 ],
  614. [ 0, 0, 0, 0 ]
  615. ] );
  616. /* eslint-enable no-multi-spaces */
  617. } );
  618. it( 'handles pasting multi-spanned table over table with multi-spans (no span exceeds selection)', () => {
  619. // +----+----+----+----+----+----+
  620. // | 00 | 02 | 03 | 05 |
  621. // + + + +----+
  622. // | | | | 15 |
  623. // +----+----+----+ +----+
  624. // | 20 | 21 | | 25 |
  625. // + +----+----+----+----+----+
  626. // | | 31 | 32 | 34 | 35 |
  627. // +----+----+----+----+----+----+
  628. // | 40 | 41 | 42 | 43 | 44 | 45 |
  629. // +----+----+----+----+----+----+
  630. setModelData( model, modelTable( [
  631. [
  632. { contents: '00', colspan: 2, rowspan: 2 },
  633. { contents: '02', rowspan: 2 },
  634. { contents: '03', colspan: 2, rowspan: 3 },
  635. '05'
  636. ],
  637. [ '15' ],
  638. [ { contents: '20', rowspan: 2 }, { contents: '21', colspan: 2 }, '25' ],
  639. [ '31', { contents: '32', colspan: 2 }, '34', '35' ],
  640. [ '40', '41', '42', '43', '44', '45' ]
  641. ] ) );
  642. tableSelection.setCellSelection(
  643. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  644. modelRoot.getNodeByPath( [ 0, 3, 2 ] )
  645. );
  646. // +----+----+----+----+----+
  647. // | aa | ac | ad | ae |
  648. // +----+----+----+----+ +
  649. // | ba | bb | |
  650. // +----+ +----+
  651. // | ca | | ce |
  652. // + +----+----+----+----+
  653. // | | db | dc | dd |
  654. // +----+----+----+----+----+
  655. pasteTable( [
  656. [ { contents: 'aa', colspan: 2 }, 'ac', 'ad', { contents: 'ae', rowspan: 2 } ],
  657. [ 'ba', { contents: 'bb', colspan: 3, rowspan: 2 } ],
  658. [ { contents: 'ca', rowspan: 2 }, 'ce' ],
  659. [ 'db', 'dc', { contents: 'dd', colspan: 2 } ]
  660. ] );
  661. // +----+----+----+----+----+----+
  662. // | aa | ac | ad | ae | 05 |
  663. // +----+----+----+----+ +----+
  664. // | ba | bb | | 15 |
  665. // +----+ +----+----+
  666. // | ca | | ce | 25 |
  667. // + +----+----+----+----+----+
  668. // | | db | dc | dd | 35 |
  669. // +----+----+----+----+----+----+
  670. // | 40 | 41 | 42 | 43 | 44 | 45 |
  671. // +----+----+----+----+----+----+
  672. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  673. [ { contents: 'aa', colspan: 2 }, 'ac', 'ad', { contents: 'ae', rowspan: 2 }, '05' ],
  674. [ 'ba', { contents: 'bb', colspan: 3, rowspan: 2 }, '15' ],
  675. [ { contents: 'ca', rowspan: 2 }, 'ce', '25' ],
  676. [ 'db', 'dc', { contents: 'dd', colspan: 2 }, '35' ],
  677. [ '40', '41', '42', '43', '44', '45' ]
  678. ] ) );
  679. /* eslint-disable no-multi-spaces */
  680. assertSelectedCells( model, [
  681. [ 1, 1, 1, 1, 0 ],
  682. [ 1, 1, 0 ],
  683. [ 1, 1, 0 ],
  684. [ 1, 1, 1, 0 ],
  685. [ 0, 0, 0, 0, 0, 0 ]
  686. ] );
  687. /* eslint-enable no-multi-spaces */
  688. } );
  689. // TODO: Skipped case - should allow pasting but no tools to compare areas (like in MergeCellsCommand).
  690. it.skip( 'handles pasting table that has cell with colspan (last row in selection is spanned)', () => {
  691. // +----+----+----+----+
  692. // | 00 | 01 | 02 | 03 |
  693. // +----+----+----+----+
  694. // | 10 | 11 | 13 |
  695. // + + +----+
  696. // | | | 23 |
  697. // +----+----+----+----+
  698. // | 30 | 31 | 32 | 33 |
  699. // +----+----+----+----+
  700. setModelData( model, modelTable( [
  701. [ '00', '01', '02', '03' ],
  702. [ { contents: '10', rowspan: 2 }, { contents: '11', colspan: 2, rowspan: 2 }, '13' ],
  703. [ '23' ],
  704. [ '30', '31', '32', '33' ]
  705. ] ) );
  706. tableSelection.setCellSelection(
  707. modelRoot.getNodeByPath( [ 0, 0, 2 ] ),
  708. modelRoot.getNodeByPath( [ 0, 1, 0 ] )
  709. );
  710. // +----+----+----+
  711. // | aa | ab | ac |
  712. // +----+----+----+
  713. // | ba | bc |
  714. // + +----+
  715. // | | cc |
  716. // +----+----+----+
  717. pasteTable( [
  718. [ 'aa', 'ab', 'ac' ],
  719. [ { contents: 'ba', colspan: 2, rowspan: 2 }, 'bc' ],
  720. [ 'cc' ]
  721. ] );
  722. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  723. [ '00', '01', '02', '03' ],
  724. [ '10', { colspan: 2, contents: 'aa' }, '13' ],
  725. [ '20', 'ba', 'bb', '23' ],
  726. [ '30', '31', '32', '33' ]
  727. ] ) );
  728. /* eslint-disable no-multi-spaces */
  729. assertSelectedCells( model, [
  730. [ 0, 0, 0, 0 ],
  731. [ 0, 1, 0 ],
  732. [ 0, 1, 1, 0 ],
  733. [ 0, 0, 0, 0 ]
  734. ] );
  735. /* eslint-enable no-multi-spaces */
  736. } );
  737. } );
  738. } );
  739. describe( 'pasted table is bigger than the selected area', () => {
  740. describe( 'no spans', () => {
  741. it( 'handles simple table paste to a simple table fragment - at the beginning of a table', () => {
  742. tableSelection.setCellSelection(
  743. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  744. modelRoot.getNodeByPath( [ 0, 1, 1 ] )
  745. );
  746. pasteTable( [
  747. [ 'aa', 'ab', 'ac' ],
  748. [ 'ba', 'bb', 'bc' ],
  749. [ 'ca', 'cb', 'cc' ]
  750. ] );
  751. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  752. [ 'aa', 'ab', '02', '03' ],
  753. [ 'ba', 'bb', '12', '13' ],
  754. [ '20', '21', '22', '23' ],
  755. [ '30', '31', '32', '33' ]
  756. ] ) );
  757. assertSelectedCells( model, [
  758. [ 1, 1, 0, 0 ],
  759. [ 1, 1, 0, 0 ],
  760. [ 0, 0, 0, 0 ],
  761. [ 0, 0, 0, 0 ]
  762. ] );
  763. } );
  764. it( 'handles simple table paste to a simple table fragment - at the end of a table', () => {
  765. tableSelection.setCellSelection(
  766. modelRoot.getNodeByPath( [ 0, 2, 2 ] ),
  767. modelRoot.getNodeByPath( [ 0, 3, 3 ] )
  768. );
  769. pasteTable( [
  770. [ 'aa', 'ab', 'ac' ],
  771. [ 'ba', 'bb', 'bc' ],
  772. [ 'ca', 'cb', 'cc' ]
  773. ] );
  774. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  775. [ '00', '01', '02', '03' ],
  776. [ '10', '11', '12', '13' ],
  777. [ '20', '21', 'aa', 'ab' ],
  778. [ '30', '31', 'ba', 'bb' ]
  779. ] ) );
  780. assertSelectedCells( model, [
  781. [ 0, 0, 0, 0 ],
  782. [ 0, 0, 0, 0 ],
  783. [ 0, 0, 1, 1 ],
  784. [ 0, 0, 1, 1 ]
  785. ] );
  786. } );
  787. it( 'handles simple table paste to a simple table fragment - in the middle of a table', () => {
  788. tableSelection.setCellSelection(
  789. modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
  790. modelRoot.getNodeByPath( [ 0, 2, 2 ] )
  791. );
  792. pasteTable( [
  793. [ 'aa', 'ab', 'ac' ],
  794. [ 'ba', 'bb', 'bc' ],
  795. [ 'ca', 'cb', 'cc' ]
  796. ] );
  797. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  798. [ '00', '01', '02', '03' ],
  799. [ '10', 'aa', 'ab', '13' ],
  800. [ '20', 'ba', 'bb', '23' ],
  801. [ '30', '31', '32', '33' ]
  802. ] ) );
  803. assertSelectedCells( model, [
  804. [ 0, 0, 0, 0 ],
  805. [ 0, 1, 1, 0 ],
  806. [ 0, 1, 1, 0 ],
  807. [ 0, 0, 0, 0 ]
  808. ] );
  809. } );
  810. it( 'handles paste to a simple row fragment - in the middle of a table', () => {
  811. tableSelection.setCellSelection(
  812. modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
  813. modelRoot.getNodeByPath( [ 0, 1, 2 ] )
  814. );
  815. pasteTable( [
  816. [ 'aa', 'ab', 'ac' ],
  817. [ 'ba', 'bb', 'bc' ],
  818. [ 'ca', 'cb', 'cc' ]
  819. ] );
  820. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  821. [ '00', '01', '02', '03' ],
  822. [ '10', 'aa', 'ab', '13' ],
  823. [ '20', '21', '22', '23' ],
  824. [ '30', '31', '32', '33' ]
  825. ] ) );
  826. assertSelectedCells( model, [
  827. [ 0, 0, 0, 0 ],
  828. [ 0, 1, 1, 0 ],
  829. [ 0, 0, 0, 0 ],
  830. [ 0, 0, 0, 0 ]
  831. ] );
  832. } );
  833. it( 'handles paste to a simple column fragment - in the middle of a table', () => {
  834. tableSelection.setCellSelection(
  835. modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
  836. modelRoot.getNodeByPath( [ 0, 2, 1 ] )
  837. );
  838. pasteTable( [
  839. [ 'aa', 'ab', 'ac' ],
  840. [ 'ba', 'bb', 'bc' ],
  841. [ 'ca', 'cb', 'cc' ]
  842. ] );
  843. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  844. [ '00', '01', '02', '03' ],
  845. [ '10', 'aa', '12', '13' ],
  846. [ '20', 'ba', '22', '23' ],
  847. [ '30', '31', '32', '33' ]
  848. ] ) );
  849. assertSelectedCells( model, [
  850. [ 0, 0, 0, 0 ],
  851. [ 0, 1, 0, 0 ],
  852. [ 0, 1, 0, 0 ],
  853. [ 0, 0, 0, 0 ]
  854. ] );
  855. } );
  856. it( 'handles simple table paste to a simple table fragment - whole table selected', () => {
  857. tableSelection.setCellSelection(
  858. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  859. modelRoot.getNodeByPath( [ 0, 3, 3 ] )
  860. );
  861. pasteTable( [
  862. [ 'aa', 'ab', 'ac', 'ad', 'ae' ],
  863. [ 'ba', 'bb', 'bc', 'bd', 'be' ],
  864. [ 'ca', 'cb', 'cc', 'cd', 'ce' ],
  865. [ 'da', 'db', 'dc', 'dd', 'de' ],
  866. [ 'ea', 'eb', 'ec', 'ed', 'ee' ]
  867. ] );
  868. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  869. [ 'aa', 'ab', 'ac', 'ad' ],
  870. [ 'ba', 'bb', 'bc', 'bd' ],
  871. [ 'ca', 'cb', 'cc', 'cd' ],
  872. [ 'da', 'db', 'dc', 'dd' ]
  873. ] ) );
  874. assertSelectedCells( model, [
  875. [ 1, 1, 1, 1 ],
  876. [ 1, 1, 1, 1 ],
  877. [ 1, 1, 1, 1 ],
  878. [ 1, 1, 1, 1 ]
  879. ] );
  880. } );
  881. } );
  882. describe( 'pasted table has spans', () => {
  883. it( 'handles pasting table that has cell with colspan', () => {
  884. tableSelection.setCellSelection(
  885. modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
  886. modelRoot.getNodeByPath( [ 0, 2, 2 ] )
  887. );
  888. pasteTable( [
  889. [ { colspan: 3, contents: 'aa' } ],
  890. [ 'ba', 'bb', 'bc' ]
  891. ] );
  892. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  893. [ '00', '01', '02', '03' ],
  894. [ '10', { colspan: 2, contents: 'aa' }, '13' ],
  895. [ '20', 'ba', 'bb', '23' ],
  896. [ '30', '31', '32', '33' ]
  897. ] ) );
  898. /* eslint-disable no-multi-spaces */
  899. assertSelectedCells( model, [
  900. [ 0, 0, 0, 0 ],
  901. [ 0, 1, 0 ],
  902. [ 0, 1, 1, 0 ],
  903. [ 0, 0, 0, 0 ]
  904. ] );
  905. /* eslint-enable no-multi-spaces */
  906. } );
  907. it( 'handles pasting table that has many cells with various colspan', () => {
  908. tableSelection.setCellSelection(
  909. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  910. modelRoot.getNodeByPath( [ 0, 3, 2 ] )
  911. );
  912. pasteTable( [
  913. [ 'aa', { colspan: 3, contents: 'ab' } ],
  914. [ { colspan: 4, contents: 'ba' } ],
  915. [ 'ca', 'cb', 'cc', 'cd' ],
  916. [ { colspan: 2, contents: 'da' }, 'dc', 'dd' ]
  917. ] );
  918. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  919. [ 'aa', { colspan: 2, contents: 'ab' }, '03' ],
  920. [ { colspan: 3, contents: 'ba' }, '13' ],
  921. [ 'ca', 'cb', 'cc', '23' ],
  922. [ { colspan: 2, contents: 'da' }, 'dc', '33' ]
  923. ] ) );
  924. /* eslint-disable no-multi-spaces */
  925. assertSelectedCells( model, [
  926. [ 1, 1, 0 ],
  927. [ 1, 0 ],
  928. [ 1, 1, 1, 0 ],
  929. [ 1, 1, 0 ]
  930. ] );
  931. /* eslint-enable no-multi-spaces */
  932. } );
  933. it( 'handles pasting table that has cell with rowspan', () => {
  934. tableSelection.setCellSelection(
  935. modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
  936. modelRoot.getNodeByPath( [ 0, 2, 2 ] )
  937. );
  938. pasteTable( [
  939. [ { rowspan: 3, contents: 'aa' }, 'ab' ],
  940. [ 'bb' ],
  941. [ 'cb' ]
  942. ] );
  943. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  944. [ '00', '01', '02', '03' ],
  945. [ '10', { rowspan: 2, contents: 'aa' }, 'ab', '13' ],
  946. [ '20', 'bb', '23' ],
  947. [ '30', '31', '32', '33' ]
  948. ] ) );
  949. /* eslint-disable no-multi-spaces */
  950. assertSelectedCells( model, [
  951. [ 0, 0, 0, 0 ],
  952. [ 0, 1, 1, 0 ],
  953. [ 0, 1, 0 ],
  954. [ 0, 0, 0, 0 ]
  955. ] );
  956. /* eslint-enable no-multi-spaces */
  957. } );
  958. it( 'handles pasting table that has many cells with various rowspan', () => {
  959. tableSelection.setCellSelection(
  960. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  961. modelRoot.getNodeByPath( [ 0, 2, 3 ] )
  962. );
  963. pasteTable( [
  964. [ 'aa', { rowspan: 3, contents: 'ab' }, { rowspan: 2, contents: 'ac' }, 'ad', 'ae' ],
  965. [ { rowspan: 3, contents: 'ba' }, 'bd', 'be' ],
  966. [ 'cc', 'cd', 'ce' ],
  967. [ 'da', 'db', 'dc', 'dd' ]
  968. ] );
  969. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  970. [ 'aa', { rowspan: 3, contents: 'ab' }, { rowspan: 2, contents: 'ac' }, 'ad' ],
  971. [ { rowspan: 2, contents: 'ba' }, 'bd' ],
  972. [ 'cc', 'cd' ],
  973. [ '30', '31', '32', '33' ]
  974. ] ) );
  975. /* eslint-disable no-multi-spaces */
  976. assertSelectedCells( model, [
  977. [ 1, 1, 1, 1 ],
  978. [ 1, 1 ],
  979. [ 1, 1 ],
  980. [ 0, 0, 0 ]
  981. ] );
  982. /* eslint-enable no-multi-spaces */
  983. } );
  984. it( 'handles pasting multi-spanned table', () => {
  985. setModelData( model, modelTable( [
  986. [ '00', '01', '02', '03', '04', '05' ],
  987. [ '10', '11', '12', '13', '14', '15' ],
  988. [ '20', '21', '22', '23', '24', '25' ],
  989. [ '30', '31', '32', '33', '34', '35' ],
  990. [ '40', '41', '42', '43', '44', '45' ]
  991. ] ) );
  992. tableSelection.setCellSelection(
  993. modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
  994. modelRoot.getNodeByPath( [ 0, 3, 3 ] )
  995. );
  996. // +----+----+----+----+----+
  997. // | aa | ac | ad | ae |
  998. // +----+----+----+----+ +
  999. // | ba | bb | |
  1000. // +----+ +----+
  1001. // | ca | | ce |
  1002. // + +----+----+----+----+
  1003. // | | db | dc | dd |
  1004. // +----+----+----+----+----+
  1005. pasteTable( [
  1006. [ { contents: 'aa', colspan: 2 }, 'ac', 'ad', { contents: 'ae', rowspan: 2 } ],
  1007. [ 'ba', { contents: 'bb', colspan: 3, rowspan: 2 } ],
  1008. [ { contents: 'ca', rowspan: 2 }, 'ce' ],
  1009. [ 'db', 'dc', { contents: 'dd', colspan: 2 } ]
  1010. ] );
  1011. // +----+----+----+----+----+----+
  1012. // | 00 | 01 | 02 | 03 | 04 | 05 |
  1013. // +----+----+----+----+----+----+
  1014. // | 10 | aa | ac | 14 | 15 |
  1015. // +----+----+----+----+----+----+
  1016. // | 20 | ba | bb | 24 | 25 |
  1017. // +----+----+ +----+----+
  1018. // | 30 | ca | | 34 | 35 |
  1019. // +----+----+----+----+----+----+
  1020. // | 40 | 41 | 42 | 43 | 44 | 45 |
  1021. // +----+----+----+----+----+----+
  1022. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  1023. [ '00', '01', '02', '03', '04', '05' ],
  1024. [ '10', { contents: 'aa', colspan: 2 }, 'ac', '14', '15' ],
  1025. [ '20', 'ba', { contents: 'bb', colspan: 2, rowspan: 2 }, '24', '25' ],
  1026. [ '30', 'ca', '34', '35' ],
  1027. [ '40', '41', '42', '43', '44', '45' ]
  1028. ] ) );
  1029. /* eslint-disable no-multi-spaces */
  1030. assertSelectedCells( model, [
  1031. [ 0, 0, 0, 0, 0, 0 ],
  1032. [ 0, 1, 1, 0 ],
  1033. [ 0, 1, 1, 0 ],
  1034. [ 0, 1, 0 ],
  1035. [ 0, 0, 0, 0, 0, 0 ]
  1036. ] );
  1037. /* eslint-enable no-multi-spaces */
  1038. } );
  1039. } );
  1040. } );
  1041. describe( 'pasted table is smaller than the selected area', () => {
  1042. it( 'blocks this case', () => {
  1043. tableSelection.setCellSelection(
  1044. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  1045. modelRoot.getNodeByPath( [ 0, 3, 3 ] )
  1046. );
  1047. pasteTable( [
  1048. [ 'aa', 'ab' ],
  1049. [ 'ba', 'bb' ]
  1050. ] );
  1051. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  1052. [ '00', '01', '02', '03' ],
  1053. [ '10', '11', '12', '13' ],
  1054. [ '20', '21', '22', '23' ],
  1055. [ '30', '31', '32', '33' ]
  1056. ] ) );
  1057. assertSelectedCells( model, [
  1058. [ 1, 1, 1, 1 ],
  1059. [ 1, 1, 1, 1 ],
  1060. [ 1, 1, 1, 1 ],
  1061. [ 1, 1, 1, 1 ]
  1062. ] );
  1063. } );
  1064. } );
  1065. } );
  1066. describe( 'Clipboard integration - paste (content scenarios)', () => {
  1067. it( 'handles multiple paragraphs', async () => {
  1068. await createEditor();
  1069. setModelData( model, modelTable( [
  1070. [ '00', '01', '02' ],
  1071. [ '01', '11', '12' ],
  1072. [ '02', '21', '22' ]
  1073. ] ) );
  1074. tableSelection.setCellSelection(
  1075. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  1076. modelRoot.getNodeByPath( [ 0, 1, 1 ] )
  1077. );
  1078. pasteTable( [
  1079. [ '<p>a</p><p>a</p><p>a</p>', 'ab' ],
  1080. [ 'ba', 'bb' ]
  1081. ] );
  1082. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  1083. [ '<paragraph>a</paragraph><paragraph>a</paragraph><paragraph>a</paragraph>', 'ab', '02' ],
  1084. [ 'ba', 'bb', '12' ],
  1085. [ '02', '21', '22' ]
  1086. ] ) );
  1087. } );
  1088. it( 'handles image in table cell', async () => {
  1089. await createEditor( [ ImageEditing, ImageCaptionEditing ] );
  1090. setModelData( model, modelTable( [
  1091. [ '00', '01', '02' ],
  1092. [ '01', '11', '12' ],
  1093. [ '02', '21', '22' ]
  1094. ] ) );
  1095. tableSelection.setCellSelection(
  1096. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  1097. modelRoot.getNodeByPath( [ 0, 1, 1 ] )
  1098. );
  1099. pasteTable( [
  1100. [ '<img src="/assets/sample.jpg">', 'ab' ],
  1101. [ 'ba', 'bb' ]
  1102. ] );
  1103. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  1104. [ '<image src="/assets/sample.jpg"><caption></caption></image>', 'ab', '02' ],
  1105. [ 'ba', 'bb', '12' ],
  1106. [ '02', '21', '22' ]
  1107. ] ) );
  1108. } );
  1109. } );
  1110. async function createEditor( extraPlugins = [] ) {
  1111. editor = await ClassicTestEditor.create( element, {
  1112. plugins: [ TableEditing, TableClipboard, Paragraph, Clipboard, ...extraPlugins ]
  1113. } );
  1114. model = editor.model;
  1115. modelRoot = model.document.getRoot();
  1116. viewDocument = editor.editing.view.document;
  1117. tableSelection = editor.plugins.get( 'TableSelection' );
  1118. }
  1119. function pasteTable( tableData ) {
  1120. const data = {
  1121. dataTransfer: createDataTransfer(),
  1122. preventDefault: sinon.spy(),
  1123. stopPropagation: sinon.spy()
  1124. };
  1125. data.dataTransfer.setData( 'text/html', viewTable( tableData ) );
  1126. viewDocument.fire( 'paste', data );
  1127. return data;
  1128. }
  1129. function createDataTransfer() {
  1130. const store = new Map();
  1131. return {
  1132. setData( type, data ) {
  1133. store.set( type, data );
  1134. },
  1135. getData( type ) {
  1136. return store.get( type );
  1137. }
  1138. };
  1139. }
  1140. } );