8
0

tableclipboard.js 62 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999
  1. /**
  2. * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  4. */
  5. /* globals document */
  6. import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
  7. import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
  8. import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard';
  9. import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
  10. import TableEditing from '../src/tableediting';
  11. import { assertSelectedCells, modelTable, viewTable } from './_utils/utils';
  12. import { assertEqualMarkup } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
  13. import TableClipboard from '../src/tableclipboard';
  14. describe( 'table clipboard', () => {
  15. let editor, model, modelRoot, tableSelection, viewDocument, element;
  16. beforeEach( async () => {
  17. element = document.createElement( 'div' );
  18. document.body.appendChild( element );
  19. editor = await ClassicTestEditor.create( element, {
  20. plugins: [ TableEditing, TableClipboard, Paragraph, Clipboard ]
  21. } );
  22. model = editor.model;
  23. modelRoot = model.document.getRoot();
  24. viewDocument = editor.editing.view.document;
  25. tableSelection = editor.plugins.get( 'TableSelection' );
  26. setModelData( model, modelTable( [
  27. [ '00[]', '01', '02' ],
  28. [ '10', '11', '12' ],
  29. [ '20', '21', '22' ]
  30. ] ) );
  31. } );
  32. afterEach( async () => {
  33. await editor.destroy();
  34. element.remove();
  35. } );
  36. describe( 'Clipboard integration', () => {
  37. describe( 'copy', () => {
  38. it( 'should do nothing for normal selection in table', () => {
  39. const dataTransferMock = createDataTransfer();
  40. const spy = sinon.spy();
  41. viewDocument.on( 'clipboardOutput', spy );
  42. viewDocument.fire( 'copy', {
  43. dataTransfer: dataTransferMock,
  44. preventDefault: sinon.spy()
  45. } );
  46. sinon.assert.calledOnce( spy );
  47. } );
  48. it( 'should copy selected table cells as a standalone table', () => {
  49. const preventDefaultSpy = sinon.spy();
  50. tableSelection.setCellSelection(
  51. modelRoot.getNodeByPath( [ 0, 0, 1 ] ),
  52. modelRoot.getNodeByPath( [ 0, 1, 2 ] )
  53. );
  54. const data = {
  55. dataTransfer: createDataTransfer(),
  56. preventDefault: preventDefaultSpy
  57. };
  58. viewDocument.fire( 'copy', data );
  59. sinon.assert.calledOnce( preventDefaultSpy );
  60. expect( data.dataTransfer.getData( 'text/html' ) ).to.equal( viewTable( [
  61. [ '01', '02' ],
  62. [ '11', '12' ]
  63. ] ) );
  64. } );
  65. it( 'should trim selected table to a selection rectangle (inner cell with colspan, no colspan after trim)', () => {
  66. setModelData( model, modelTable( [
  67. [ '00[]', '01', '02' ],
  68. [ '10', { contents: '11', colspan: 2 } ],
  69. [ '20', '21', '22' ]
  70. ] ) );
  71. tableSelection.setCellSelection(
  72. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  73. modelRoot.getNodeByPath( [ 0, 2, 1 ] )
  74. );
  75. assertClipboardContentOnMethod( 'copy', viewTable( [
  76. [ '00', '01' ],
  77. [ '10', '11' ],
  78. [ '20', '21' ]
  79. ] ) );
  80. } );
  81. it( 'should trim selected table to a selection rectangle (inner cell with colspan, has colspan after trim)', () => {
  82. setModelData( model, modelTable( [
  83. [ '00[]', '01', '02' ],
  84. [ { contents: '10', colspan: 3 } ],
  85. [ '20', '21', '22' ]
  86. ] ) );
  87. tableSelection.setCellSelection(
  88. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  89. modelRoot.getNodeByPath( [ 0, 2, 1 ] )
  90. );
  91. assertClipboardContentOnMethod( 'copy', viewTable( [
  92. [ '00', '01' ],
  93. [ { contents: '10', colspan: 2 } ],
  94. [ '20', '21' ]
  95. ] ) );
  96. } );
  97. it( 'should trim selected table to a selection rectangle (inner cell with rowspan, no colspan after trim)', () => {
  98. setModelData( model, modelTable( [
  99. [ '00[]', '01', '02' ],
  100. [ '10', { contents: '11', rowspan: 2 }, '12' ],
  101. [ '20', '21', '22' ]
  102. ] ) );
  103. tableSelection.setCellSelection(
  104. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  105. modelRoot.getNodeByPath( [ 0, 1, 2 ] )
  106. );
  107. assertClipboardContentOnMethod( 'copy', viewTable( [
  108. [ '00', '01', '02' ],
  109. [ '10', '11', '12' ]
  110. ] ) );
  111. } );
  112. it( 'should trim selected table to a selection rectangle (inner cell with rowspan, has rowspan after trim)', () => {
  113. setModelData( model, modelTable( [
  114. [ '00[]', { contents: '01', rowspan: 3 }, '02' ],
  115. [ '10', '12' ],
  116. [ '20', '22' ]
  117. ] ) );
  118. tableSelection.setCellSelection(
  119. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  120. modelRoot.getNodeByPath( [ 0, 1, 1 ] )
  121. );
  122. assertClipboardContentOnMethod( 'copy', viewTable( [
  123. [ '00', { contents: '01', rowspan: 2 }, '02' ],
  124. [ '10', '12' ]
  125. ] ) );
  126. } );
  127. it( 'should prepend spanned columns with empty cells (outside cell with colspan)', () => {
  128. setModelData( model, modelTable( [
  129. [ '00[]', '01', '02' ],
  130. [ { contents: '10', colspan: 2 }, '12' ],
  131. [ '20', '21', '22' ]
  132. ] ) );
  133. tableSelection.setCellSelection(
  134. modelRoot.getNodeByPath( [ 0, 0, 1 ] ),
  135. modelRoot.getNodeByPath( [ 0, 2, 2 ] )
  136. );
  137. assertClipboardContentOnMethod( 'copy', viewTable( [
  138. [ '01', '02' ],
  139. [ ' ', '12' ],
  140. [ '21', '22' ]
  141. ] ) );
  142. } );
  143. it( 'should prepend spanned columns with empty cells (outside cell with rowspan)', () => {
  144. setModelData( model, modelTable( [
  145. [ '00[]', { contents: '01', rowspan: 2 }, '02' ],
  146. [ '10', '12' ],
  147. [ '20', '21', '22' ]
  148. ] ) );
  149. tableSelection.setCellSelection(
  150. modelRoot.getNodeByPath( [ 0, 1, 0 ] ),
  151. modelRoot.getNodeByPath( [ 0, 2, 2 ] )
  152. );
  153. assertClipboardContentOnMethod( 'copy', viewTable( [
  154. [ '10', ' ', '12' ],
  155. [ '20', '21', '22' ]
  156. ] ) );
  157. } );
  158. it( 'should fix selected table to a selection rectangle (hardcore case)', () => {
  159. // This test check how previous simple rules run together (mixed prepending and trimming).
  160. // In the example below a selection is set from cell "32" to "88"
  161. //
  162. // Input table: Copied table:
  163. //
  164. // +----+----+----+----+----+----+----+----+----+
  165. // | 00 | 01 | 02 | 03 | 04 | 06 | 07 | 08 |
  166. // +----+----+ +----+ +----+----+----+
  167. // | 10 | 11 | | 13 | | 16 | 17 | 18 |
  168. // +----+----+ +----+ +----+----+----+ +----+----+----+---------+----+----+
  169. // | 20 | 21 | | 23 | | 26 | | 21 | | 23 | | | 26 | |
  170. // +----+----+ +----+ +----+----+----+ +----+----+----+----+----+----+----+
  171. // | 30 | 31 | | 33 | | 36 | 37 | | 31 | | 33 | | | 36 | 37 |
  172. // +----+----+----+----+ +----+----+----+ +----+----+----+----+----+----+----+
  173. // | 40 | | 46 | 47 | 48 | | | | | | | 46 | 47 |
  174. // +----+----+----+----+ +----+----+----+ ==> +----+----+----+----+----+----+----+
  175. // | 50 | 51 | 52 | 53 | | 56 | 57 | 58 | | 51 | 52 | 53 | | | 56 | 57 |
  176. // +----+----+----+----+----+----+ +----+----+ +----+----+----+----+----+----+----+
  177. // | 60 | 61 | 64 | 65 | | 67 | 68 | | 61 | | | 64 | 65 | | 67 |
  178. // +----+----+----+----+----+----+ +----+----+ +----+----+----+----+----+----+----+
  179. // | 70 | 71 | 72 | 73 | 74 | 75 | | 77 | 78 | | 71 | 72 | 73 | 74 | 75 | | 77 |
  180. // +----+ +----+----+----+----+ +----+----+ +----+----+----+----+----+----+----+
  181. // | 80 | | 82 | 83 | 84 | 85 | | 87 | 88 |
  182. // +----+----+----+----+----+----+----+----+----+
  183. //
  184. setModelData( model, modelTable( [
  185. [ '00', '01', { contents: '02', rowspan: 4 }, '03', { contents: '04', colspan: 2, rowspan: 7 }, '07', '07', '08' ],
  186. [ '10', '11', '13', '17', '17', '18' ],
  187. [ '20', '21', '23', { contents: '27', colspan: 3 } ],
  188. [ '30', '31', '33', '37', { contents: '37', colspan: 2 } ],
  189. [ { contents: '40', colspan: 4 }, '47', '47', '48' ],
  190. [ '50', '51', '52', '53', { contents: '57', rowspan: 4 }, '57', '58' ],
  191. [ '60', { contents: '61', colspan: 3 }, '67', '68' ],
  192. [ '70', { contents: '71', rowspan: 2 }, '72', '73', '74', '75', '77', '78' ],
  193. [ '80', '82', '83', '84', '85', '87', '88' ]
  194. ] ) );
  195. tableSelection.setCellSelection(
  196. modelRoot.getNodeByPath( [ 0, 2, 1 ] ),
  197. modelRoot.getNodeByPath( [ 0, 7, 6 ] )
  198. );
  199. assertClipboardContentOnMethod( 'copy', viewTable( [
  200. [ '21', ' ', '23', ' ', ' ', { contents: '27', colspan: 2 } ],
  201. [ '31', ' ', '33', ' ', ' ', '37', '37' ],
  202. [ ' ', ' ', ' ', ' ', ' ', '47', '47' ],
  203. [ '51', '52', '53', ' ', ' ', { contents: '57', rowspan: 3 }, '57' ],
  204. [ { contents: '61', colspan: 3 }, ' ', ' ', ' ', '67' ],
  205. [ '71', '72', '73', '74', '75', '77' ]
  206. ] ) );
  207. } );
  208. it( 'should update table heading attributes (selection with headings)', () => {
  209. setModelData( model, modelTable( [
  210. [ '00', '01', '02', '03', '04' ],
  211. [ '10', '11', '12', '13', '14' ],
  212. [ '20', '21', '22', '23', '24' ],
  213. [ '30', '31', '32', '33', '34' ],
  214. [ '40', '41', '42', '43', '44' ]
  215. ], { headingRows: 3, headingColumns: 2 } ) );
  216. tableSelection.setCellSelection(
  217. modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
  218. modelRoot.getNodeByPath( [ 0, 3, 3 ] )
  219. );
  220. assertClipboardContentOnMethod( 'copy', viewTable( [
  221. [ '11', '12', '13' ],
  222. [ '21', '22', '23' ],
  223. [ { contents: '31', isHeading: true }, '32', '33' ] // TODO: bug in viewTable
  224. ], { headingRows: 2, headingColumns: 1 } ) );
  225. } );
  226. it( 'should update table heading attributes (selection without headings)', () => {
  227. setModelData( model, modelTable( [
  228. [ '00', '01', '02', '03', '04' ],
  229. [ '10', '11', '12', '13', '14' ],
  230. [ '20', '21', '22', '23', '24' ],
  231. [ '30', '31', '32', '33', '34' ],
  232. [ '40', '41', '42', '43', '44' ]
  233. ], { headingRows: 3, headingColumns: 2 } ) );
  234. tableSelection.setCellSelection(
  235. modelRoot.getNodeByPath( [ 0, 3, 2 ] ),
  236. modelRoot.getNodeByPath( [ 0, 4, 4 ] )
  237. );
  238. assertClipboardContentOnMethod( 'copy', viewTable( [
  239. [ '32', '33', '34' ],
  240. [ '42', '43', '44' ]
  241. ] ) );
  242. } );
  243. } );
  244. describe( 'cut', () => {
  245. it( 'should not block clipboardOutput if no multi-cell selection', () => {
  246. setModelData( model, modelTable( [
  247. [ '[00]', '01', '02' ],
  248. [ '10', '11', '12' ],
  249. [ '20', '21', '22' ]
  250. ] ) );
  251. const dataTransferMock = createDataTransfer();
  252. viewDocument.fire( 'cut', {
  253. dataTransfer: dataTransferMock,
  254. preventDefault: sinon.spy()
  255. } );
  256. expect( dataTransferMock.getData( 'text/html' ) ).to.equal( '00' );
  257. } );
  258. it( 'should be preventable', () => {
  259. tableSelection.setCellSelection(
  260. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  261. modelRoot.getNodeByPath( [ 0, 1, 1 ] )
  262. );
  263. viewDocument.on( 'clipboardOutput', evt => evt.stop(), { priority: 'high' } );
  264. viewDocument.fire( 'cut', {
  265. dataTransfer: createDataTransfer(),
  266. preventDefault: sinon.spy()
  267. } );
  268. assertEqualMarkup( getModelData( model ), modelTable( [
  269. [ { contents: '00', isSelected: true }, { contents: '01', isSelected: true }, '02' ],
  270. [ { contents: '10', isSelected: true }, { contents: '11', isSelected: true }, '12' ],
  271. [ '20', '21', '22' ]
  272. ] ) );
  273. } );
  274. it( 'is clears selected table cells', () => {
  275. const spy = sinon.spy();
  276. viewDocument.on( 'clipboardOutput', spy );
  277. tableSelection.setCellSelection(
  278. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  279. modelRoot.getNodeByPath( [ 0, 1, 1 ] )
  280. );
  281. viewDocument.fire( 'cut', {
  282. dataTransfer: createDataTransfer(),
  283. preventDefault: sinon.spy()
  284. } );
  285. assertEqualMarkup( getModelData( model ), modelTable( [
  286. [ '', '', '02' ],
  287. [ '', '[]', '12' ],
  288. [ '20', '21', '22' ]
  289. ] ) );
  290. } );
  291. it( 'should copy selected table cells as a standalone table', () => {
  292. const preventDefaultSpy = sinon.spy();
  293. tableSelection.setCellSelection(
  294. modelRoot.getNodeByPath( [ 0, 0, 1 ] ),
  295. modelRoot.getNodeByPath( [ 0, 1, 2 ] )
  296. );
  297. const data = {
  298. dataTransfer: createDataTransfer(),
  299. preventDefault: preventDefaultSpy
  300. };
  301. viewDocument.fire( 'cut', data );
  302. sinon.assert.calledOnce( preventDefaultSpy );
  303. expect( data.dataTransfer.getData( 'text/html' ) ).to.equal( viewTable( [
  304. [ '01', '02' ],
  305. [ '11', '12' ]
  306. ] ) );
  307. } );
  308. it( 'should be disabled in a readonly mode', () => {
  309. const preventDefaultStub = sinon.stub();
  310. editor.isReadOnly = true;
  311. tableSelection.setCellSelection(
  312. modelRoot.getNodeByPath( [ 0, 0, 1 ] ),
  313. modelRoot.getNodeByPath( [ 0, 1, 2 ] )
  314. );
  315. const data = {
  316. dataTransfer: createDataTransfer(),
  317. preventDefault: preventDefaultStub
  318. };
  319. viewDocument.fire( 'cut', data );
  320. editor.isReadOnly = false;
  321. expect( data.dataTransfer.getData( 'text/html' ) ).to.be.undefined;
  322. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  323. [ '00', '01', '02' ],
  324. [ '10', '11', '12' ],
  325. [ '20', '21', '22' ]
  326. ] ) );
  327. sinon.assert.calledOnce( preventDefaultStub );
  328. } );
  329. } );
  330. describe( 'paste', () => {
  331. beforeEach( () => {
  332. setModelData( model, modelTable( [
  333. [ '00[]', '01', '02', '03' ],
  334. [ '10', '11', '12', '13' ],
  335. [ '20', '21', '22', '23' ],
  336. [ '30', '31', '32', '33' ]
  337. ] ) );
  338. } );
  339. it( 'should be disabled in a readonly mode', () => {
  340. editor.isReadOnly = true;
  341. tableSelection.setCellSelection(
  342. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  343. modelRoot.getNodeByPath( [ 0, 1, 1 ] )
  344. );
  345. const data = pasteTable( [
  346. [ 'aa', 'ab' ],
  347. [ 'ba', 'bb' ]
  348. ] );
  349. editor.isReadOnly = false;
  350. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  351. [ '00', '01', '02', '03' ],
  352. [ '10', '11', '12', '13' ],
  353. [ '20', '21', '22', '23' ],
  354. [ '30', '31', '32', '33' ]
  355. ] ) );
  356. sinon.assert.calledOnce( data.preventDefault );
  357. } );
  358. it( 'should allow normal paste if no table cells are selected', () => {
  359. const data = {
  360. dataTransfer: createDataTransfer(),
  361. preventDefault: sinon.spy(),
  362. stopPropagation: sinon.spy()
  363. };
  364. data.dataTransfer.setData( 'text/html', '<p>foo</p>' );
  365. viewDocument.fire( 'paste', data );
  366. editor.isReadOnly = false;
  367. assertEqualMarkup( getModelData( model ), modelTable( [
  368. [ '00foo[]', '01', '02', '03' ],
  369. [ '10', '11', '12', '13' ],
  370. [ '20', '21', '22', '23' ],
  371. [ '30', '31', '32', '33' ]
  372. ] ) );
  373. } );
  374. it( 'should block non-rectangular selection', () => {
  375. setModelData( model, modelTable( [
  376. [ { contents: '00', colspan: 3 } ],
  377. [ '10', '11', '12' ],
  378. [ '20', '21', '22' ]
  379. ] ) );
  380. tableSelection.setCellSelection(
  381. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  382. modelRoot.getNodeByPath( [ 0, 1, 1 ] )
  383. );
  384. pasteTable( [
  385. [ 'aa', 'ab' ],
  386. [ 'ba', 'bb' ]
  387. ] );
  388. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  389. [ { contents: '00', colspan: 3 } ],
  390. [ '10', '11', '12' ],
  391. [ '20', '21', '22' ]
  392. ] ) );
  393. } );
  394. describe( 'single cell selected', () => {
  395. it( 'blocks this case', () => {
  396. setModelData( model, modelTable( [
  397. [ '00', '01', '02' ],
  398. [ '10', '11', '12' ],
  399. [ '20', '21', '22' ]
  400. ] ) );
  401. tableSelection.setCellSelection(
  402. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  403. modelRoot.getNodeByPath( [ 0, 0, 0 ] )
  404. );
  405. pasteTable( [
  406. [ 'aa', 'ab' ],
  407. [ 'ba', 'bb' ]
  408. ] );
  409. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  410. [ '00', '01', '02' ],
  411. [ '10', '11', '12' ],
  412. [ '20', '21', '22' ]
  413. ] ) );
  414. } );
  415. } );
  416. describe( 'pasted table is equal to the selected area', () => {
  417. describe( 'no spans', () => {
  418. it( 'handles simple table paste to a simple table fragment - at the beginning of a table', () => {
  419. tableSelection.setCellSelection(
  420. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  421. modelRoot.getNodeByPath( [ 0, 1, 1 ] )
  422. );
  423. pasteTable( [
  424. [ 'aa', 'ab' ],
  425. [ 'ba', 'bb' ]
  426. ] );
  427. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  428. [ 'aa', 'ab', '02', '03' ],
  429. [ 'ba', 'bb', '12', '13' ],
  430. [ '20', '21', '22', '23' ],
  431. [ '30', '31', '32', '33' ]
  432. ] ) );
  433. assertSelectedCells( model, [
  434. [ 1, 1, 0, 0 ],
  435. [ 1, 1, 0, 0 ],
  436. [ 0, 0, 0, 0 ],
  437. [ 0, 0, 0, 0 ]
  438. ] );
  439. } );
  440. it( 'handles simple table paste to a simple table fragment - at the end of a table', () => {
  441. tableSelection.setCellSelection(
  442. modelRoot.getNodeByPath( [ 0, 2, 2 ] ),
  443. modelRoot.getNodeByPath( [ 0, 3, 3 ] )
  444. );
  445. pasteTable( [
  446. [ 'aa', 'ab' ],
  447. [ 'ba', 'bb' ]
  448. ] );
  449. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  450. [ '00', '01', '02', '03' ],
  451. [ '10', '11', '12', '13' ],
  452. [ '20', '21', 'aa', 'ab' ],
  453. [ '30', '31', 'ba', 'bb' ]
  454. ] ) );
  455. assertSelectedCells( model, [
  456. [ 0, 0, 0, 0 ],
  457. [ 0, 0, 0, 0 ],
  458. [ 0, 0, 1, 1 ],
  459. [ 0, 0, 1, 1 ]
  460. ] );
  461. } );
  462. it( 'handles simple table paste to a simple table fragment - in the middle of a table', () => {
  463. tableSelection.setCellSelection(
  464. modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
  465. modelRoot.getNodeByPath( [ 0, 2, 2 ] )
  466. );
  467. pasteTable( [
  468. [ 'aa', 'ab' ],
  469. [ 'ba', 'bb' ]
  470. ] );
  471. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  472. [ '00', '01', '02', '03' ],
  473. [ '10', 'aa', 'ab', '13' ],
  474. [ '20', 'ba', 'bb', '23' ],
  475. [ '30', '31', '32', '33' ]
  476. ] ) );
  477. assertSelectedCells( model, [
  478. [ 0, 0, 0, 0 ],
  479. [ 0, 1, 1, 0 ],
  480. [ 0, 1, 1, 0 ],
  481. [ 0, 0, 0, 0 ]
  482. ] );
  483. } );
  484. it( 'handles simple row paste to a simple row fragment - in the middle of a table', () => {
  485. tableSelection.setCellSelection(
  486. modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
  487. modelRoot.getNodeByPath( [ 0, 1, 2 ] )
  488. );
  489. pasteTable( [
  490. [ 'aa', 'ab' ]
  491. ] );
  492. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  493. [ '00', '01', '02', '03' ],
  494. [ '10', 'aa', 'ab', '13' ],
  495. [ '20', '21', '22', '23' ],
  496. [ '30', '31', '32', '33' ]
  497. ] ) );
  498. assertSelectedCells( model, [
  499. [ 0, 0, 0, 0 ],
  500. [ 0, 1, 1, 0 ],
  501. [ 0, 0, 0, 0 ],
  502. [ 0, 0, 0, 0 ]
  503. ] );
  504. } );
  505. it( 'handles simple column paste to a simple column fragment - in the middle of a table', () => {
  506. tableSelection.setCellSelection(
  507. modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
  508. modelRoot.getNodeByPath( [ 0, 2, 1 ] )
  509. );
  510. pasteTable( [
  511. [ 'aa' ],
  512. [ 'ba' ]
  513. ] );
  514. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  515. [ '00', '01', '02', '03' ],
  516. [ '10', 'aa', '12', '13' ],
  517. [ '20', 'ba', '22', '23' ],
  518. [ '30', '31', '32', '33' ]
  519. ] ) );
  520. assertSelectedCells( model, [
  521. [ 0, 0, 0, 0 ],
  522. [ 0, 1, 0, 0 ],
  523. [ 0, 1, 0, 0 ],
  524. [ 0, 0, 0, 0 ]
  525. ] );
  526. } );
  527. it( 'handles simple table paste to a simple table fragment - whole table selected', () => {
  528. tableSelection.setCellSelection(
  529. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  530. modelRoot.getNodeByPath( [ 0, 3, 3 ] )
  531. );
  532. pasteTable( [
  533. [ 'aa', 'ab', 'ac', 'ad' ],
  534. [ 'ba', 'bb', 'bc', 'bd' ],
  535. [ 'ca', 'cb', 'cc', 'cd' ],
  536. [ 'da', 'db', 'dc', 'dd' ]
  537. ] );
  538. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  539. [ 'aa', 'ab', 'ac', 'ad' ],
  540. [ 'ba', 'bb', 'bc', 'bd' ],
  541. [ 'ca', 'cb', 'cc', 'cd' ],
  542. [ 'da', 'db', 'dc', 'dd' ]
  543. ] ) );
  544. assertSelectedCells( model, [
  545. [ 1, 1, 1, 1 ],
  546. [ 1, 1, 1, 1 ],
  547. [ 1, 1, 1, 1 ],
  548. [ 1, 1, 1, 1 ]
  549. ] );
  550. } );
  551. } );
  552. describe( 'pasted table has spans', () => {
  553. it( 'handles pasting table that has cell with colspan', () => {
  554. tableSelection.setCellSelection(
  555. modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
  556. modelRoot.getNodeByPath( [ 0, 2, 2 ] )
  557. );
  558. pasteTable( [
  559. [ { colspan: 2, contents: 'aa' } ],
  560. [ 'ba', 'bb' ]
  561. ] );
  562. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  563. [ '00', '01', '02', '03' ],
  564. [ '10', { colspan: 2, contents: 'aa' }, '13' ],
  565. [ '20', 'ba', 'bb', '23' ],
  566. [ '30', '31', '32', '33' ]
  567. ] ) );
  568. /* eslint-disable no-multi-spaces */
  569. assertSelectedCells( model, [
  570. [ 0, 0, 0, 0 ],
  571. [ 0, 1, 0 ],
  572. [ 0, 1, 1, 0 ],
  573. [ 0, 0, 0, 0 ]
  574. ] );
  575. /* eslint-enable no-multi-spaces */
  576. } );
  577. it( 'handles pasting table that has many cells with various colspan', () => {
  578. tableSelection.setCellSelection(
  579. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  580. modelRoot.getNodeByPath( [ 0, 3, 2 ] )
  581. );
  582. pasteTable( [
  583. [ 'aa', { colspan: 2, contents: 'ab' } ],
  584. [ { colspan: 3, contents: 'ba' } ],
  585. [ 'ca', 'cb', 'cc' ],
  586. [ { colspan: 2, contents: 'da' }, 'dc' ]
  587. ] );
  588. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  589. [ 'aa', { colspan: 2, contents: 'ab' }, '03' ],
  590. [ { colspan: 3, contents: 'ba' }, '13' ],
  591. [ 'ca', 'cb', 'cc', '23' ],
  592. [ { colspan: 2, contents: 'da' }, 'dc', '33' ]
  593. ] ) );
  594. /* eslint-disable no-multi-spaces */
  595. assertSelectedCells( model, [
  596. [ 1, 1, 0 ],
  597. [ 1, 0 ],
  598. [ 1, 1, 1, 0 ],
  599. [ 1, 1, 0 ]
  600. ] );
  601. /* eslint-enable no-multi-spaces */
  602. } );
  603. it( 'handles pasting table that has cell with rowspan', () => {
  604. tableSelection.setCellSelection(
  605. modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
  606. modelRoot.getNodeByPath( [ 0, 2, 2 ] )
  607. );
  608. pasteTable( [
  609. [ { rowspan: 2, contents: 'aa' }, 'ab' ],
  610. [ 'bb' ]
  611. ] );
  612. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  613. [ '00', '01', '02', '03' ],
  614. [ '10', { rowspan: 2, contents: 'aa' }, 'ab', '13' ],
  615. [ '20', 'bb', '23' ],
  616. [ '30', '31', '32', '33' ]
  617. ] ) );
  618. /* eslint-disable no-multi-spaces */
  619. assertSelectedCells( model, [
  620. [ 0, 0, 0, 0 ],
  621. [ 0, 1, 1, 0 ],
  622. [ 0, 1, 0 ],
  623. [ 0, 0, 0, 0 ]
  624. ] );
  625. /* eslint-enable no-multi-spaces */
  626. } );
  627. it( 'handles pasting table that has many cells with various rowspan', () => {
  628. tableSelection.setCellSelection(
  629. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  630. modelRoot.getNodeByPath( [ 0, 2, 3 ] )
  631. );
  632. pasteTable( [
  633. [ 'aa', { rowspan: 3, contents: 'ab' }, { rowspan: 2, contents: 'ac' }, 'ad' ],
  634. [ { rowspan: 2, contents: 'ba' }, 'bd' ],
  635. [ 'cc', 'cd' ]
  636. ] );
  637. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  638. [ 'aa', { rowspan: 3, contents: 'ab' }, { rowspan: 2, contents: 'ac' }, 'ad' ],
  639. [ { rowspan: 2, contents: 'ba' }, 'bd' ],
  640. [ 'cc', 'cd' ],
  641. [ '30', '31', '32', '33' ]
  642. ] ) );
  643. /* eslint-disable no-multi-spaces */
  644. assertSelectedCells( model, [
  645. [ 1, 1, 1, 1 ],
  646. [ 1, 1 ],
  647. [ 1, 1 ],
  648. [ 0, 0, 0 ]
  649. ] );
  650. /* eslint-enable no-multi-spaces */
  651. } );
  652. it( 'handles pasting multi-spanned table', () => {
  653. setModelData( model, modelTable( [
  654. [ '00', '01', '02', '03', '04', '05' ],
  655. [ '10', '11', '12', '13', '14', '15' ],
  656. [ '20', '21', '22', '23', '24', '25' ],
  657. [ '30', '31', '32', '33', '34', '35' ],
  658. [ '40', '41', '42', '43', '44', '45' ]
  659. ] ) );
  660. tableSelection.setCellSelection(
  661. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  662. modelRoot.getNodeByPath( [ 0, 3, 4 ] )
  663. );
  664. // +----+----+----+----+----+
  665. // | aa | ac | ad | ae |
  666. // +----+----+----+----+ +
  667. // | ba | bb | |
  668. // +----+ +----+
  669. // | ca | | ce |
  670. // + +----+----+----+----+
  671. // | | db | dc | dd |
  672. // +----+----+----+----+----+
  673. pasteTable( [
  674. [ { contents: 'aa', colspan: 2 }, 'ac', 'ad', { contents: 'ae', rowspan: 2 } ],
  675. [ 'ba', { contents: 'bb', colspan: 3, rowspan: 2 } ],
  676. [ { contents: 'ca', rowspan: 2 }, 'ce' ],
  677. [ 'db', 'dc', { contents: 'dd', colspan: 2 } ]
  678. ] );
  679. // +----+----+----+----+----+----+
  680. // | aa | ac | ad | ae | 05 |
  681. // +----+----+----+----+ +----+
  682. // | ba | bb | | 15 |
  683. // +----+ +----+----+
  684. // | ca | | ce | 25 |
  685. // + +----+----+----+----+----+
  686. // | | db | dc | dd | 35 |
  687. // +----+----+----+----+----+----+
  688. // | 40 | 41 | 42 | 43 | 44 | 45 |
  689. // +----+----+----+----+----+----+
  690. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  691. [ { contents: 'aa', colspan: 2 }, 'ac', 'ad', { contents: 'ae', rowspan: 2 }, '05' ],
  692. [ 'ba', { contents: 'bb', colspan: 3, rowspan: 2 }, '15' ],
  693. [ { contents: 'ca', rowspan: 2 }, 'ce', '25' ],
  694. [ 'db', 'dc', { contents: 'dd', colspan: 2 }, '35' ],
  695. [ '40', '41', '42', '43', '44', '45' ]
  696. ] ) );
  697. /* eslint-disable no-multi-spaces */
  698. assertSelectedCells( model, [
  699. [ 1, 1, 1, 1, 0 ],
  700. [ 1, 1, 0 ],
  701. [ 1, 1, 0 ],
  702. [ 1, 1, 1, 0 ],
  703. [ 0, 0, 0, 0, 0, 0 ]
  704. ] );
  705. /* eslint-enable no-multi-spaces */
  706. } );
  707. } );
  708. describe( 'content table has spans', () => {
  709. it( 'handles pasting simple table over a table with colspans (no colspan exceeds selection)', () => {
  710. setModelData( model, modelTable( [
  711. [ '00[]', '01', '02', '03' ],
  712. [ { colspan: 3, contents: '10' }, '13' ],
  713. [ { colspan: 2, contents: '20' }, '22', '23' ],
  714. [ '30', '31', { colspan: 2, contents: '31' } ]
  715. ] ) );
  716. tableSelection.setCellSelection(
  717. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  718. modelRoot.getNodeByPath( [ 0, 2, 1 ] )
  719. );
  720. pasteTable( [
  721. [ 'aa', 'ab', 'ac' ],
  722. [ 'ba', 'bb', 'bc' ],
  723. [ 'ca', 'cb', 'cc' ]
  724. ] );
  725. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  726. [ 'aa', 'ab', 'ac', '03' ],
  727. [ 'ba', 'bb', 'bc', '13' ],
  728. [ 'ca', 'cb', 'cc', '23' ],
  729. [ '30', '31', { colspan: 2, contents: '31' } ]
  730. ] ) );
  731. /* eslint-disable no-multi-spaces */
  732. assertSelectedCells( model, [
  733. [ 1, 1, 1, 0 ],
  734. [ 1, 1, 1, 0 ],
  735. [ 1, 1, 1, 0 ],
  736. [ 0, 0, 0 ]
  737. ] );
  738. /* eslint-enable no-multi-spaces */
  739. } );
  740. it( 'handles pasting simple table over a table with rowspans (no rowspan exceeds selection)', () => {
  741. setModelData( model, modelTable( [
  742. [ '00', { rowspan: 3, contents: '01' }, { rowspan: 2, contents: '02' }, '03' ],
  743. [ { rowspan: 2, contents: '10' }, '13' ],
  744. [ '22', '23' ],
  745. [ '30', '31', '32', '33' ]
  746. ] ) );
  747. tableSelection.setCellSelection(
  748. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  749. modelRoot.getNodeByPath( [ 0, 2, 0 ] )
  750. );
  751. pasteTable( [
  752. [ 'aa', 'ab', 'ac' ],
  753. [ 'ba', 'bb', 'bc' ],
  754. [ 'ca', 'cb', 'cc' ]
  755. ] );
  756. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  757. [ 'aa', 'ab', 'ac', '03' ],
  758. [ 'ba', 'bb', 'bc', '13' ],
  759. [ 'ca', 'cb', 'cc', '23' ],
  760. [ '30', '31', '32', '33' ]
  761. ] ) );
  762. assertSelectedCells( model, [
  763. [ 1, 1, 1, 0 ],
  764. [ 1, 1, 1, 0 ],
  765. [ 1, 1, 1, 0 ],
  766. [ 0, 0, 0, 0 ]
  767. ] );
  768. } );
  769. it( 'handles pasting simple table over table with multi-spans (no span exceeds selection)', () => {
  770. // +----+----+----+----+----+----+
  771. // | 00 | 02 | 03 | 05 |
  772. // + + + +----+
  773. // | | | | 15 |
  774. // +----+----+----+ +----+
  775. // | 20 | 21 | | 25 |
  776. // + +----+----+----+----+----+
  777. // | | 31 | 32 | 34 | 35 |
  778. // +----+----+----+----+----+----+
  779. // | 40 | 41 | 42 | 43 | 44 | 45 |
  780. // +----+----+----+----+----+----+
  781. setModelData( model, modelTable( [
  782. [
  783. { contents: '00', colspan: 2, rowspan: 2 },
  784. { contents: '02', rowspan: 2 },
  785. { contents: '03', colspan: 2, rowspan: 3 },
  786. '05'
  787. ],
  788. [ '15' ],
  789. [ { contents: '20', rowspan: 2 }, { contents: '21', colspan: 2 }, '25' ],
  790. [ '31', { contents: '32', colspan: 2 }, '34', '35' ],
  791. [ '40', '41', '42', '43', '44', '45' ]
  792. ] ) );
  793. tableSelection.setCellSelection(
  794. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  795. modelRoot.getNodeByPath( [ 0, 3, 2 ] )
  796. );
  797. pasteTable( [
  798. [ 'aa', 'ab', 'ac', 'ad', 'ae' ],
  799. [ 'ba', 'bb', 'bc', 'bd', 'be' ],
  800. [ 'ca', 'cb', 'cc', 'cd', 'ce' ],
  801. [ 'da', 'db', 'dc', 'dd', 'de' ]
  802. ] );
  803. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  804. [ 'aa', 'ab', 'ac', 'ad', 'ae', '05' ],
  805. [ 'ba', 'bb', 'bc', 'bd', 'be', '15' ],
  806. [ 'ca', 'cb', 'cc', 'cd', 'ce', '25' ],
  807. [ 'da', 'db', 'dc', 'dd', 'de', '35' ],
  808. [ '40', '41', '42', '43', '44', '45' ]
  809. ] ) );
  810. assertSelectedCells( model, [
  811. [ 1, 1, 1, 1, 1, 0 ],
  812. [ 1, 1, 1, 1, 1, 0 ],
  813. [ 1, 1, 1, 1, 1, 0 ],
  814. [ 1, 1, 1, 1, 1, 0 ],
  815. [ 0, 0, 0, 0, 0, 0 ]
  816. ] );
  817. } );
  818. // TODO: Skipped case - should allow pasting but no tools to compare areas (like in MergeCellsCommand).
  819. it.skip( 'handles pasting table that has cell with colspan (last row in selection is spanned)', () => {
  820. // +----+----+----+----+
  821. // | 00 | 01 | 02 | 03 |
  822. // +----+----+----+----+
  823. // | 10 | 11 | 13 |
  824. // + + +----+
  825. // | | | 23 |
  826. // +----+----+----+----+
  827. // | 30 | 31 | 32 | 33 |
  828. // +----+----+----+----+
  829. setModelData( model, modelTable( [
  830. [ '00', '01', '02', '03' ],
  831. [ { contents: '10', rowspan: 2 }, { contents: '11', colspan: 2, rowspan: 2 }, '13' ],
  832. [ '23' ],
  833. [ '30', '31', '32', '33' ]
  834. ] ) );
  835. tableSelection.setCellSelection(
  836. modelRoot.getNodeByPath( [ 0, 0, 2 ] ),
  837. modelRoot.getNodeByPath( [ 0, 1, 0 ] )
  838. );
  839. pasteTable( [
  840. [ 'aa', 'ab', 'ac' ],
  841. [ 'ba', 'bb', 'bc' ],
  842. [ 'ca', 'cb', 'cc' ]
  843. ] );
  844. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  845. [ 'aa', 'ab', 'ac', '03' ],
  846. [ 'ba', 'bb', 'bc', '13' ],
  847. [ 'ca', 'cb', 'cc', '23' ],
  848. [ '30', '31', '32', '33' ]
  849. ] ) );
  850. assertSelectedCells( model, [
  851. [ 1, 1, 1, 0 ],
  852. [ 1, 1, 1, 0 ],
  853. [ 1, 1, 1, 0 ],
  854. [ 0, 0, 0, 0 ]
  855. ] );
  856. } );
  857. } );
  858. describe( 'content and paste tables have spans', () => {
  859. it( 'handles pasting colspanned table over table with colspans (no colspan exceeds selection)', () => {
  860. setModelData( model, modelTable( [
  861. [ '00[]', '01', '02', '03' ],
  862. [ { colspan: 3, contents: '10' }, '13' ],
  863. [ { colspan: 2, contents: '20' }, '22', '23' ],
  864. [ '30', '31', { colspan: 2, contents: '31' } ]
  865. ] ) );
  866. tableSelection.setCellSelection(
  867. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  868. modelRoot.getNodeByPath( [ 0, 2, 1 ] )
  869. );
  870. pasteTable( [
  871. [ 'aa', { colspan: 2, contents: 'ab' } ],
  872. [ { colspan: 3, contents: 'ba' } ],
  873. [ 'ca', 'cb', 'cc' ]
  874. ] );
  875. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  876. [ 'aa', { colspan: 2, contents: 'ab' }, '03' ],
  877. [ { colspan: 3, contents: 'ba' }, '13' ],
  878. [ 'ca', 'cb', 'cc', '23' ],
  879. [ '30', '31', { colspan: 2, contents: '31' } ]
  880. ] ) );
  881. /* eslint-disable no-multi-spaces */
  882. assertSelectedCells( model, [
  883. [ 1, 1, 0 ],
  884. [ 1, 0 ],
  885. [ 1, 1, 1, 0 ],
  886. [ 0, 0, 0 ]
  887. ] );
  888. /* eslint-enable no-multi-spaces */
  889. } );
  890. it( 'handles pasting rowspanned table over table with rowspans (no rowspan exceeds selection)', () => {
  891. setModelData( model, modelTable( [
  892. [ { rowspan: 3, contents: '00' }, { rowspan: 2, contents: '01' }, '02', '03' ],
  893. [ { rowspan: 2, contents: '12' }, '13' ],
  894. [ '21', '23' ],
  895. [ '30', '31', '32', '33' ]
  896. ] ) );
  897. tableSelection.setCellSelection(
  898. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  899. modelRoot.getNodeByPath( [ 0, 2, 1 ] )
  900. );
  901. pasteTable( [
  902. [ 'aa', { rowspan: 3, contents: 'ab' }, { rowspan: 2, contents: 'ac' }, 'ad' ],
  903. [ { rowspan: 2, contents: 'ba' }, 'bd' ],
  904. [ 'cc', 'cd' ]
  905. ] );
  906. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  907. [ 'aa', { rowspan: 3, contents: 'ab' }, { rowspan: 2, contents: 'ac' }, 'ad' ],
  908. [ { rowspan: 2, contents: 'ba' }, 'bd' ],
  909. [ 'cc', 'cd' ],
  910. [ '30', '31', '32', '33' ]
  911. ] ) );
  912. /* eslint-disable no-multi-spaces */
  913. assertSelectedCells( model, [
  914. [ 1, 1, 1, 1 ],
  915. [ 1, 1 ],
  916. [ 1, 1 ],
  917. [ 0, 0, 0, 0 ]
  918. ] );
  919. /* eslint-enable no-multi-spaces */
  920. } );
  921. it( 'handles pasting multi-spanned table over table with multi-spans (no span exceeds selection)', () => {
  922. // +----+----+----+----+----+----+
  923. // | 00 | 02 | 03 | 05 |
  924. // + + + +----+
  925. // | | | | 15 |
  926. // +----+----+----+ +----+
  927. // | 20 | 21 | | 25 |
  928. // + +----+----+----+----+----+
  929. // | | 31 | 32 | 34 | 35 |
  930. // +----+----+----+----+----+----+
  931. // | 40 | 41 | 42 | 43 | 44 | 45 |
  932. // +----+----+----+----+----+----+
  933. setModelData( model, modelTable( [
  934. [
  935. { contents: '00', colspan: 2, rowspan: 2 },
  936. { contents: '02', rowspan: 2 },
  937. { contents: '03', colspan: 2, rowspan: 3 },
  938. '05'
  939. ],
  940. [ '15' ],
  941. [ { contents: '20', rowspan: 2 }, { contents: '21', colspan: 2 }, '25' ],
  942. [ '31', { contents: '32', colspan: 2 }, '34', '35' ],
  943. [ '40', '41', '42', '43', '44', '45' ]
  944. ] ) );
  945. tableSelection.setCellSelection(
  946. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  947. modelRoot.getNodeByPath( [ 0, 3, 2 ] )
  948. );
  949. // +----+----+----+----+----+
  950. // | aa | ac | ad | ae |
  951. // +----+----+----+----+ +
  952. // | ba | bb | |
  953. // +----+ +----+
  954. // | ca | | ce |
  955. // + +----+----+----+----+
  956. // | | db | dc | dd |
  957. // +----+----+----+----+----+
  958. pasteTable( [
  959. [ { contents: 'aa', colspan: 2 }, 'ac', 'ad', { contents: 'ae', rowspan: 2 } ],
  960. [ 'ba', { contents: 'bb', colspan: 3, rowspan: 2 } ],
  961. [ { contents: 'ca', rowspan: 2 }, 'ce' ],
  962. [ 'db', 'dc', { contents: 'dd', colspan: 2 } ]
  963. ] );
  964. // +----+----+----+----+----+----+
  965. // | aa | ac | ad | ae | 05 |
  966. // +----+----+----+----+ +----+
  967. // | ba | bb | | 15 |
  968. // +----+ +----+----+
  969. // | ca | | ce | 25 |
  970. // + +----+----+----+----+----+
  971. // | | db | dc | dd | 35 |
  972. // +----+----+----+----+----+----+
  973. // | 40 | 41 | 42 | 43 | 44 | 45 |
  974. // +----+----+----+----+----+----+
  975. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  976. [ { contents: 'aa', colspan: 2 }, 'ac', 'ad', { contents: 'ae', rowspan: 2 }, '05' ],
  977. [ 'ba', { contents: 'bb', colspan: 3, rowspan: 2 }, '15' ],
  978. [ { contents: 'ca', rowspan: 2 }, 'ce', '25' ],
  979. [ 'db', 'dc', { contents: 'dd', colspan: 2 }, '35' ],
  980. [ '40', '41', '42', '43', '44', '45' ]
  981. ] ) );
  982. /* eslint-disable no-multi-spaces */
  983. assertSelectedCells( model, [
  984. [ 1, 1, 1, 1, 0 ],
  985. [ 1, 1, 0 ],
  986. [ 1, 1, 0 ],
  987. [ 1, 1, 1, 0 ],
  988. [ 0, 0, 0, 0, 0, 0 ]
  989. ] );
  990. /* eslint-enable no-multi-spaces */
  991. } );
  992. // TODO: Skipped case - should allow pasting but no tools to compare areas (like in MergeCellsCommand).
  993. it.skip( 'handles pasting table that has cell with colspan (last row in selection is spanned)', () => {
  994. // +----+----+----+----+
  995. // | 00 | 01 | 02 | 03 |
  996. // +----+----+----+----+
  997. // | 10 | 11 | 13 |
  998. // + + +----+
  999. // | | | 23 |
  1000. // +----+----+----+----+
  1001. // | 30 | 31 | 32 | 33 |
  1002. // +----+----+----+----+
  1003. setModelData( model, modelTable( [
  1004. [ '00', '01', '02', '03' ],
  1005. [ { contents: '10', rowspan: 2 }, { contents: '11', colspan: 2, rowspan: 2 }, '13' ],
  1006. [ '23' ],
  1007. [ '30', '31', '32', '33' ]
  1008. ] ) );
  1009. tableSelection.setCellSelection(
  1010. modelRoot.getNodeByPath( [ 0, 0, 2 ] ),
  1011. modelRoot.getNodeByPath( [ 0, 1, 0 ] )
  1012. );
  1013. // +----+----+----+
  1014. // | aa | ab | ac |
  1015. // +----+----+----+
  1016. // | ba | bc |
  1017. // + +----+
  1018. // | | cc |
  1019. // +----+----+----+
  1020. pasteTable( [
  1021. [ 'aa', 'ab', 'ac' ],
  1022. [ { contents: 'ba', colspan: 2, rowspan: 2 }, 'bc' ],
  1023. [ 'cc' ]
  1024. ] );
  1025. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  1026. [ '00', '01', '02', '03' ],
  1027. [ '10', { colspan: 2, contents: 'aa' }, '13' ],
  1028. [ '20', 'ba', 'bb', '23' ],
  1029. [ '30', '31', '32', '33' ]
  1030. ] ) );
  1031. /* eslint-disable no-multi-spaces */
  1032. assertSelectedCells( model, [
  1033. [ 0, 0, 0, 0 ],
  1034. [ 0, 1, 0 ],
  1035. [ 0, 1, 1, 0 ],
  1036. [ 0, 0, 0, 0 ]
  1037. ] );
  1038. /* eslint-enable no-multi-spaces */
  1039. } );
  1040. } );
  1041. } );
  1042. describe( 'pasted table is bigger than the selected area', () => {
  1043. describe( 'no spans', () => {
  1044. it( 'handles simple table paste to a simple table fragment - at the beginning of a table', () => {
  1045. tableSelection.setCellSelection(
  1046. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  1047. modelRoot.getNodeByPath( [ 0, 1, 1 ] )
  1048. );
  1049. pasteTable( [
  1050. [ 'aa', 'ab', 'ac' ],
  1051. [ 'ba', 'bb', 'bc' ],
  1052. [ 'ca', 'cb', 'cc' ]
  1053. ] );
  1054. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  1055. [ 'aa', 'ab', '02', '03' ],
  1056. [ 'ba', 'bb', '12', '13' ],
  1057. [ '20', '21', '22', '23' ],
  1058. [ '30', '31', '32', '33' ]
  1059. ] ) );
  1060. assertSelectedCells( model, [
  1061. [ 1, 1, 0, 0 ],
  1062. [ 1, 1, 0, 0 ],
  1063. [ 0, 0, 0, 0 ],
  1064. [ 0, 0, 0, 0 ]
  1065. ] );
  1066. } );
  1067. it( 'handles simple table paste to a simple table fragment - at the end of a table', () => {
  1068. tableSelection.setCellSelection(
  1069. modelRoot.getNodeByPath( [ 0, 2, 2 ] ),
  1070. modelRoot.getNodeByPath( [ 0, 3, 3 ] )
  1071. );
  1072. pasteTable( [
  1073. [ 'aa', 'ab', 'ac' ],
  1074. [ 'ba', 'bb', 'bc' ],
  1075. [ 'ca', 'cb', 'cc' ]
  1076. ] );
  1077. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  1078. [ '00', '01', '02', '03' ],
  1079. [ '10', '11', '12', '13' ],
  1080. [ '20', '21', 'aa', 'ab' ],
  1081. [ '30', '31', 'ba', 'bb' ]
  1082. ] ) );
  1083. assertSelectedCells( model, [
  1084. [ 0, 0, 0, 0 ],
  1085. [ 0, 0, 0, 0 ],
  1086. [ 0, 0, 1, 1 ],
  1087. [ 0, 0, 1, 1 ]
  1088. ] );
  1089. } );
  1090. it( 'handles simple table paste to a simple table fragment - in the middle of a table', () => {
  1091. tableSelection.setCellSelection(
  1092. modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
  1093. modelRoot.getNodeByPath( [ 0, 2, 2 ] )
  1094. );
  1095. pasteTable( [
  1096. [ 'aa', 'ab', 'ac' ],
  1097. [ 'ba', 'bb', 'bc' ],
  1098. [ 'ca', 'cb', 'cc' ]
  1099. ] );
  1100. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  1101. [ '00', '01', '02', '03' ],
  1102. [ '10', 'aa', 'ab', '13' ],
  1103. [ '20', 'ba', 'bb', '23' ],
  1104. [ '30', '31', '32', '33' ]
  1105. ] ) );
  1106. assertSelectedCells( model, [
  1107. [ 0, 0, 0, 0 ],
  1108. [ 0, 1, 1, 0 ],
  1109. [ 0, 1, 1, 0 ],
  1110. [ 0, 0, 0, 0 ]
  1111. ] );
  1112. } );
  1113. it( 'handles paste to a simple row fragment - in the middle of a table', () => {
  1114. tableSelection.setCellSelection(
  1115. modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
  1116. modelRoot.getNodeByPath( [ 0, 1, 2 ] )
  1117. );
  1118. pasteTable( [
  1119. [ 'aa', 'ab', 'ac' ],
  1120. [ 'ba', 'bb', 'bc' ],
  1121. [ 'ca', 'cb', 'cc' ]
  1122. ] );
  1123. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  1124. [ '00', '01', '02', '03' ],
  1125. [ '10', 'aa', 'ab', '13' ],
  1126. [ '20', '21', '22', '23' ],
  1127. [ '30', '31', '32', '33' ]
  1128. ] ) );
  1129. assertSelectedCells( model, [
  1130. [ 0, 0, 0, 0 ],
  1131. [ 0, 1, 1, 0 ],
  1132. [ 0, 0, 0, 0 ],
  1133. [ 0, 0, 0, 0 ]
  1134. ] );
  1135. } );
  1136. it( 'handles paste to a simple column fragment - in the middle of a table', () => {
  1137. tableSelection.setCellSelection(
  1138. modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
  1139. modelRoot.getNodeByPath( [ 0, 2, 1 ] )
  1140. );
  1141. pasteTable( [
  1142. [ 'aa', 'ab', 'ac' ],
  1143. [ 'ba', 'bb', 'bc' ],
  1144. [ 'ca', 'cb', 'cc' ]
  1145. ] );
  1146. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  1147. [ '00', '01', '02', '03' ],
  1148. [ '10', 'aa', '12', '13' ],
  1149. [ '20', 'ba', '22', '23' ],
  1150. [ '30', '31', '32', '33' ]
  1151. ] ) );
  1152. assertSelectedCells( model, [
  1153. [ 0, 0, 0, 0 ],
  1154. [ 0, 1, 0, 0 ],
  1155. [ 0, 1, 0, 0 ],
  1156. [ 0, 0, 0, 0 ]
  1157. ] );
  1158. } );
  1159. it( 'handles simple table paste to a simple table fragment - whole table selected', () => {
  1160. tableSelection.setCellSelection(
  1161. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  1162. modelRoot.getNodeByPath( [ 0, 3, 3 ] )
  1163. );
  1164. pasteTable( [
  1165. [ 'aa', 'ab', 'ac', 'ad', 'ae' ],
  1166. [ 'ba', 'bb', 'bc', 'bd', 'be' ],
  1167. [ 'ca', 'cb', 'cc', 'cd', 'ce' ],
  1168. [ 'da', 'db', 'dc', 'dd', 'de' ],
  1169. [ 'ea', 'eb', 'ec', 'ed', 'ee' ]
  1170. ] );
  1171. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  1172. [ 'aa', 'ab', 'ac', 'ad' ],
  1173. [ 'ba', 'bb', 'bc', 'bd' ],
  1174. [ 'ca', 'cb', 'cc', 'cd' ],
  1175. [ 'da', 'db', 'dc', 'dd' ]
  1176. ] ) );
  1177. assertSelectedCells( model, [
  1178. [ 1, 1, 1, 1 ],
  1179. [ 1, 1, 1, 1 ],
  1180. [ 1, 1, 1, 1 ],
  1181. [ 1, 1, 1, 1 ]
  1182. ] );
  1183. } );
  1184. } );
  1185. describe.skip( 'pasted table has spans', () => {
  1186. it( 'handles pasting table that has cell with colspan', () => {
  1187. tableSelection.setCellSelection(
  1188. modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
  1189. modelRoot.getNodeByPath( [ 0, 2, 2 ] )
  1190. );
  1191. pasteTable( [
  1192. [ { colspan: 2, contents: 'aa' } ],
  1193. [ 'ba', 'bb' ]
  1194. ] );
  1195. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  1196. [ '00', '01', '02', '03' ],
  1197. [ '10', { colspan: 2, contents: 'aa' }, '13' ],
  1198. [ '20', 'ba', 'bb', '23' ],
  1199. [ '30', '31', '32', '33' ]
  1200. ] ) );
  1201. /* eslint-disable no-multi-spaces */
  1202. assertSelectedCells( model, [
  1203. [ 0, 0, 0, 0 ],
  1204. [ 0, 1, 0 ],
  1205. [ 0, 1, 1, 0 ],
  1206. [ 0, 0, 0, 0 ]
  1207. ] );
  1208. /* eslint-enable no-multi-spaces */
  1209. } );
  1210. it( 'handles pasting table that has many cells with various colspan', () => {
  1211. tableSelection.setCellSelection(
  1212. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  1213. modelRoot.getNodeByPath( [ 0, 3, 2 ] )
  1214. );
  1215. pasteTable( [
  1216. [ 'aa', { colspan: 2, contents: 'ab' } ],
  1217. [ { colspan: 3, contents: 'ba' } ],
  1218. [ 'ca', 'cb', 'cc' ],
  1219. [ { colspan: 2, contents: 'da' }, 'dc' ]
  1220. ] );
  1221. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  1222. [ 'aa', { colspan: 2, contents: 'ab' }, '03' ],
  1223. [ { colspan: 3, contents: 'ba' }, '13' ],
  1224. [ 'ca', 'cb', 'cc', '23' ],
  1225. [ { colspan: 2, contents: 'da' }, 'dc', '33' ]
  1226. ] ) );
  1227. /* eslint-disable no-multi-spaces */
  1228. assertSelectedCells( model, [
  1229. [ 1, 1, 0 ],
  1230. [ 1, 0 ],
  1231. [ 1, 1, 1, 0 ],
  1232. [ 1, 1, 0 ]
  1233. ] );
  1234. /* eslint-enable no-multi-spaces */
  1235. } );
  1236. it( 'handles pasting table that has cell with rowspan', () => {
  1237. tableSelection.setCellSelection(
  1238. modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
  1239. modelRoot.getNodeByPath( [ 0, 2, 2 ] )
  1240. );
  1241. pasteTable( [
  1242. [ { rowspan: 2, contents: 'aa' }, 'ab' ],
  1243. [ 'bb' ]
  1244. ] );
  1245. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  1246. [ '00', '01', '02', '03' ],
  1247. [ '10', { rowspan: 2, contents: 'aa' }, 'ab', '13' ],
  1248. [ '20', 'bb', '23' ],
  1249. [ '30', '31', '32', '33' ]
  1250. ] ) );
  1251. /* eslint-disable no-multi-spaces */
  1252. assertSelectedCells( model, [
  1253. [ 0, 0, 0, 0 ],
  1254. [ 0, 1, 1, 0 ],
  1255. [ 0, 1, 0 ],
  1256. [ 0, 0, 0, 0 ]
  1257. ] );
  1258. /* eslint-enable no-multi-spaces */
  1259. } );
  1260. it( 'handles pasting table that has many cells with various rowspan', () => {
  1261. tableSelection.setCellSelection(
  1262. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  1263. modelRoot.getNodeByPath( [ 0, 2, 3 ] )
  1264. );
  1265. pasteTable( [
  1266. [ 'aa', { rowspan: 3, contents: 'ab' }, { rowspan: 2, contents: 'ac' }, 'ad' ],
  1267. [ { rowspan: 2, contents: 'ba' }, 'bd' ],
  1268. [ 'cc', 'cd' ]
  1269. ] );
  1270. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  1271. [ 'aa', { rowspan: 3, contents: 'ab' }, { rowspan: 2, contents: 'ac' }, 'ad' ],
  1272. [ { rowspan: 2, contents: 'ba' }, 'bd' ],
  1273. [ 'cc', 'cd' ],
  1274. [ '30', '31', '32', '33' ]
  1275. ] ) );
  1276. /* eslint-disable no-multi-spaces */
  1277. assertSelectedCells( model, [
  1278. [ 1, 1, 1, 1 ],
  1279. [ 1, 1 ],
  1280. [ 1, 1 ],
  1281. [ 0, 0, 0 ]
  1282. ] );
  1283. /* eslint-enable no-multi-spaces */
  1284. } );
  1285. it( 'handles pasting multi-spanned table', () => {
  1286. setModelData( model, modelTable( [
  1287. [ '00', '01', '02', '03', '04', '05' ],
  1288. [ '10', '11', '12', '13', '14', '15' ],
  1289. [ '20', '21', '22', '23', '24', '25' ],
  1290. [ '30', '31', '32', '33', '34', '35' ],
  1291. [ '40', '41', '42', '43', '44', '45' ]
  1292. ] ) );
  1293. tableSelection.setCellSelection(
  1294. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  1295. modelRoot.getNodeByPath( [ 0, 3, 4 ] )
  1296. );
  1297. // +----+----+----+----+----+
  1298. // | aa | ac | ad | ae |
  1299. // +----+----+----+----+ +
  1300. // | ba | bb | |
  1301. // +----+ +----+
  1302. // | ca | | ce |
  1303. // + +----+----+----+----+
  1304. // | | db | dc | dd |
  1305. // +----+----+----+----+----+
  1306. pasteTable( [
  1307. [ { contents: 'aa', colspan: 2 }, 'ac', 'ad', { contents: 'ae', rowspan: 2 } ],
  1308. [ 'ba', { contents: 'bb', colspan: 3, rowspan: 2 } ],
  1309. [ { contents: 'ca', rowspan: 2 }, 'ce' ],
  1310. [ 'db', 'dc', { contents: 'dd', colspan: 2 } ]
  1311. ] );
  1312. // +----+----+----+----+----+----+
  1313. // | aa | ac | ad | ae | 05 |
  1314. // +----+----+----+----+ +----+
  1315. // | ba | bb | | 15 |
  1316. // +----+ +----+----+
  1317. // | ca | | ce | 25 |
  1318. // + +----+----+----+----+----+
  1319. // | | db | dc | dd | 35 |
  1320. // +----+----+----+----+----+----+
  1321. // | 40 | 41 | 42 | 43 | 44 | 45 |
  1322. // +----+----+----+----+----+----+
  1323. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  1324. [ { contents: 'aa', colspan: 2 }, 'ac', 'ad', { contents: 'ae', rowspan: 2 }, '05' ],
  1325. [ 'ba', { contents: 'bb', colspan: 3, rowspan: 2 }, '15' ],
  1326. [ { contents: 'ca', rowspan: 2 }, 'ce', '25' ],
  1327. [ 'db', 'dc', { contents: 'dd', colspan: 2 }, '35' ],
  1328. [ '40', '41', '42', '43', '44', '45' ]
  1329. ] ) );
  1330. /* eslint-disable no-multi-spaces */
  1331. assertSelectedCells( model, [
  1332. [ 1, 1, 1, 1, 0 ],
  1333. [ 1, 1, 0 ],
  1334. [ 1, 1, 0 ],
  1335. [ 1, 1, 1, 0 ],
  1336. [ 0, 0, 0, 0, 0, 0 ]
  1337. ] );
  1338. /* eslint-enable no-multi-spaces */
  1339. } );
  1340. } );
  1341. describe.skip( 'content table has spans', () => {
  1342. it( 'handles pasting simple table over a table with colspans (no colspan exceeds selection)', () => {
  1343. setModelData( model, modelTable( [
  1344. [ '00[]', '01', '02', '03' ],
  1345. [ { colspan: 3, contents: '10' }, '13' ],
  1346. [ { colspan: 2, contents: '20' }, '22', '23' ],
  1347. [ '30', '31', { colspan: 2, contents: '31' } ]
  1348. ] ) );
  1349. tableSelection.setCellSelection(
  1350. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  1351. modelRoot.getNodeByPath( [ 0, 2, 1 ] )
  1352. );
  1353. pasteTable( [
  1354. [ 'aa', 'ab', 'ac' ],
  1355. [ 'ba', 'bb', 'bc' ],
  1356. [ 'ca', 'cb', 'cc' ]
  1357. ] );
  1358. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  1359. [ 'aa', 'ab', 'ac', '03' ],
  1360. [ 'ba', 'bb', 'bc', '13' ],
  1361. [ 'ca', 'cb', 'cc', '23' ],
  1362. [ '30', '31', { colspan: 2, contents: '31' } ]
  1363. ] ) );
  1364. /* eslint-disable no-multi-spaces */
  1365. assertSelectedCells( model, [
  1366. [ 1, 1, 1, 0 ],
  1367. [ 1, 1, 1, 0 ],
  1368. [ 1, 1, 1, 0 ],
  1369. [ 0, 0, 0 ]
  1370. ] );
  1371. /* eslint-enable no-multi-spaces */
  1372. } );
  1373. it( 'handles pasting simple table over a table with rowspans (no rowspan exceeds selection)', () => {
  1374. setModelData( model, modelTable( [
  1375. [ '00', { rowspan: 3, contents: '01' }, { rowspan: 2, contents: '02' }, '03' ],
  1376. [ { rowspan: 2, contents: '10' }, '13' ],
  1377. [ '22', '23' ],
  1378. [ '30', '31', '32', '33' ]
  1379. ] ) );
  1380. tableSelection.setCellSelection(
  1381. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  1382. modelRoot.getNodeByPath( [ 0, 2, 0 ] )
  1383. );
  1384. pasteTable( [
  1385. [ 'aa', 'ab', 'ac' ],
  1386. [ 'ba', 'bb', 'bc' ],
  1387. [ 'ca', 'cb', 'cc' ]
  1388. ] );
  1389. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  1390. [ 'aa', 'ab', 'ac', '03' ],
  1391. [ 'ba', 'bb', 'bc', '13' ],
  1392. [ 'ca', 'cb', 'cc', '23' ],
  1393. [ '30', '31', '32', '33' ]
  1394. ] ) );
  1395. assertSelectedCells( model, [
  1396. [ 1, 1, 1, 0 ],
  1397. [ 1, 1, 1, 0 ],
  1398. [ 1, 1, 1, 0 ],
  1399. [ 0, 0, 0, 0 ]
  1400. ] );
  1401. } );
  1402. it( 'handles pasting simple table over table with multi-spans (no span exceeds selection)', () => {
  1403. // +----+----+----+----+----+----+
  1404. // | 00 | 02 | 03 | 05 |
  1405. // + + + +----+
  1406. // | | | | 15 |
  1407. // +----+----+----+ +----+
  1408. // | 20 | 21 | | 25 |
  1409. // + +----+----+----+----+----+
  1410. // | | 31 | 32 | 34 | 35 |
  1411. // +----+----+----+----+----+----+
  1412. // | 40 | 41 | 42 | 43 | 44 | 45 |
  1413. // +----+----+----+----+----+----+
  1414. setModelData( model, modelTable( [
  1415. [
  1416. { contents: '00', colspan: 2, rowspan: 2 },
  1417. { contents: '02', rowspan: 2 },
  1418. { contents: '03', colspan: 2, rowspan: 3 },
  1419. '05'
  1420. ],
  1421. [ '15' ],
  1422. [ { contents: '20', rowspan: 2 }, { contents: '21', colspan: 2 }, '25' ],
  1423. [ '31', { contents: '32', colspan: 2 }, '34', '35' ],
  1424. [ '40', '41', '42', '43', '44', '45' ]
  1425. ] ) );
  1426. tableSelection.setCellSelection(
  1427. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  1428. modelRoot.getNodeByPath( [ 0, 3, 2 ] )
  1429. );
  1430. pasteTable( [
  1431. [ 'aa', 'ab', 'ac', 'ad', 'ae' ],
  1432. [ 'ba', 'bb', 'bc', 'bd', 'be' ],
  1433. [ 'ca', 'cb', 'cc', 'cd', 'ce' ],
  1434. [ 'da', 'db', 'dc', 'dd', 'de' ]
  1435. ] );
  1436. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  1437. [ 'aa', 'ab', 'ac', 'ad', 'ae', '05' ],
  1438. [ 'ba', 'bb', 'bc', 'bd', 'be', '15' ],
  1439. [ 'ca', 'cb', 'cc', 'cd', 'ce', '25' ],
  1440. [ 'da', 'db', 'dc', 'dd', 'de', '35' ],
  1441. [ '40', '41', '42', '43', '44', '45' ]
  1442. ] ) );
  1443. assertSelectedCells( model, [
  1444. [ 1, 1, 1, 1, 1, 0 ],
  1445. [ 1, 1, 1, 1, 1, 0 ],
  1446. [ 1, 1, 1, 1, 1, 0 ],
  1447. [ 1, 1, 1, 1, 1, 0 ],
  1448. [ 0, 0, 0, 0, 0, 0 ]
  1449. ] );
  1450. } );
  1451. // TODO: Skipped case - should allow pasting but no tools to compare areas (like in MergeCellsCommand).
  1452. it.skip( 'handles pasting table that has cell with colspan (last row in selection is spanned)', () => {
  1453. // +----+----+----+----+
  1454. // | 00 | 01 | 02 | 03 |
  1455. // +----+----+----+----+
  1456. // | 10 | 11 | 13 |
  1457. // + + +----+
  1458. // | | | 23 |
  1459. // +----+----+----+----+
  1460. // | 30 | 31 | 32 | 33 |
  1461. // +----+----+----+----+
  1462. setModelData( model, modelTable( [
  1463. [ '00', '01', '02', '03' ],
  1464. [ { contents: '10', rowspan: 2 }, { contents: '11', colspan: 2, rowspan: 2 }, '13' ],
  1465. [ '23' ],
  1466. [ '30', '31', '32', '33' ]
  1467. ] ) );
  1468. tableSelection.setCellSelection(
  1469. modelRoot.getNodeByPath( [ 0, 0, 2 ] ),
  1470. modelRoot.getNodeByPath( [ 0, 1, 0 ] )
  1471. );
  1472. pasteTable( [
  1473. [ 'aa', 'ab', 'ac' ],
  1474. [ 'ba', 'bb', 'bc' ],
  1475. [ 'ca', 'cb', 'cc' ]
  1476. ] );
  1477. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  1478. [ 'aa', 'ab', 'ac', '03' ],
  1479. [ 'ba', 'bb', 'bc', '13' ],
  1480. [ 'ca', 'cb', 'cc', '23' ],
  1481. [ '30', '31', '32', '33' ]
  1482. ] ) );
  1483. assertSelectedCells( model, [
  1484. [ 1, 1, 1, 0 ],
  1485. [ 1, 1, 1, 0 ],
  1486. [ 1, 1, 1, 0 ],
  1487. [ 0, 0, 0, 0 ]
  1488. ] );
  1489. } );
  1490. } );
  1491. describe.skip( 'content and paste tables have spans', () => {
  1492. it( 'handles pasting colspanned table over table with colspans (no colspan exceeds selection)', () => {
  1493. setModelData( model, modelTable( [
  1494. [ '00[]', '01', '02', '03' ],
  1495. [ { colspan: 3, contents: '10' }, '13' ],
  1496. [ { colspan: 2, contents: '20' }, '22', '23' ],
  1497. [ '30', '31', { colspan: 2, contents: '31' } ]
  1498. ] ) );
  1499. tableSelection.setCellSelection(
  1500. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  1501. modelRoot.getNodeByPath( [ 0, 2, 1 ] )
  1502. );
  1503. pasteTable( [
  1504. [ 'aa', { colspan: 2, contents: 'ab' } ],
  1505. [ { colspan: 3, contents: 'ba' } ],
  1506. [ 'ca', 'cb', 'cc' ]
  1507. ] );
  1508. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  1509. [ 'aa', { colspan: 2, contents: 'ab' }, '03' ],
  1510. [ { colspan: 3, contents: 'ba' }, '13' ],
  1511. [ 'ca', 'cb', 'cc', '23' ],
  1512. [ '30', '31', { colspan: 2, contents: '31' } ]
  1513. ] ) );
  1514. /* eslint-disable no-multi-spaces */
  1515. assertSelectedCells( model, [
  1516. [ 1, 1, 0 ],
  1517. [ 1, 0 ],
  1518. [ 1, 1, 1, 0 ],
  1519. [ 0, 0, 0 ]
  1520. ] );
  1521. /* eslint-enable no-multi-spaces */
  1522. } );
  1523. it( 'handles pasting rowspanned table over table with rowspans (no rowspan exceeds selection)', () => {
  1524. setModelData( model, modelTable( [
  1525. [ { rowspan: 3, contents: '00' }, { rowspan: 2, contents: '01' }, '02', '03' ],
  1526. [ { rowspan: 2, contents: '12' }, '13' ],
  1527. [ '21', '23' ],
  1528. [ '30', '31', '32', '33' ]
  1529. ] ) );
  1530. tableSelection.setCellSelection(
  1531. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  1532. modelRoot.getNodeByPath( [ 0, 2, 1 ] )
  1533. );
  1534. pasteTable( [
  1535. [ 'aa', { rowspan: 3, contents: 'ab' }, { rowspan: 2, contents: 'ac' }, 'ad' ],
  1536. [ { rowspan: 2, contents: 'ba' }, 'bd' ],
  1537. [ 'cc', 'cd' ]
  1538. ] );
  1539. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  1540. [ 'aa', { rowspan: 3, contents: 'ab' }, { rowspan: 2, contents: 'ac' }, 'ad' ],
  1541. [ { rowspan: 2, contents: 'ba' }, 'bd' ],
  1542. [ 'cc', 'cd' ],
  1543. [ '30', '31', '32', '33' ]
  1544. ] ) );
  1545. /* eslint-disable no-multi-spaces */
  1546. assertSelectedCells( model, [
  1547. [ 1, 1, 1, 1 ],
  1548. [ 1, 1 ],
  1549. [ 1, 1 ],
  1550. [ 0, 0, 0, 0 ]
  1551. ] );
  1552. /* eslint-enable no-multi-spaces */
  1553. } );
  1554. it( 'handles pasting multi-spanned table over table with multi-spans (no span exceeds selection)', () => {
  1555. // +----+----+----+----+----+----+
  1556. // | 00 | 02 | 03 | 05 |
  1557. // + + + +----+
  1558. // | | | | 15 |
  1559. // +----+----+----+ +----+
  1560. // | 20 | 21 | | 25 |
  1561. // + +----+----+----+----+----+
  1562. // | | 31 | 32 | 34 | 35 |
  1563. // +----+----+----+----+----+----+
  1564. // | 40 | 41 | 42 | 43 | 44 | 45 |
  1565. // +----+----+----+----+----+----+
  1566. setModelData( model, modelTable( [
  1567. [
  1568. { contents: '00', colspan: 2, rowspan: 2 },
  1569. { contents: '02', rowspan: 2 },
  1570. { contents: '03', colspan: 2, rowspan: 3 },
  1571. '05'
  1572. ],
  1573. [ '15' ],
  1574. [ { contents: '20', rowspan: 2 }, { contents: '21', colspan: 2 }, '25' ],
  1575. [ '31', { contents: '32', colspan: 2 }, '34', '35' ],
  1576. [ '40', '41', '42', '43', '44', '45' ]
  1577. ] ) );
  1578. tableSelection.setCellSelection(
  1579. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  1580. modelRoot.getNodeByPath( [ 0, 3, 2 ] )
  1581. );
  1582. // +----+----+----+----+----+
  1583. // | aa | ac | ad | ae |
  1584. // +----+----+----+----+ +
  1585. // | ba | bb | |
  1586. // +----+ +----+
  1587. // | ca | | ce |
  1588. // + +----+----+----+----+
  1589. // | | db | dc | dd |
  1590. // +----+----+----+----+----+
  1591. pasteTable( [
  1592. [ { contents: 'aa', colspan: 2 }, 'ac', 'ad', { contents: 'ae', rowspan: 2 } ],
  1593. [ 'ba', { contents: 'bb', colspan: 3, rowspan: 2 } ],
  1594. [ { contents: 'ca', rowspan: 2 }, 'ce' ],
  1595. [ 'db', 'dc', { contents: 'dd', colspan: 2 } ]
  1596. ] );
  1597. // +----+----+----+----+----+----+
  1598. // | aa | ac | ad | ae | 05 |
  1599. // +----+----+----+----+ +----+
  1600. // | ba | bb | | 15 |
  1601. // +----+ +----+----+
  1602. // | ca | | ce | 25 |
  1603. // + +----+----+----+----+----+
  1604. // | | db | dc | dd | 35 |
  1605. // +----+----+----+----+----+----+
  1606. // | 40 | 41 | 42 | 43 | 44 | 45 |
  1607. // +----+----+----+----+----+----+
  1608. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  1609. [ { contents: 'aa', colspan: 2 }, 'ac', 'ad', { contents: 'ae', rowspan: 2 }, '05' ],
  1610. [ 'ba', { contents: 'bb', colspan: 3, rowspan: 2 }, '15' ],
  1611. [ { contents: 'ca', rowspan: 2 }, 'ce', '25' ],
  1612. [ 'db', 'dc', { contents: 'dd', colspan: 2 }, '35' ],
  1613. [ '40', '41', '42', '43', '44', '45' ]
  1614. ] ) );
  1615. /* eslint-disable no-multi-spaces */
  1616. assertSelectedCells( model, [
  1617. [ 1, 1, 1, 1, 0 ],
  1618. [ 1, 1, 0 ],
  1619. [ 1, 1, 0 ],
  1620. [ 1, 1, 1, 0 ],
  1621. [ 0, 0, 0, 0, 0, 0 ]
  1622. ] );
  1623. /* eslint-enable no-multi-spaces */
  1624. } );
  1625. // TODO: Skipped case - should allow pasting but no tools to compare areas (like in MergeCellsCommand).
  1626. it.skip( 'handles pasting table that has cell with colspan (last row in selection is spanned)', () => {
  1627. // +----+----+----+----+
  1628. // | 00 | 01 | 02 | 03 |
  1629. // +----+----+----+----+
  1630. // | 10 | 11 | 13 |
  1631. // + + +----+
  1632. // | | | 23 |
  1633. // +----+----+----+----+
  1634. // | 30 | 31 | 32 | 33 |
  1635. // +----+----+----+----+
  1636. setModelData( model, modelTable( [
  1637. [ '00', '01', '02', '03' ],
  1638. [ { contents: '10', rowspan: 2 }, { contents: '11', colspan: 2, rowspan: 2 }, '13' ],
  1639. [ '23' ],
  1640. [ '30', '31', '32', '33' ]
  1641. ] ) );
  1642. tableSelection.setCellSelection(
  1643. modelRoot.getNodeByPath( [ 0, 0, 2 ] ),
  1644. modelRoot.getNodeByPath( [ 0, 1, 0 ] )
  1645. );
  1646. // +----+----+----+
  1647. // | aa | ab | ac |
  1648. // +----+----+----+
  1649. // | ba | bc |
  1650. // + +----+
  1651. // | | cc |
  1652. // +----+----+----+
  1653. pasteTable( [
  1654. [ 'aa', 'ab', 'ac' ],
  1655. [ { contents: 'ba', colspan: 2, rowspan: 2 }, 'bc' ],
  1656. [ 'cc' ]
  1657. ] );
  1658. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  1659. [ '00', '01', '02', '03' ],
  1660. [ '10', { colspan: 2, contents: 'aa' }, '13' ],
  1661. [ '20', 'ba', 'bb', '23' ],
  1662. [ '30', '31', '32', '33' ]
  1663. ] ) );
  1664. /* eslint-disable no-multi-spaces */
  1665. assertSelectedCells( model, [
  1666. [ 0, 0, 0, 0 ],
  1667. [ 0, 1, 0 ],
  1668. [ 0, 1, 1, 0 ],
  1669. [ 0, 0, 0, 0 ]
  1670. ] );
  1671. /* eslint-enable no-multi-spaces */
  1672. } );
  1673. } );
  1674. } );
  1675. describe( 'pasted table is smaller than the selected area', () => {
  1676. it( 'blocks this case', () => {
  1677. tableSelection.setCellSelection(
  1678. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  1679. modelRoot.getNodeByPath( [ 0, 3, 3 ] )
  1680. );
  1681. pasteTable( [
  1682. [ 'aa', 'ab' ],
  1683. [ 'ba', 'bb' ]
  1684. ] );
  1685. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  1686. [ '00', '01', '02', '03' ],
  1687. [ '10', '11', '12', '13' ],
  1688. [ '20', '21', '22', '23' ],
  1689. [ '30', '31', '32', '33' ]
  1690. ] ) );
  1691. assertSelectedCells( model, [
  1692. [ 1, 1, 1, 1 ],
  1693. [ 1, 1, 1, 1 ],
  1694. [ 1, 1, 1, 1 ],
  1695. [ 1, 1, 1, 1 ]
  1696. ] );
  1697. } );
  1698. } );
  1699. } );
  1700. } );
  1701. function assertClipboardContentOnMethod( method, expectedViewTable ) {
  1702. const data = {
  1703. dataTransfer: createDataTransfer(),
  1704. preventDefault: sinon.spy()
  1705. };
  1706. viewDocument.fire( method, data );
  1707. expect( data.dataTransfer.getData( 'text/html' ) ).to.equal( expectedViewTable );
  1708. }
  1709. function pasteTable( tableData ) {
  1710. const data = {
  1711. dataTransfer: createDataTransfer(),
  1712. preventDefault: sinon.spy(),
  1713. stopPropagation: sinon.spy()
  1714. };
  1715. data.dataTransfer.setData( 'text/html', viewTable( tableData ) );
  1716. viewDocument.fire( 'paste', data );
  1717. return data;
  1718. }
  1719. function createDataTransfer() {
  1720. const store = new Map();
  1721. return {
  1722. setData( type, data ) {
  1723. store.set( type, data );
  1724. },
  1725. getData( type ) {
  1726. return store.get( type );
  1727. }
  1728. };
  1729. }
  1730. } );