upcasttable.js 16 KB

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