downcast.js 45 KB

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