tableclipboard-paste.js 44 KB

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