8
0

upcasttable.js 16 KB

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