downcast.js 53 KB

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