tableclipboard-paste.js 40 KB

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