tableclipboard-paste.js 42 KB

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