8
0

tableclipboard-paste.js 48 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615
  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, 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 ] ) // Cell 34.
  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. it( 'handles pasting simple table over a table with rowspan (rowspan before selection)', () => {
  568. // +----+----+----+----+----+
  569. // | 00 | 01 | 02 | 03 | 04 |
  570. // +----+----+----+----+----+
  571. // | 10 | 11 | 12 | 13 | 14 |
  572. // +----+ +----+----+----+
  573. // | 20 | | 22 | 23 | 24 |
  574. // +----+ +----+----+----+
  575. // | 30 | | 32 | 33 | 34 |
  576. // +----+----+----+----+----+
  577. // | 40 | 41 | 42 | 43 | 44 |
  578. // +----+----+----+----+----+
  579. setModelData( model, modelTable( [
  580. [ '00', '01', '02', '03', '04' ],
  581. [ '10', { contents: '11', rowspan: 3 }, '12', '13', '14' ],
  582. [ '20', '22', '23', '24' ],
  583. [ '30', '32', '33', '34' ],
  584. [ '40', '41', '42', '43', '44' ]
  585. ] ) );
  586. tableSelection.setCellSelection(
  587. modelRoot.getNodeByPath( [ 0, 1, 2 ] ),
  588. modelRoot.getNodeByPath( [ 0, 3, 2 ] ) // Cell 33.
  589. );
  590. pasteTable( [
  591. [ 'aa', 'ab' ],
  592. [ 'ba', 'bb' ],
  593. [ 'ca', 'cb' ]
  594. ] );
  595. // +----+----+----+----+----+
  596. // | 00 | 01 | 02 | 03 | 04 |
  597. // +----+----+----+----+----+
  598. // | 10 | 11 | aa | ab | 14 |
  599. // +----+ +----+----+----+
  600. // | 20 | | ba | bb | 24 |
  601. // +----+ +----+----+----+
  602. // | 30 | | ca | cb | 34 |
  603. // +----+----+----+----+----+
  604. // | 40 | 41 | 42 | 43 | 44 |
  605. // +----+----+----+----+----+
  606. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  607. [ '00', '01', '02', '03', '04' ],
  608. [ '10', { contents: '11', rowspan: 3 }, 'aa', 'ab', '14' ],
  609. [ '20', 'ba', 'bb', '24' ],
  610. [ '30', 'ca', 'cb', '34' ],
  611. [ '40', '41', '42', '43', '44' ]
  612. ] ) );
  613. /* eslint-disable no-multi-spaces */
  614. assertSelectedCells( model, [
  615. [ 0, 0, 0, 0, 0 ],
  616. [ 0, 0, 1, 1, 0 ],
  617. [ 0, 1, 1, 0 ],
  618. [ 0, 1, 1, 0 ],
  619. [ 0, 0, 0, 0, 0 ]
  620. ] );
  621. /* eslint-enable no-multi-spaces */
  622. } );
  623. it( 'handles pasting simple table over a table with rowspans (rowspan before selection)', () => {
  624. // +----+----+----+----+----+----+
  625. // | 00 | 01 | 02 | 03 | 04 | 05 |
  626. // +----+----+----+----+----+----+
  627. // | 10 | 12 | 13 | 14 | 15 |
  628. // +----+----+----+----+----+----+
  629. // | 20 | 23 | 24 | 25 |
  630. // +----+----+----+----+----+----+
  631. // | 30 | 31 | 32 | 33 | 34 | 35 |
  632. // +----+----+----+----+----+----+
  633. setModelData( model, modelTable( [
  634. [ '00', '01', '02', '03', '04', '05' ],
  635. [ { contents: '10', colspan: 2 }, '12', '13', '14', '15' ],
  636. [ { contents: '20', colspan: 3 }, '23', '24', '25' ],
  637. [ '30', '31', '32', '33', '34', '35' ]
  638. ] ) );
  639. tableSelection.setCellSelection(
  640. modelRoot.getNodeByPath( [ 0, 1, 2 ] ), // Cell 13.
  641. modelRoot.getNodeByPath( [ 0, 2, 2 ] ) // Cell 24.
  642. );
  643. pasteTable( [
  644. [ 'aa', 'ab' ],
  645. [ 'ba', 'bb' ]
  646. ] );
  647. // +----+----+----+----+----+----+
  648. // | 00 | 01 | 02 | 03 | 04 | 05 |
  649. // +----+----+----+----+----+----+
  650. // | 10 | 12 | aa | ab | 15 |
  651. // +----+----+----+----+----+----+
  652. // | 20 | ba | bb | 25 |
  653. // +----+----+----+----+----+----+
  654. // | 30 | 31 | 32 | 33 | 34 | 35 |
  655. // +----+----+----+----+----+----+
  656. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  657. [ '00', '01', '02', '03', '04', '05' ],
  658. [ { contents: '10', colspan: 2 }, '12', 'aa', 'ab', '15' ],
  659. [ { contents: '20', colspan: 3 }, 'ba', 'bb', '25' ],
  660. [ '30', '31', '32', '33', '34', '35' ]
  661. ] ) );
  662. /* eslint-disable no-multi-spaces */
  663. assertSelectedCells( model, [
  664. [ 0, 0, 0, 0, 0, 0 ],
  665. [ 0, 0, 1, 1, 0 ],
  666. [ 0, 1, 1, 0 ],
  667. [ 0, 0, 0, 0, 0, 0 ]
  668. ] );
  669. /* eslint-enable no-multi-spaces */
  670. } );
  671. // TODO: Skipped case - should allow pasting but no tools to compare areas (like in MergeCellsCommand).
  672. it.skip( 'handles pasting table that has cell with colspan (last row in selection is spanned)', () => {
  673. // +----+----+----+----+
  674. // | 00 | 01 | 02 | 03 |
  675. // +----+----+----+----+
  676. // | 10 | 11 | 13 |
  677. // + + +----+
  678. // | | | 23 |
  679. // +----+----+----+----+
  680. // | 30 | 31 | 32 | 33 |
  681. // +----+----+----+----+
  682. setModelData( model, modelTable( [
  683. [ '00', '01', '02', '03' ],
  684. [ { contents: '10', rowspan: 2 }, { contents: '11', colspan: 2, rowspan: 2 }, '13' ],
  685. [ '23' ],
  686. [ '30', '31', '32', '33' ]
  687. ] ) );
  688. tableSelection.setCellSelection(
  689. modelRoot.getNodeByPath( [ 0, 0, 2 ] ),
  690. modelRoot.getNodeByPath( [ 0, 1, 0 ] )
  691. );
  692. pasteTable( [
  693. [ 'aa', 'ab', 'ac' ],
  694. [ 'ba', 'bb', 'bc' ],
  695. [ 'ca', 'cb', 'cc' ]
  696. ] );
  697. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  698. [ 'aa', 'ab', 'ac', '03' ],
  699. [ 'ba', 'bb', 'bc', '13' ],
  700. [ 'ca', 'cb', 'cc', '23' ],
  701. [ '30', '31', '32', '33' ]
  702. ] ) );
  703. assertSelectedCells( model, [
  704. [ 1, 1, 1, 0 ],
  705. [ 1, 1, 1, 0 ],
  706. [ 1, 1, 1, 0 ],
  707. [ 0, 0, 0, 0 ]
  708. ] );
  709. } );
  710. } );
  711. describe( 'content and paste tables have spans', () => {
  712. it( 'handles pasting colspanned table over table with colspans (no colspan exceeds selection)', () => {
  713. setModelData( model, modelTable( [
  714. [ '00[]', '01', '02', '03' ],
  715. [ { colspan: 3, contents: '10' }, '13' ],
  716. [ { colspan: 2, contents: '20' }, '22', '23' ],
  717. [ '30', '31', { colspan: 2, contents: '31' } ]
  718. ] ) );
  719. tableSelection.setCellSelection(
  720. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  721. modelRoot.getNodeByPath( [ 0, 2, 1 ] )
  722. );
  723. pasteTable( [
  724. [ 'aa', { colspan: 2, contents: 'ab' } ],
  725. [ { colspan: 3, contents: 'ba' } ],
  726. [ 'ca', 'cb', 'cc' ]
  727. ] );
  728. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  729. [ 'aa', { colspan: 2, contents: 'ab' }, '03' ],
  730. [ { colspan: 3, contents: 'ba' }, '13' ],
  731. [ 'ca', 'cb', 'cc', '23' ],
  732. [ '30', '31', { colspan: 2, contents: '31' } ]
  733. ] ) );
  734. /* eslint-disable no-multi-spaces */
  735. assertSelectedCells( model, [
  736. [ 1, 1, 0 ],
  737. [ 1, 0 ],
  738. [ 1, 1, 1, 0 ],
  739. [ 0, 0, 0 ]
  740. ] );
  741. /* eslint-enable no-multi-spaces */
  742. } );
  743. it( 'handles pasting rowspanned table over table with rowspans (no rowspan exceeds selection)', () => {
  744. setModelData( model, modelTable( [
  745. [ { rowspan: 3, contents: '00' }, { rowspan: 2, contents: '01' }, '02', '03' ],
  746. [ { rowspan: 2, contents: '12' }, '13' ],
  747. [ '21', '23' ],
  748. [ '30', '31', '32', '33' ]
  749. ] ) );
  750. tableSelection.setCellSelection(
  751. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  752. modelRoot.getNodeByPath( [ 0, 2, 1 ] )
  753. );
  754. pasteTable( [
  755. [ 'aa', { rowspan: 3, contents: 'ab' }, { rowspan: 2, contents: 'ac' }, 'ad' ],
  756. [ { rowspan: 2, contents: 'ba' }, 'bd' ],
  757. [ 'cc', 'cd' ]
  758. ] );
  759. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  760. [ 'aa', { rowspan: 3, contents: 'ab' }, { rowspan: 2, contents: 'ac' }, 'ad' ],
  761. [ { rowspan: 2, contents: 'ba' }, 'bd' ],
  762. [ 'cc', 'cd' ],
  763. [ '30', '31', '32', '33' ]
  764. ] ) );
  765. /* eslint-disable no-multi-spaces */
  766. assertSelectedCells( model, [
  767. [ 1, 1, 1, 1 ],
  768. [ 1, 1 ],
  769. [ 1, 1 ],
  770. [ 0, 0, 0, 0 ]
  771. ] );
  772. /* eslint-enable no-multi-spaces */
  773. } );
  774. it( 'handles pasting multi-spanned table over table with multi-spans (no span exceeds selection)', () => {
  775. // +----+----+----+----+----+----+
  776. // | 00 | 02 | 03 | 05 |
  777. // + + + +----+
  778. // | | | | 15 |
  779. // +----+----+----+ +----+
  780. // | 20 | 21 | | 25 |
  781. // + +----+----+----+----+----+
  782. // | | 31 | 32 | 34 | 35 |
  783. // +----+----+----+----+----+----+
  784. // | 40 | 41 | 42 | 43 | 44 | 45 |
  785. // +----+----+----+----+----+----+
  786. setModelData( model, modelTable( [
  787. [
  788. { contents: '00', colspan: 2, rowspan: 2 },
  789. { contents: '02', rowspan: 2 },
  790. { contents: '03', colspan: 2, rowspan: 3 },
  791. '05'
  792. ],
  793. [ '15' ],
  794. [ { contents: '20', rowspan: 2 }, { contents: '21', colspan: 2 }, '25' ],
  795. [ '31', { contents: '32', colspan: 2 }, '34', '35' ],
  796. [ '40', '41', '42', '43', '44', '45' ]
  797. ] ) );
  798. tableSelection.setCellSelection(
  799. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  800. modelRoot.getNodeByPath( [ 0, 3, 2 ] )
  801. );
  802. // +----+----+----+----+----+
  803. // | aa | ac | ad | ae |
  804. // +----+----+----+----+ +
  805. // | ba | bb | |
  806. // +----+ +----+
  807. // | ca | | ce |
  808. // + +----+----+----+----+
  809. // | | db | dc | dd |
  810. // +----+----+----+----+----+
  811. pasteTable( [
  812. [ { contents: 'aa', colspan: 2 }, 'ac', 'ad', { contents: 'ae', rowspan: 2 } ],
  813. [ 'ba', { contents: 'bb', colspan: 3, rowspan: 2 } ],
  814. [ { contents: 'ca', rowspan: 2 }, 'ce' ],
  815. [ 'db', 'dc', { contents: 'dd', colspan: 2 } ]
  816. ] );
  817. // +----+----+----+----+----+----+
  818. // | aa | ac | ad | ae | 05 |
  819. // +----+----+----+----+ +----+
  820. // | ba | bb | | 15 |
  821. // +----+ +----+----+
  822. // | ca | | ce | 25 |
  823. // + +----+----+----+----+----+
  824. // | | db | dc | dd | 35 |
  825. // +----+----+----+----+----+----+
  826. // | 40 | 41 | 42 | 43 | 44 | 45 |
  827. // +----+----+----+----+----+----+
  828. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  829. [ { contents: 'aa', colspan: 2 }, 'ac', 'ad', { contents: 'ae', rowspan: 2 }, '05' ],
  830. [ 'ba', { contents: 'bb', colspan: 3, rowspan: 2 }, '15' ],
  831. [ { contents: 'ca', rowspan: 2 }, 'ce', '25' ],
  832. [ 'db', 'dc', { contents: 'dd', colspan: 2 }, '35' ],
  833. [ '40', '41', '42', '43', '44', '45' ]
  834. ] ) );
  835. /* eslint-disable no-multi-spaces */
  836. assertSelectedCells( model, [
  837. [ 1, 1, 1, 1, 0 ],
  838. [ 1, 1, 0 ],
  839. [ 1, 1, 0 ],
  840. [ 1, 1, 1, 0 ],
  841. [ 0, 0, 0, 0, 0, 0 ]
  842. ] );
  843. /* eslint-enable no-multi-spaces */
  844. } );
  845. // TODO: Skipped case - should allow pasting but no tools to compare areas (like in MergeCellsCommand).
  846. it.skip( 'handles pasting table that has cell with colspan (last row in selection is spanned)', () => {
  847. // +----+----+----+----+
  848. // | 00 | 01 | 02 | 03 |
  849. // +----+----+----+----+
  850. // | 10 | 11 | 13 |
  851. // + + +----+
  852. // | | | 23 |
  853. // +----+----+----+----+
  854. // | 30 | 31 | 32 | 33 |
  855. // +----+----+----+----+
  856. setModelData( model, modelTable( [
  857. [ '00', '01', '02', '03' ],
  858. [ { contents: '10', rowspan: 2 }, { contents: '11', colspan: 2, rowspan: 2 }, '13' ],
  859. [ '23' ],
  860. [ '30', '31', '32', '33' ]
  861. ] ) );
  862. tableSelection.setCellSelection(
  863. modelRoot.getNodeByPath( [ 0, 0, 2 ] ),
  864. modelRoot.getNodeByPath( [ 0, 1, 0 ] )
  865. );
  866. // +----+----+----+
  867. // | aa | ab | ac |
  868. // +----+----+----+
  869. // | ba | bc |
  870. // + +----+
  871. // | | cc |
  872. // +----+----+----+
  873. pasteTable( [
  874. [ 'aa', 'ab', 'ac' ],
  875. [ { contents: 'ba', colspan: 2, rowspan: 2 }, 'bc' ],
  876. [ 'cc' ]
  877. ] );
  878. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  879. [ '00', '01', '02', '03' ],
  880. [ '10', { colspan: 2, contents: 'aa' }, '13' ],
  881. [ '20', 'ba', 'bb', '23' ],
  882. [ '30', '31', '32', '33' ]
  883. ] ) );
  884. /* eslint-disable no-multi-spaces */
  885. assertSelectedCells( model, [
  886. [ 0, 0, 0, 0 ],
  887. [ 0, 1, 0 ],
  888. [ 0, 1, 1, 0 ],
  889. [ 0, 0, 0, 0 ]
  890. ] );
  891. /* eslint-enable no-multi-spaces */
  892. } );
  893. } );
  894. } );
  895. describe( 'pasted table is bigger than the selected area', () => {
  896. describe( 'no spans', () => {
  897. it( 'handles simple table paste to a simple table fragment - at the beginning of a table', () => {
  898. tableSelection.setCellSelection(
  899. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  900. modelRoot.getNodeByPath( [ 0, 1, 1 ] )
  901. );
  902. pasteTable( [
  903. [ 'aa', 'ab', 'ac' ],
  904. [ 'ba', 'bb', 'bc' ],
  905. [ 'ca', 'cb', 'cc' ]
  906. ] );
  907. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  908. [ 'aa', 'ab', '02', '03' ],
  909. [ 'ba', 'bb', '12', '13' ],
  910. [ '20', '21', '22', '23' ],
  911. [ '30', '31', '32', '33' ]
  912. ] ) );
  913. assertSelectedCells( model, [
  914. [ 1, 1, 0, 0 ],
  915. [ 1, 1, 0, 0 ],
  916. [ 0, 0, 0, 0 ],
  917. [ 0, 0, 0, 0 ]
  918. ] );
  919. } );
  920. it( 'handles simple table paste to a simple table fragment - at the end of a table', () => {
  921. tableSelection.setCellSelection(
  922. modelRoot.getNodeByPath( [ 0, 2, 2 ] ),
  923. modelRoot.getNodeByPath( [ 0, 3, 3 ] )
  924. );
  925. pasteTable( [
  926. [ 'aa', 'ab', 'ac' ],
  927. [ 'ba', 'bb', 'bc' ],
  928. [ 'ca', 'cb', 'cc' ]
  929. ] );
  930. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  931. [ '00', '01', '02', '03' ],
  932. [ '10', '11', '12', '13' ],
  933. [ '20', '21', 'aa', 'ab' ],
  934. [ '30', '31', 'ba', 'bb' ]
  935. ] ) );
  936. assertSelectedCells( model, [
  937. [ 0, 0, 0, 0 ],
  938. [ 0, 0, 0, 0 ],
  939. [ 0, 0, 1, 1 ],
  940. [ 0, 0, 1, 1 ]
  941. ] );
  942. } );
  943. it( 'handles simple table paste to a simple table fragment - in the middle of a table', () => {
  944. tableSelection.setCellSelection(
  945. modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
  946. modelRoot.getNodeByPath( [ 0, 2, 2 ] )
  947. );
  948. pasteTable( [
  949. [ 'aa', 'ab', 'ac' ],
  950. [ 'ba', 'bb', 'bc' ],
  951. [ 'ca', 'cb', 'cc' ]
  952. ] );
  953. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  954. [ '00', '01', '02', '03' ],
  955. [ '10', 'aa', 'ab', '13' ],
  956. [ '20', 'ba', 'bb', '23' ],
  957. [ '30', '31', '32', '33' ]
  958. ] ) );
  959. assertSelectedCells( model, [
  960. [ 0, 0, 0, 0 ],
  961. [ 0, 1, 1, 0 ],
  962. [ 0, 1, 1, 0 ],
  963. [ 0, 0, 0, 0 ]
  964. ] );
  965. } );
  966. it( 'handles paste to a simple row fragment - in the middle of a table', () => {
  967. tableSelection.setCellSelection(
  968. modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
  969. modelRoot.getNodeByPath( [ 0, 1, 2 ] )
  970. );
  971. pasteTable( [
  972. [ 'aa', 'ab', 'ac' ],
  973. [ 'ba', 'bb', 'bc' ],
  974. [ 'ca', 'cb', 'cc' ]
  975. ] );
  976. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  977. [ '00', '01', '02', '03' ],
  978. [ '10', 'aa', 'ab', '13' ],
  979. [ '20', '21', '22', '23' ],
  980. [ '30', '31', '32', '33' ]
  981. ] ) );
  982. assertSelectedCells( model, [
  983. [ 0, 0, 0, 0 ],
  984. [ 0, 1, 1, 0 ],
  985. [ 0, 0, 0, 0 ],
  986. [ 0, 0, 0, 0 ]
  987. ] );
  988. } );
  989. it( 'handles paste to a simple column fragment - in the middle of a table', () => {
  990. tableSelection.setCellSelection(
  991. modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
  992. modelRoot.getNodeByPath( [ 0, 2, 1 ] )
  993. );
  994. pasteTable( [
  995. [ 'aa', 'ab', 'ac' ],
  996. [ 'ba', 'bb', 'bc' ],
  997. [ 'ca', 'cb', 'cc' ]
  998. ] );
  999. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  1000. [ '00', '01', '02', '03' ],
  1001. [ '10', 'aa', '12', '13' ],
  1002. [ '20', 'ba', '22', '23' ],
  1003. [ '30', '31', '32', '33' ]
  1004. ] ) );
  1005. assertSelectedCells( model, [
  1006. [ 0, 0, 0, 0 ],
  1007. [ 0, 1, 0, 0 ],
  1008. [ 0, 1, 0, 0 ],
  1009. [ 0, 0, 0, 0 ]
  1010. ] );
  1011. } );
  1012. it( 'handles simple table paste to a simple table fragment - whole table selected', () => {
  1013. tableSelection.setCellSelection(
  1014. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  1015. modelRoot.getNodeByPath( [ 0, 3, 3 ] )
  1016. );
  1017. pasteTable( [
  1018. [ 'aa', 'ab', 'ac', 'ad', 'ae' ],
  1019. [ 'ba', 'bb', 'bc', 'bd', 'be' ],
  1020. [ 'ca', 'cb', 'cc', 'cd', 'ce' ],
  1021. [ 'da', 'db', 'dc', 'dd', 'de' ],
  1022. [ 'ea', 'eb', 'ec', 'ed', 'ee' ]
  1023. ] );
  1024. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  1025. [ 'aa', 'ab', 'ac', 'ad' ],
  1026. [ 'ba', 'bb', 'bc', 'bd' ],
  1027. [ 'ca', 'cb', 'cc', 'cd' ],
  1028. [ 'da', 'db', 'dc', 'dd' ]
  1029. ] ) );
  1030. assertSelectedCells( model, [
  1031. [ 1, 1, 1, 1 ],
  1032. [ 1, 1, 1, 1 ],
  1033. [ 1, 1, 1, 1 ],
  1034. [ 1, 1, 1, 1 ]
  1035. ] );
  1036. } );
  1037. } );
  1038. describe( 'pasted table has spans', () => {
  1039. it( 'handles pasting table that has cell with colspan', () => {
  1040. tableSelection.setCellSelection(
  1041. modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
  1042. modelRoot.getNodeByPath( [ 0, 2, 2 ] )
  1043. );
  1044. pasteTable( [
  1045. [ { colspan: 3, contents: 'aa' } ],
  1046. [ 'ba', 'bb', 'bc' ]
  1047. ] );
  1048. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  1049. [ '00', '01', '02', '03' ],
  1050. [ '10', { colspan: 2, contents: 'aa' }, '13' ],
  1051. [ '20', 'ba', 'bb', '23' ],
  1052. [ '30', '31', '32', '33' ]
  1053. ] ) );
  1054. /* eslint-disable no-multi-spaces */
  1055. assertSelectedCells( model, [
  1056. [ 0, 0, 0, 0 ],
  1057. [ 0, 1, 0 ],
  1058. [ 0, 1, 1, 0 ],
  1059. [ 0, 0, 0, 0 ]
  1060. ] );
  1061. /* eslint-enable no-multi-spaces */
  1062. } );
  1063. it( 'handles pasting table that has many cells with various colspan', () => {
  1064. tableSelection.setCellSelection(
  1065. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  1066. modelRoot.getNodeByPath( [ 0, 3, 2 ] )
  1067. );
  1068. pasteTable( [
  1069. [ 'aa', { colspan: 3, contents: 'ab' } ],
  1070. [ { colspan: 4, contents: 'ba' } ],
  1071. [ 'ca', 'cb', 'cc', 'cd' ],
  1072. [ { colspan: 2, contents: 'da' }, 'dc', 'dd' ]
  1073. ] );
  1074. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  1075. [ 'aa', { colspan: 2, contents: 'ab' }, '03' ],
  1076. [ { colspan: 3, contents: 'ba' }, '13' ],
  1077. [ 'ca', 'cb', 'cc', '23' ],
  1078. [ { colspan: 2, contents: 'da' }, 'dc', '33' ]
  1079. ] ) );
  1080. /* eslint-disable no-multi-spaces */
  1081. assertSelectedCells( model, [
  1082. [ 1, 1, 0 ],
  1083. [ 1, 0 ],
  1084. [ 1, 1, 1, 0 ],
  1085. [ 1, 1, 0 ]
  1086. ] );
  1087. /* eslint-enable no-multi-spaces */
  1088. } );
  1089. it( 'handles pasting table that has cell with rowspan', () => {
  1090. tableSelection.setCellSelection(
  1091. modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
  1092. modelRoot.getNodeByPath( [ 0, 2, 2 ] )
  1093. );
  1094. pasteTable( [
  1095. [ { rowspan: 3, contents: 'aa' }, 'ab' ],
  1096. [ 'bb' ],
  1097. [ 'cb' ]
  1098. ] );
  1099. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  1100. [ '00', '01', '02', '03' ],
  1101. [ '10', { rowspan: 2, contents: 'aa' }, 'ab', '13' ],
  1102. [ '20', 'bb', '23' ],
  1103. [ '30', '31', '32', '33' ]
  1104. ] ) );
  1105. /* eslint-disable no-multi-spaces */
  1106. assertSelectedCells( model, [
  1107. [ 0, 0, 0, 0 ],
  1108. [ 0, 1, 1, 0 ],
  1109. [ 0, 1, 0 ],
  1110. [ 0, 0, 0, 0 ]
  1111. ] );
  1112. /* eslint-enable no-multi-spaces */
  1113. } );
  1114. it( 'handles pasting table that has many cells with various rowspan', () => {
  1115. tableSelection.setCellSelection(
  1116. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  1117. modelRoot.getNodeByPath( [ 0, 2, 3 ] )
  1118. );
  1119. pasteTable( [
  1120. [ 'aa', { rowspan: 3, contents: 'ab' }, { rowspan: 2, contents: 'ac' }, 'ad', 'ae' ],
  1121. [ { rowspan: 3, contents: 'ba' }, 'bd', 'be' ],
  1122. [ 'cc', 'cd', 'ce' ],
  1123. [ 'da', 'db', 'dc', 'dd' ]
  1124. ] );
  1125. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  1126. [ 'aa', { rowspan: 3, contents: 'ab' }, { rowspan: 2, contents: 'ac' }, 'ad' ],
  1127. [ { rowspan: 2, contents: 'ba' }, 'bd' ],
  1128. [ 'cc', 'cd' ],
  1129. [ '30', '31', '32', '33' ]
  1130. ] ) );
  1131. /* eslint-disable no-multi-spaces */
  1132. assertSelectedCells( model, [
  1133. [ 1, 1, 1, 1 ],
  1134. [ 1, 1 ],
  1135. [ 1, 1 ],
  1136. [ 0, 0, 0, 0 ]
  1137. ] );
  1138. /* eslint-enable no-multi-spaces */
  1139. } );
  1140. it( 'handles pasting multi-spanned table', () => {
  1141. setModelData( model, modelTable( [
  1142. [ '00', '01', '02', '03', '04', '05' ],
  1143. [ '10', '11', '12', '13', '14', '15' ],
  1144. [ '20', '21', '22', '23', '24', '25' ],
  1145. [ '30', '31', '32', '33', '34', '35' ],
  1146. [ '40', '41', '42', '43', '44', '45' ]
  1147. ] ) );
  1148. tableSelection.setCellSelection(
  1149. modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
  1150. modelRoot.getNodeByPath( [ 0, 3, 3 ] )
  1151. );
  1152. // +----+----+----+----+----+
  1153. // | aa | ac | ad | ae |
  1154. // +----+----+----+----+ +
  1155. // | ba | bb | |
  1156. // +----+ +----+
  1157. // | ca | | ce |
  1158. // + +----+----+----+----+
  1159. // | | db | dc | dd |
  1160. // +----+----+----+----+----+
  1161. pasteTable( [
  1162. [ { contents: 'aa', colspan: 2 }, 'ac', 'ad', { contents: 'ae', rowspan: 2 } ],
  1163. [ 'ba', { contents: 'bb', colspan: 3, rowspan: 2 } ],
  1164. [ { contents: 'ca', rowspan: 2 }, 'ce' ],
  1165. [ 'db', 'dc', { contents: 'dd', colspan: 2 } ]
  1166. ] );
  1167. // +----+----+----+----+----+----+
  1168. // | 00 | 01 | 02 | 03 | 04 | 05 |
  1169. // +----+----+----+----+----+----+
  1170. // | 10 | aa | ac | 14 | 15 |
  1171. // +----+----+----+----+----+----+
  1172. // | 20 | ba | bb | 24 | 25 |
  1173. // +----+----+ +----+----+
  1174. // | 30 | ca | | 34 | 35 |
  1175. // +----+----+----+----+----+----+
  1176. // | 40 | 41 | 42 | 43 | 44 | 45 |
  1177. // +----+----+----+----+----+----+
  1178. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  1179. [ '00', '01', '02', '03', '04', '05' ],
  1180. [ '10', { contents: 'aa', colspan: 2 }, 'ac', '14', '15' ],
  1181. [ '20', 'ba', { contents: 'bb', colspan: 2, rowspan: 2 }, '24', '25' ],
  1182. [ '30', 'ca', '34', '35' ],
  1183. [ '40', '41', '42', '43', '44', '45' ]
  1184. ] ) );
  1185. /* eslint-disable no-multi-spaces */
  1186. assertSelectedCells( model, [
  1187. [ 0, 0, 0, 0, 0, 0 ],
  1188. [ 0, 1, 1, 0, 0 ],
  1189. [ 0, 1, 1, 0, 0 ],
  1190. [ 0, 1, 0, 0 ],
  1191. [ 0, 0, 0, 0, 0, 0 ]
  1192. ] );
  1193. /* eslint-enable no-multi-spaces */
  1194. } );
  1195. } );
  1196. } );
  1197. describe( 'pasted table is smaller than the selected area', () => {
  1198. it( 'blocks this case', () => {
  1199. tableSelection.setCellSelection(
  1200. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  1201. modelRoot.getNodeByPath( [ 0, 3, 3 ] )
  1202. );
  1203. // Catches the temporary console log in the CK_DEBUG mode.
  1204. sinon.stub( console, 'log' );
  1205. pasteTable( [
  1206. [ 'aa', 'ab' ],
  1207. [ 'ba', 'bb' ]
  1208. ] );
  1209. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  1210. [ '00', '01', '02', '03' ],
  1211. [ '10', '11', '12', '13' ],
  1212. [ '20', '21', '22', '23' ],
  1213. [ '30', '31', '32', '33' ]
  1214. ] ) );
  1215. assertSelectedCells( model, [
  1216. [ 1, 1, 1, 1 ],
  1217. [ 1, 1, 1, 1 ],
  1218. [ 1, 1, 1, 1 ],
  1219. [ 1, 1, 1, 1 ]
  1220. ] );
  1221. } );
  1222. } );
  1223. } );
  1224. describe( 'Clipboard integration - paste (content scenarios)', () => {
  1225. it( 'handles multiple paragraphs', async () => {
  1226. await createEditor();
  1227. setModelData( model, modelTable( [
  1228. [ '00', '01', '02' ],
  1229. [ '01', '11', '12' ],
  1230. [ '02', '21', '22' ]
  1231. ] ) );
  1232. tableSelection.setCellSelection(
  1233. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  1234. modelRoot.getNodeByPath( [ 0, 1, 1 ] )
  1235. );
  1236. pasteTable( [
  1237. [ '<p>a</p><p>a</p><p>a</p>', 'ab' ],
  1238. [ 'ba', 'bb' ]
  1239. ] );
  1240. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  1241. [ '<paragraph>a</paragraph><paragraph>a</paragraph><paragraph>a</paragraph>', 'ab', '02' ],
  1242. [ 'ba', 'bb', '12' ],
  1243. [ '02', '21', '22' ]
  1244. ] ) );
  1245. } );
  1246. it( 'handles image in table cell', async () => {
  1247. await createEditor( [ ImageEditing, ImageCaptionEditing ] );
  1248. setModelData( model, modelTable( [
  1249. [ '00', '01', '02' ],
  1250. [ '01', '11', '12' ],
  1251. [ '02', '21', '22' ]
  1252. ] ) );
  1253. tableSelection.setCellSelection(
  1254. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  1255. modelRoot.getNodeByPath( [ 0, 1, 1 ] )
  1256. );
  1257. pasteTable( [
  1258. [ '<img src="/assets/sample.jpg">', 'ab' ],
  1259. [ 'ba', 'bb' ]
  1260. ] );
  1261. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  1262. [ '<image src="/assets/sample.jpg"><caption></caption></image>', 'ab', '02' ],
  1263. [ 'ba', 'bb', '12' ],
  1264. [ '02', '21', '22' ]
  1265. ] ) );
  1266. } );
  1267. it( 'handles mixed nested content in table cell', async () => {
  1268. await createEditor( [ ImageEditing, ImageCaptionEditing, BlockQuoteEditing, HorizontalLineEditing, ListEditing ] );
  1269. setModelData( model, modelTable( [
  1270. [ '00', '01', '02' ],
  1271. [ '01', '11', '12' ],
  1272. [ '02', '21', '22' ]
  1273. ] ) );
  1274. tableSelection.setCellSelection(
  1275. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  1276. modelRoot.getNodeByPath( [ 0, 1, 1 ] )
  1277. );
  1278. const img = '<img src="/assets/sample.jpg">';
  1279. const list = '<ul><li>foo</li><li>bar</li></ul>';
  1280. const blockquote = `<blockquote><p>baz</p>${ list }</blockquote>`;
  1281. pasteTable( [
  1282. [ `${ img }${ list }${ blockquote }`, 'ab' ],
  1283. [ 'ba', 'bb' ]
  1284. ] );
  1285. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  1286. [
  1287. '<image src="/assets/sample.jpg"><caption></caption></image>' +
  1288. '<listItem listIndent="0" listType="bulleted">foo</listItem>' +
  1289. '<listItem listIndent="0" listType="bulleted">bar</listItem>' +
  1290. '<blockQuote>' +
  1291. '<paragraph>baz</paragraph>' +
  1292. '<listItem listIndent="0" listType="bulleted">foo</listItem>' +
  1293. '<listItem listIndent="0" listType="bulleted">bar</listItem>' +
  1294. '</blockQuote>',
  1295. 'ab',
  1296. '02' ],
  1297. [ 'ba', 'bb', '12' ],
  1298. [ '02', '21', '22' ]
  1299. ] ) );
  1300. } );
  1301. it( 'handles table cell properties', async () => {
  1302. await createEditor( [ TableCellPropertiesEditing ] );
  1303. setModelData( model, modelTable( [
  1304. [ '00', '01', '02' ],
  1305. [ '01', '11', '12' ],
  1306. [ '02', '21', '22' ]
  1307. ] ) );
  1308. tableSelection.setCellSelection(
  1309. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  1310. modelRoot.getNodeByPath( [ 0, 1, 1 ] )
  1311. );
  1312. pasteTable( [
  1313. [ { contents: 'aa', style: 'border:1px solid #f00;background:#ba7;width:1337px' }, 'ab' ],
  1314. [ 'ba', 'bb' ]
  1315. ] );
  1316. const tableCell = model.document.getRoot().getNodeByPath( [ 0, 0, 0 ] );
  1317. expect( tableCell.getAttribute( 'borderColor' ) ).to.deep.equal( {
  1318. top: '#f00',
  1319. right: '#f00',
  1320. bottom: '#f00',
  1321. left: '#f00'
  1322. } );
  1323. expect( tableCell.getAttribute( 'borderStyle' ) ).to.deep.equal( {
  1324. top: 'solid',
  1325. right: 'solid',
  1326. bottom: 'solid',
  1327. left: 'solid'
  1328. } );
  1329. expect( tableCell.getAttribute( 'borderWidth' ) ).to.deep.equal( {
  1330. top: '1px',
  1331. right: '1px',
  1332. bottom: '1px',
  1333. left: '1px'
  1334. } );
  1335. expect( tableCell.getAttribute( 'backgroundColor' ) ).to.equal( '#ba7' );
  1336. expect( tableCell.getAttribute( 'width' ) ).to.equal( '1337px' );
  1337. } );
  1338. it( 'discards table properties', async () => {
  1339. await createEditor( [ TableCellPropertiesEditing ] );
  1340. setModelData( model, modelTable( [
  1341. [ '00', '01', '02' ],
  1342. [ '01', '11', '12' ],
  1343. [ '02', '21', '22' ]
  1344. ] ) );
  1345. tableSelection.setCellSelection(
  1346. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  1347. modelRoot.getNodeByPath( [ 0, 1, 1 ] )
  1348. );
  1349. const tableStyle = 'border:1px solid #f00;background:#ba7;width:1337px';
  1350. const pastedTable = `<table style="${ tableStyle }"><tr><td>aa</td><td>ab</td></tr><tr><td>ba</td><td>bb</td></tr></table>`;
  1351. const data = {
  1352. dataTransfer: createDataTransfer(),
  1353. preventDefault: sinon.spy(),
  1354. stopPropagation: sinon.spy()
  1355. };
  1356. data.dataTransfer.setData( 'text/html', pastedTable );
  1357. viewDocument.fire( 'paste', data );
  1358. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  1359. [ 'aa', 'ab', '02' ],
  1360. [ 'ba', 'bb', '12' ],
  1361. [ '02', '21', '22' ]
  1362. ] ) );
  1363. } );
  1364. } );
  1365. async function createEditor( extraPlugins = [] ) {
  1366. editor = await ClassicTestEditor.create( element, {
  1367. plugins: [ TableEditing, TableClipboard, Paragraph, Clipboard, ...extraPlugins ]
  1368. } );
  1369. model = editor.model;
  1370. modelRoot = model.document.getRoot();
  1371. viewDocument = editor.editing.view.document;
  1372. tableSelection = editor.plugins.get( 'TableSelection' );
  1373. }
  1374. function pasteTable( tableData ) {
  1375. const data = {
  1376. dataTransfer: createDataTransfer(),
  1377. preventDefault: sinon.spy(),
  1378. stopPropagation: sinon.spy()
  1379. };
  1380. data.dataTransfer.setData( 'text/html', viewTable( tableData ) );
  1381. viewDocument.fire( 'paste', data );
  1382. return data;
  1383. }
  1384. function createDataTransfer() {
  1385. const store = new Map();
  1386. return {
  1387. setData( type, data ) {
  1388. store.set( type, data );
  1389. },
  1390. getData( type ) {
  1391. return store.get( type );
  1392. }
  1393. };
  1394. }
  1395. } );