downcast.js 51 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636
  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. import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
  6. import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
  7. import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
  8. import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
  9. import { setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
  10. import { assertEqualMarkup } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
  11. import { modelTable, viewTable } from '../_utils/utils';
  12. import TableEditing from '../../src/tableediting';
  13. describe.only( 'downcast converters', () => {
  14. let editor, model, root, view;
  15. testUtils.createSinonSandbox();
  16. beforeEach( async () => {
  17. editor = await VirtualTestEditor.create( { plugins: [ Paragraph, TableEditing ] } );
  18. model = editor.model;
  19. root = model.document.getRoot( 'main' );
  20. view = editor.editing.view;
  21. } );
  22. afterEach( () => {
  23. return editor.destroy();
  24. } );
  25. describe( 'downcastInsertTable()', () => {
  26. describe( 'editing pipeline', () => {
  27. it( 'should create table as a widget', () => {
  28. setModelData( model, modelTable( [ [ '' ] ] ) );
  29. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  30. '<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false">' +
  31. '<div class="ck ck-widget__selection-handle"></div>' +
  32. '<table>' +
  33. '<tbody>' +
  34. '<tr>' +
  35. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  36. '<span style="display:inline-block"></span>' +
  37. '</td>' +
  38. '</tr>' +
  39. '</tbody>' +
  40. '</table>' +
  41. '</figure>'
  42. );
  43. } );
  44. } );
  45. describe( 'data pipeline', () => {
  46. it( 'should create table with tbody and thead', () => {
  47. setModelData( model, modelTable( [
  48. [ '00' ],
  49. [ '10' ]
  50. ], { headingRows: 1 } ) );
  51. assertEqualMarkup( editor.getData(),
  52. '<figure class="table">' +
  53. '<table>' +
  54. '<thead>' +
  55. '<tr><th>00</th></tr>' +
  56. '</thead>' +
  57. '<tbody>' +
  58. '<tr><td>10</td></tr>' +
  59. '</tbody>' +
  60. '</table>' +
  61. '</figure>'
  62. );
  63. } );
  64. it( 'should create table with thead', () => {
  65. setModelData( model, modelTable( [
  66. [ '00' ],
  67. [ '10' ]
  68. ], { headingRows: 2 } ) );
  69. assertEqualMarkup( editor.getData(),
  70. '<figure class="table">' +
  71. '<table>' +
  72. '<thead>' +
  73. '<tr><th>00</th></tr>' +
  74. '<tr><th>10</th></tr>' +
  75. '</thead>' +
  76. '</table>' +
  77. '</figure>'
  78. );
  79. } );
  80. it( 'should create table with heading columns and rows', () => {
  81. setModelData( model, modelTable( [
  82. [ '00', '01', '02', '03' ],
  83. [ '10', '11', '12', '13' ]
  84. ], { headingColumns: 3, headingRows: 1 } ) );
  85. assertEqualMarkup( editor.getData(),
  86. '<figure class="table">' +
  87. '<table>' +
  88. '<thead>' +
  89. '<tr><th>00</th><th>01</th><th>02</th><th>03</th></tr>' +
  90. '</thead>' +
  91. '<tbody>' +
  92. '<tr><th>10</th><th>11</th><th>12</th><td>13</td></tr>' +
  93. '</tbody>' +
  94. '</table>' +
  95. '</figure>'
  96. );
  97. } );
  98. it( 'should create table with block content', () => {
  99. setModelData( model, modelTable( [
  100. [ '<paragraph>00</paragraph><paragraph>foo</paragraph>', '01' ]
  101. ] ) );
  102. assertEqualMarkup( editor.getData(),
  103. '<figure class="table">' +
  104. '<table>' +
  105. '<tbody>' +
  106. '<tr>' +
  107. '<td><p>00</p><p>foo</p></td>' +
  108. '<td>01</td>' +
  109. '</tr>' +
  110. '</tbody>' +
  111. '</table>' +
  112. '</figure>'
  113. );
  114. } );
  115. it( 'should create table with block content (attribute on paragraph)', () => {
  116. editor.conversion.attributeToAttribute(
  117. {
  118. model: { key: 'alignment', values: [ 'right', 'center', 'justify' ] },
  119. view: {
  120. right: { key: 'style', value: { 'text-align': 'right' } },
  121. center: { key: 'style', value: { 'text-align': 'center' } },
  122. justify: { key: 'style', value: { 'text-align': 'justify' } }
  123. }
  124. }
  125. );
  126. setModelData( model, modelTable( [
  127. [ '<paragraph alignment="right">00</paragraph>' ]
  128. ] ) );
  129. assertEqualMarkup( editor.getData(),
  130. '<figure class="table">' +
  131. '<table>' +
  132. '<tbody>' +
  133. '<tr>' +
  134. '<td><p style="text-align:right;">00</p></td>' +
  135. '</tr>' +
  136. '</tbody>' +
  137. '</table>' +
  138. '</figure>'
  139. );
  140. } );
  141. it( 'should be possible to overwrite', () => {
  142. editor.conversion.elementToElement( { model: 'tableRow', view: 'tr', converterPriority: 'high' } );
  143. editor.conversion.elementToElement( { model: 'tableCell', view: 'td', converterPriority: 'high' } );
  144. editor.conversion.for( 'downcast' ).add( dispatcher => {
  145. dispatcher.on( 'insert:table', ( evt, data, conversionApi ) => {
  146. conversionApi.consumable.consume( data.item, 'insert' );
  147. const tableElement = conversionApi.writer.createContainerElement( 'table', { foo: 'bar' } );
  148. const viewPosition = conversionApi.mapper.toViewPosition( data.range.start );
  149. conversionApi.mapper.bindElements( data.item, tableElement );
  150. conversionApi.writer.insert( viewPosition, tableElement );
  151. }, { priority: 'high' } );
  152. } );
  153. setModelData( model, modelTable( [ [ '' ] ] ) );
  154. assertEqualMarkup( editor.getData(),
  155. '<table foo="bar">' +
  156. '<tr><td><p>&nbsp;</p></td></tr>' +
  157. '</table>'
  158. );
  159. } );
  160. it( 'should re-create table on reinsert', () => {
  161. model.schema.register( 'wrapper', {
  162. allowWhere: '$block',
  163. allowContentOf: '$root'
  164. } );
  165. editor.conversion.elementToElement( { model: 'wrapper', view: 'div' } );
  166. setModelData( model, modelTable( [ [ '[]' ] ] ) );
  167. assertEqualMarkup( editor.getData(),
  168. '<figure class="table">' +
  169. '<table>' +
  170. '<tbody>' +
  171. '<tr><td>&nbsp;</td></tr>' +
  172. '</tbody>' +
  173. '</table>' +
  174. '</figure>'
  175. );
  176. model.change( writer => {
  177. const table = model.document.getRoot().getChild( 0 );
  178. const range = writer.createRange( writer.createPositionBefore( table ), writer.createPositionAfter( table ) );
  179. const wrapper = writer.createElement( 'wrapper' );
  180. writer.wrap( range, wrapper );
  181. } );
  182. assertEqualMarkup( editor.getData(),
  183. '<div>' +
  184. '<figure class="table">' +
  185. '<table>' +
  186. '<tbody>' +
  187. '<tr><td>&nbsp;</td></tr>' +
  188. '</tbody>' +
  189. '</table>' +
  190. '</figure>' +
  191. '</div>'
  192. );
  193. } );
  194. describe( 'headingColumns attribute', () => {
  195. it( 'should mark heading columns table cells', () => {
  196. setModelData( model, modelTable( [
  197. [ '00', '01', '02' ],
  198. [ '10', '11', '12' ]
  199. ], { headingColumns: 2 } ) );
  200. assertEqualMarkup( editor.getData(),
  201. '<figure class="table">' +
  202. '<table>' +
  203. '<tbody>' +
  204. '<tr><th>00</th><th>01</th><td>02</td></tr>' +
  205. '<tr><th>10</th><th>11</th><td>12</td></tr>' +
  206. '</tbody>' +
  207. '</table>' +
  208. '</figure>'
  209. );
  210. } );
  211. it( 'should mark heading columns table cells when one has colspan attribute', () => {
  212. setModelData( model, modelTable( [
  213. [ '00', '01', '02', '03' ],
  214. [ { colspan: 2, contents: '10' }, '12', '13' ]
  215. ], { headingColumns: 3 } ) );
  216. assertEqualMarkup( editor.getData(),
  217. '<figure class="table">' +
  218. '<table>' +
  219. '<tbody>' +
  220. '<tr><th>00</th><th>01</th><th>02</th><td>03</td></tr>' +
  221. '<tr><th colspan="2">10</th><th>12</th><td>13</td></tr>' +
  222. '</tbody>' +
  223. '</table>' +
  224. '</figure>'
  225. );
  226. } );
  227. it( 'should work with colspan and rowspan attributes on table cells', () => {
  228. // The table in this test looks like a table below:
  229. //
  230. // Row headings | Normal cells
  231. // |
  232. // +----+----+----+----+
  233. // | 00 | 01 | 02 | 03 |
  234. // | +----+ +----+
  235. // | | 11 | | 13 |
  236. // |----+----+ +----+
  237. // | 20 | | 23 |
  238. // | +----+----+
  239. // | | 32 | 33 |
  240. // +----+----+----+----+
  241. setModelData( model, modelTable( [
  242. [ { rowspan: 2, contents: '00' }, '01', { rowspan: 3, contents: '02' }, '03' ],
  243. [ '11', '13' ],
  244. [ { colspan: 2, rowspan: 2, contents: '20' }, '23' ],
  245. [ '32', '33' ]
  246. ], { headingColumns: 3 } ) );
  247. assertEqualMarkup( editor.getData(),
  248. '<figure class="table">' +
  249. '<table>' +
  250. '<tbody>' +
  251. '<tr><th rowspan="2">00</th><th>01</th><th rowspan="3">02</th><td>03</td></tr>' +
  252. '<tr><th>11</th><td>13</td></tr>' +
  253. '<tr><th rowspan="2" colspan="2">20</th><td>23</td></tr>' +
  254. '<tr><th>32</th><td>33</td></tr>' +
  255. '</tbody>' +
  256. '</table>' +
  257. '</figure>'
  258. );
  259. } );
  260. } );
  261. it( 'should create table with tbody', () => {
  262. setModelData( model, modelTable( [ [ '' ] ] ) );
  263. assertEqualMarkup( editor.getData(),
  264. '<figure class="table">' +
  265. '<table>' +
  266. '<tbody>' +
  267. '<tr><td>&nbsp;</td></tr>' +
  268. '</tbody>' +
  269. '</table>' +
  270. '</figure>'
  271. );
  272. } );
  273. } );
  274. } );
  275. describe( 'downcastInsertRow()', () => {
  276. // The insert row downcast conversion is not executed in data pipeline.
  277. describe( 'editing pipeline', () => {
  278. it( 'should react to changed rows', () => {
  279. setModelData( model, modelTable( [
  280. [ '00', '01' ]
  281. ] ) );
  282. const table = root.getChild( 0 );
  283. model.change( writer => {
  284. const row = writer.createElement( 'tableRow' );
  285. writer.insert( row, table, 1 );
  286. writer.insertElement( 'tableCell', row, 'end' );
  287. writer.insertElement( 'tableCell', row, 'end' );
  288. } );
  289. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  290. [ '00', '01' ],
  291. [ '', '' ]
  292. ], { asWidget: true } ) );
  293. } );
  294. it( 'should properly consume already added rows', () => {
  295. setModelData( model, modelTable( [
  296. [ '00', '01' ]
  297. ] ) );
  298. const table = root.getChild( 0 );
  299. model.change( writer => {
  300. const row = writer.createElement( 'tableRow' );
  301. writer.insert( row, table, 1 );
  302. writer.insertElement( 'tableCell', row, 'end' );
  303. writer.insertElement( 'tableCell', row, 'end' );
  304. } );
  305. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  306. [ '00', '01' ],
  307. [ '', '' ]
  308. ], { asWidget: true } ) );
  309. model.change( writer => {
  310. const row = writer.createElement( 'tableRow' );
  311. writer.insert( row, table, 2 );
  312. writer.insertElement( 'tableCell', row, 'end' );
  313. writer.insertElement( 'tableCell', row, 'end' );
  314. } );
  315. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  316. [ '00', '01' ],
  317. [ '', '' ],
  318. [ '', '' ]
  319. ], { asWidget: true } ) );
  320. } );
  321. it( 'should insert row on proper index', () => {
  322. setModelData( model, modelTable( [
  323. [ '00', '01' ],
  324. [ '21', '22' ],
  325. [ '31', '32' ]
  326. ] ) );
  327. const table = root.getChild( 0 );
  328. model.change( writer => {
  329. const row = writer.createElement( 'tableRow' );
  330. writer.insert( row, table, 1 );
  331. writer.insertElement( 'tableCell', row, 'end' );
  332. writer.insertElement( 'tableCell', row, 'end' );
  333. } );
  334. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  335. [ '00', '01' ],
  336. [ '', '' ],
  337. [ '21', '22' ],
  338. [ '31', '32' ]
  339. ], { asWidget: true } ) );
  340. } );
  341. it( 'should insert row on proper index when table has heading rows defined - insert in body', () => {
  342. setModelData( model, modelTable( [
  343. [ '00', '01' ],
  344. [ '21', '22' ],
  345. [ '31', '32' ]
  346. ], { headingRows: 1, asWidget: true } ) );
  347. const table = root.getChild( 0 );
  348. model.change( writer => {
  349. const row = writer.createElement( 'tableRow' );
  350. writer.insert( row, table, 1 );
  351. writer.insertElement( 'tableCell', row, 'end' );
  352. writer.insertElement( 'tableCell', row, 'end' );
  353. } );
  354. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  355. [ '00', '01' ],
  356. [ '', '' ],
  357. [ '21', '22' ],
  358. [ '31', '32' ]
  359. ], { headingRows: 1, asWidget: true } ) );
  360. } );
  361. it( 'should insert row on proper index when table has heading rows defined - insert in heading', () => {
  362. setModelData( model, modelTable( [
  363. [ '00', '01' ],
  364. [ '21', '22' ],
  365. [ '31', '32' ]
  366. ], { headingRows: 2 } ) );
  367. const table = root.getChild( 0 );
  368. model.change( writer => {
  369. const row = writer.createElement( 'tableRow' );
  370. writer.insert( row, table, 1 );
  371. writer.insertElement( 'tableCell', row, 'end' );
  372. writer.insertElement( 'tableCell', row, 'end' );
  373. } );
  374. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  375. [ '00', '01' ],
  376. [ '', '' ],
  377. [ '21', '22' ],
  378. [ '31', '32' ]
  379. ], { headingRows: 3, asWidget: true } ) );
  380. } );
  381. it( 'should react to changed rows when previous rows\' cells has rowspans', () => {
  382. setModelData( model, modelTable( [
  383. [ { rowspan: 2, contents: '00' }, '01' ],
  384. [ '22' ]
  385. ] ) );
  386. const table = root.getChild( 0 );
  387. model.change( writer => {
  388. const row = writer.createElement( 'tableRow' );
  389. writer.insert( row, table, 2 );
  390. writer.insertElement( 'tableCell', row, 'end' );
  391. writer.insertElement( 'tableCell', row, 'end' );
  392. } );
  393. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  394. [ { rowspan: 2, contents: '00' }, '01' ],
  395. [ '22' ],
  396. [ '', '' ]
  397. ], { asWidget: true } ) );
  398. } );
  399. it( 'should properly create row headings', () => {
  400. setModelData( model, modelTable( [
  401. [ { rowspan: 2, contents: '00' }, '01' ],
  402. [ '22' ]
  403. ], { headingColumns: 1 } ) );
  404. const table = root.getChild( 0 );
  405. model.change( writer => {
  406. const firstRow = writer.createElement( 'tableRow' );
  407. writer.insert( firstRow, table, 2 );
  408. writer.insert( writer.createElement( 'tableCell' ), firstRow, 'end' );
  409. const secondRow = writer.createElement( 'tableRow' );
  410. writer.insert( secondRow, table, 3 );
  411. writer.insert( writer.createElement( 'tableCell' ), secondRow, 'end' );
  412. writer.insert( writer.createElement( 'tableCell' ), secondRow, 'end' );
  413. } );
  414. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  415. [ { rowspan: 2, contents: '00', isHeading: true }, '01' ],
  416. [ '22' ],
  417. [ { contents: '', isHeading: true }, '' ],
  418. [ { contents: '', isHeading: true }, '' ]
  419. ], { asWidget: true } ) );
  420. } );
  421. it( 'should create table cell inside inserted row as a widget', () => {
  422. setModelData( model, modelTable( [ [ '00' ] ] ) );
  423. const table = root.getChild( 0 );
  424. model.change( writer => {
  425. const firstRow = writer.createElement( 'tableRow' );
  426. writer.insert( firstRow, table, 1 );
  427. writer.insert( writer.createElement( 'tableCell' ), firstRow, 'end' );
  428. } );
  429. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  430. '<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false">' +
  431. '<div class="ck ck-widget__selection-handle"></div>' +
  432. '<table>' +
  433. '<tbody>' +
  434. '<tr>' +
  435. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  436. '<span style="display:inline-block">00</span>' +
  437. '</td>' +
  438. '</tr>' +
  439. '<tr>' +
  440. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  441. '<span style="display:inline-block"></span>' +
  442. '</td>' +
  443. '</tr>' +
  444. '</tbody>' +
  445. '</table>' +
  446. '</figure>'
  447. );
  448. } );
  449. } );
  450. } );
  451. describe( 'downcastInsertCell()', () => {
  452. // The insert table cell downcast conversion is not executed in data pipeline.
  453. describe( 'editing pipeline', () => {
  454. it( 'should add tableCell on proper index in tr', () => {
  455. setModelData( model, modelTable( [
  456. [ '00', '01' ]
  457. ] ) );
  458. const table = root.getChild( 0 );
  459. model.change( writer => {
  460. const row = table.getChild( 0 );
  461. writer.insertElement( 'tableCell', row, 1 );
  462. } );
  463. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  464. [ '00', '', '01' ]
  465. ], { asWidget: true } ) );
  466. } );
  467. it( 'should add tableCell on proper index in tr when previous have colspans', () => {
  468. setModelData( model, modelTable( [
  469. [ { colspan: 2, contents: '00' }, '13' ]
  470. ] ) );
  471. const table = root.getChild( 0 );
  472. model.change( writer => {
  473. const row = table.getChild( 0 );
  474. writer.insertElement( 'tableCell', row, 1 );
  475. } );
  476. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  477. [ { colspan: 2, contents: '00' }, '', '13' ]
  478. ], { asWidget: true } ) );
  479. } );
  480. it( 'should add tableCell on proper index in tr when previous row have rowspans', () => {
  481. setModelData( model, modelTable( [
  482. [ { rowspan: 2, contents: '00' }, '01', '02' ],
  483. [ '11', '12' ]
  484. ] ) );
  485. const table = root.getChild( 0 );
  486. model.change( writer => {
  487. writer.insertElement( 'tableCell', table.getChild( 0 ), 1 );
  488. writer.insertElement( 'tableCell', table.getChild( 1 ), 0 );
  489. } );
  490. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  491. [ { rowspan: 2, contents: '00' }, '', '01', '02' ],
  492. [ '', '11', '12' ]
  493. ], { asWidget: true } ) );
  494. } );
  495. it( 'split cell simulation - simple', () => {
  496. setModelData( model, modelTable( [
  497. [ '00', '01' ],
  498. [ '10', '11' ]
  499. ] ) );
  500. const table = root.getChild( 0 );
  501. model.change( writer => {
  502. const firstRow = table.getChild( 0 );
  503. const secondRow = table.getChild( 1 );
  504. writer.insertElement( 'tableCell', firstRow, 1 );
  505. writer.setAttribute( 'colspan', 2, secondRow.getChild( 0 ) );
  506. } );
  507. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  508. [ '00', '', '01' ],
  509. [ { colspan: 2, contents: '10' }, '11' ]
  510. ], { asWidget: true } ) );
  511. } );
  512. it( 'merge simulation - simple', () => {
  513. setModelData( model, modelTable( [
  514. [ '00', '01' ],
  515. [ '10', '11' ]
  516. ] ) );
  517. const table = root.getChild( 0 );
  518. model.change( writer => {
  519. const firstRow = table.getChild( 0 );
  520. writer.setAttribute( 'colspan', 2, firstRow.getChild( 0 ) );
  521. writer.remove( firstRow.getChild( 1 ) );
  522. } );
  523. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  524. [ { colspan: 2, contents: '00' } ],
  525. [ '10', '11' ]
  526. ], { asWidget: true } ) );
  527. } );
  528. it( 'should create inserted table cell as a widget', () => {
  529. setModelData( model, modelTable( [ [ '00' ] ] ) );
  530. const table = root.getChild( 0 );
  531. model.change( writer => {
  532. const row = table.getChild( 0 );
  533. writer.insert( writer.createElement( 'tableCell' ), row, 'end' );
  534. } );
  535. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  536. '<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false">' +
  537. '<div class="ck ck-widget__selection-handle"></div>' +
  538. '<table>' +
  539. '<tbody>' +
  540. '<tr>' +
  541. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  542. '<span style="display:inline-block">00</span>' +
  543. '</td>' +
  544. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  545. '<span style="display:inline-block"></span>' +
  546. '</td>' +
  547. '</tr>' +
  548. '</tbody>' +
  549. '</table>' +
  550. '</figure>'
  551. );
  552. } );
  553. } );
  554. } );
  555. describe( 'downcastTableHeadingColumnsChange()', () => {
  556. // The heading columns change downcast conversion is not executed in data pipeline.
  557. describe( 'editing pipeline', () => {
  558. it( 'should work for adding heading columns', () => {
  559. setModelData( model, modelTable( [
  560. [ '00', '01' ],
  561. [ '10', '11' ]
  562. ] ) );
  563. const table = root.getChild( 0 );
  564. model.change( writer => {
  565. writer.setAttribute( 'headingColumns', 1, table );
  566. } );
  567. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  568. [ { isHeading: true, contents: '00' }, '01' ],
  569. [ { isHeading: true, contents: '10' }, '11' ]
  570. ], { headingColumns: 1, asWidget: true } ) );
  571. } );
  572. it( 'should work for changing heading columns to a bigger number', () => {
  573. setModelData( model, modelTable( [
  574. [ '00', '01', '02', '03' ],
  575. [ '10', '11', '12', '13' ]
  576. ], { headingColumns: 1 } ) );
  577. const table = root.getChild( 0 );
  578. model.change( writer => {
  579. writer.setAttribute( 'headingColumns', 3, table );
  580. } );
  581. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  582. [ { isHeading: true, contents: '00' }, { isHeading: true, contents: '01' }, { isHeading: true, contents: '02' }, '03' ],
  583. [ { isHeading: true, contents: '10' }, { isHeading: true, contents: '11' }, { isHeading: true, contents: '12' }, '13' ]
  584. ], { asWidget: true } ) );
  585. } );
  586. it( 'should work for changing heading columns to a smaller number', () => {
  587. setModelData( model, modelTable( [
  588. [ { isHeading: true, contents: '00' }, { isHeading: true, contents: '01' }, { isHeading: true, contents: '02' }, '03' ],
  589. [ { isHeading: true, contents: '10' }, { isHeading: true, contents: '11' }, { isHeading: true, contents: '12' }, '13' ]
  590. ], { headingColumns: 3 } ) );
  591. const table = root.getChild( 0 );
  592. model.change( writer => {
  593. writer.setAttribute( 'headingColumns', 1, table );
  594. } );
  595. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  596. [ { isHeading: true, contents: '00' }, '01', '02', '03' ],
  597. [ { isHeading: true, contents: '10' }, '11', '12', '13' ]
  598. ], { headingColumns: 3, asWidget: true } ) );
  599. } );
  600. it( 'should work for removing heading columns', () => {
  601. setModelData( model, modelTable( [
  602. [ '00', '01' ],
  603. [ '10', '11' ]
  604. ], { headingColumns: 1 } ) );
  605. const table = root.getChild( 0 );
  606. model.change( writer => {
  607. writer.removeAttribute( 'headingColumns', table );
  608. } );
  609. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  610. [ '00', '01' ],
  611. [ '10', '11' ]
  612. ], { asWidget: true } ) );
  613. } );
  614. it( 'should be possible to overwrite', () => {
  615. editor.conversion.attributeToAttribute( { model: 'headingColumns', view: 'headingColumns', converterPriority: 'high' } );
  616. setModelData( model, modelTable( [ [ '00[] ' ] ] ) );
  617. const table = root.getChild( 0 );
  618. model.change( writer => {
  619. writer.setAttribute( 'headingColumns', 1, table );
  620. } );
  621. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  622. '<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false" headingColumns="1">' +
  623. '<div class="ck ck-widget__selection-handle"></div>' +
  624. '<table>' +
  625. '<tbody>' +
  626. '<tr>' +
  627. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  628. '<span style="display:inline-block">00</span>' +
  629. '</td>' +
  630. '</tr>' +
  631. '</tbody>' +
  632. '</table>' +
  633. '</figure>'
  634. );
  635. } );
  636. it( 'should work with adding table cells', () => {
  637. setModelData( model, modelTable( [
  638. [ { rowspan: 2, contents: '00' }, '01', '13', '14' ],
  639. [ '11', '12', '13' ],
  640. [ { colspan: 2, contents: '20' }, '22', '23' ]
  641. ], { headingColumns: 2 } ) );
  642. const table = root.getChild( 0 );
  643. model.change( writer => {
  644. // Inserting column in heading columns so update table's attribute also
  645. writer.setAttribute( 'headingColumns', 3, table );
  646. writer.insertElement( 'tableCell', table.getChild( 0 ), 2 );
  647. writer.insertElement( 'tableCell', table.getChild( 1 ), 1 );
  648. writer.insertElement( 'tableCell', table.getChild( 2 ), 1 );
  649. } );
  650. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  651. [
  652. { isHeading: true, rowspan: 2, contents: '00' },
  653. { isHeading: true, contents: '01' },
  654. { isHeading: true, contents: '' },
  655. '13',
  656. '14'
  657. ],
  658. [
  659. { isHeading: true, contents: '11' },
  660. { isHeading: true, contents: '' },
  661. '12',
  662. '13'
  663. ],
  664. [
  665. { isHeading: true, colspan: 2, contents: '20' },
  666. { isHeading: true, contents: '' },
  667. '22',
  668. '23'
  669. ]
  670. ], { asWidget: true } ) );
  671. } );
  672. it( 'should create renamed cell as a widget', () => {
  673. setModelData( model, modelTable( [ [ '00' ] ] ) );
  674. const table = root.getChild( 0 );
  675. model.change( writer => {
  676. writer.setAttribute( 'headingRows', 1, table );
  677. } );
  678. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  679. '<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false">' +
  680. '<div class="ck ck-widget__selection-handle"></div>' +
  681. '<table>' +
  682. '<thead>' +
  683. '<tr>' +
  684. '<th class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  685. '<span style="display:inline-block">00</span>' +
  686. '</th>' +
  687. '</tr>' +
  688. '</thead>' +
  689. '</table>' +
  690. '</figure>'
  691. );
  692. } );
  693. } );
  694. } );
  695. describe( 'downcastTableHeadingRowsChange()', () => {
  696. // The heading rows change downcast conversion is not executed in data pipeline.
  697. describe( 'editing pipeline', () => {
  698. it( 'should work for adding heading rows', () => {
  699. setModelData( model, modelTable( [
  700. [ '00', '01' ],
  701. [ '10', '11' ],
  702. [ '20', '21' ]
  703. ] ) );
  704. const table = root.getChild( 0 );
  705. model.change( writer => {
  706. writer.setAttribute( 'headingRows', 2, table );
  707. } );
  708. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  709. [ '00', '01' ],
  710. [ '10', '11' ],
  711. [ '20', '21' ]
  712. ], { headingRows: 2, asWidget: true } ) );
  713. } );
  714. it( 'should work for changing number of heading rows to a bigger number', () => {
  715. setModelData( model, modelTable( [
  716. [ '00', '01' ],
  717. [ '10', '11' ],
  718. [ '20', '21' ]
  719. ], { headingRows: 1 } ) );
  720. const table = root.getChild( 0 );
  721. model.change( writer => {
  722. writer.setAttribute( 'headingRows', 2, table );
  723. } );
  724. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  725. [ '00', '01' ],
  726. [ '10', '11' ],
  727. [ '20', '21' ]
  728. ], { headingRows: 2, asWidget: true } ) );
  729. } );
  730. it( 'should work for changing number of heading rows to a smaller number', () => {
  731. setModelData( model, modelTable( [
  732. [ '00', '01' ],
  733. [ '10', '11' ],
  734. [ '20', '21' ],
  735. [ '30', '31' ]
  736. ], { headingRows: 3 } ) );
  737. const table = root.getChild( 0 );
  738. model.change( writer => {
  739. writer.setAttribute( 'headingRows', 2, table );
  740. } );
  741. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  742. [ '00', '01' ],
  743. [ '10', '11' ],
  744. [ '20', '21' ],
  745. [ '30', '31' ]
  746. ], { headingRows: 2, asWidget: true } ) );
  747. } );
  748. it( 'should work for removing heading rows', () => {
  749. setModelData( model, modelTable( [
  750. [ '00', '01' ],
  751. [ '10', '11' ]
  752. ], { headingRows: 2 } ) );
  753. const table = root.getChild( 0 );
  754. model.change( writer => {
  755. writer.removeAttribute( 'headingRows', table );
  756. } );
  757. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  758. [ '00', '01' ],
  759. [ '10', '11' ]
  760. ], { asWidget: true } ) );
  761. } );
  762. it( 'should work for making heading rows without tbody', () => {
  763. setModelData( model, modelTable( [
  764. [ '00', '01' ],
  765. [ '10', '11' ]
  766. ] ) );
  767. const table = root.getChild( 0 );
  768. model.change( writer => {
  769. writer.setAttribute( 'headingRows', 2, table );
  770. } );
  771. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  772. [ '00', '01' ],
  773. [ '10', '11' ]
  774. ], { headingRows: 2, asWidget: true } ) );
  775. } );
  776. it( 'should be possible to overwrite', () => {
  777. editor.conversion.attributeToAttribute( {
  778. model: 'headingRows',
  779. view: 'headingRows',
  780. converterPriority: 'high'
  781. } );
  782. setModelData( model, modelTable( [ [ '00' ] ] ) );
  783. const table = root.getChild( 0 );
  784. model.change( writer => {
  785. writer.setAttribute( 'headingRows', 1, table );
  786. } );
  787. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  788. '<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false" headingRows="1">' +
  789. '<div class="ck ck-widget__selection-handle"></div>' +
  790. '<table>' +
  791. '<tbody>' +
  792. '<tr>' +
  793. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  794. '<span style="display:inline-block">00</span>' +
  795. '</td>' +
  796. '</tr>' +
  797. '</tbody>' +
  798. '</table>' +
  799. '</figure>'
  800. );
  801. } );
  802. it( 'should work with adding table rows at the beginning of a table', () => {
  803. setModelData( model, modelTable( [
  804. [ '00', '01' ],
  805. [ '10', '11' ]
  806. ], { headingRows: 1 } ) );
  807. const table = root.getChild( 0 );
  808. model.change( writer => {
  809. writer.setAttribute( 'headingRows', 2, table );
  810. const tableRow = writer.createElement( 'tableRow' );
  811. writer.insert( tableRow, table, 0 );
  812. writer.insertElement( 'tableCell', tableRow, 'end' );
  813. writer.insertElement( 'tableCell', tableRow, 'end' );
  814. } );
  815. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  816. [ '', '' ],
  817. [ '00', '01' ],
  818. [ '10', '11' ]
  819. ], { headingRows: 2, asWidget: true } ) );
  820. } );
  821. it( 'should work with adding a table row and expanding heading', () => {
  822. setModelData( model, modelTable( [
  823. [ '00', '01' ],
  824. [ '10', '11' ],
  825. [ '20', '21' ]
  826. ], { headingRows: 1 } ) );
  827. const table = root.getChild( 0 );
  828. model.change( writer => {
  829. writer.setAttribute( 'headingRows', 2, table );
  830. const tableRow = writer.createElement( 'tableRow' );
  831. writer.insert( tableRow, table, 1 );
  832. writer.insertElement( 'tableCell', tableRow, 'end' );
  833. writer.insertElement( 'tableCell', tableRow, 'end' );
  834. } );
  835. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  836. [ '00', '01' ],
  837. [ '', '' ],
  838. [ '10', '11' ],
  839. [ '20', '21' ]
  840. ], { headingRows: 2, asWidget: true } ) );
  841. } );
  842. it( 'should create renamed cell as a widget', () => {
  843. setModelData( model, modelTable( [ [ '00' ] ] ) );
  844. const table = root.getChild( 0 );
  845. model.change( writer => {
  846. writer.setAttribute( 'headingColumns', 1, table );
  847. } );
  848. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  849. '<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false">' +
  850. '<div class="ck ck-widget__selection-handle"></div>' +
  851. '<table>' +
  852. '<tbody>' +
  853. '<tr>' +
  854. '<th class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  855. '<span style="display:inline-block">00</span>' +
  856. '</th>' +
  857. '</tr>' +
  858. '</tbody>' +
  859. '</table>' +
  860. '</figure>'
  861. );
  862. } );
  863. } );
  864. } );
  865. describe( 'downcastRemoveRow()', () => {
  866. // The remove row downcast conversion is not executed in data pipeline.
  867. describe( 'editing pipeline', () => {
  868. it( 'should react to removed row from the beginning of a body rows (no heading rows)', () => {
  869. setModelData( model, modelTable( [
  870. [ '00[]', '01' ],
  871. [ '10', '11' ]
  872. ] ) );
  873. const table = root.getChild( 0 );
  874. model.change( writer => {
  875. writer.remove( table.getChild( 1 ) );
  876. } );
  877. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  878. '<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false">' +
  879. '<div class="ck ck-widget__selection-handle"></div>' +
  880. '<table>' +
  881. '<tbody>' +
  882. '<tr>' +
  883. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  884. '<span style="display:inline-block">00</span>' +
  885. '</td>' +
  886. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  887. '<span style="display:inline-block">01</span>' +
  888. '</td>' +
  889. '</tr>' +
  890. '</tbody>' +
  891. '</table>' +
  892. '</figure>'
  893. );
  894. } );
  895. it( 'should react to removed row form the end of a body rows (no heading rows)', () => {
  896. setModelData( model, modelTable( [
  897. [ '00[]', '01' ],
  898. [ '10', '11' ]
  899. ] ) );
  900. const table = root.getChild( 0 );
  901. model.change( writer => {
  902. writer.remove( table.getChild( 0 ) );
  903. } );
  904. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  905. '<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false">' +
  906. '<div class="ck ck-widget__selection-handle"></div>' +
  907. '<table>' +
  908. '<tbody>' +
  909. '<tr>' +
  910. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  911. '<span style="display:inline-block">10</span>' +
  912. '</td>' +
  913. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  914. '<span style="display:inline-block">11</span>' +
  915. '</td>' +
  916. '</tr>' +
  917. '</tbody>' +
  918. '</table>' +
  919. '</figure>'
  920. );
  921. } );
  922. it( 'should react to removed row from the beginning of a heading rows (no body rows)', () => {
  923. setModelData( model, modelTable( [
  924. [ '00[]', '01' ],
  925. [ '10', '11' ]
  926. ], { headingRows: 2 } ) );
  927. const table = root.getChild( 0 );
  928. model.change( writer => {
  929. // Removing row from a heading section changes requires changing heading rows attribute.
  930. writer.setAttribute( 'headingRows', 1, table );
  931. writer.remove( table.getChild( 0 ) );
  932. } );
  933. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  934. '<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false">' +
  935. '<div class="ck ck-widget__selection-handle"></div>' +
  936. '<table>' +
  937. '<thead>' +
  938. '<tr>' +
  939. '<th class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  940. '<span style="display:inline-block">10</span>' +
  941. '</th>' +
  942. '<th class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  943. '<span style="display:inline-block">11</span>' +
  944. '</th>' +
  945. '</tr>' +
  946. '</thead>' +
  947. '</table>' +
  948. '</figure>'
  949. );
  950. } );
  951. it( 'should react to removed row form the end of a heading rows (no body rows)', () => {
  952. setModelData( model, modelTable( [
  953. [ '00[]', '01' ],
  954. [ '10', '11' ]
  955. ], { headingRows: 2 } ) );
  956. const table = root.getChild( 0 );
  957. model.change( writer => {
  958. // Removing row from a heading section changes requires changing heading rows attribute.
  959. writer.setAttribute( 'headingRows', 1, table );
  960. writer.remove( table.getChild( 1 ) );
  961. } );
  962. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  963. '<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false">' +
  964. '<div class="ck ck-widget__selection-handle"></div>' +
  965. '<table>' +
  966. '<thead>' +
  967. '<tr>' +
  968. '<th class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  969. '<span style="display:inline-block">00</span>' +
  970. '</th>' +
  971. '<th class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  972. '<span style="display:inline-block">01</span>' +
  973. '</th>' +
  974. '</tr>' +
  975. '</thead>' +
  976. '</table>' +
  977. '</figure>'
  978. );
  979. } );
  980. it( 'should react to removed row form the end of a heading rows (first cell in body has colspan)', () => {
  981. setModelData( model, modelTable( [
  982. [ '00[]', '01', '02', '03' ],
  983. [ { rowspan: 2, colspan: 2, contents: '10' }, '12', '13' ],
  984. [ '22', '23' ]
  985. ], { headingRows: 1 } ) );
  986. const table = root.getChild( 0 );
  987. model.change( writer => {
  988. // Removing row from a heading section changes requires changing heading rows attribute.
  989. writer.remove( table.getChild( 0 ) );
  990. writer.setAttribute( 'headingRows', 0, table );
  991. } );
  992. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  993. '<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false">' +
  994. '<div class="ck ck-widget__selection-handle"></div>' +
  995. '<table>' +
  996. '<tbody>' +
  997. '<tr>' +
  998. '<td class="ck-editor__editable ck-editor__nested-editable" ' +
  999. 'colspan="2" contenteditable="true" rowspan="2">' +
  1000. '<span style="display:inline-block">10</span>' +
  1001. '</td>' +
  1002. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  1003. '<span style="display:inline-block">12</span>' +
  1004. '</td>' +
  1005. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  1006. '<span style="display:inline-block">13</span>' +
  1007. '</td>' +
  1008. '</tr>' +
  1009. '<tr>' +
  1010. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  1011. '<span style="display:inline-block">22</span>' +
  1012. '</td>' +
  1013. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  1014. '<span style="display:inline-block">23</span>' +
  1015. '</td>' +
  1016. '</tr>' +
  1017. '</tbody>' +
  1018. '</table>' +
  1019. '</figure>'
  1020. );
  1021. } );
  1022. it( 'should remove empty thead if a last row was removed from a heading rows (has heading and body)', () => {
  1023. setModelData( model, modelTable( [
  1024. [ '00[]', '01' ],
  1025. [ '10', '11' ]
  1026. ], { headingRows: 1 } ) );
  1027. const table = root.getChild( 0 );
  1028. model.change( writer => {
  1029. // Removing row from a heading section changes requires changing heading rows attribute.
  1030. writer.removeAttribute( 'headingRows', table );
  1031. writer.remove( table.getChild( 0 ) );
  1032. } );
  1033. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  1034. '<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false">' +
  1035. '<div class="ck ck-widget__selection-handle"></div>' +
  1036. '<table>' +
  1037. '<tbody>' +
  1038. '<tr>' +
  1039. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  1040. '<span style="display:inline-block">10</span>' +
  1041. '</td>' +
  1042. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  1043. '<span style="display:inline-block">11</span>' +
  1044. '</td>' +
  1045. '</tr>' +
  1046. '</tbody>' +
  1047. '</table>' +
  1048. '</figure>'
  1049. );
  1050. } );
  1051. it( 'should remove empty tbody if a last row was removed a body rows (has heading and body)', () => {
  1052. setModelData( model, modelTable( [
  1053. [ '00[]', '01' ],
  1054. [ '10', '11' ]
  1055. ], { headingRows: 1 } ) );
  1056. const table = root.getChild( 0 );
  1057. model.change( writer => {
  1058. writer.remove( table.getChild( 1 ) );
  1059. } );
  1060. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  1061. '<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false">' +
  1062. '<div class="ck ck-widget__selection-handle"></div>' +
  1063. '<table>' +
  1064. '<thead>' +
  1065. '<tr>' +
  1066. '<th class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  1067. '<span style="display:inline-block">00</span>' +
  1068. '</th>' +
  1069. '<th class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  1070. '<span style="display:inline-block">01</span>' +
  1071. '</th>' +
  1072. '</tr>' +
  1073. '</thead>' +
  1074. '</table>' +
  1075. '</figure>'
  1076. );
  1077. } );
  1078. } );
  1079. } );
  1080. describe( 'marker highlight conversion on table cell', () => {
  1081. describe( 'single class in highlight descriptor', () => {
  1082. beforeEach( async () => {
  1083. editor = await VirtualTestEditor.create( { plugins: [ Paragraph, TableEditing ] } );
  1084. model = editor.model;
  1085. root = model.document.getRoot( 'main' );
  1086. view = editor.editing.view;
  1087. markerConversion( editor.conversion );
  1088. } );
  1089. it( 'should apply marker class on tableCell - on inserting a table', () => {
  1090. setModelData( model, modelTable( [ [ '00' ] ] ) );
  1091. model.change( writer => {
  1092. const cell = root.getNodeByPath( [ 0, 0, 0 ] );
  1093. writer.addMarker( 'marker:yellow', {
  1094. range: writer.createRangeOn( cell ),
  1095. usingOperation: false
  1096. } );
  1097. checkCustomPropertyForHighlight( editor.editing.mapper.toViewElement( cell ) );
  1098. } );
  1099. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  1100. '<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false">' +
  1101. '<div class="ck ck-widget__selection-handle"></div>' +
  1102. '<table>' +
  1103. '<tbody>' +
  1104. '<tr>' +
  1105. '<td class="ck-editor__editable ck-editor__nested-editable highlight-yellow" contenteditable="true">' +
  1106. '<span style="display:inline-block">00</span>' +
  1107. '</td>' +
  1108. '</tr>' +
  1109. '</tbody>' +
  1110. '</table>' +
  1111. '</figure>'
  1112. );
  1113. model.change( writer => {
  1114. writer.removeMarker( 'marker:yellow' );
  1115. } );
  1116. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  1117. '<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false">' +
  1118. '<div class="ck ck-widget__selection-handle"></div>' +
  1119. '<table>' +
  1120. '<tbody>' +
  1121. '<tr>' +
  1122. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  1123. '<span style="display:inline-block">00</span>' +
  1124. '</td>' +
  1125. '</tr>' +
  1126. '</tbody>' +
  1127. '</table>' +
  1128. '</figure>'
  1129. );
  1130. } );
  1131. it( 'should apply marker class on tableCell - on inserting a row', () => {
  1132. setModelData( model, modelTable( [ [ '00' ] ] ) );
  1133. const table = root.getChild( 0 );
  1134. model.change( writer => {
  1135. const firstRow = writer.createElement( 'tableRow' );
  1136. const cell = writer.createElement( 'tableCell' );
  1137. writer.insert( firstRow, table, 1 );
  1138. writer.insert( cell, firstRow, 'end' );
  1139. writer.addMarker( 'marker:yellow', {
  1140. range: writer.createRangeOn( cell ),
  1141. usingOperation: false
  1142. } );
  1143. } );
  1144. const cell = root.getNodeByPath( [ 0, 1, 0 ] );
  1145. checkCustomPropertyForHighlight( editor.editing.mapper.toViewElement( cell ) );
  1146. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  1147. '<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false">' +
  1148. '<div class="ck ck-widget__selection-handle"></div>' +
  1149. '<table>' +
  1150. '<tbody>' +
  1151. '<tr>' +
  1152. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  1153. '<span style="display:inline-block">00</span>' +
  1154. '</td>' +
  1155. '</tr>' +
  1156. '<tr>' +
  1157. '<td class="ck-editor__editable ck-editor__nested-editable highlight-yellow" contenteditable="true">' +
  1158. '<span style="display:inline-block"></span>' +
  1159. '</td>' +
  1160. '</tr>' +
  1161. '</tbody>' +
  1162. '</table>' +
  1163. '</figure>'
  1164. );
  1165. model.change( writer => {
  1166. writer.removeMarker( 'marker:yellow' );
  1167. } );
  1168. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  1169. '<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false">' +
  1170. '<div class="ck ck-widget__selection-handle"></div>' +
  1171. '<table>' +
  1172. '<tbody>' +
  1173. '<tr>' +
  1174. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  1175. '<span style="display:inline-block">00</span>' +
  1176. '</td>' +
  1177. '</tr>' +
  1178. '<tr>' +
  1179. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  1180. '<span style="display:inline-block"></span>' +
  1181. '</td>' +
  1182. '</tr>' +
  1183. '</tbody>' +
  1184. '</table>' +
  1185. '</figure>'
  1186. );
  1187. } );
  1188. it( 'should apply marker class on tableCell - on inserting a table cell', () => {
  1189. setModelData( model, modelTable( [ [ '00' ] ] ) );
  1190. const table = root.getChild( 0 );
  1191. model.change( writer => {
  1192. const row = table.getChild( 0 );
  1193. const cell = writer.createElement( 'tableCell' );
  1194. writer.insert( cell, row, 'end' );
  1195. writer.addMarker( 'marker:yellow', {
  1196. range: writer.createRangeOn( cell ),
  1197. usingOperation: false
  1198. } );
  1199. } );
  1200. const cell = root.getNodeByPath( [ 0, 0, 1 ] );
  1201. checkCustomPropertyForHighlight( editor.editing.mapper.toViewElement( cell ) );
  1202. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  1203. '<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false">' +
  1204. '<div class="ck ck-widget__selection-handle"></div>' +
  1205. '<table>' +
  1206. '<tbody>' +
  1207. '<tr>' +
  1208. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  1209. '<span style="display:inline-block">00</span>' +
  1210. '</td>' +
  1211. '<td class="ck-editor__editable ck-editor__nested-editable highlight-yellow" contenteditable="true">' +
  1212. '<span style="display:inline-block"></span>' +
  1213. '</td>' +
  1214. '</tr>' +
  1215. '</tbody>' +
  1216. '</table>' +
  1217. '</figure>'
  1218. );
  1219. model.change( writer => {
  1220. writer.removeMarker( 'marker:yellow' );
  1221. } );
  1222. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  1223. '<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false">' +
  1224. '<div class="ck ck-widget__selection-handle"></div>' +
  1225. '<table>' +
  1226. '<tbody>' +
  1227. '<tr>' +
  1228. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  1229. '<span style="display:inline-block">00</span>' +
  1230. '</td>' +
  1231. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  1232. '<span style="display:inline-block"></span>' +
  1233. '</td>' +
  1234. '</tr>' +
  1235. '</tbody>' +
  1236. '</table>' +
  1237. '</figure>'
  1238. );
  1239. } );
  1240. it( 'should preserve marker class on tableCell - when changing heading columns', () => {
  1241. setModelData( model, modelTable( [
  1242. [ '00', '01', '02', '03' ],
  1243. [ '10', '11', '12', '13' ]
  1244. ], { headingColumns: 1 } ) );
  1245. const table = root.getChild( 0 );
  1246. model.change( writer => {
  1247. const cell = root.getNodeByPath( [ 0, 0, 1 ] );
  1248. writer.addMarker( 'marker:yellow', {
  1249. range: writer.createRangeOn( cell ),
  1250. usingOperation: false
  1251. } );
  1252. } );
  1253. model.change( writer => {
  1254. writer.setAttribute( 'headingColumns', 3, table );
  1255. } );
  1256. const cell = root.getNodeByPath( [ 0, 0, 1 ] );
  1257. const viewElement = editor.editing.mapper.toViewElement( cell );
  1258. checkCustomPropertyForHighlight( viewElement );
  1259. expect( viewElement.hasClass( 'highlight-yellow' ) ).to.be.true;
  1260. model.change( writer => {
  1261. writer.removeMarker( 'marker:yellow' );
  1262. } );
  1263. expect( viewElement.hasClass( 'highlight-yellow' ) ).to.be.false;
  1264. } );
  1265. it( 'should preserve marker class on tableCell - when changing heading rows', () => {
  1266. setModelData( model, modelTable( [
  1267. [ '00', '01' ],
  1268. [ '10', '11' ],
  1269. [ '20', '21' ]
  1270. ], { headingRows: 1 } ) );
  1271. const table = root.getChild( 0 );
  1272. model.change( writer => {
  1273. const cell = root.getNodeByPath( [ 0, 1, 0 ] );
  1274. writer.addMarker( 'marker:yellow', {
  1275. range: writer.createRangeOn( cell ),
  1276. usingOperation: false
  1277. } );
  1278. } );
  1279. model.change( writer => {
  1280. writer.setAttribute( 'headingRows', 2, table );
  1281. } );
  1282. const cell = root.getNodeByPath( [ 0, 1, 0 ] );
  1283. const viewElement = editor.editing.mapper.toViewElement( cell );
  1284. checkCustomPropertyForHighlight( viewElement );
  1285. expect( viewElement.hasClass( 'highlight-yellow' ) ).to.be.true;
  1286. model.change( writer => {
  1287. writer.removeMarker( 'marker:yellow' );
  1288. } );
  1289. expect( viewElement.hasClass( 'highlight-yellow' ) ).to.be.false;
  1290. } );
  1291. } );
  1292. describe( 'multiple classes in highlight descriptor', () => {
  1293. beforeEach( async () => {
  1294. editor = await VirtualTestEditor.create( { plugins: [ Paragraph, TableEditing ] } );
  1295. model = editor.model;
  1296. root = model.document.getRoot( 'main' );
  1297. view = editor.editing.view;
  1298. markerConversion( editor.conversion, [ 'marker', 'user-marker' ] );
  1299. } );
  1300. it( 'should apply marker class on tableCell - on inserting a table', () => {
  1301. setModelData( model, modelTable( [ [ '00' ] ] ) );
  1302. model.change( writer => {
  1303. const cell = root.getNodeByPath( [ 0, 0, 0 ] );
  1304. writer.addMarker( 'marker:yellow', {
  1305. range: writer.createRangeOn( cell ),
  1306. usingOperation: false
  1307. } );
  1308. checkCustomPropertyForHighlight( editor.editing.mapper.toViewElement( cell ) );
  1309. } );
  1310. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  1311. '<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false">' +
  1312. '<div class="ck ck-widget__selection-handle"></div>' +
  1313. '<table>' +
  1314. '<tbody>' +
  1315. '<tr>' +
  1316. '<td class="ck-editor__editable ck-editor__nested-editable highlight-yellow marker user-marker"' +
  1317. ' contenteditable="true">' +
  1318. '<span style="display:inline-block">00</span>' +
  1319. '</td>' +
  1320. '</tr>' +
  1321. '</tbody>' +
  1322. '</table>' +
  1323. '</figure>'
  1324. );
  1325. model.change( writer => {
  1326. writer.removeMarker( 'marker:yellow' );
  1327. } );
  1328. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  1329. '<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false">' +
  1330. '<div class="ck ck-widget__selection-handle"></div>' +
  1331. '<table>' +
  1332. '<tbody>' +
  1333. '<tr>' +
  1334. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  1335. '<span style="display:inline-block">00</span>' +
  1336. '</td>' +
  1337. '</tr>' +
  1338. '</tbody>' +
  1339. '</table>' +
  1340. '</figure>'
  1341. );
  1342. } );
  1343. } );
  1344. function markerConversion( conversion, extraClasses = null ) {
  1345. conversion.for( 'editingDowncast' ).markerToHighlight( {
  1346. model: 'marker',
  1347. view: data => {
  1348. const className = 'highlight-' + data.markerName.split( ':' )[ 1 ];
  1349. return {
  1350. classes: extraClasses ? [ ...extraClasses, className ] : className
  1351. };
  1352. }
  1353. } );
  1354. }
  1355. function checkCustomPropertyForHighlight( viewElement ) {
  1356. const set = viewElement.getCustomProperty( 'addHighlight' );
  1357. const remove = viewElement.getCustomProperty( 'removeHighlight' );
  1358. expect( typeof set ).to.equal( 'function' );
  1359. expect( typeof remove ).to.equal( 'function' );
  1360. }
  1361. } );
  1362. } );