8
0

upcasttable.js 15 KB

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