downcast.js 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488
  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. } );
  288. } );
  289. it( 'should create table as a widget', () => {
  290. setModelData( model, modelTable( [ [ '' ] ] ) );
  291. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  292. '<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false">' +
  293. '<div class="ck ck-widget__selection-handle"></div>' +
  294. '<table>' +
  295. '<tbody>' +
  296. '<tr>' +
  297. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  298. '<span style="display:inline-block"></span>' +
  299. '</td>' +
  300. '</tr>' +
  301. '</tbody>' +
  302. '</table>' +
  303. '</figure>'
  304. );
  305. } );
  306. } );
  307. } );
  308. describe( 'downcastInsertRow()', () => {
  309. // The beforeEach is duplicated due to ckeditor/ckeditor5#6574. New test are written using TableEditing.
  310. beforeEach( () => {
  311. return VirtualTestEditor.create()
  312. .then( newEditor => {
  313. editor = newEditor;
  314. model = editor.model;
  315. doc = model.document;
  316. root = doc.getRoot( 'main' );
  317. view = editor.editing.view;
  318. defaultSchema( model.schema );
  319. defaultConversion( editor.conversion );
  320. } );
  321. } );
  322. it( 'should react to changed rows', () => {
  323. setModelData( model, modelTable( [
  324. [ '00', '01' ]
  325. ] ) );
  326. const table = root.getChild( 0 );
  327. model.change( writer => {
  328. const row = writer.createElement( 'tableRow' );
  329. writer.insert( row, table, 1 );
  330. writer.insertElement( 'tableCell', row, 'end' );
  331. writer.insertElement( 'tableCell', row, 'end' );
  332. } );
  333. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  334. [ '00', '01' ],
  335. [ '', '' ]
  336. ] ) );
  337. } );
  338. it( 'should properly consume already added rows', () => {
  339. setModelData( model, modelTable( [
  340. [ '00', '01' ]
  341. ] ) );
  342. const table = root.getChild( 0 );
  343. model.change( writer => {
  344. const row = writer.createElement( 'tableRow' );
  345. writer.insert( row, table, 1 );
  346. writer.insertElement( 'tableCell', row, 'end' );
  347. writer.insertElement( 'tableCell', row, 'end' );
  348. } );
  349. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  350. [ '00', '01' ],
  351. [ '', '' ]
  352. ] ) );
  353. model.change( writer => {
  354. const row = writer.createElement( 'tableRow' );
  355. writer.insert( row, table, 2 );
  356. writer.insertElement( 'tableCell', row, 'end' );
  357. writer.insertElement( 'tableCell', row, 'end' );
  358. } );
  359. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  360. [ '00', '01' ],
  361. [ '', '' ],
  362. [ '', '' ]
  363. ] ) );
  364. } );
  365. it( 'should insert row on proper index', () => {
  366. setModelData( model, modelTable( [
  367. [ '00', '01' ],
  368. [ '21', '22' ],
  369. [ '31', '32' ]
  370. ] ) );
  371. const table = root.getChild( 0 );
  372. model.change( writer => {
  373. const row = writer.createElement( 'tableRow' );
  374. writer.insert( row, table, 1 );
  375. writer.insertElement( 'tableCell', row, 'end' );
  376. writer.insertElement( 'tableCell', row, 'end' );
  377. } );
  378. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  379. [ '00', '01' ],
  380. [ '', '' ],
  381. [ '21', '22' ],
  382. [ '31', '32' ]
  383. ] ) );
  384. } );
  385. it( 'should insert row on proper index when table has heading rows defined - insert in body', () => {
  386. setModelData( model, modelTable( [
  387. [ '00', '01' ],
  388. [ '21', '22' ],
  389. [ '31', '32' ]
  390. ], { headingRows: 1 } ) );
  391. const table = root.getChild( 0 );
  392. model.change( writer => {
  393. const row = writer.createElement( 'tableRow' );
  394. writer.insert( row, table, 1 );
  395. writer.insertElement( 'tableCell', row, 'end' );
  396. writer.insertElement( 'tableCell', row, 'end' );
  397. } );
  398. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  399. [ '00', '01' ],
  400. [ '', '' ],
  401. [ '21', '22' ],
  402. [ '31', '32' ]
  403. ], { headingRows: 1 } ) );
  404. } );
  405. it( 'should insert row on proper index when table has heading rows defined - insert in heading', () => {
  406. setModelData( model, modelTable( [
  407. [ '00', '01' ],
  408. [ '21', '22' ],
  409. [ '31', '32' ]
  410. ], { headingRows: 2 } ) );
  411. const table = root.getChild( 0 );
  412. model.change( writer => {
  413. const row = writer.createElement( 'tableRow' );
  414. writer.insert( row, table, 1 );
  415. writer.insertElement( 'tableCell', row, 'end' );
  416. writer.insertElement( 'tableCell', row, 'end' );
  417. } );
  418. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  419. [ '00', '01' ],
  420. [ '', '' ],
  421. [ '21', '22' ],
  422. [ '31', '32' ]
  423. ], { headingRows: 3 } ) );
  424. } );
  425. it( 'should react to changed rows when previous rows\' cells has rowspans', () => {
  426. setModelData( model, modelTable( [
  427. [ { rowspan: 3, contents: '00' }, '01' ],
  428. [ '22' ]
  429. ] ) );
  430. const table = root.getChild( 0 );
  431. model.change( writer => {
  432. const row = writer.createElement( 'tableRow' );
  433. writer.insert( row, table, 2 );
  434. writer.insertElement( 'tableCell', row, 'end' );
  435. } );
  436. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  437. [ { rowspan: 3, contents: '00' }, '01' ],
  438. [ '22' ],
  439. [ '' ]
  440. ] ) );
  441. } );
  442. it( 'should properly create row headings', () => {
  443. setModelData( model, modelTable( [
  444. [ { rowspan: 3, contents: '00' }, '01' ],
  445. [ '22' ]
  446. ], { headingColumns: 1 } ) );
  447. const table = root.getChild( 0 );
  448. model.change( writer => {
  449. const firstRow = writer.createElement( 'tableRow' );
  450. writer.insert( firstRow, table, 2 );
  451. writer.insert( writer.createElement( 'tableCell' ), firstRow, 'end' );
  452. const secondRow = writer.createElement( 'tableRow' );
  453. writer.insert( secondRow, table, 3 );
  454. writer.insert( writer.createElement( 'tableCell' ), secondRow, 'end' );
  455. writer.insert( writer.createElement( 'tableCell' ), secondRow, 'end' );
  456. } );
  457. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  458. [ { rowspan: 3, contents: '00', isHeading: true }, '01' ],
  459. [ '22' ],
  460. [ '' ],
  461. [ { contents: '', isHeading: true }, '' ]
  462. ] ) );
  463. } );
  464. describe( 'options.asWidget=true', () => {
  465. beforeEach( () => {
  466. return VirtualTestEditor.create()
  467. .then( newEditor => {
  468. editor = newEditor;
  469. model = editor.model;
  470. doc = model.document;
  471. root = doc.getRoot( 'main' );
  472. view = editor.editing.view;
  473. defaultSchema( model.schema );
  474. defaultConversion( editor.conversion, true );
  475. } );
  476. } );
  477. it( 'should create table cell inside inserted row as a widget', () => {
  478. setModelData( model, modelTable( [ [ '00' ] ] ) );
  479. const table = root.getChild( 0 );
  480. model.change( writer => {
  481. const firstRow = writer.createElement( 'tableRow' );
  482. writer.insert( firstRow, table, 1 );
  483. writer.insert( writer.createElement( 'tableCell' ), firstRow, 'end' );
  484. } );
  485. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  486. '<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false">' +
  487. '<div class="ck ck-widget__selection-handle"></div>' +
  488. '<table>' +
  489. '<tbody>' +
  490. '<tr>' +
  491. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  492. '<span style="display:inline-block">00</span>' +
  493. '</td>' +
  494. '</tr>' +
  495. '<tr>' +
  496. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true"></td>' +
  497. '</tr>' +
  498. '</tbody>' +
  499. '</table>' +
  500. '</figure>'
  501. );
  502. } );
  503. } );
  504. } );
  505. describe( 'downcastInsertCell()', () => {
  506. // The beforeEach is duplicated due to ckeditor/ckeditor5#6574. New test are written using TableEditing.
  507. beforeEach( () => {
  508. return VirtualTestEditor.create()
  509. .then( newEditor => {
  510. editor = newEditor;
  511. model = editor.model;
  512. doc = model.document;
  513. root = doc.getRoot( 'main' );
  514. view = editor.editing.view;
  515. defaultSchema( model.schema );
  516. defaultConversion( editor.conversion );
  517. } );
  518. } );
  519. it( 'should add tableCell on proper index in tr', () => {
  520. setModelData( model, modelTable( [
  521. [ '00', '01' ]
  522. ] ) );
  523. const table = root.getChild( 0 );
  524. model.change( writer => {
  525. const row = table.getChild( 0 );
  526. writer.insertElement( 'tableCell', row, 1 );
  527. } );
  528. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  529. [ '00', '', '01' ]
  530. ] ) );
  531. } );
  532. it( 'should add tableCell on proper index in tr when previous have colspans', () => {
  533. setModelData( model, modelTable( [
  534. [ { colspan: 2, contents: '00' }, '13' ]
  535. ] ) );
  536. const table = root.getChild( 0 );
  537. model.change( writer => {
  538. const row = table.getChild( 0 );
  539. writer.insertElement( 'tableCell', row, 1 );
  540. } );
  541. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  542. [ { colspan: 2, contents: '00' }, '', '13' ]
  543. ] ) );
  544. } );
  545. it( 'should add tableCell on proper index in tr when previous row have rowspans', () => {
  546. setModelData( model, modelTable( [
  547. [ { rowspan: 2, contents: '00' }, '13' ],
  548. [ '11', '12' ]
  549. ] ) );
  550. const table = root.getChild( 0 );
  551. model.change( writer => {
  552. writer.insertElement( 'tableCell', table.getChild( 0 ), 1 );
  553. writer.insertElement( 'tableCell', table.getChild( 1 ), 0 );
  554. } );
  555. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  556. [ { rowspan: 2, contents: '00' }, '', '13' ],
  557. [ '', '11', '12' ]
  558. ] ) );
  559. } );
  560. it( 'split cell simulation - simple', () => {
  561. setModelData( model, modelTable( [
  562. [ '00', '01' ],
  563. [ '10', '11' ]
  564. ] ) );
  565. const table = root.getChild( 0 );
  566. model.change( writer => {
  567. const firstRow = table.getChild( 0 );
  568. const secondRow = table.getChild( 1 );
  569. writer.insertElement( 'tableCell', firstRow, 1 );
  570. writer.setAttribute( 'colspan', 2, secondRow.getChild( 0 ) );
  571. } );
  572. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  573. [ '00', '', '01' ],
  574. [ { colspan: 2, contents: '10' }, '11' ]
  575. ] ) );
  576. } );
  577. it( 'merge simulation - simple', () => {
  578. setModelData( model, modelTable( [
  579. [ '00', '01' ],
  580. [ '10', '11' ]
  581. ] ) );
  582. const table = root.getChild( 0 );
  583. model.change( writer => {
  584. const firstRow = table.getChild( 0 );
  585. writer.setAttribute( 'colspan', 2, firstRow.getChild( 0 ) );
  586. writer.remove( firstRow.getChild( 1 ) );
  587. } );
  588. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  589. [ { colspan: 2, contents: '00' } ],
  590. [ '10', '11' ]
  591. ] ) );
  592. } );
  593. describe( 'options.asWidget=true', () => {
  594. beforeEach( () => {
  595. return VirtualTestEditor.create()
  596. .then( newEditor => {
  597. editor = newEditor;
  598. model = editor.model;
  599. doc = model.document;
  600. root = doc.getRoot( 'main' );
  601. view = editor.editing.view;
  602. defaultSchema( model.schema );
  603. defaultConversion( editor.conversion, true );
  604. } );
  605. } );
  606. it( 'should create inserted table cell as a widget', () => {
  607. setModelData( model, modelTable( [ [ '00' ] ] ) );
  608. const table = root.getChild( 0 );
  609. model.change( writer => {
  610. const row = table.getChild( 0 );
  611. writer.insert( writer.createElement( 'tableCell' ), row, 'end' );
  612. } );
  613. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  614. '<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false">' +
  615. '<div class="ck ck-widget__selection-handle"></div>' +
  616. '<table>' +
  617. '<tbody>' +
  618. '<tr>' +
  619. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  620. '<span style="display:inline-block">00</span>' +
  621. '</td>' +
  622. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true"></td>' +
  623. '</tr>' +
  624. '</tbody>' +
  625. '</table>' +
  626. '</figure>'
  627. );
  628. } );
  629. } );
  630. } );
  631. describe( 'downcastTableHeadingColumnsChange()', () => {
  632. // The beforeEach is duplicated due to ckeditor/ckeditor5#6574. New test are written using TableEditing.
  633. beforeEach( () => {
  634. return VirtualTestEditor.create()
  635. .then( newEditor => {
  636. editor = newEditor;
  637. model = editor.model;
  638. doc = model.document;
  639. root = doc.getRoot( 'main' );
  640. view = editor.editing.view;
  641. defaultSchema( model.schema );
  642. defaultConversion( editor.conversion );
  643. } );
  644. } );
  645. it( 'should work for adding heading columns', () => {
  646. setModelData( model, modelTable( [
  647. [ '00', '01' ],
  648. [ '10', '11' ]
  649. ] ) );
  650. const table = root.getChild( 0 );
  651. model.change( writer => {
  652. writer.setAttribute( 'headingColumns', 1, table );
  653. } );
  654. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  655. [ { isHeading: true, contents: '00' }, '01' ],
  656. [ { isHeading: true, contents: '10' }, '11' ]
  657. ], { headingColumns: 1 } ) );
  658. } );
  659. it( 'should work for changing heading columns to a bigger number', () => {
  660. setModelData( model, modelTable( [
  661. [ '00', '01', '02', '03' ],
  662. [ '10', '11', '12', '13' ]
  663. ], { headingColumns: 1 } ) );
  664. const table = root.getChild( 0 );
  665. model.change( writer => {
  666. writer.setAttribute( 'headingColumns', 3, table );
  667. } );
  668. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  669. [ { isHeading: true, contents: '00' }, { isHeading: true, contents: '01' }, { isHeading: true, contents: '02' }, '03' ],
  670. [ { isHeading: true, contents: '10' }, { isHeading: true, contents: '11' }, { isHeading: true, contents: '12' }, '13' ]
  671. ] ) );
  672. } );
  673. it( 'should work for changing heading columns to a smaller number', () => {
  674. setModelData( model, modelTable( [
  675. [ { isHeading: true, contents: '00' }, { isHeading: true, contents: '01' }, { isHeading: true, contents: '02' }, '03' ],
  676. [ { isHeading: true, contents: '10' }, { isHeading: true, contents: '11' }, { isHeading: true, contents: '12' }, '13' ]
  677. ], { headingColumns: 3 } ) );
  678. const table = root.getChild( 0 );
  679. model.change( writer => {
  680. writer.setAttribute( 'headingColumns', 1, table );
  681. } );
  682. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  683. [ { isHeading: true, contents: '00' }, '01', '02', '03' ],
  684. [ { isHeading: true, contents: '10' }, '11', '12', '13' ]
  685. ], { headingColumns: 3 } ) );
  686. } );
  687. it( 'should work for removing heading columns', () => {
  688. setModelData( model, modelTable( [
  689. [ '00', '01' ],
  690. [ '10', '11' ]
  691. ], { headingColumns: 1 } ) );
  692. const table = root.getChild( 0 );
  693. model.change( writer => {
  694. writer.removeAttribute( 'headingColumns', table );
  695. } );
  696. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  697. [ '00', '01' ],
  698. [ '10', '11' ]
  699. ] ) );
  700. } );
  701. it( 'should be possible to overwrite', () => {
  702. editor.conversion.attributeToAttribute( { model: 'headingColumns', view: 'headingColumns', converterPriority: 'high' } );
  703. setModelData( model, modelTable( [ [ '00' ] ] ) );
  704. const table = root.getChild( 0 );
  705. model.change( writer => {
  706. writer.setAttribute( 'headingColumns', 1, table );
  707. } );
  708. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  709. '<figure class="table" headingColumns="1">' +
  710. '<table>' +
  711. '<tbody>' +
  712. '<tr><td>00</td></tr>' +
  713. '</tbody>' +
  714. '</table>' +
  715. '</figure>'
  716. );
  717. } );
  718. it( 'should work with adding table cells', () => {
  719. setModelData( model, modelTable( [
  720. [ { rowspan: 2, contents: '00' }, '01', '13', '14' ],
  721. [ '11', '12', '13' ],
  722. [ { colspan: 2, contents: '20' }, '22', '23' ]
  723. ], { headingColumns: 2 } ) );
  724. const table = root.getChild( 0 );
  725. model.change( writer => {
  726. // Inserting column in heading columns so update table's attribute also
  727. writer.setAttribute( 'headingColumns', 3, table );
  728. writer.insertElement( 'tableCell', table.getChild( 0 ), 2 );
  729. writer.insertElement( 'tableCell', table.getChild( 1 ), 1 );
  730. writer.insertElement( 'tableCell', table.getChild( 2 ), 1 );
  731. } );
  732. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  733. [
  734. { isHeading: true, rowspan: 2, contents: '00' },
  735. { isHeading: true, contents: '01' },
  736. { isHeading: true, contents: '' },
  737. '13',
  738. '14'
  739. ],
  740. [
  741. { isHeading: true, contents: '11' },
  742. { isHeading: true, contents: '' },
  743. '12',
  744. '13'
  745. ],
  746. [
  747. { isHeading: true, colspan: 2, contents: '20' },
  748. { isHeading: true, contents: '' },
  749. '22',
  750. '23'
  751. ]
  752. ] ) );
  753. } );
  754. describe( 'options.asWidget=true', () => {
  755. beforeEach( () => {
  756. return VirtualTestEditor.create()
  757. .then( newEditor => {
  758. editor = newEditor;
  759. model = editor.model;
  760. doc = model.document;
  761. root = doc.getRoot( 'main' );
  762. view = editor.editing.view;
  763. defaultSchema( model.schema );
  764. defaultConversion( editor.conversion, true );
  765. } );
  766. } );
  767. it( 'should create renamed cell as a widget', () => {
  768. setModelData( model, modelTable( [ [ '00' ] ] ) );
  769. const table = root.getChild( 0 );
  770. model.change( writer => {
  771. writer.setAttribute( 'headingRows', 1, table );
  772. } );
  773. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  774. '<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false">' +
  775. '<div class="ck ck-widget__selection-handle"></div>' +
  776. '<table>' +
  777. '<thead>' +
  778. '<tr>' +
  779. '<th class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  780. '<span style="display:inline-block">00</span>' +
  781. '</th>' +
  782. '</tr>' +
  783. '</thead>' +
  784. '</table>' +
  785. '</figure>'
  786. );
  787. } );
  788. } );
  789. } );
  790. describe( 'downcastTableHeadingRowsChange()', () => {
  791. // The beforeEach is duplicated due to ckeditor/ckeditor5#6574. New test are written using TableEditing.
  792. beforeEach( () => {
  793. return VirtualTestEditor.create( { plugins: [ Paragraph, TableEditing ] } )
  794. .then( newEditor => {
  795. editor = newEditor;
  796. model = editor.model;
  797. doc = model.document;
  798. root = doc.getRoot( 'main' );
  799. view = editor.editing.view;
  800. } );
  801. } );
  802. // The heading rows change downcast conversion is not executed in data pipeline.
  803. describe( 'editing pipeline', () => {
  804. it( 'should work for adding heading rows', () => {
  805. setModelData( model, modelTable( [
  806. [ '00', '01' ],
  807. [ '10', '11' ],
  808. [ '20', '21' ]
  809. ] ) );
  810. const table = root.getChild( 0 );
  811. model.change( writer => {
  812. writer.setAttribute( 'headingRows', 2, table );
  813. } );
  814. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  815. [ '00', '01' ],
  816. [ '10', '11' ],
  817. [ '20', '21' ]
  818. ], { headingRows: 2, asWidget: true } ) );
  819. } );
  820. it( 'should work for changing number of heading rows to a bigger number', () => {
  821. setModelData( model, modelTable( [
  822. [ '00', '01' ],
  823. [ '10', '11' ],
  824. [ '20', '21' ]
  825. ], { headingRows: 1 } ) );
  826. const table = root.getChild( 0 );
  827. model.change( writer => {
  828. writer.setAttribute( 'headingRows', 2, table );
  829. } );
  830. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  831. [ '00', '01' ],
  832. [ '10', '11' ],
  833. [ '20', '21' ]
  834. ], { headingRows: 2, asWidget: true } ) );
  835. } );
  836. it( 'should work for changing number of heading rows to a smaller number', () => {
  837. setModelData( model, modelTable( [
  838. [ '00', '01' ],
  839. [ '10', '11' ],
  840. [ '20', '21' ],
  841. [ '30', '31' ]
  842. ], { headingRows: 3 } ) );
  843. const table = root.getChild( 0 );
  844. model.change( writer => {
  845. writer.setAttribute( 'headingRows', 2, table );
  846. } );
  847. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  848. [ '00', '01' ],
  849. [ '10', '11' ],
  850. [ '20', '21' ],
  851. [ '30', '31' ]
  852. ], { headingRows: 2, asWidget: true } ) );
  853. } );
  854. it( 'should work for removing heading rows', () => {
  855. setModelData( model, modelTable( [
  856. [ '00', '01' ],
  857. [ '10', '11' ]
  858. ], { headingRows: 2 } ) );
  859. const table = root.getChild( 0 );
  860. model.change( writer => {
  861. writer.removeAttribute( 'headingRows', table );
  862. } );
  863. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  864. [ '00', '01' ],
  865. [ '10', '11' ]
  866. ], { asWidget: true } ) );
  867. } );
  868. it( 'should work for making heading rows without tbody', () => {
  869. setModelData( model, modelTable( [
  870. [ '00', '01' ],
  871. [ '10', '11' ]
  872. ] ) );
  873. const table = root.getChild( 0 );
  874. model.change( writer => {
  875. writer.setAttribute( 'headingRows', 2, table );
  876. } );
  877. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  878. [ '00', '01' ],
  879. [ '10', '11' ]
  880. ], { headingRows: 2, asWidget: true } ) );
  881. } );
  882. it( 'should be possible to overwrite', () => {
  883. editor.conversion.attributeToAttribute( {
  884. model: 'headingRows',
  885. view: 'headingRows',
  886. converterPriority: 'high'
  887. } );
  888. setModelData( model, modelTable( [ [ '00' ] ] ) );
  889. const table = root.getChild( 0 );
  890. model.change( writer => {
  891. writer.setAttribute( 'headingRows', 1, table );
  892. } );
  893. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  894. '<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false" headingRows="1">' +
  895. '<div class="ck ck-widget__selection-handle"></div>' +
  896. '<table>' +
  897. '<tbody>' +
  898. '<tr>' +
  899. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  900. '<span style="display:inline-block">00</span>' +
  901. '</td>' +
  902. '</tr>' +
  903. '</tbody>' +
  904. '</table>' +
  905. '</figure>'
  906. );
  907. } );
  908. it( 'should work with adding table rows at the beginning of a table', () => {
  909. setModelData( model, modelTable( [
  910. [ '00', '01' ],
  911. [ '10', '11' ]
  912. ], { headingRows: 1 } ) );
  913. const table = root.getChild( 0 );
  914. model.change( writer => {
  915. writer.setAttribute( 'headingRows', 2, table );
  916. const tableRow = writer.createElement( 'tableRow' );
  917. writer.insert( tableRow, table, 0 );
  918. writer.insertElement( 'tableCell', tableRow, 'end' );
  919. writer.insertElement( 'tableCell', tableRow, 'end' );
  920. } );
  921. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  922. [ '', '' ],
  923. [ '00', '01' ],
  924. [ '10', '11' ]
  925. ], { headingRows: 2, asWidget: true } ) );
  926. } );
  927. it( 'should work with adding a table row and expanding heading', () => {
  928. setModelData( model, modelTable( [
  929. [ '00', '01' ],
  930. [ '10', '11' ],
  931. [ '20', '21' ]
  932. ], { headingRows: 1 } ) );
  933. const table = root.getChild( 0 );
  934. model.change( writer => {
  935. writer.setAttribute( 'headingRows', 2, table );
  936. const tableRow = writer.createElement( 'tableRow' );
  937. writer.insert( tableRow, table, 1 );
  938. writer.insertElement( 'tableCell', tableRow, 'end' );
  939. writer.insertElement( 'tableCell', tableRow, 'end' );
  940. } );
  941. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  942. [ '00', '01' ],
  943. [ '', '' ],
  944. [ '10', '11' ],
  945. [ '20', '21' ]
  946. ], { headingRows: 2, asWidget: true } ) );
  947. } );
  948. it( 'should create renamed cell as a widget', () => {
  949. setModelData( model, modelTable( [ [ '00' ] ] ) );
  950. const table = root.getChild( 0 );
  951. model.change( writer => {
  952. writer.setAttribute( 'headingColumns', 1, table );
  953. } );
  954. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  955. '<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false">' +
  956. '<div class="ck ck-widget__selection-handle"></div>' +
  957. '<table>' +
  958. '<tbody>' +
  959. '<tr>' +
  960. '<th class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  961. '<span style="display:inline-block">00</span>' +
  962. '</th>' +
  963. '</tr>' +
  964. '</tbody>' +
  965. '</table>' +
  966. '</figure>'
  967. );
  968. } );
  969. } );
  970. } );
  971. describe( 'downcastRemoveRow()', () => {
  972. // The beforeEach is duplicated due to ckeditor/ckeditor5#6574. New test are written using TableEditing.
  973. beforeEach( async () => {
  974. editor = await VirtualTestEditor.create( { plugins: [ Paragraph, TableEditing ] } );
  975. model = editor.model;
  976. root = model.document.getRoot( 'main' );
  977. view = editor.editing.view;
  978. } );
  979. // The remove row downcast conversion is not executed in data pipeline.
  980. describe( 'editing pipeline', () => {
  981. it( 'should react to removed row from the beginning of a body rows (no heading rows)', () => {
  982. setModelData( model, modelTable( [
  983. [ '00[]', '01' ],
  984. [ '10', '11' ]
  985. ] ) );
  986. const table = root.getChild( 0 );
  987. model.change( writer => {
  988. writer.remove( table.getChild( 1 ) );
  989. } );
  990. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  991. '<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false">' +
  992. '<div class="ck ck-widget__selection-handle"></div>' +
  993. '<table>' +
  994. '<tbody>' +
  995. '<tr>' +
  996. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  997. '<span style="display:inline-block">00</span>' +
  998. '</td>' +
  999. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  1000. '<span style="display:inline-block">01</span>' +
  1001. '</td>' +
  1002. '</tr>' +
  1003. '</tbody>' +
  1004. '</table>' +
  1005. '</figure>'
  1006. );
  1007. } );
  1008. it( 'should react to removed row form the end of a body rows (no heading rows)', () => {
  1009. setModelData( model, modelTable( [
  1010. [ '00[]', '01' ],
  1011. [ '10', '11' ]
  1012. ] ) );
  1013. const table = root.getChild( 0 );
  1014. model.change( writer => {
  1015. writer.remove( table.getChild( 0 ) );
  1016. } );
  1017. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  1018. '<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false">' +
  1019. '<div class="ck ck-widget__selection-handle"></div>' +
  1020. '<table>' +
  1021. '<tbody>' +
  1022. '<tr>' +
  1023. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  1024. '<span style="display:inline-block">10</span>' +
  1025. '</td>' +
  1026. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  1027. '<span style="display:inline-block">11</span>' +
  1028. '</td>' +
  1029. '</tr>' +
  1030. '</tbody>' +
  1031. '</table>' +
  1032. '</figure>'
  1033. );
  1034. } );
  1035. it( 'should react to removed row from the beginning of a heading rows (no body rows)', () => {
  1036. setModelData( model, modelTable( [
  1037. [ '00[]', '01' ],
  1038. [ '10', '11' ]
  1039. ], { headingRows: 2 } ) );
  1040. const table = root.getChild( 0 );
  1041. model.change( writer => {
  1042. // Removing row from a heading section changes requires changing heading rows attribute.
  1043. writer.setAttribute( 'headingRows', 1, table );
  1044. writer.remove( table.getChild( 0 ) );
  1045. } );
  1046. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  1047. '<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false">' +
  1048. '<div class="ck ck-widget__selection-handle"></div>' +
  1049. '<table>' +
  1050. '<thead>' +
  1051. '<tr>' +
  1052. '<th class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  1053. '<span style="display:inline-block">10</span>' +
  1054. '</th>' +
  1055. '<th class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  1056. '<span style="display:inline-block">11</span>' +
  1057. '</th>' +
  1058. '</tr>' +
  1059. '</thead>' +
  1060. '</table>' +
  1061. '</figure>'
  1062. );
  1063. } );
  1064. it( 'should react to removed row form the end of a heading rows (no body rows)', () => {
  1065. setModelData( model, modelTable( [
  1066. [ '00[]', '01' ],
  1067. [ '10', '11' ]
  1068. ], { headingRows: 2 } ) );
  1069. const table = root.getChild( 0 );
  1070. model.change( writer => {
  1071. // Removing row from a heading section changes requires changing heading rows attribute.
  1072. writer.setAttribute( 'headingRows', 1, table );
  1073. writer.remove( table.getChild( 1 ) );
  1074. } );
  1075. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  1076. '<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false">' +
  1077. '<div class="ck ck-widget__selection-handle"></div>' +
  1078. '<table>' +
  1079. '<thead>' +
  1080. '<tr>' +
  1081. '<th class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  1082. '<span style="display:inline-block">00</span>' +
  1083. '</th>' +
  1084. '<th class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  1085. '<span style="display:inline-block">01</span>' +
  1086. '</th>' +
  1087. '</tr>' +
  1088. '</thead>' +
  1089. '</table>' +
  1090. '</figure>'
  1091. );
  1092. } );
  1093. it( 'should react to removed row form the end of a heading rows (first cell in body has colspan)', () => {
  1094. setModelData( model, modelTable( [
  1095. [ '00[]', '01', '02', '03' ],
  1096. [ { rowspan: 2, colspan: 2, contents: '10' }, '12', '13' ],
  1097. [ '22', '23' ]
  1098. ], { headingRows: 1 } ) );
  1099. const table = root.getChild( 0 );
  1100. model.change( writer => {
  1101. // Removing row from a heading section changes requires changing heading rows attribute.
  1102. writer.remove( table.getChild( 0 ) );
  1103. writer.setAttribute( 'headingRows', 0, table );
  1104. } );
  1105. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  1106. '<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false">' +
  1107. '<div class="ck ck-widget__selection-handle"></div>' +
  1108. '<table>' +
  1109. '<tbody>' +
  1110. '<tr>' +
  1111. '<td class="ck-editor__editable ck-editor__nested-editable" ' +
  1112. 'colspan="2" contenteditable="true" rowspan="2">' +
  1113. '<span style="display:inline-block">10</span>' +
  1114. '</td>' +
  1115. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  1116. '<span style="display:inline-block">12</span>' +
  1117. '</td>' +
  1118. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  1119. '<span style="display:inline-block">13</span>' +
  1120. '</td>' +
  1121. '</tr>' +
  1122. '<tr>' +
  1123. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  1124. '<span style="display:inline-block">22</span>' +
  1125. '</td>' +
  1126. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  1127. '<span style="display:inline-block">23</span>' +
  1128. '</td>' +
  1129. '</tr>' +
  1130. '</tbody>' +
  1131. '</table>' +
  1132. '</figure>'
  1133. );
  1134. } );
  1135. it( 'should remove empty thead if a last row was removed from a heading rows (has heading and body)', () => {
  1136. setModelData( model, modelTable( [
  1137. [ '00[]', '01' ],
  1138. [ '10', '11' ]
  1139. ], { headingRows: 1 } ) );
  1140. const table = root.getChild( 0 );
  1141. model.change( writer => {
  1142. // Removing row from a heading section changes requires changing heading rows attribute.
  1143. writer.removeAttribute( 'headingRows', table );
  1144. writer.remove( table.getChild( 0 ) );
  1145. } );
  1146. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  1147. '<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false">' +
  1148. '<div class="ck ck-widget__selection-handle"></div>' +
  1149. '<table>' +
  1150. '<tbody>' +
  1151. '<tr>' +
  1152. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  1153. '<span style="display:inline-block">10</span>' +
  1154. '</td>' +
  1155. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  1156. '<span style="display:inline-block">11</span>' +
  1157. '</td>' +
  1158. '</tr>' +
  1159. '</tbody>' +
  1160. '</table>' +
  1161. '</figure>'
  1162. );
  1163. } );
  1164. it( 'should remove empty tbody if a last row was removed a body rows (has heading and body)', () => {
  1165. setModelData( model, modelTable( [
  1166. [ '00[]', '01' ],
  1167. [ '10', '11' ]
  1168. ], { headingRows: 1 } ) );
  1169. const table = root.getChild( 0 );
  1170. model.change( writer => {
  1171. writer.remove( table.getChild( 1 ) );
  1172. } );
  1173. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  1174. '<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false">' +
  1175. '<div class="ck ck-widget__selection-handle"></div>' +
  1176. '<table>' +
  1177. '<thead>' +
  1178. '<tr>' +
  1179. '<th class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  1180. '<span style="display:inline-block">00</span>' +
  1181. '</th>' +
  1182. '<th class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  1183. '<span style="display:inline-block">01</span>' +
  1184. '</th>' +
  1185. '</tr>' +
  1186. '</thead>' +
  1187. '</table>' +
  1188. '</figure>'
  1189. );
  1190. } );
  1191. } );
  1192. } );
  1193. describe( 'options.asWidget=true', () => {
  1194. beforeEach( () => {
  1195. return VirtualTestEditor.create()
  1196. .then( newEditor => {
  1197. editor = newEditor;
  1198. model = editor.model;
  1199. doc = model.document;
  1200. root = doc.getRoot( 'main' );
  1201. view = editor.editing.view;
  1202. defaultSchema( model.schema );
  1203. defaultConversion( editor.conversion, true );
  1204. editor.conversion.for( 'downcast' ).add( paragraphInTableCell() );
  1205. } );
  1206. } );
  1207. it( 'should rename <span> to <p> when more then one block content inside table cell', () => {
  1208. setModelData( model, modelTable( [
  1209. [ '00[]' ]
  1210. ] ) );
  1211. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  1212. [ '00' ]
  1213. ], { asWidget: true } ) );
  1214. const table = root.getChild( 0 );
  1215. model.change( writer => {
  1216. const nodeByPath = table.getNodeByPath( [ 0, 0, 0 ] );
  1217. const paragraph = writer.createElement( 'paragraph' );
  1218. writer.insert( paragraph, nodeByPath, 'after' );
  1219. writer.setSelection( nodeByPath.nextSibling, 0 );
  1220. } );
  1221. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  1222. [ '<p>00</p><p></p>' ]
  1223. ], { asWidget: true } ) );
  1224. } );
  1225. it( 'should rename <span> to <p> for single paragraph with attribute', () => {
  1226. model.schema.extend( '$block', { allowAttributes: 'foo' } );
  1227. editor.conversion.attributeToAttribute( { model: 'foo', view: 'foo' } );
  1228. setModelData( model, modelTable( [
  1229. [ '<paragraph foo="bar">00[]</paragraph>' ]
  1230. ] ) );
  1231. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  1232. [ '<p foo="bar">00</p>' ]
  1233. ], { asWidget: true } ) );
  1234. } );
  1235. } );
  1236. } );