downcast.js 36 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226
  1. /**
  2. * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  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 {
  9. downcastInsertCell,
  10. downcastInsertRow,
  11. downcastInsertTable,
  12. downcastRemoveRow,
  13. downcastTableHeadingColumnsChange,
  14. downcastTableHeadingRowsChange
  15. } from '../../src/converters/downcast';
  16. import { formatTable, formattedViewTable, modelTable, viewTable } from '../_utils/utils';
  17. describe( 'downcast converters', () => {
  18. let editor, model, doc, root, viewDocument;
  19. beforeEach( () => {
  20. return VirtualTestEditor.create()
  21. .then( newEditor => {
  22. editor = newEditor;
  23. model = editor.model;
  24. doc = model.document;
  25. root = doc.getRoot( 'main' );
  26. viewDocument = editor.editing.view;
  27. const conversion = editor.conversion;
  28. const schema = model.schema;
  29. schema.register( 'table', {
  30. allowWhere: '$block',
  31. allowAttributes: [ 'headingRows', 'headingColumns' ],
  32. isObject: true
  33. } );
  34. schema.register( 'tableRow', { allowIn: 'table' } );
  35. schema.register( 'tableCell', {
  36. allowIn: 'tableRow',
  37. allowContentOf: '$block',
  38. allowAttributes: [ 'colspan', 'rowspan' ],
  39. isLimit: true
  40. } );
  41. conversion.for( 'downcast' ).add( downcastInsertTable() );
  42. conversion.attributeToAttribute( { model: 'colspan', view: 'colspan' } );
  43. conversion.attributeToAttribute( { model: 'rowspan', view: 'rowspan' } );
  44. // Insert conversion
  45. conversion.for( 'downcast' ).add( downcastInsertRow() );
  46. conversion.for( 'downcast' ).add( downcastInsertCell() );
  47. conversion.for( 'downcast' ).add( downcastRemoveRow() );
  48. conversion.for( 'downcast' ).add( downcastTableHeadingRowsChange() );
  49. conversion.for( 'downcast' ).add( downcastTableHeadingColumnsChange() );
  50. } );
  51. } );
  52. describe( 'downcastInsertTable()', () => {
  53. it( 'should create table with tbody', () => {
  54. setModelData( model,
  55. '<table>' +
  56. '<tableRow><tableCell></tableCell></tableRow>' +
  57. '</table>'
  58. );
  59. expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal(
  60. '<table>' +
  61. '<tbody>' +
  62. '<tr><td></td></tr>' +
  63. '</tbody>' +
  64. '</table>'
  65. );
  66. } );
  67. it( 'should create table with tbody and thead', () => {
  68. setModelData( model,
  69. '<table headingRows="1">' +
  70. '<tableRow><tableCell>1</tableCell></tableRow>' +
  71. '<tableRow><tableCell>2</tableCell></tableRow>' +
  72. '</table>'
  73. );
  74. expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal(
  75. '<table>' +
  76. '<thead>' +
  77. '<tr><th>1</th></tr>' +
  78. '</thead>' +
  79. '<tbody>' +
  80. '<tr><td>2</td></tr>' +
  81. '</tbody>' +
  82. '</table>'
  83. );
  84. } );
  85. it( 'should create table with thead', () => {
  86. setModelData( model,
  87. '<table headingRows="2">' +
  88. '<tableRow><tableCell>1</tableCell></tableRow>' +
  89. '<tableRow><tableCell>2</tableCell></tableRow>' +
  90. '</table>'
  91. );
  92. expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal(
  93. '<table>' +
  94. '<thead>' +
  95. '<tr><th>1</th></tr>' +
  96. '<tr><th>2</th></tr>' +
  97. '</thead>' +
  98. '</table>'
  99. );
  100. } );
  101. it( 'should create table with heading columns and rows', () => {
  102. setModelData( model,
  103. '<table headingColumns="3" headingRows="1">' +
  104. '<tableRow>' +
  105. '<tableCell>11</tableCell><tableCell>12</tableCell><tableCell>13</tableCell><tableCell>14</tableCell>' +
  106. '</tableRow>' +
  107. '<tableRow>' +
  108. '<tableCell>21</tableCell><tableCell>22</tableCell><tableCell>23</tableCell><tableCell>24</tableCell>' +
  109. '</tableRow>' +
  110. '</table>'
  111. );
  112. expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal(
  113. '<table>' +
  114. '<thead>' +
  115. '<tr><th>11</th><th>12</th><th>13</th><th>14</th></tr>' +
  116. '</thead>' +
  117. '<tbody>' +
  118. '<tr><th>21</th><th>22</th><th>23</th><td>24</td></tr>' +
  119. '</tbody>' +
  120. '</table>'
  121. );
  122. } );
  123. it( 'should be possible to overwrite', () => {
  124. editor.conversion.elementToElement( { model: 'tableRow', view: 'tr', converterPriority: 'high' } );
  125. editor.conversion.elementToElement( { model: 'tableCell', view: 'td', converterPriority: 'high' } );
  126. editor.conversion.for( 'downcast' ).add( dispatcher => {
  127. dispatcher.on( 'insert:table', ( evt, data, conversionApi ) => {
  128. conversionApi.consumable.consume( data.item, 'insert' );
  129. const tableElement = conversionApi.writer.createContainerElement( 'table', { foo: 'bar' } );
  130. const viewPosition = conversionApi.mapper.toViewPosition( data.range.start );
  131. conversionApi.mapper.bindElements( data.item, tableElement );
  132. conversionApi.writer.insert( viewPosition, tableElement );
  133. }, { priority: 'high' } );
  134. } );
  135. setModelData( model,
  136. '<table>' +
  137. '<tableRow><tableCell></tableCell></tableRow>' +
  138. '</table>'
  139. );
  140. expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal(
  141. '<table foo="bar">' +
  142. '<tr><td></td></tr>' +
  143. '</table>'
  144. );
  145. } );
  146. describe( 'headingColumns attribute', () => {
  147. it( 'should mark heading columns table cells', () => {
  148. setModelData( model,
  149. '<table headingColumns="2">' +
  150. '<tableRow><tableCell>11</tableCell><tableCell>12</tableCell><tableCell>13</tableCell></tableRow>' +
  151. '<tableRow><tableCell>21</tableCell><tableCell>22</tableCell><tableCell>23</tableCell></tableRow>' +
  152. '</table>'
  153. );
  154. expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal(
  155. '<table>' +
  156. '<tbody>' +
  157. '<tr><th>11</th><th>12</th><td>13</td></tr>' +
  158. '<tr><th>21</th><th>22</th><td>23</td></tr>' +
  159. '</tbody>' +
  160. '</table>'
  161. );
  162. } );
  163. it( 'should mark heading columns table cells when one has colspan attribute', () => {
  164. setModelData( model,
  165. '<table headingColumns="3">' +
  166. '<tableRow>' +
  167. '<tableCell>11</tableCell><tableCell>12</tableCell><tableCell>13</tableCell><tableCell>14</tableCell>' +
  168. '</tableRow>' +
  169. '<tableRow><tableCell colspan="2">21</tableCell><tableCell>23</tableCell><tableCell>24</tableCell></tableRow>' +
  170. '</table>'
  171. );
  172. expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal(
  173. '<table>' +
  174. '<tbody>' +
  175. '<tr><th>11</th><th>12</th><th>13</th><td>14</td></tr>' +
  176. '<tr><th colspan="2">21</th><th>23</th><td>24</td></tr>' +
  177. '</tbody>' +
  178. '</table>'
  179. );
  180. } );
  181. it( 'should work with colspan and rowspan attributes on table cells', () => {
  182. // The table in this test looks like a table below:
  183. //
  184. // Row headings | Normal cells
  185. // |
  186. // +----+----+----+----+
  187. // | 11 | 12 | 13 | 14 |
  188. // | +----+ +----+
  189. // | | 22 | | 24 |
  190. // |----+----+ +----+
  191. // | 31 | | 34 |
  192. // | +----+----+
  193. // | | 43 | 44 |
  194. // +----+----+----+----+
  195. setModelData( model,
  196. '<table headingColumns="3">' +
  197. '<tableRow>' +
  198. '<tableCell rowspan="2">11</tableCell>' +
  199. '<tableCell>12</tableCell>' +
  200. '<tableCell rowspan="3">13</tableCell>' +
  201. '<tableCell>14</tableCell>' +
  202. '</tableRow>' +
  203. '<tableRow><tableCell>22</tableCell><tableCell>24</tableCell></tableRow>' +
  204. '<tableRow><tableCell colspan="2" rowspan="2">31</tableCell><tableCell>34</tableCell></tableRow>' +
  205. '<tableRow><tableCell>43</tableCell><tableCell>44</tableCell></tableRow>' +
  206. '</table>'
  207. );
  208. expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal(
  209. '<table>' +
  210. '<tbody>' +
  211. '<tr><th rowspan="2">11</th><th>12</th><th rowspan="3">13</th><td>14</td></tr>' +
  212. '<tr><th>22</th><td>24</td></tr>' +
  213. '<tr><th colspan="2" rowspan="2">31</th><td>34</td></tr>' +
  214. '<tr><th>43</th><td>44</td></tr>' +
  215. '</tbody>' +
  216. '</table>'
  217. );
  218. } );
  219. } );
  220. describe( 'asWidget', () => {
  221. beforeEach( () => {
  222. return VirtualTestEditor.create()
  223. .then( newEditor => {
  224. editor = newEditor;
  225. model = editor.model;
  226. doc = model.document;
  227. root = doc.getRoot( 'main' );
  228. viewDocument = editor.editing.view;
  229. const conversion = editor.conversion;
  230. const schema = model.schema;
  231. schema.register( 'table', {
  232. allowWhere: '$block',
  233. allowAttributes: [ 'headingRows', 'headingColumns' ],
  234. isObject: true
  235. } );
  236. schema.register( 'tableRow', { allowIn: 'table' } );
  237. schema.register( 'tableCell', {
  238. allowIn: 'tableRow',
  239. allowContentOf: '$block',
  240. allowAttributes: [ 'colspan', 'rowspan' ],
  241. isLimit: true
  242. } );
  243. conversion.for( 'downcast' ).add( downcastInsertTable( { asWidget: true } ) );
  244. conversion.for( 'downcast' ).add( downcastInsertRow( { asWidget: true } ) );
  245. conversion.for( 'downcast' ).add( downcastInsertCell( { asWidget: true } ) );
  246. conversion.attributeToAttribute( { model: 'colspan', view: 'colspan' } );
  247. conversion.attributeToAttribute( { model: 'rowspan', view: 'rowspan' } );
  248. } );
  249. } );
  250. it( 'should create table as a widget', () => {
  251. setModelData( model,
  252. '<table>' +
  253. '<tableRow><tableCell></tableCell></tableRow>' +
  254. '</table>'
  255. );
  256. expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal(
  257. '<table class="ck-widget" contenteditable="false">' +
  258. '<tbody>' +
  259. '<tr><td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true"></td></tr>' +
  260. '</tbody>' +
  261. '</table>'
  262. );
  263. } );
  264. } );
  265. } );
  266. describe( 'downcastInsertRow()', () => {
  267. it( 'should react to changed rows', () => {
  268. setModelData( model, modelTable( [
  269. [ '11', '12' ]
  270. ] ) );
  271. const table = root.getChild( 0 );
  272. model.change( writer => {
  273. const row = writer.createElement( 'tableRow' );
  274. writer.insert( row, table, 1 );
  275. writer.insertElement( 'tableCell', row, 'end' );
  276. writer.insertElement( 'tableCell', row, 'end' );
  277. } );
  278. expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( viewTable( [
  279. [ '11', '12' ],
  280. [ '', '' ]
  281. ] ) );
  282. } );
  283. it( 'should properly consume already added rows', () => {
  284. setModelData( model, modelTable( [
  285. [ '11', '12' ]
  286. ] ) );
  287. const table = root.getChild( 0 );
  288. model.change( writer => {
  289. const row = writer.createElement( 'tableRow' );
  290. writer.insert( row, table, 1 );
  291. writer.insertElement( 'tableCell', row, 'end' );
  292. writer.insertElement( 'tableCell', row, 'end' );
  293. } );
  294. expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( viewTable( [
  295. [ '11', '12' ],
  296. [ '', '' ]
  297. ] ) );
  298. model.change( writer => {
  299. const row = writer.createElement( 'tableRow' );
  300. writer.insert( row, table, 2 );
  301. writer.insertElement( 'tableCell', row, 'end' );
  302. writer.insertElement( 'tableCell', row, 'end' );
  303. } );
  304. expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( viewTable( [
  305. [ '11', '12' ],
  306. [ '', '' ],
  307. [ '', '' ]
  308. ] ) );
  309. } );
  310. it( 'should insert row on proper index', () => {
  311. setModelData( model, modelTable( [
  312. [ '11', '12' ],
  313. [ '21', '22' ],
  314. [ '31', '32' ]
  315. ] ) );
  316. const table = root.getChild( 0 );
  317. model.change( writer => {
  318. const row = writer.createElement( 'tableRow' );
  319. writer.insert( row, table, 1 );
  320. writer.insertElement( 'tableCell', row, 'end' );
  321. writer.insertElement( 'tableCell', row, 'end' );
  322. } );
  323. expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( viewTable( [
  324. [ '11', '12' ],
  325. [ '', '' ],
  326. [ '21', '22' ],
  327. [ '31', '32' ]
  328. ] ) );
  329. } );
  330. it( 'should insert row on proper index when table has heading rows defined - insert in body', () => {
  331. setModelData( model, modelTable( [
  332. [ '11', '12' ],
  333. [ '21', '22' ],
  334. [ '31', '32' ]
  335. ], { headingRows: 1 } ) );
  336. const table = root.getChild( 0 );
  337. model.change( writer => {
  338. const row = writer.createElement( 'tableRow' );
  339. writer.insert( row, table, 1 );
  340. writer.insertElement( 'tableCell', row, 'end' );
  341. writer.insertElement( 'tableCell', row, 'end' );
  342. } );
  343. expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( viewTable( [
  344. [ '11', '12' ],
  345. [ '', '' ],
  346. [ '21', '22' ],
  347. [ '31', '32' ]
  348. ], { headingRows: 1 } ) );
  349. } );
  350. it( 'should insert row on proper index when table has heading rows defined - insert in heading', () => {
  351. setModelData( model, modelTable( [
  352. [ '11', '12' ],
  353. [ '21', '22' ],
  354. [ '31', '32' ]
  355. ], { headingRows: 2 } ) );
  356. const table = root.getChild( 0 );
  357. model.change( writer => {
  358. const row = writer.createElement( 'tableRow' );
  359. writer.insert( row, table, 1 );
  360. writer.insertElement( 'tableCell', row, 'end' );
  361. writer.insertElement( 'tableCell', row, 'end' );
  362. } );
  363. expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( viewTable( [
  364. [ '11', '12' ],
  365. [ '', '' ],
  366. [ '21', '22' ],
  367. [ '31', '32' ]
  368. ], { headingRows: 3 } ) );
  369. } );
  370. it( 'should react to changed rows when previous rows\' cells has rowspans', () => {
  371. setModelData( model, modelTable( [
  372. [ { rowspan: 3, contents: '11' }, '12' ],
  373. [ '22' ]
  374. ] ) );
  375. const table = root.getChild( 0 );
  376. model.change( writer => {
  377. const row = writer.createElement( 'tableRow' );
  378. writer.insert( row, table, 2 );
  379. writer.insertElement( 'tableCell', row, 'end' );
  380. } );
  381. expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( viewTable( [
  382. [ { rowspan: 3, contents: '11' }, '12' ],
  383. [ '22' ],
  384. [ '' ]
  385. ] ) );
  386. } );
  387. it( 'should properly create row headings', () => {
  388. setModelData( model, modelTable( [
  389. [ { rowspan: 3, contents: '11' }, '12' ],
  390. [ '22' ]
  391. ], { headingColumns: 1 } ) );
  392. const table = root.getChild( 0 );
  393. model.change( writer => {
  394. const firstRow = writer.createElement( 'tableRow' );
  395. writer.insert( firstRow, table, 2 );
  396. writer.insert( writer.createElement( 'tableCell' ), firstRow, 'end' );
  397. const secondRow = writer.createElement( 'tableRow' );
  398. writer.insert( secondRow, table, 3 );
  399. writer.insert( writer.createElement( 'tableCell' ), secondRow, 'end' );
  400. writer.insert( writer.createElement( 'tableCell' ), secondRow, 'end' );
  401. } );
  402. expect( formatTable( getViewData( viewDocument, { withoutSelection: true } ) ) ).to.equal( formattedViewTable( [
  403. [ { rowspan: 3, contents: '11', isHeading: true }, '12' ],
  404. [ '22' ],
  405. [ '' ],
  406. [ { contents: '', isHeading: true }, '' ]
  407. ] ) );
  408. } );
  409. describe( 'asWidget', () => {
  410. beforeEach( () => {
  411. return VirtualTestEditor.create()
  412. .then( newEditor => {
  413. editor = newEditor;
  414. model = editor.model;
  415. doc = model.document;
  416. root = doc.getRoot( 'main' );
  417. viewDocument = editor.editing.view;
  418. const conversion = editor.conversion;
  419. const schema = model.schema;
  420. schema.register( 'table', {
  421. allowWhere: '$block',
  422. allowAttributes: [ 'headingRows', 'headingColumns' ],
  423. isObject: true
  424. } );
  425. schema.register( 'tableRow', { allowIn: 'table' } );
  426. schema.register( 'tableCell', {
  427. allowIn: 'tableRow',
  428. allowContentOf: '$block',
  429. allowAttributes: [ 'colspan', 'rowspan' ],
  430. isLimit: true
  431. } );
  432. conversion.for( 'downcast' ).add( downcastInsertTable( { asWidget: true } ) );
  433. conversion.for( 'downcast' ).add( downcastInsertRow( { asWidget: true } ) );
  434. conversion.for( 'downcast' ).add( downcastInsertCell( { asWidget: true } ) );
  435. conversion.attributeToAttribute( { model: 'colspan', view: 'colspan' } );
  436. conversion.attributeToAttribute( { model: 'rowspan', view: 'rowspan' } );
  437. } );
  438. } );
  439. it( 'should create table cell inside inserted row as a widget', () => {
  440. setModelData( model,
  441. '<table>' +
  442. '<tableRow><tableCell>foo</tableCell></tableRow>' +
  443. '</table>'
  444. );
  445. const table = root.getChild( 0 );
  446. model.change( writer => {
  447. const firstRow = writer.createElement( 'tableRow' );
  448. writer.insert( firstRow, table, 1 );
  449. writer.insert( writer.createElement( 'tableCell' ), firstRow, 'end' );
  450. } );
  451. expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal(
  452. '<table class="ck-widget" contenteditable="false">' +
  453. '<tbody>' +
  454. '<tr><td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">foo</td></tr>' +
  455. '<tr><td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true"></td></tr>' +
  456. '</tbody>' +
  457. '</table>'
  458. );
  459. } );
  460. } );
  461. } );
  462. describe( 'downcastInsertCell()', () => {
  463. it( 'should add tableCell on proper index in tr', () => {
  464. setModelData( model, modelTable( [
  465. [ '11', '12' ]
  466. ] ) );
  467. const table = root.getChild( 0 );
  468. model.change( writer => {
  469. const row = table.getChild( 0 );
  470. writer.insertElement( 'tableCell', row, 1 );
  471. } );
  472. expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( viewTable( [
  473. [ '11', '', '12' ]
  474. ] ) );
  475. } );
  476. it( 'should add tableCell on proper index in tr when previous have colspans', () => {
  477. setModelData( model, modelTable( [
  478. [ { colspan: 2, contents: '11' }, '13' ]
  479. ] ) );
  480. const table = root.getChild( 0 );
  481. model.change( writer => {
  482. const row = table.getChild( 0 );
  483. writer.insertElement( 'tableCell', row, 1 );
  484. } );
  485. expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( viewTable( [
  486. [ { colspan: 2, contents: '11' }, '', '13' ]
  487. ] ) );
  488. } );
  489. it( 'should add tableCell on proper index in tr when previous row have rowspans', () => {
  490. setModelData( model, modelTable( [
  491. [ { rowspan: 2, contents: '11' }, '13' ],
  492. [ '22', '23' ]
  493. ] ) );
  494. const table = root.getChild( 0 );
  495. model.change( writer => {
  496. writer.insertElement( 'tableCell', table.getChild( 0 ), 1 );
  497. writer.insertElement( 'tableCell', table.getChild( 1 ), 0 );
  498. } );
  499. expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( viewTable( [
  500. [ { rowspan: 2, contents: '11' }, '', '13' ],
  501. [ '', '22', '23' ]
  502. ] ) );
  503. } );
  504. it( 'split cell simulation - simple', () => {
  505. setModelData( model, modelTable( [
  506. [ '11', '12' ],
  507. [ '21', '22' ]
  508. ] ) );
  509. const table = root.getChild( 0 );
  510. model.change( writer => {
  511. const firstRow = table.getChild( 0 );
  512. const secondRow = table.getChild( 1 );
  513. writer.insertElement( 'tableCell', firstRow, 1 );
  514. writer.setAttribute( 'colspan', 2, secondRow.getChild( 0 ) );
  515. } );
  516. expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( viewTable( [
  517. [ '11', '', '12' ],
  518. [ { colspan: 2, contents: '21' }, '22' ]
  519. ] ) );
  520. } );
  521. it( 'merge simulation - simple', () => {
  522. setModelData( model, modelTable( [
  523. [ '11', '12' ],
  524. [ '21', '22' ]
  525. ] ) );
  526. const table = root.getChild( 0 );
  527. model.change( writer => {
  528. const firstRow = table.getChild( 0 );
  529. writer.setAttribute( 'colspan', 2, firstRow.getChild( 0 ) );
  530. writer.remove( firstRow.getChild( 1 ) );
  531. } );
  532. expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( viewTable( [
  533. [ { colspan: 2, contents: '11' } ],
  534. [ '21', '22' ]
  535. ] ) );
  536. } );
  537. describe( 'asWidget', () => {
  538. beforeEach( () => {
  539. return VirtualTestEditor.create()
  540. .then( newEditor => {
  541. editor = newEditor;
  542. model = editor.model;
  543. doc = model.document;
  544. root = doc.getRoot( 'main' );
  545. viewDocument = editor.editing.view;
  546. const conversion = editor.conversion;
  547. const schema = model.schema;
  548. schema.register( 'table', {
  549. allowWhere: '$block',
  550. allowAttributes: [ 'headingRows', 'headingColumns' ],
  551. isObject: true
  552. } );
  553. schema.register( 'tableRow', { allowIn: 'table' } );
  554. schema.register( 'tableCell', {
  555. allowIn: 'tableRow',
  556. allowContentOf: '$block',
  557. allowAttributes: [ 'colspan', 'rowspan' ],
  558. isLimit: true
  559. } );
  560. conversion.for( 'downcast' ).add( downcastInsertTable( { asWidget: true } ) );
  561. conversion.for( 'downcast' ).add( downcastInsertRow( { asWidget: true } ) );
  562. conversion.for( 'downcast' ).add( downcastInsertCell( { asWidget: true } ) );
  563. conversion.attributeToAttribute( { model: 'colspan', view: 'colspan' } );
  564. conversion.attributeToAttribute( { model: 'rowspan', view: 'rowspan' } );
  565. } );
  566. } );
  567. it( 'should create inserted table cell as a widget', () => {
  568. setModelData( model,
  569. '<table>' +
  570. '<tableRow><tableCell>foo</tableCell></tableRow>' +
  571. '</table>'
  572. );
  573. const table = root.getChild( 0 );
  574. model.change( writer => {
  575. const row = table.getChild( 0 );
  576. writer.insert( writer.createElement( 'tableCell' ), row, 'end' );
  577. } );
  578. expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal(
  579. '<table class="ck-widget" contenteditable="false">' +
  580. '<tbody>' +
  581. '<tr>' +
  582. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">foo</td>' +
  583. '<td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true"></td>' +
  584. '</tr>' +
  585. '</tbody>' +
  586. '</table>'
  587. );
  588. } );
  589. } );
  590. } );
  591. describe( 'downcastTableHeadingColumnsChange()', () => {
  592. it( 'should work for adding heading columns', () => {
  593. setModelData( model, modelTable( [
  594. [ '11', '12' ],
  595. [ '21', '22' ]
  596. ] ) );
  597. const table = root.getChild( 0 );
  598. model.change( writer => {
  599. writer.setAttribute( 'headingColumns', 1, table );
  600. } );
  601. expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( viewTable( [
  602. [ { isHeading: true, contents: '11' }, '12' ],
  603. [ { isHeading: true, contents: '21' }, '22' ]
  604. ] ) );
  605. } );
  606. it( 'should work for changing heading columns to a bigger number', () => {
  607. setModelData( model, modelTable( [
  608. [ '11', '12', '13', '14' ],
  609. [ '21', '22', '23', '24' ]
  610. ], { headingColumns: 1 } ) );
  611. const table = root.getChild( 0 );
  612. model.change( writer => {
  613. writer.setAttribute( 'headingColumns', 3, table );
  614. } );
  615. expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( viewTable( [
  616. [ { isHeading: true, contents: '11' }, { isHeading: true, contents: '12' }, { isHeading: true, contents: '13' }, '14' ],
  617. [ { isHeading: true, contents: '21' }, { isHeading: true, contents: '22' }, { isHeading: true, contents: '23' }, '24' ]
  618. ] ) );
  619. } );
  620. it( 'should work for changing heading columns to a smaller number', () => {
  621. setModelData( model, modelTable( [
  622. [ { isHeading: true, contents: '11' }, { isHeading: true, contents: '12' }, { isHeading: true, contents: '13' }, '14' ],
  623. [ { isHeading: true, contents: '21' }, { isHeading: true, contents: '22' }, { isHeading: true, contents: '23' }, '24' ]
  624. ], { headingColumns: 3 } ) );
  625. const table = root.getChild( 0 );
  626. model.change( writer => {
  627. writer.setAttribute( 'headingColumns', 1, table );
  628. } );
  629. expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( viewTable( [
  630. [ { isHeading: true, contents: '11' }, '12', '13', '14' ],
  631. [ { isHeading: true, contents: '21' }, '22', '23', '24' ]
  632. ], { headingColumns: 3 } ) );
  633. } );
  634. it( 'should work for removing heading columns', () => {
  635. setModelData( model, modelTable( [
  636. [ '11', '12' ],
  637. [ '21', '22' ]
  638. ], { headingColumns: 1 } ) );
  639. const table = root.getChild( 0 );
  640. model.change( writer => {
  641. writer.removeAttribute( 'headingColumns', table );
  642. } );
  643. expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( viewTable( [
  644. [ '11', '12' ],
  645. [ '21', '22' ]
  646. ] ) );
  647. } );
  648. it( 'should be possible to overwrite', () => {
  649. editor.conversion.attributeToAttribute( { model: 'headingColumns', view: 'headingColumns', converterPriority: 'high' } );
  650. setModelData( model, modelTable( [ [ '11' ] ] ) );
  651. const table = root.getChild( 0 );
  652. model.change( writer => {
  653. writer.setAttribute( 'headingColumns', 1, table );
  654. } );
  655. expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal(
  656. '<table headingColumns="1"><tbody><tr><td>11</td></tr></tbody></table>'
  657. );
  658. } );
  659. it( 'should work with adding table cells', () => {
  660. setModelData( model, modelTable( [
  661. [ { rowspan: 2, contents: '11' }, '12', '13', '14' ],
  662. [ '22', '23', '24' ],
  663. [ { colspan: 2, contents: '31' }, '33', '34' ]
  664. ], { headingColumns: 2 } ) );
  665. const table = root.getChild( 0 );
  666. model.change( writer => {
  667. // Inserting column in heading columns so update table's attribute also
  668. writer.setAttribute( 'headingColumns', 3, table );
  669. writer.insertElement( 'tableCell', table.getChild( 0 ), 2 );
  670. writer.insertElement( 'tableCell', table.getChild( 1 ), 1 );
  671. writer.insertElement( 'tableCell', table.getChild( 2 ), 1 );
  672. } );
  673. expect( formatTable( getViewData( viewDocument, { withoutSelection: true } ) ) ).to.equal( formattedViewTable( [
  674. [
  675. { isHeading: true, rowspan: 2, contents: '11' },
  676. { isHeading: true, contents: '12' },
  677. { isHeading: true, contents: '' },
  678. '13',
  679. '14'
  680. ],
  681. [
  682. { isHeading: true, contents: '22' },
  683. { isHeading: true, contents: '' },
  684. '23',
  685. '24'
  686. ],
  687. [
  688. { isHeading: true, colspan: 2, contents: '31' },
  689. { isHeading: true, contents: '' },
  690. '33',
  691. '34'
  692. ]
  693. ] ) );
  694. } );
  695. describe( 'asWidget', () => {
  696. beforeEach( () => {
  697. return VirtualTestEditor.create()
  698. .then( newEditor => {
  699. editor = newEditor;
  700. model = editor.model;
  701. doc = model.document;
  702. root = doc.getRoot( 'main' );
  703. viewDocument = editor.editing.view;
  704. const conversion = editor.conversion;
  705. const schema = model.schema;
  706. schema.register( 'table', {
  707. allowWhere: '$block',
  708. allowAttributes: [ 'headingRows', 'headingColumns' ],
  709. isObject: true
  710. } );
  711. schema.register( 'tableRow', { allowIn: 'table' } );
  712. schema.register( 'tableCell', {
  713. allowIn: 'tableRow',
  714. allowContentOf: '$block',
  715. allowAttributes: [ 'colspan', 'rowspan' ],
  716. isLimit: true
  717. } );
  718. conversion.for( 'downcast' ).add( downcastInsertTable( { asWidget: true } ) );
  719. conversion.for( 'downcast' ).add( downcastInsertRow( { asWidget: true } ) );
  720. conversion.for( 'downcast' ).add( downcastInsertCell( { asWidget: true } ) );
  721. conversion.for( 'downcast' ).add( downcastTableHeadingColumnsChange( { asWidget: true } ) );
  722. conversion.for( 'downcast' ).add( downcastTableHeadingRowsChange( { asWidget: true } ) );
  723. conversion.attributeToAttribute( { model: 'colspan', view: 'colspan' } );
  724. conversion.attributeToAttribute( { model: 'rowspan', view: 'rowspan' } );
  725. } );
  726. } );
  727. it( 'should create renamed cell as a widget', () => {
  728. setModelData( model,
  729. '<table>' +
  730. '<tableRow><tableCell>foo</tableCell></tableRow>' +
  731. '</table>'
  732. );
  733. const table = root.getChild( 0 );
  734. model.change( writer => {
  735. writer.setAttribute( 'headingRows', 1, table );
  736. } );
  737. expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal(
  738. '<table class="ck-widget" contenteditable="false">' +
  739. '<thead>' +
  740. '<tr><th class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">foo</th></tr>' +
  741. '</thead>' +
  742. '</table>'
  743. );
  744. } );
  745. } );
  746. } );
  747. describe( 'downcastTableHeadingRowsChange()', () => {
  748. it( 'should work for adding heading rows', () => {
  749. setModelData( model, modelTable( [
  750. [ '11', '12' ],
  751. [ '21', '22' ],
  752. [ '31', '32' ]
  753. ] ) );
  754. const table = root.getChild( 0 );
  755. model.change( writer => {
  756. writer.setAttribute( 'headingRows', 2, table );
  757. } );
  758. expect( formatTable( getViewData( viewDocument, { withoutSelection: true } ) ) ).to.equal( formattedViewTable( [
  759. [ '11', '12' ],
  760. [ '21', '22' ],
  761. [ '31', '32' ]
  762. ], { headingRows: 2 } ) );
  763. } );
  764. it( 'should work for changing number of heading rows to a bigger number', () => {
  765. setModelData( model, modelTable( [
  766. [ '11', '12' ],
  767. [ '21', '22' ],
  768. [ '31', '32' ]
  769. ], { headingRows: 1 } ) );
  770. const table = root.getChild( 0 );
  771. model.change( writer => {
  772. writer.setAttribute( 'headingRows', 2, table );
  773. } );
  774. expect( formatTable( getViewData( viewDocument, { withoutSelection: true } ) ) ).to.equal( formattedViewTable( [
  775. [ '11', '12' ],
  776. [ '21', '22' ],
  777. [ '31', '32' ]
  778. ], { headingRows: 2 } ) );
  779. } );
  780. it( 'should work for changing number of heading rows to a smaller number', () => {
  781. setModelData( model, modelTable( [
  782. [ '11', '12' ],
  783. [ '21', '22' ],
  784. [ '31', '32' ],
  785. [ '41', '42' ]
  786. ], { headingRows: 3 } ) );
  787. const table = root.getChild( 0 );
  788. model.change( writer => {
  789. writer.setAttribute( 'headingRows', 2, table );
  790. } );
  791. expect( formatTable( getViewData( viewDocument, { withoutSelection: true } ) ) ).to.equal( formattedViewTable( [
  792. [ '11', '12' ],
  793. [ '21', '22' ],
  794. [ '31', '32' ],
  795. [ '41', '42' ]
  796. ], { headingRows: 2 } ) );
  797. } );
  798. it( 'should work for removing heading rows', () => {
  799. setModelData( model, modelTable( [
  800. [ '11', '12' ],
  801. [ '21', '22' ]
  802. ], { headingRows: 2 } ) );
  803. const table = root.getChild( 0 );
  804. model.change( writer => {
  805. writer.removeAttribute( 'headingRows', table );
  806. } );
  807. expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( viewTable( [
  808. [ '11', '12' ],
  809. [ '21', '22' ]
  810. ] ) );
  811. } );
  812. it( 'should work for making heading rows without tbody', () => {
  813. setModelData( model, modelTable( [
  814. [ '11', '12' ],
  815. [ '21', '22' ]
  816. ] ) );
  817. const table = root.getChild( 0 );
  818. model.change( writer => {
  819. writer.setAttribute( 'headingRows', 2, table );
  820. } );
  821. expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( viewTable( [
  822. [ '11', '12' ],
  823. [ '21', '22' ]
  824. ], { headingRows: 2 } ) );
  825. } );
  826. it( 'should be possible to overwrite', () => {
  827. editor.conversion.attributeToAttribute( { model: 'headingRows', view: 'headingRows', converterPriority: 'high' } );
  828. setModelData( model, modelTable( [ [ '11' ] ] ) );
  829. const table = root.getChild( 0 );
  830. model.change( writer => {
  831. writer.setAttribute( 'headingRows', 1, table );
  832. } );
  833. expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal(
  834. '<table headingRows="1"><tbody><tr><td>11</td></tr></tbody></table>'
  835. );
  836. } );
  837. it( 'should work with adding table rows at the beginning of a table', () => {
  838. setModelData( model, modelTable( [
  839. [ '00', '01' ],
  840. [ '10', '11' ]
  841. ], { headingRows: 1 } ) );
  842. const table = root.getChild( 0 );
  843. model.change( writer => {
  844. writer.setAttribute( 'headingRows', 2, table );
  845. const tableRow = writer.createElement( 'tableRow' );
  846. writer.insert( tableRow, table, 0 );
  847. writer.insertElement( 'tableCell', tableRow, 'end' );
  848. writer.insertElement( 'tableCell', tableRow, 'end' );
  849. } );
  850. expect( formatTable( getViewData( viewDocument, { withoutSelection: true } ) ) ).to.equal( formattedViewTable( [
  851. [ '', '' ],
  852. [ '00', '01' ],
  853. [ '10', '11' ]
  854. ], { headingRows: 2 } ) );
  855. } );
  856. describe( 'asWidget', () => {
  857. beforeEach( () => {
  858. return VirtualTestEditor.create()
  859. .then( newEditor => {
  860. editor = newEditor;
  861. model = editor.model;
  862. doc = model.document;
  863. root = doc.getRoot( 'main' );
  864. viewDocument = editor.editing.view;
  865. const conversion = editor.conversion;
  866. const schema = model.schema;
  867. schema.register( 'table', {
  868. allowWhere: '$block',
  869. allowAttributes: [ 'headingRows', 'headingColumns' ],
  870. isObject: true
  871. } );
  872. schema.register( 'tableRow', { allowIn: 'table' } );
  873. schema.register( 'tableCell', {
  874. allowIn: 'tableRow',
  875. allowContentOf: '$block',
  876. allowAttributes: [ 'colspan', 'rowspan' ],
  877. isLimit: true
  878. } );
  879. conversion.for( 'downcast' ).add( downcastInsertTable( { asWidget: true } ) );
  880. conversion.for( 'downcast' ).add( downcastInsertRow( { asWidget: true } ) );
  881. conversion.for( 'downcast' ).add( downcastInsertCell( { asWidget: true } ) );
  882. conversion.for( 'downcast' ).add( downcastTableHeadingColumnsChange( { asWidget: true } ) );
  883. conversion.for( 'downcast' ).add( downcastTableHeadingRowsChange( { asWidget: true } ) );
  884. conversion.attributeToAttribute( { model: 'colspan', view: 'colspan' } );
  885. conversion.attributeToAttribute( { model: 'rowspan', view: 'rowspan' } );
  886. } );
  887. } );
  888. it( 'should create renamed cell as a widget', () => {
  889. setModelData( model,
  890. '<table>' +
  891. '<tableRow><tableCell>foo</tableCell></tableRow>' +
  892. '</table>'
  893. );
  894. const table = root.getChild( 0 );
  895. model.change( writer => {
  896. writer.setAttribute( 'headingColumns', 1, table );
  897. } );
  898. expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal(
  899. '<table class="ck-widget" contenteditable="false">' +
  900. '<tbody>' +
  901. '<tr><th class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">foo</th></tr>' +
  902. '</tbody>' +
  903. '</table>'
  904. );
  905. } );
  906. } );
  907. } );
  908. describe( 'downcastRemoveRow()', () => {
  909. it( 'should react to removed row from the beginning of a tbody', () => {
  910. setModelData( model, modelTable( [
  911. [ '00[]', '01' ],
  912. [ '10', '11' ]
  913. ] ) );
  914. const table = root.getChild( 0 );
  915. model.change( writer => {
  916. writer.remove( table.getChild( 1 ) );
  917. } );
  918. expect( formatTable( getViewData( viewDocument, { withoutSelection: true } ) ) ).to.equal( formattedViewTable( [
  919. [ '00', '01' ]
  920. ] ) );
  921. } );
  922. it( 'should react to removed row form the end of a tbody', () => {
  923. setModelData( model, modelTable( [
  924. [ '00[]', '01' ],
  925. [ '10', '11' ]
  926. ] ) );
  927. const table = root.getChild( 0 );
  928. model.change( writer => {
  929. writer.remove( table.getChild( 0 ) );
  930. } );
  931. expect( formatTable( getViewData( viewDocument, { withoutSelection: true } ) ) ).to.equal( formattedViewTable( [
  932. [ '10', '11' ]
  933. ] ) );
  934. } );
  935. it( 'should react to removed row from the beginning of a thead', () => {
  936. setModelData( model, modelTable( [
  937. [ '00[]', '01' ],
  938. [ '10', '11' ]
  939. ], { headingRows: 2 } ) );
  940. const table = root.getChild( 0 );
  941. model.change( writer => {
  942. writer.remove( table.getChild( 1 ) );
  943. } );
  944. expect( formatTable( getViewData( viewDocument, { withoutSelection: true } ) ) ).to.equal( formattedViewTable( [
  945. [ '00', '01' ]
  946. ], { headingRows: 2 } ) );
  947. } );
  948. it( 'should react to removed row form the end of a thead', () => {
  949. setModelData( model, modelTable( [
  950. [ '00[]', '01' ],
  951. [ '10', '11' ]
  952. ], { headingRows: 2 } ) );
  953. const table = root.getChild( 0 );
  954. model.change( writer => {
  955. writer.remove( table.getChild( 0 ) );
  956. } );
  957. expect( formatTable( getViewData( viewDocument, { withoutSelection: true } ) ) ).to.equal( formattedViewTable( [
  958. [ '10', '11' ]
  959. ], { headingRows: 2 } ) );
  960. } );
  961. it( 'should remove empty thead section if a last row was removed from thead', () => {
  962. setModelData( model, modelTable( [
  963. [ '00[]', '01' ],
  964. [ '10', '11' ]
  965. ], { headingRows: 1 } ) );
  966. const table = root.getChild( 0 );
  967. model.change( writer => {
  968. writer.setAttribute( 'headingRows', 0, table );
  969. writer.remove( table.getChild( 0 ) );
  970. } );
  971. expect( formatTable( getViewData( viewDocument, { withoutSelection: true } ) ) ).to.equal( formattedViewTable( [
  972. [ '10', '11' ]
  973. ] ) );
  974. } );
  975. it( 'should remove empty tbody section if a last row was removed from tbody', () => {
  976. setModelData( model, modelTable( [
  977. [ '00[]', '01' ],
  978. [ '10', '11' ]
  979. ], { headingRows: 1 } ) );
  980. const table = root.getChild( 0 );
  981. model.change( writer => {
  982. writer.remove( table.getChild( 1 ) );
  983. } );
  984. expect( formatTable( getViewData( viewDocument, { withoutSelection: true } ) ) ).to.equal( formattedViewTable( [
  985. [ '00', '01' ]
  986. ], { headingRows: 1 } ) );
  987. } );
  988. } );
  989. } );