upcasttable.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  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 { upcastElementToElement } from '@ckeditor/ckeditor5-engine/src/conversion/upcast-converters';
  7. import { getData as getModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
  8. import upcastTable, { upcastTableCell } from '../../src/converters/upcasttable';
  9. import { defaultSchema, formatTable } from '../_utils/utils';
  10. import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
  11. describe( 'upcastTable()', () => {
  12. let editor, model;
  13. beforeEach( () => {
  14. return VirtualTestEditor
  15. .create( {
  16. plugins: [ Paragraph ]
  17. } )
  18. .then( newEditor => {
  19. editor = newEditor;
  20. model = editor.model;
  21. const conversion = editor.conversion;
  22. defaultSchema( model.schema, false );
  23. // Table conversion.
  24. conversion.for( 'upcast' ).add( upcastTable() );
  25. // Table row conversion.
  26. conversion.for( 'upcast' ).add( upcastElementToElement( { model: 'tableRow', view: 'tr' } ) );
  27. // Table cell conversion.
  28. conversion.for( 'upcast' ).add( upcastTableCell( 'td' ) );
  29. conversion.for( 'upcast' ).add( upcastTableCell( 'th' ) );
  30. // Table attributes conversion.
  31. conversion.attributeToAttribute( { model: 'colspan', view: 'colspan' } );
  32. conversion.attributeToAttribute( { model: 'rowspan', view: 'rowspan' } );
  33. // Since this part of test tests only view->model conversion editing pipeline is not necessary
  34. // so defining model->view converters won't be necessary.
  35. editor.editing.destroy();
  36. } );
  37. } );
  38. function expectModel( data ) {
  39. expect( formatTable( getModelData( model, { withoutSelection: true } ) ) ).to.equal( formatTable( data ) );
  40. }
  41. it( 'should convert table figure', () => {
  42. editor.setData(
  43. '<figure class="table">' +
  44. '<table>' +
  45. '<tr><td>1</td></tr>' +
  46. '</table>' +
  47. '</figure>'
  48. );
  49. expectModel(
  50. '<table>' +
  51. '<tableRow><tableCell><paragraph>1</paragraph></tableCell></tableRow>' +
  52. '</table>'
  53. );
  54. } );
  55. it( 'should create table model from table without thead', () => {
  56. editor.setData(
  57. '<table>' +
  58. '<tr><td>1</td></tr>' +
  59. '</table>'
  60. );
  61. expectModel(
  62. '<table>' +
  63. '<tableRow><tableCell><paragraph>1</paragraph></tableCell></tableRow>' +
  64. '</table>'
  65. );
  66. } );
  67. it( 'should not convert empty figure', () => {
  68. '<figure class="table"></figure>';
  69. expectModel( '<paragraph></paragraph>' );
  70. } );
  71. it( 'should convert if figure do not have class="table" attribute', () => {
  72. editor.setData(
  73. '<figure>' +
  74. '<table>' +
  75. '<tr><td>1</td></tr>' +
  76. '</table>' +
  77. '</figure>'
  78. );
  79. expectModel(
  80. '<table>' +
  81. '<tableRow><tableCell><paragraph>1</paragraph></tableCell></tableRow>' +
  82. '</table>'
  83. );
  84. } );
  85. it( 'should create table model from table with one thead with one row', () => {
  86. editor.setData(
  87. '<table>' +
  88. '<thead><tr><td>1</td></tr></thead>' +
  89. '</table>'
  90. );
  91. expectModel(
  92. '<table headingRows="1">' +
  93. '<tableRow><tableCell><paragraph>1</paragraph></tableCell></tableRow>' +
  94. '</table>'
  95. );
  96. } );
  97. it( 'should create table model from table with one thead with more then one row', () => {
  98. editor.setData(
  99. '<table>' +
  100. '<thead>' +
  101. '<tr><td>1</td></tr>' +
  102. '<tr><td>2</td></tr>' +
  103. '<tr><td>3</td></tr>' +
  104. '</thead>' +
  105. '</table>'
  106. );
  107. expectModel(
  108. '<table headingRows="3">' +
  109. '<tableRow><tableCell><paragraph>1</paragraph></tableCell></tableRow>' +
  110. '<tableRow><tableCell><paragraph>2</paragraph></tableCell></tableRow>' +
  111. '<tableRow><tableCell><paragraph>3</paragraph></tableCell></tableRow>' +
  112. '</table>'
  113. );
  114. } );
  115. it( 'should create table model from table with two theads with one row', () => {
  116. editor.setData(
  117. '<table>' +
  118. '<thead><tr><td>1</td></tr></thead>' +
  119. '<tbody><tr><td>2</td></tr></tbody>' +
  120. '<thead><tr><td>3</td></tr></thead>' +
  121. '</table>'
  122. );
  123. expectModel(
  124. '<table headingRows="1">' +
  125. '<tableRow><tableCell><paragraph>1</paragraph></tableCell></tableRow>' +
  126. '<tableRow><tableCell><paragraph>2</paragraph></tableCell></tableRow>' +
  127. '<tableRow><tableCell><paragraph>3</paragraph></tableCell></tableRow>' +
  128. '</table>'
  129. );
  130. } );
  131. it( 'should create table model from table with thead after the tbody', () => {
  132. editor.setData(
  133. '<table>' +
  134. '<tbody><tr><td>2</td></tr></tbody>' +
  135. '<thead><tr><td>1</td></tr></thead>' +
  136. '</table>'
  137. );
  138. expectModel(
  139. '<table headingRows="1">' +
  140. '<tableRow><tableCell><paragraph>1</paragraph></tableCell></tableRow>' +
  141. '<tableRow><tableCell><paragraph>2</paragraph></tableCell></tableRow>' +
  142. '</table>'
  143. );
  144. } );
  145. it( 'should create table model from table with one tfoot with one row', () => {
  146. editor.setData(
  147. '<table>' +
  148. '<tfoot><tr><td>1</td></tr></tfoot>' +
  149. '</table>'
  150. );
  151. expectModel(
  152. '<table>' +
  153. '<tableRow><tableCell><paragraph>1</paragraph></tableCell></tableRow>' +
  154. '</table>'
  155. );
  156. } );
  157. it( 'should create valid table model from empty table', () => {
  158. editor.setData(
  159. '<table>' +
  160. '</table>'
  161. );
  162. expectModel(
  163. '<table><tableRow><tableCell><paragraph></paragraph></tableCell></tableRow></table>'
  164. );
  165. } );
  166. it( 'should skip unknown table children', () => {
  167. editor.setData(
  168. '<table>' +
  169. '<caption>foo</caption>' +
  170. '<tr><td>bar</td></tr>' +
  171. '</table>'
  172. );
  173. expectModel(
  174. '<table><tableRow><tableCell><paragraph>bar</paragraph></tableCell></tableRow></table>'
  175. );
  176. } );
  177. it( 'should create table model from some broken table', () => {
  178. editor.setData(
  179. '<table><td><z>foo</z></td></table>'
  180. );
  181. expectModel(
  182. '<table><tableRow><tableCell><paragraph>foo</paragraph></tableCell></tableRow></table>'
  183. );
  184. } );
  185. it( 'should fix if inside other blocks', () => {
  186. // Using <div> instead of <p> as it breaks on Edge.
  187. editor.model.schema.register( 'div', {
  188. inheritAllFrom: '$block'
  189. } );
  190. editor.conversion.for( 'upcast' ).add( upcastElementToElement( { model: 'div', view: 'div' } ) );
  191. editor.setData(
  192. '<div>foo' +
  193. '<table>' +
  194. '<tbody><tr><td>2</td></tr></tbody>' +
  195. '<thead><tr><td>1</td></tr></thead>' +
  196. '</table>' +
  197. 'bar</div>'
  198. );
  199. expectModel(
  200. '<div>foo</div>' +
  201. '<table headingRows="1">' +
  202. '<tableRow><tableCell><paragraph>1</paragraph></tableCell></tableRow>' +
  203. '<tableRow><tableCell><paragraph>2</paragraph></tableCell></tableRow>' +
  204. '</table>' +
  205. '<div>bar</div>'
  206. );
  207. } );
  208. it( 'should be possible to overwrite table conversion', () => {
  209. editor.model.schema.register( 'fooTable', {
  210. allowWhere: '$block',
  211. allowAttributes: [ 'headingRows' ],
  212. isObject: true
  213. } );
  214. editor.model.schema.register( 'fooCell', {
  215. allowIn: 'fooRow',
  216. isObject: true
  217. } );
  218. editor.model.schema.register( 'fooRow', {
  219. allowIn: 'fooTable',
  220. isObject: true
  221. } );
  222. editor.conversion.elementToElement( { model: 'fooTable', view: 'table', converterPriority: 'high' } );
  223. editor.conversion.elementToElement( { model: 'fooRow', view: 'tr', converterPriority: 'high' } );
  224. editor.conversion.elementToElement( { model: 'fooCell', view: 'td', converterPriority: 'high' } );
  225. editor.conversion.elementToElement( { model: 'fooCell', view: 'th', converterPriority: 'high' } );
  226. editor.setData(
  227. '<table>' +
  228. '<thead><tr><td>foo</td></tr></thead>' +
  229. '</table>'
  230. );
  231. expectModel(
  232. '<fooTable><fooRow><fooCell></fooCell></fooRow></fooTable>'
  233. );
  234. } );
  235. it( 'should strip table in table', () => {
  236. editor.setData(
  237. '<table>' +
  238. '<tr>' +
  239. '<td>' +
  240. '<table>' +
  241. '<tr>' +
  242. '<td>tableception</td>' +
  243. '</tr>' +
  244. '</table>' +
  245. '</td>' +
  246. '</tr>' +
  247. '</table>'
  248. );
  249. expectModel(
  250. '<table>' +
  251. '<tableRow>' +
  252. '<tableCell>' +
  253. '<paragraph>tableception</paragraph>' +
  254. '</tableCell>' +
  255. '</tableRow>' +
  256. '</table>'
  257. );
  258. } );
  259. describe( 'headingColumns', () => {
  260. it( 'should properly calculate heading columns', () => {
  261. editor.setData(
  262. '<table>' +
  263. '<tbody>' +
  264. // This row starts with 1 th (3 total).
  265. '<tr><th>21</th><td>22</td><th>23</th><th>24</th></tr>' +
  266. // This row starts with 2 th (2 total). This one has max number of heading columns: 2.
  267. '<tr><th>31</th><th>32</th><td>33</td><td>34</td></tr>' +
  268. // This row starts with 1 th (1 total).
  269. '<tr><th>41</th><td>42</td><td>43</td><td>44</td></tr>' +
  270. // This row starts with 0 th (3 total).
  271. '<tr><td>51</td><th>52</th><th>53</th><th>54</th></tr>' +
  272. '</tbody>' +
  273. '<thead>' +
  274. // This row has 4 ths but it is a thead.
  275. '<tr><th>11</th><th>12</th><th>13</th><th>14</th></tr>' +
  276. '</thead>' +
  277. '</table>'
  278. );
  279. expectModel(
  280. '<table headingColumns="2" headingRows="1">' +
  281. '<tableRow>' +
  282. '<tableCell><paragraph>11</paragraph></tableCell>' +
  283. '<tableCell><paragraph>12</paragraph></tableCell>' +
  284. '<tableCell><paragraph>13</paragraph></tableCell>' +
  285. '<tableCell><paragraph>14</paragraph></tableCell>' +
  286. '</tableRow>' +
  287. '<tableRow>' +
  288. '<tableCell><paragraph>21</paragraph></tableCell>' +
  289. '<tableCell><paragraph>22</paragraph></tableCell>' +
  290. '<tableCell><paragraph>23</paragraph></tableCell>' +
  291. '<tableCell><paragraph>24</paragraph></tableCell>' +
  292. '</tableRow>' +
  293. '<tableRow>' +
  294. '<tableCell><paragraph>31</paragraph></tableCell>' +
  295. '<tableCell><paragraph>32</paragraph></tableCell>' +
  296. '<tableCell><paragraph>33</paragraph></tableCell>' +
  297. '<tableCell><paragraph>34</paragraph></tableCell>' +
  298. '</tableRow>' +
  299. '<tableRow>' +
  300. '<tableCell><paragraph>41</paragraph></tableCell>' +
  301. '<tableCell><paragraph>42</paragraph></tableCell>' +
  302. '<tableCell><paragraph>43</paragraph></tableCell>' +
  303. '<tableCell><paragraph>44</paragraph></tableCell>' +
  304. '</tableRow>' +
  305. '<tableRow>' +
  306. '<tableCell><paragraph>51</paragraph></tableCell>' +
  307. '<tableCell><paragraph>52</paragraph></tableCell>' +
  308. '<tableCell><paragraph>53</paragraph></tableCell>' +
  309. '<tableCell><paragraph>54</paragraph></tableCell>' +
  310. '</tableRow>' +
  311. '</table>'
  312. );
  313. } );
  314. it( 'should calculate heading columns of cells with colspan', () => {
  315. editor.setData(
  316. '<table>' +
  317. '<tbody>' +
  318. // This row has colspan of 3 so it should be the whole table should have 3 heading columns.
  319. '<tr><th>21</th><th>22</th><td>23</td><td>24</td></tr>' +
  320. '<tr><th colspan="2">31</th><th>33</th><td>34</td></tr>' +
  321. '</tbody>' +
  322. '<thead>' +
  323. // This row has 4 ths but it is a thead.
  324. '<tr><th>11</th><th>12</th><th>13</th><th>14</th></tr>' +
  325. '</thead>' +
  326. '</table>'
  327. );
  328. expectModel(
  329. '<table headingColumns="3" headingRows="1">' +
  330. '<tableRow>' +
  331. '<tableCell><paragraph>11</paragraph></tableCell>' +
  332. '<tableCell><paragraph>12</paragraph></tableCell>' +
  333. '<tableCell><paragraph>13</paragraph></tableCell>' +
  334. '<tableCell><paragraph>14</paragraph></tableCell>' +
  335. '</tableRow>' +
  336. '<tableRow>' +
  337. '<tableCell><paragraph>21</paragraph></tableCell>' +
  338. '<tableCell><paragraph>22</paragraph></tableCell>' +
  339. '<tableCell><paragraph>23</paragraph></tableCell>' +
  340. '<tableCell><paragraph>24</paragraph></tableCell>' +
  341. '</tableRow>' +
  342. '<tableRow>' +
  343. '<tableCell colspan="2"><paragraph>31</paragraph></tableCell>' +
  344. '<tableCell><paragraph>33</paragraph></tableCell>' +
  345. '<tableCell><paragraph>34</paragraph></tableCell>' +
  346. '</tableRow>' +
  347. '</table>'
  348. );
  349. } );
  350. } );
  351. } );