upcasttable.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  1. /**
  2. * @license Copyright (c) 2003-2019, 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. // Using <div> instead of <p> as it breaks on Edge.
  177. editor.model.schema.register( 'div', {
  178. inheritAllFrom: '$block'
  179. } );
  180. editor.conversion.for( 'upcast' ).elementToElement( { model: 'div', view: 'div' } );
  181. editor.setData(
  182. '<div>foo' +
  183. '<table>' +
  184. '<tbody><tr><td>2</td></tr></tbody>' +
  185. '<thead><tr><td>1</td></tr></thead>' +
  186. '</table>' +
  187. 'bar</div>'
  188. );
  189. expectModel(
  190. '<div>foo</div>' +
  191. '<table headingRows="1">' +
  192. '<tableRow><tableCell><paragraph>1</paragraph></tableCell></tableRow>' +
  193. '<tableRow><tableCell><paragraph>2</paragraph></tableCell></tableRow>' +
  194. '</table>' +
  195. '<div>bar</div>'
  196. );
  197. } );
  198. it( 'should be possible to overwrite table conversion', () => {
  199. editor.model.schema.register( 'fooTable', {
  200. allowWhere: '$block',
  201. allowAttributes: [ 'headingRows' ],
  202. isObject: true
  203. } );
  204. editor.model.schema.register( 'fooCell', {
  205. allowIn: 'fooRow',
  206. isObject: true
  207. } );
  208. editor.model.schema.register( 'fooRow', {
  209. allowIn: 'fooTable',
  210. isObject: true
  211. } );
  212. editor.conversion.elementToElement( { model: 'fooTable', view: 'table', converterPriority: 'high' } );
  213. editor.conversion.elementToElement( { model: 'fooRow', view: 'tr', converterPriority: 'high' } );
  214. editor.conversion.elementToElement( { model: 'fooCell', view: 'td', converterPriority: 'high' } );
  215. editor.conversion.elementToElement( { model: 'fooCell', view: 'th', converterPriority: 'high' } );
  216. editor.setData(
  217. '<table>' +
  218. '<thead><tr><td>foo</td></tr></thead>' +
  219. '</table>'
  220. );
  221. expectModel(
  222. '<fooTable><fooRow><fooCell></fooCell></fooRow></fooTable>'
  223. );
  224. } );
  225. it( 'should strip table in table', () => {
  226. editor.setData(
  227. '<table>' +
  228. '<tr>' +
  229. '<td>' +
  230. '<table>' +
  231. '<tr>' +
  232. '<td>tableception</td>' +
  233. '</tr>' +
  234. '</table>' +
  235. '</td>' +
  236. '</tr>' +
  237. '</table>'
  238. );
  239. expectModel(
  240. '<table>' +
  241. '<tableRow>' +
  242. '<tableCell>' +
  243. '<paragraph>tableception</paragraph>' +
  244. '</tableCell>' +
  245. '</tableRow>' +
  246. '</table>'
  247. );
  248. } );
  249. describe( 'headingColumns', () => {
  250. it( 'should properly calculate heading columns', () => {
  251. editor.setData(
  252. '<table>' +
  253. '<tbody>' +
  254. // This row starts with 1 th (3 total).
  255. '<tr><th>21</th><td>22</td><th>23</th><th>24</th></tr>' +
  256. // This row starts with 2 th (2 total). This one has max number of heading columns: 2.
  257. '<tr><th>31</th><th>32</th><td>33</td><td>34</td></tr>' +
  258. // This row starts with 1 th (1 total).
  259. '<tr><th>41</th><td>42</td><td>43</td><td>44</td></tr>' +
  260. // This row starts with 0 th (3 total).
  261. '<tr><td>51</td><th>52</th><th>53</th><th>54</th></tr>' +
  262. '</tbody>' +
  263. '<thead>' +
  264. // This row has 4 ths but it is a thead.
  265. '<tr><th>11</th><th>12</th><th>13</th><th>14</th></tr>' +
  266. '</thead>' +
  267. '</table>'
  268. );
  269. expectModel(
  270. '<table headingColumns="2" headingRows="1">' +
  271. '<tableRow>' +
  272. '<tableCell><paragraph>11</paragraph></tableCell>' +
  273. '<tableCell><paragraph>12</paragraph></tableCell>' +
  274. '<tableCell><paragraph>13</paragraph></tableCell>' +
  275. '<tableCell><paragraph>14</paragraph></tableCell>' +
  276. '</tableRow>' +
  277. '<tableRow>' +
  278. '<tableCell><paragraph>21</paragraph></tableCell>' +
  279. '<tableCell><paragraph>22</paragraph></tableCell>' +
  280. '<tableCell><paragraph>23</paragraph></tableCell>' +
  281. '<tableCell><paragraph>24</paragraph></tableCell>' +
  282. '</tableRow>' +
  283. '<tableRow>' +
  284. '<tableCell><paragraph>31</paragraph></tableCell>' +
  285. '<tableCell><paragraph>32</paragraph></tableCell>' +
  286. '<tableCell><paragraph>33</paragraph></tableCell>' +
  287. '<tableCell><paragraph>34</paragraph></tableCell>' +
  288. '</tableRow>' +
  289. '<tableRow>' +
  290. '<tableCell><paragraph>41</paragraph></tableCell>' +
  291. '<tableCell><paragraph>42</paragraph></tableCell>' +
  292. '<tableCell><paragraph>43</paragraph></tableCell>' +
  293. '<tableCell><paragraph>44</paragraph></tableCell>' +
  294. '</tableRow>' +
  295. '<tableRow>' +
  296. '<tableCell><paragraph>51</paragraph></tableCell>' +
  297. '<tableCell><paragraph>52</paragraph></tableCell>' +
  298. '<tableCell><paragraph>53</paragraph></tableCell>' +
  299. '<tableCell><paragraph>54</paragraph></tableCell>' +
  300. '</tableRow>' +
  301. '</table>'
  302. );
  303. } );
  304. it( 'should calculate heading columns of cells with colspan', () => {
  305. editor.setData(
  306. '<table>' +
  307. '<tbody>' +
  308. // This row has colspan of 3 so it should be the whole table should have 3 heading columns.
  309. '<tr><th>21</th><th>22</th><td>23</td><td>24</td></tr>' +
  310. '<tr><th colspan="2">31</th><th>33</th><td>34</td></tr>' +
  311. '</tbody>' +
  312. '<thead>' +
  313. // This row has 4 ths but it is a thead.
  314. '<tr><th>11</th><th>12</th><th>13</th><th>14</th></tr>' +
  315. '</thead>' +
  316. '</table>'
  317. );
  318. expectModel(
  319. '<table headingColumns="3" headingRows="1">' +
  320. '<tableRow>' +
  321. '<tableCell><paragraph>11</paragraph></tableCell>' +
  322. '<tableCell><paragraph>12</paragraph></tableCell>' +
  323. '<tableCell><paragraph>13</paragraph></tableCell>' +
  324. '<tableCell><paragraph>14</paragraph></tableCell>' +
  325. '</tableRow>' +
  326. '<tableRow>' +
  327. '<tableCell><paragraph>21</paragraph></tableCell>' +
  328. '<tableCell><paragraph>22</paragraph></tableCell>' +
  329. '<tableCell><paragraph>23</paragraph></tableCell>' +
  330. '<tableCell><paragraph>24</paragraph></tableCell>' +
  331. '</tableRow>' +
  332. '<tableRow>' +
  333. '<tableCell colspan="2"><paragraph>31</paragraph></tableCell>' +
  334. '<tableCell><paragraph>33</paragraph></tableCell>' +
  335. '<tableCell><paragraph>34</paragraph></tableCell>' +
  336. '</tableRow>' +
  337. '</table>'
  338. );
  339. } );
  340. } );
  341. describe( 'block contents', () => {
  342. it( 'should upcast table with empty table cell to paragraph', () => {
  343. editor.setData(
  344. '<table>' +
  345. '<tbody>' +
  346. '<tr>' +
  347. '<td>foo</td>' +
  348. '</tr>' +
  349. '</tbody>' +
  350. '</table>'
  351. );
  352. expectModel( modelTable( [
  353. [ 'foo' ]
  354. ] ) );
  355. } );
  356. it( 'should upcast table with <p> in table cell', () => {
  357. editor.setData(
  358. '<table>' +
  359. '<tbody>' +
  360. '<tr>' +
  361. '<td><p>foo</p></td>' +
  362. '</tr>' +
  363. '</tbody>' +
  364. '</table>'
  365. );
  366. expectModel( modelTable( [
  367. [ 'foo' ]
  368. ] ) );
  369. } );
  370. it( 'should upcast table inline content to single <paragraph>', () => {
  371. editor.model.schema.extend( '$text', { allowAttributes: 'bold' } );
  372. editor.conversion.attributeToElement( { model: 'bold', view: 'strong' } );
  373. editor.setData(
  374. '<table>' +
  375. '<tbody>' +
  376. '<tr>' +
  377. '<td>foo <strong>bar</strong></td>' +
  378. '</tr>' +
  379. '</tbody>' +
  380. '</table>'
  381. );
  382. expectModel( modelTable( [
  383. [ 'foo <$text bold="true">bar</$text>' ]
  384. ] ) );
  385. } );
  386. it( 'should upcast table with multiple <p> in table cell', () => {
  387. editor.setData(
  388. '<table>' +
  389. '<tbody>' +
  390. '<tr>' +
  391. '<td>' +
  392. '<p>foo</p>' +
  393. '<p>bar</p>' +
  394. '<p>baz</p>' +
  395. '</td>' +
  396. '</tr>' +
  397. '</tbody>' +
  398. '</table>'
  399. );
  400. expectModel( modelTable( [
  401. [ '<paragraph>foo</paragraph><paragraph>bar</paragraph><paragraph>baz</paragraph>' ]
  402. ] ) );
  403. } );
  404. it( 'should upcast table with <img> in table cell', () => {
  405. editor.setData(
  406. '<table>' +
  407. '<tbody>' +
  408. '<tr>' +
  409. '<td><img src="sample.png"></td>' +
  410. '</tr>' +
  411. '</tbody>' +
  412. '</table>'
  413. );
  414. expectModel( modelTable( [
  415. [ '<image src="sample.png"></image>' ]
  416. ] ) );
  417. } );
  418. } );
  419. describe( 'handling redundant whitespacing between table elements', () => {
  420. it( 'table without thead/tbody/tfoot', () => {
  421. editor.setData(
  422. '<table> ' +
  423. '<tr> <td>1</td></tr>' +
  424. '</table>'
  425. );
  426. expectModel(
  427. '<table>' +
  428. '<tableRow><tableCell><paragraph>1</paragraph></tableCell></tableRow>' +
  429. '</table>'
  430. );
  431. } );
  432. it( 'table with thead only', () => {
  433. editor.setData(
  434. '<table>' +
  435. '<thead>' +
  436. '<tr><td>1</td></tr> ' +
  437. '<tr><td>2</td></tr>' +
  438. ' <tr><td>3</td></tr>' +
  439. '</thead>' +
  440. '</table>'
  441. );
  442. expectModel(
  443. '<table headingRows="3">' +
  444. '<tableRow><tableCell><paragraph>1</paragraph></tableCell></tableRow>' +
  445. '<tableRow><tableCell><paragraph>2</paragraph></tableCell></tableRow>' +
  446. '<tableRow><tableCell><paragraph>3</paragraph></tableCell></tableRow>' +
  447. '</table>'
  448. );
  449. } );
  450. it( 'table with thead and tbody', () => {
  451. editor.setData(
  452. '<table>' +
  453. '<thead> ' +
  454. '<tr><td>1</td></tr>' +
  455. '<tr><td>2</td></tr>\n\n ' +
  456. '</thead>' +
  457. '<tbody>' +
  458. '<tr><td>3</td></tr> ' +
  459. '\n\n <tr><td>4</td></tr>' +
  460. '<tr> <td>5</td></tr> ' +
  461. '</tbody>' +
  462. '</table>'
  463. );
  464. expectModel(
  465. '<table headingRows="2">' +
  466. '<tableRow><tableCell><paragraph>1</paragraph></tableCell></tableRow>' +
  467. '<tableRow><tableCell><paragraph>2</paragraph></tableCell></tableRow>' +
  468. '<tableRow><tableCell><paragraph>3</paragraph></tableCell></tableRow>' +
  469. '<tableRow><tableCell><paragraph>4</paragraph></tableCell></tableRow>' +
  470. '<tableRow><tableCell><paragraph>5</paragraph></tableCell></tableRow>' +
  471. '</table>'
  472. );
  473. } );
  474. it( 'table with tbody and tfoot', () => {
  475. editor.setData(
  476. '<table>' +
  477. '<tbody>' +
  478. ' <tr><td>1</td></tr>' +
  479. '<tr><td>2</td></tr>' +
  480. '<tr><td>3</td></tr> ' +
  481. '<tr><td>4</td></tr>\n\n\n' +
  482. '</tbody>\n\n' +
  483. ' <tfoot>' +
  484. '<tr> <td>5</td>\n\n\n</tr> ' +
  485. '</tfoot>' +
  486. '</table>'
  487. );
  488. expectModel(
  489. '<table>' +
  490. '<tableRow><tableCell><paragraph>1</paragraph></tableCell></tableRow>' +
  491. '<tableRow><tableCell><paragraph>2</paragraph></tableCell></tableRow>' +
  492. '<tableRow><tableCell><paragraph>3</paragraph></tableCell></tableRow>' +
  493. '<tableRow><tableCell><paragraph>4</paragraph></tableCell></tableRow>' +
  494. '<tableRow><tableCell><paragraph>5</paragraph></tableCell></tableRow>' +
  495. '</table>'
  496. );
  497. } );
  498. } );
  499. } );