upcasttable.js 12 KB

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