downcast.js 36 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289
  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 { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
  7. import { setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
  8. import { defaultConversion, defaultSchema, modelTable, viewTable } from '../_utils/utils';
  9. import env from '@ckeditor/ckeditor5-utils/src/env';
  10. import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
  11. import { assertEqualMarkup } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
  12. function paragraphInTableCell() {
  13. return dispatcher => dispatcher.on( 'insert:paragraph', ( evt, data, conversionApi ) => {
  14. const tableCell = data.item.parent;
  15. if ( tableCell.is( 'tableCell' ) && tableCell.childCount > 1 ) {
  16. for ( const child of tableCell.getChildren() ) {
  17. if ( child.name != 'paragraph' ) {
  18. continue;
  19. }
  20. const viewElement = conversionApi.mapper.toViewElement( child );
  21. if ( viewElement && viewElement.name === 'span' ) {
  22. conversionApi.mapper.unbindModelElement( tableCell );
  23. conversionApi.writer.removeStyle( 'display', viewElement );
  24. conversionApi.writer.rename( 'p', viewElement );
  25. conversionApi.mapper.bindElements( child, viewElement );
  26. }
  27. }
  28. }
  29. }, { converterPriority: 'highest' } );
  30. }
  31. describe( 'downcast converters', () => {
  32. let editor, model, doc, root, view;
  33. testUtils.createSinonSandbox();
  34. beforeEach( () => {
  35. // Most tests assume non-edge environment but we do not set `contenteditable=false` on Edge so stub `env.isEdge`.
  36. testUtils.sinon.stub( env, 'isEdge' ).get( () => false );
  37. return VirtualTestEditor.create()
  38. .then( newEditor => {
  39. editor = newEditor;
  40. model = editor.model;
  41. doc = model.document;
  42. root = doc.getRoot( 'main' );
  43. view = editor.editing.view;
  44. defaultSchema( model.schema );
  45. defaultConversion( editor.conversion );
  46. } );
  47. } );
  48. describe( 'downcastInsertTable()', () => {
  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. it( 'should react to changed rows', () => {
  310. setModelData( model, modelTable( [
  311. [ '00', '01' ]
  312. ] ) );
  313. const table = root.getChild( 0 );
  314. model.change( writer => {
  315. const row = writer.createElement( 'tableRow' );
  316. writer.insert( row, table, 1 );
  317. writer.insertElement( 'tableCell', row, 'end' );
  318. writer.insertElement( 'tableCell', row, 'end' );
  319. } );
  320. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  321. [ '00', '01' ],
  322. [ '', '' ]
  323. ] ) );
  324. } );
  325. it( 'should properly consume already added rows', () => {
  326. setModelData( model, modelTable( [
  327. [ '00', '01' ]
  328. ] ) );
  329. const table = root.getChild( 0 );
  330. model.change( writer => {
  331. const row = writer.createElement( 'tableRow' );
  332. writer.insert( row, table, 1 );
  333. writer.insertElement( 'tableCell', row, 'end' );
  334. writer.insertElement( 'tableCell', row, 'end' );
  335. } );
  336. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  337. [ '00', '01' ],
  338. [ '', '' ]
  339. ] ) );
  340. model.change( writer => {
  341. const row = writer.createElement( 'tableRow' );
  342. writer.insert( row, table, 2 );
  343. writer.insertElement( 'tableCell', row, 'end' );
  344. writer.insertElement( 'tableCell', row, 'end' );
  345. } );
  346. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  347. [ '00', '01' ],
  348. [ '', '' ],
  349. [ '', '' ]
  350. ] ) );
  351. } );
  352. it( 'should insert row on proper index', () => {
  353. setModelData( model, modelTable( [
  354. [ '00', '01' ],
  355. [ '21', '22' ],
  356. [ '31', '32' ]
  357. ] ) );
  358. const table = root.getChild( 0 );
  359. model.change( writer => {
  360. const row = writer.createElement( 'tableRow' );
  361. writer.insert( row, table, 1 );
  362. writer.insertElement( 'tableCell', row, 'end' );
  363. writer.insertElement( 'tableCell', row, 'end' );
  364. } );
  365. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  366. [ '00', '01' ],
  367. [ '', '' ],
  368. [ '21', '22' ],
  369. [ '31', '32' ]
  370. ] ) );
  371. } );
  372. it( 'should insert row on proper index when table has heading rows defined - insert in body', () => {
  373. setModelData( model, modelTable( [
  374. [ '00', '01' ],
  375. [ '21', '22' ],
  376. [ '31', '32' ]
  377. ], { headingRows: 1 } ) );
  378. const table = root.getChild( 0 );
  379. model.change( writer => {
  380. const row = writer.createElement( 'tableRow' );
  381. writer.insert( row, table, 1 );
  382. writer.insertElement( 'tableCell', row, 'end' );
  383. writer.insertElement( 'tableCell', row, 'end' );
  384. } );
  385. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  386. [ '00', '01' ],
  387. [ '', '' ],
  388. [ '21', '22' ],
  389. [ '31', '32' ]
  390. ], { headingRows: 1 } ) );
  391. } );
  392. it( 'should insert row on proper index when table has heading rows defined - insert in heading', () => {
  393. setModelData( model, modelTable( [
  394. [ '00', '01' ],
  395. [ '21', '22' ],
  396. [ '31', '32' ]
  397. ], { headingRows: 2 } ) );
  398. const table = root.getChild( 0 );
  399. model.change( writer => {
  400. const row = writer.createElement( 'tableRow' );
  401. writer.insert( row, table, 1 );
  402. writer.insertElement( 'tableCell', row, 'end' );
  403. writer.insertElement( 'tableCell', row, 'end' );
  404. } );
  405. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  406. [ '00', '01' ],
  407. [ '', '' ],
  408. [ '21', '22' ],
  409. [ '31', '32' ]
  410. ], { headingRows: 3 } ) );
  411. } );
  412. it( 'should react to changed rows when previous rows\' cells has rowspans', () => {
  413. setModelData( model, modelTable( [
  414. [ { rowspan: 3, contents: '00' }, '01' ],
  415. [ '22' ]
  416. ] ) );
  417. const table = root.getChild( 0 );
  418. model.change( writer => {
  419. const row = writer.createElement( 'tableRow' );
  420. writer.insert( row, table, 2 );
  421. writer.insertElement( 'tableCell', row, 'end' );
  422. } );
  423. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  424. [ { rowspan: 3, contents: '00' }, '01' ],
  425. [ '22' ],
  426. [ '' ]
  427. ] ) );
  428. } );
  429. it( 'should properly create row headings', () => {
  430. setModelData( model, modelTable( [
  431. [ { rowspan: 3, contents: '00' }, '01' ],
  432. [ '22' ]
  433. ], { headingColumns: 1 } ) );
  434. const table = root.getChild( 0 );
  435. model.change( writer => {
  436. const firstRow = writer.createElement( 'tableRow' );
  437. writer.insert( firstRow, table, 2 );
  438. writer.insert( writer.createElement( 'tableCell' ), firstRow, 'end' );
  439. const secondRow = writer.createElement( 'tableRow' );
  440. writer.insert( secondRow, table, 3 );
  441. writer.insert( writer.createElement( 'tableCell' ), secondRow, 'end' );
  442. writer.insert( writer.createElement( 'tableCell' ), secondRow, 'end' );
  443. } );
  444. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  445. [ { rowspan: 3, contents: '00', isHeading: true }, '01' ],
  446. [ '22' ],
  447. [ '' ],
  448. [ { contents: '', isHeading: true }, '' ]
  449. ] ) );
  450. } );
  451. describe( 'options.asWidget=true', () => {
  452. beforeEach( () => {
  453. return VirtualTestEditor.create()
  454. .then( newEditor => {
  455. editor = newEditor;
  456. model = editor.model;
  457. doc = model.document;
  458. root = doc.getRoot( 'main' );
  459. view = editor.editing.view;
  460. defaultSchema( model.schema );
  461. defaultConversion( editor.conversion, true );
  462. } );
  463. } );
  464. it( 'should create table cell inside inserted row as a widget', () => {
  465. setModelData( model, modelTable( [ [ '00' ] ] ) );
  466. const table = root.getChild( 0 );
  467. model.change( writer => {
  468. const firstRow = writer.createElement( 'tableRow' );
  469. writer.insert( firstRow, table, 1 );
  470. writer.insert( writer.createElement( 'tableCell' ), firstRow, 'end' );
  471. } );
  472. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  473. '<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false">' +
  474. '<div class="ck ck-widget__selection-handle"></div>' +
  475. '<table>' +
  476. '<tbody>' +
  477. '<tr>' +
  478. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  479. '<span style="display:inline-block">00</span>' +
  480. '</td>' +
  481. '</tr>' +
  482. '<tr>' +
  483. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true"></td>' +
  484. '</tr>' +
  485. '</tbody>' +
  486. '</table>' +
  487. '</figure>'
  488. );
  489. } );
  490. } );
  491. } );
  492. describe( 'downcastInsertCell()', () => {
  493. it( 'should add tableCell on proper index in tr', () => {
  494. setModelData( model, modelTable( [
  495. [ '00', '01' ]
  496. ] ) );
  497. const table = root.getChild( 0 );
  498. model.change( writer => {
  499. const row = table.getChild( 0 );
  500. writer.insertElement( 'tableCell', row, 1 );
  501. } );
  502. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  503. [ '00', '', '01' ]
  504. ] ) );
  505. } );
  506. it( 'should add tableCell on proper index in tr when previous have colspans', () => {
  507. setModelData( model, modelTable( [
  508. [ { colspan: 2, contents: '00' }, '13' ]
  509. ] ) );
  510. const table = root.getChild( 0 );
  511. model.change( writer => {
  512. const row = table.getChild( 0 );
  513. writer.insertElement( 'tableCell', row, 1 );
  514. } );
  515. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  516. [ { colspan: 2, contents: '00' }, '', '13' ]
  517. ] ) );
  518. } );
  519. it( 'should add tableCell on proper index in tr when previous row have rowspans', () => {
  520. setModelData( model, modelTable( [
  521. [ { rowspan: 2, contents: '00' }, '13' ],
  522. [ '11', '12' ]
  523. ] ) );
  524. const table = root.getChild( 0 );
  525. model.change( writer => {
  526. writer.insertElement( 'tableCell', table.getChild( 0 ), 1 );
  527. writer.insertElement( 'tableCell', table.getChild( 1 ), 0 );
  528. } );
  529. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  530. [ { rowspan: 2, contents: '00' }, '', '13' ],
  531. [ '', '11', '12' ]
  532. ] ) );
  533. } );
  534. it( 'split cell simulation - simple', () => {
  535. setModelData( model, modelTable( [
  536. [ '00', '01' ],
  537. [ '10', '11' ]
  538. ] ) );
  539. const table = root.getChild( 0 );
  540. model.change( writer => {
  541. const firstRow = table.getChild( 0 );
  542. const secondRow = table.getChild( 1 );
  543. writer.insertElement( 'tableCell', firstRow, 1 );
  544. writer.setAttribute( 'colspan', 2, secondRow.getChild( 0 ) );
  545. } );
  546. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  547. [ '00', '', '01' ],
  548. [ { colspan: 2, contents: '10' }, '11' ]
  549. ] ) );
  550. } );
  551. it( 'merge simulation - simple', () => {
  552. setModelData( model, modelTable( [
  553. [ '00', '01' ],
  554. [ '10', '11' ]
  555. ] ) );
  556. const table = root.getChild( 0 );
  557. model.change( writer => {
  558. const firstRow = table.getChild( 0 );
  559. writer.setAttribute( 'colspan', 2, firstRow.getChild( 0 ) );
  560. writer.remove( firstRow.getChild( 1 ) );
  561. } );
  562. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  563. [ { colspan: 2, contents: '00' } ],
  564. [ '10', '11' ]
  565. ] ) );
  566. } );
  567. describe( 'options.asWidget=true', () => {
  568. beforeEach( () => {
  569. return VirtualTestEditor.create()
  570. .then( newEditor => {
  571. editor = newEditor;
  572. model = editor.model;
  573. doc = model.document;
  574. root = doc.getRoot( 'main' );
  575. view = editor.editing.view;
  576. defaultSchema( model.schema );
  577. defaultConversion( editor.conversion, true );
  578. } );
  579. } );
  580. it( 'should create inserted table cell as a widget', () => {
  581. setModelData( model, modelTable( [ [ '00' ] ] ) );
  582. const table = root.getChild( 0 );
  583. model.change( writer => {
  584. const row = table.getChild( 0 );
  585. writer.insert( writer.createElement( 'tableCell' ), row, 'end' );
  586. } );
  587. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  588. '<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false">' +
  589. '<div class="ck ck-widget__selection-handle"></div>' +
  590. '<table>' +
  591. '<tbody>' +
  592. '<tr>' +
  593. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  594. '<span style="display:inline-block">00</span>' +
  595. '</td>' +
  596. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true"></td>' +
  597. '</tr>' +
  598. '</tbody>' +
  599. '</table>' +
  600. '</figure>'
  601. );
  602. } );
  603. } );
  604. } );
  605. describe( 'downcastTableHeadingColumnsChange()', () => {
  606. it( 'should work for adding heading columns', () => {
  607. setModelData( model, modelTable( [
  608. [ '00', '01' ],
  609. [ '10', '11' ]
  610. ] ) );
  611. const table = root.getChild( 0 );
  612. model.change( writer => {
  613. writer.setAttribute( 'headingColumns', 1, table );
  614. } );
  615. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  616. [ { isHeading: true, contents: '00' }, '01' ],
  617. [ { isHeading: true, contents: '10' }, '11' ]
  618. ], { headingColumns: 1 } ) );
  619. } );
  620. it( 'should work for changing heading columns to a bigger number', () => {
  621. setModelData( model, modelTable( [
  622. [ '00', '01', '02', '03' ],
  623. [ '10', '11', '12', '13' ]
  624. ], { headingColumns: 1 } ) );
  625. const table = root.getChild( 0 );
  626. model.change( writer => {
  627. writer.setAttribute( 'headingColumns', 3, table );
  628. } );
  629. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  630. [ { isHeading: true, contents: '00' }, { isHeading: true, contents: '01' }, { isHeading: true, contents: '02' }, '03' ],
  631. [ { isHeading: true, contents: '10' }, { isHeading: true, contents: '11' }, { isHeading: true, contents: '12' }, '13' ]
  632. ] ) );
  633. } );
  634. it( 'should work for changing heading columns to a smaller number', () => {
  635. setModelData( model, modelTable( [
  636. [ { isHeading: true, contents: '00' }, { isHeading: true, contents: '01' }, { isHeading: true, contents: '02' }, '03' ],
  637. [ { isHeading: true, contents: '10' }, { isHeading: true, contents: '11' }, { isHeading: true, contents: '12' }, '13' ]
  638. ], { headingColumns: 3 } ) );
  639. const table = root.getChild( 0 );
  640. model.change( writer => {
  641. writer.setAttribute( 'headingColumns', 1, table );
  642. } );
  643. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  644. [ { isHeading: true, contents: '00' }, '01', '02', '03' ],
  645. [ { isHeading: true, contents: '10' }, '11', '12', '13' ]
  646. ], { headingColumns: 3 } ) );
  647. } );
  648. it( 'should work for removing heading columns', () => {
  649. setModelData( model, modelTable( [
  650. [ '00', '01' ],
  651. [ '10', '11' ]
  652. ], { headingColumns: 1 } ) );
  653. const table = root.getChild( 0 );
  654. model.change( writer => {
  655. writer.removeAttribute( 'headingColumns', table );
  656. } );
  657. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  658. [ '00', '01' ],
  659. [ '10', '11' ]
  660. ] ) );
  661. } );
  662. it( 'should be possible to overwrite', () => {
  663. editor.conversion.attributeToAttribute( { model: 'headingColumns', view: 'headingColumns', converterPriority: 'high' } );
  664. setModelData( model, modelTable( [ [ '00' ] ] ) );
  665. const table = root.getChild( 0 );
  666. model.change( writer => {
  667. writer.setAttribute( 'headingColumns', 1, table );
  668. } );
  669. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  670. '<figure class="table" headingColumns="1">' +
  671. '<table>' +
  672. '<tbody>' +
  673. '<tr><td>00</td></tr>' +
  674. '</tbody>' +
  675. '</table>' +
  676. '</figure>'
  677. );
  678. } );
  679. it( 'should work with adding table cells', () => {
  680. setModelData( model, modelTable( [
  681. [ { rowspan: 2, contents: '00' }, '01', '13', '14' ],
  682. [ '11', '12', '13' ],
  683. [ { colspan: 2, contents: '20' }, '22', '23' ]
  684. ], { headingColumns: 2 } ) );
  685. const table = root.getChild( 0 );
  686. model.change( writer => {
  687. // Inserting column in heading columns so update table's attribute also
  688. writer.setAttribute( 'headingColumns', 3, table );
  689. writer.insertElement( 'tableCell', table.getChild( 0 ), 2 );
  690. writer.insertElement( 'tableCell', table.getChild( 1 ), 1 );
  691. writer.insertElement( 'tableCell', table.getChild( 2 ), 1 );
  692. } );
  693. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  694. [
  695. { isHeading: true, rowspan: 2, contents: '00' },
  696. { isHeading: true, contents: '01' },
  697. { isHeading: true, contents: '' },
  698. '13',
  699. '14'
  700. ],
  701. [
  702. { isHeading: true, contents: '11' },
  703. { isHeading: true, contents: '' },
  704. '12',
  705. '13'
  706. ],
  707. [
  708. { isHeading: true, colspan: 2, contents: '20' },
  709. { isHeading: true, contents: '' },
  710. '22',
  711. '23'
  712. ]
  713. ] ) );
  714. } );
  715. describe( 'options.asWidget=true', () => {
  716. beforeEach( () => {
  717. return VirtualTestEditor.create()
  718. .then( newEditor => {
  719. editor = newEditor;
  720. model = editor.model;
  721. doc = model.document;
  722. root = doc.getRoot( 'main' );
  723. view = editor.editing.view;
  724. defaultSchema( model.schema );
  725. defaultConversion( editor.conversion, true );
  726. } );
  727. } );
  728. it( 'should create renamed cell as a widget', () => {
  729. setModelData( model, modelTable( [ [ '00' ] ] ) );
  730. const table = root.getChild( 0 );
  731. model.change( writer => {
  732. writer.setAttribute( 'headingRows', 1, table );
  733. } );
  734. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  735. '<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false">' +
  736. '<div class="ck ck-widget__selection-handle"></div>' +
  737. '<table>' +
  738. '<thead>' +
  739. '<tr>' +
  740. '<th class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  741. '<span style="display:inline-block">00</span>' +
  742. '</th>' +
  743. '</tr>' +
  744. '</thead>' +
  745. '</table>' +
  746. '</figure>'
  747. );
  748. } );
  749. } );
  750. } );
  751. describe( 'downcastTableHeadingRowsChange()', () => {
  752. it( 'should work for adding heading rows', () => {
  753. setModelData( model, modelTable( [
  754. [ '00', '01' ],
  755. [ '10', '11' ],
  756. [ '20', '21' ]
  757. ] ) );
  758. const table = root.getChild( 0 );
  759. model.change( writer => {
  760. writer.setAttribute( 'headingRows', 2, table );
  761. } );
  762. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  763. [ '00', '01' ],
  764. [ '10', '11' ],
  765. [ '20', '21' ]
  766. ], { headingRows: 2 } ) );
  767. } );
  768. it( 'should work for changing number of heading rows to a bigger number', () => {
  769. setModelData( model, modelTable( [
  770. [ '00', '01' ],
  771. [ '10', '11' ],
  772. [ '20', '21' ]
  773. ], { headingRows: 1 } ) );
  774. const table = root.getChild( 0 );
  775. model.change( writer => {
  776. writer.setAttribute( 'headingRows', 2, table );
  777. } );
  778. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  779. [ '00', '01' ],
  780. [ '10', '11' ],
  781. [ '20', '21' ]
  782. ], { headingRows: 2 } ) );
  783. } );
  784. it( 'should work for changing number of heading rows to a smaller number', () => {
  785. setModelData( model, modelTable( [
  786. [ '00', '01' ],
  787. [ '10', '11' ],
  788. [ '20', '21' ],
  789. [ '30', '31' ]
  790. ], { headingRows: 3 } ) );
  791. const table = root.getChild( 0 );
  792. model.change( writer => {
  793. writer.setAttribute( 'headingRows', 2, table );
  794. } );
  795. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  796. [ '00', '01' ],
  797. [ '10', '11' ],
  798. [ '20', '21' ],
  799. [ '30', '31' ]
  800. ], { headingRows: 2 } ) );
  801. } );
  802. it( 'should work for removing heading rows', () => {
  803. setModelData( model, modelTable( [
  804. [ '00', '01' ],
  805. [ '10', '11' ]
  806. ], { headingRows: 2 } ) );
  807. const table = root.getChild( 0 );
  808. model.change( writer => {
  809. writer.removeAttribute( 'headingRows', table );
  810. } );
  811. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  812. [ '00', '01' ],
  813. [ '10', '11' ]
  814. ] ) );
  815. } );
  816. it( 'should work for making heading rows without tbody', () => {
  817. setModelData( model, modelTable( [
  818. [ '00', '01' ],
  819. [ '10', '11' ]
  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. ], { headingRows: 2 } ) );
  829. } );
  830. it( 'should be possible to overwrite', () => {
  831. editor.conversion.attributeToAttribute( { model: 'headingRows', view: 'headingRows', converterPriority: 'high' } );
  832. setModelData( model, modelTable( [ [ '00' ] ] ) );
  833. const table = root.getChild( 0 );
  834. model.change( writer => {
  835. writer.setAttribute( 'headingRows', 1, table );
  836. } );
  837. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  838. '<figure class="table" headingRows="1">' +
  839. '<table>' +
  840. '<tbody>' +
  841. '<tr><td>00</td></tr>' +
  842. '</tbody>' +
  843. '</table>' +
  844. '</figure>'
  845. );
  846. } );
  847. it( 'should work with adding table rows at the beginning of a table', () => {
  848. setModelData( model, modelTable( [
  849. [ '00', '01' ],
  850. [ '10', '11' ]
  851. ], { headingRows: 1 } ) );
  852. const table = root.getChild( 0 );
  853. model.change( writer => {
  854. writer.setAttribute( 'headingRows', 2, table );
  855. const tableRow = writer.createElement( 'tableRow' );
  856. writer.insert( tableRow, table, 0 );
  857. writer.insertElement( 'tableCell', tableRow, 'end' );
  858. writer.insertElement( 'tableCell', tableRow, 'end' );
  859. } );
  860. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  861. [ '', '' ],
  862. [ '00', '01' ],
  863. [ '10', '11' ]
  864. ], { headingRows: 2 } ) );
  865. } );
  866. it( 'should work with adding a table row and expanding heading', () => {
  867. setModelData( model, modelTable( [
  868. [ '00', '01' ],
  869. [ '10', '11' ],
  870. [ '20', '21' ]
  871. ], { headingRows: 1 } ) );
  872. const table = root.getChild( 0 );
  873. model.change( writer => {
  874. writer.setAttribute( 'headingRows', 2, table );
  875. const tableRow = writer.createElement( 'tableRow' );
  876. writer.insert( tableRow, table, 1 );
  877. writer.insertElement( 'tableCell', tableRow, 'end' );
  878. writer.insertElement( 'tableCell', tableRow, 'end' );
  879. } );
  880. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  881. [ '00', '01' ],
  882. [ '', '' ],
  883. [ '10', '11' ],
  884. [ '20', '21' ]
  885. ], { headingRows: 2 } ) );
  886. } );
  887. describe( 'options.asWidget=true', () => {
  888. beforeEach( () => {
  889. return VirtualTestEditor.create()
  890. .then( newEditor => {
  891. editor = newEditor;
  892. model = editor.model;
  893. doc = model.document;
  894. root = doc.getRoot( 'main' );
  895. view = editor.editing.view;
  896. defaultSchema( model.schema );
  897. defaultConversion( editor.conversion, true );
  898. } );
  899. } );
  900. it( 'should create renamed cell as a widget', () => {
  901. setModelData( model, modelTable( [ [ '00' ] ] ) );
  902. const table = root.getChild( 0 );
  903. model.change( writer => {
  904. writer.setAttribute( 'headingColumns', 1, table );
  905. } );
  906. assertEqualMarkup( getViewData( view, { withoutSelection: true } ),
  907. '<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false">' +
  908. '<div class="ck ck-widget__selection-handle"></div>' +
  909. '<table>' +
  910. '<tbody>' +
  911. '<tr>' +
  912. '<th class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
  913. '<span style="display:inline-block">00</span>' +
  914. '</th>' +
  915. '</tr>' +
  916. '</tbody>' +
  917. '</table>' +
  918. '</figure>'
  919. );
  920. } );
  921. } );
  922. } );
  923. describe( 'downcastRemoveRow()', () => {
  924. it( 'should react to removed row from the beginning of a tbody', () => {
  925. setModelData( model, modelTable( [
  926. [ '00[]', '01' ],
  927. [ '10', '11' ]
  928. ] ) );
  929. const table = root.getChild( 0 );
  930. model.change( writer => {
  931. writer.remove( table.getChild( 1 ) );
  932. } );
  933. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  934. [ '00', '01' ]
  935. ] ) );
  936. } );
  937. it( 'should react to removed row form the end of a tbody', () => {
  938. setModelData( model, modelTable( [
  939. [ '00[]', '01' ],
  940. [ '10', '11' ]
  941. ] ) );
  942. const table = root.getChild( 0 );
  943. model.change( writer => {
  944. writer.remove( table.getChild( 0 ) );
  945. } );
  946. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  947. [ '10', '11' ]
  948. ] ) );
  949. } );
  950. it( 'should react to removed row from the beginning of a thead', () => {
  951. setModelData( model, modelTable( [
  952. [ '00[]', '01' ],
  953. [ '10', '11' ]
  954. ], { headingRows: 2 } ) );
  955. const table = root.getChild( 0 );
  956. model.change( writer => {
  957. writer.remove( table.getChild( 1 ) );
  958. } );
  959. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  960. [ '00', '01' ]
  961. ], { headingRows: 2 } ) );
  962. } );
  963. it( 'should react to removed row form the end of a thead', () => {
  964. setModelData( model, modelTable( [
  965. [ '00[]', '01' ],
  966. [ '10', '11' ]
  967. ], { headingRows: 2 } ) );
  968. const table = root.getChild( 0 );
  969. model.change( writer => {
  970. writer.remove( table.getChild( 0 ) );
  971. } );
  972. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  973. [ '10', '11' ]
  974. ], { headingRows: 2 } ) );
  975. } );
  976. it( 'should remove empty thead section if a last row was removed from thead', () => {
  977. setModelData( model, modelTable( [
  978. [ '00[]', '01' ],
  979. [ '10', '11' ]
  980. ], { headingRows: 1 } ) );
  981. const table = root.getChild( 0 );
  982. model.change( writer => {
  983. writer.setAttribute( 'headingRows', 0, table );
  984. writer.remove( table.getChild( 0 ) );
  985. } );
  986. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  987. [ '10', '11' ]
  988. ] ) );
  989. } );
  990. it( 'should remove empty tbody section if a last row was removed from tbody', () => {
  991. setModelData( model, modelTable( [
  992. [ '00[]', '01' ],
  993. [ '10', '11' ]
  994. ], { headingRows: 1 } ) );
  995. const table = root.getChild( 0 );
  996. model.change( writer => {
  997. writer.remove( table.getChild( 1 ) );
  998. } );
  999. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  1000. [ '00', '01' ]
  1001. ], { headingRows: 1 } ) );
  1002. } );
  1003. } );
  1004. describe( 'options.asWidget=true', () => {
  1005. beforeEach( () => {
  1006. return VirtualTestEditor.create()
  1007. .then( newEditor => {
  1008. editor = newEditor;
  1009. model = editor.model;
  1010. doc = model.document;
  1011. root = doc.getRoot( 'main' );
  1012. view = editor.editing.view;
  1013. defaultSchema( model.schema );
  1014. defaultConversion( editor.conversion, true );
  1015. editor.conversion.for( 'downcast' ).add( paragraphInTableCell() );
  1016. } );
  1017. } );
  1018. it( 'should rename <span> to <p> when more then one block content inside table cell', () => {
  1019. setModelData( model, modelTable( [
  1020. [ '00[]' ]
  1021. ] ) );
  1022. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  1023. [ '00' ]
  1024. ], { asWidget: true } ) );
  1025. const table = root.getChild( 0 );
  1026. model.change( writer => {
  1027. const nodeByPath = table.getNodeByPath( [ 0, 0, 0 ] );
  1028. const paragraph = writer.createElement( 'paragraph' );
  1029. writer.insert( paragraph, nodeByPath, 'after' );
  1030. writer.setSelection( nodeByPath.nextSibling, 0 );
  1031. } );
  1032. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  1033. [ '<p>00</p><p></p>' ]
  1034. ], { asWidget: true } ) );
  1035. } );
  1036. it( 'should rename <span> to <p> for single paragraph with attribute', () => {
  1037. model.schema.extend( '$block', { allowAttributes: 'foo' } );
  1038. editor.conversion.attributeToAttribute( { model: 'foo', view: 'foo' } );
  1039. setModelData( model, modelTable( [
  1040. [ '<paragraph foo="bar">00[]</paragraph>' ]
  1041. ] ) );
  1042. assertEqualMarkup( getViewData( view, { withoutSelection: true } ), viewTable( [
  1043. [ '<p foo="bar">00</p>' ]
  1044. ], { asWidget: true } ) );
  1045. } );
  1046. } );
  1047. } );