upcasttable.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  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 upcastTable, { upcastTableCell } from '../../src/converters/upcasttable';
  9. import { formatTable } from '../_utils/utils';
  10. import Paragraph from '../../../ckeditor5-paragraph/src/paragraph';
  11. describe( 'upcastTable()', () => {
  12. let editor, model;
  13. beforeEach( () => {
  14. return VirtualTestEditor
  15. .create( {
  16. plugins: [ Paragraph ]
  17. } )
  18. .then( newEditor => {
  19. editor = newEditor;
  20. model = editor.model;
  21. const conversion = editor.conversion;
  22. const schema = model.schema;
  23. schema.register( 'table', {
  24. allowWhere: '$block',
  25. allowAttributes: [ 'headingRows', 'headingColumns' ],
  26. isLimit: true,
  27. isObject: true
  28. } );
  29. schema.register( 'tableRow', {
  30. allowIn: 'table',
  31. isLimit: true
  32. } );
  33. schema.register( 'tableCell', {
  34. allowIn: 'tableRow',
  35. allowAttributes: [ 'colspan', 'rowspan' ],
  36. isLimit: true
  37. } );
  38. schema.extend( '$block', { allowIn: 'tableCell' } );
  39. conversion.for( 'upcast' ).add( upcastTable() );
  40. // Table row upcast only since downcast conversion is done in `downcastTable()`.
  41. conversion.for( 'upcast' ).add( upcastElementToElement( { model: 'tableRow', view: 'tr' } ) );
  42. // Table cell conversion.
  43. conversion.for( 'upcast' ).add( upcastTableCell( 'td' ) );
  44. conversion.for( 'upcast' ).add( upcastTableCell( 'th' ) );
  45. conversion.attributeToAttribute( { model: 'colspan', view: 'colspan' } );
  46. conversion.attributeToAttribute( { model: 'rowspan', view: 'rowspan' } );
  47. // Since this part of test tests only view->model conversion editing pipeline is not necessary
  48. // so defining model->view converters won't be necessary.
  49. editor.editing.destroy();
  50. } );
  51. } );
  52. function expectModel( data ) {
  53. expect( formatTable( getModelData( model, { withoutSelection: true } ) ) ).to.equal( formatTable( data ) );
  54. }
  55. it( 'should convert table figure', () => {
  56. editor.setData(
  57. '<figure class="table">' +
  58. '<table>' +
  59. '<tr><td>1</td></tr>' +
  60. '</table>' +
  61. '</figure>'
  62. );
  63. expectModel(
  64. '<table>' +
  65. '<tableRow><tableCell><paragraph>1</paragraph></tableCell></tableRow>' +
  66. '</table>'
  67. );
  68. } );
  69. it( 'should create table model from table without thead', () => {
  70. editor.setData(
  71. '<table>' +
  72. '<tr><td>1</td></tr>' +
  73. '</table>'
  74. );
  75. expectModel(
  76. '<table>' +
  77. '<tableRow><tableCell><paragraph>1</paragraph></tableCell></tableRow>' +
  78. '</table>'
  79. );
  80. } );
  81. it( 'should not convert empty figure', () => {
  82. '<figure class="table"></figure>';
  83. expectModel( '<paragraph></paragraph>' );
  84. } );
  85. it( 'should convert if figure do not have class="table" attribute', () => {
  86. editor.setData(
  87. '<figure>' +
  88. '<table>' +
  89. '<tr><td>1</td></tr>' +
  90. '</table>' +
  91. '</figure>'
  92. );
  93. expectModel(
  94. '<table>' +
  95. '<tableRow><tableCell><paragraph>1</paragraph></tableCell></tableRow>' +
  96. '</table>'
  97. );
  98. } );
  99. it( 'should create table model from table with one thead with one row', () => {
  100. editor.setData(
  101. '<table>' +
  102. '<thead><tr><td>1</td></tr></thead>' +
  103. '</table>'
  104. );
  105. expectModel(
  106. '<table headingRows="1">' +
  107. '<tableRow><tableCell><paragraph>1</paragraph></tableCell></tableRow>' +
  108. '</table>'
  109. );
  110. } );
  111. it( 'should create table model from table with one thead with more then one row', () => {
  112. editor.setData(
  113. '<table>' +
  114. '<thead>' +
  115. '<tr><td>1</td></tr>' +
  116. '<tr><td>2</td></tr>' +
  117. '<tr><td>3</td></tr>' +
  118. '</thead>' +
  119. '</table>'
  120. );
  121. expectModel(
  122. '<table headingRows="3">' +
  123. '<tableRow><tableCell><paragraph>1</paragraph></tableCell></tableRow>' +
  124. '<tableRow><tableCell><paragraph>2</paragraph></tableCell></tableRow>' +
  125. '<tableRow><tableCell><paragraph>3</paragraph></tableCell></tableRow>' +
  126. '</table>'
  127. );
  128. } );
  129. it( 'should create table model from table with two theads with one row', () => {
  130. editor.setData(
  131. '<table>' +
  132. '<thead><tr><td>1</td></tr></thead>' +
  133. '<tbody><tr><td>2</td></tr></tbody>' +
  134. '<thead><tr><td>3</td></tr></thead>' +
  135. '</table>'
  136. );
  137. expectModel(
  138. '<table headingRows="1">' +
  139. '<tableRow><tableCell><paragraph>1</paragraph></tableCell></tableRow>' +
  140. '<tableRow><tableCell><paragraph>2</paragraph></tableCell></tableRow>' +
  141. '<tableRow><tableCell><paragraph>3</paragraph></tableCell></tableRow>' +
  142. '</table>'
  143. );
  144. } );
  145. it( 'should create table model from table with thead after the tbody', () => {
  146. editor.setData(
  147. '<table>' +
  148. '<tbody><tr><td>2</td></tr></tbody>' +
  149. '<thead><tr><td>1</td></tr></thead>' +
  150. '</table>'
  151. );
  152. expectModel(
  153. '<table headingRows="1">' +
  154. '<tableRow><tableCell><paragraph>1</paragraph></tableCell></tableRow>' +
  155. '<tableRow><tableCell><paragraph>2</paragraph></tableCell></tableRow>' +
  156. '</table>'
  157. );
  158. } );
  159. it( 'should create table model from table with one tfoot with one row', () => {
  160. editor.setData(
  161. '<table>' +
  162. '<tfoot><tr><td>1</td></tr></tfoot>' +
  163. '</table>'
  164. );
  165. expectModel(
  166. '<table>' +
  167. '<tableRow><tableCell><paragraph>1</paragraph></tableCell></tableRow>' +
  168. '</table>'
  169. );
  170. } );
  171. it( 'should create valid table model from empty table', () => {
  172. editor.setData(
  173. '<table>' +
  174. '</table>'
  175. );
  176. expectModel(
  177. '<table><tableRow><tableCell><paragraph></paragraph></tableCell></tableRow></table>'
  178. );
  179. } );
  180. it( 'should skip unknown table children', () => {
  181. editor.setData(
  182. '<table>' +
  183. '<caption>foo</caption>' +
  184. '<tr><td>bar</td></tr>' +
  185. '</table>'
  186. );
  187. expectModel(
  188. '<table><tableRow><tableCell><paragraph>bar</paragraph></tableCell></tableRow></table>'
  189. );
  190. } );
  191. it( 'should create table model from some broken table', () => {
  192. editor.setData(
  193. '<table><td><z>foo</z></td></table>'
  194. );
  195. expectModel(
  196. '<table><tableRow><tableCell><paragraph>foo</paragraph></tableCell></tableRow></table>'
  197. );
  198. } );
  199. it( 'should fix if inside other blocks', () => {
  200. // Using <div> instead of <p> as it breaks on Edge.
  201. editor.model.schema.register( 'div', {
  202. inheritAllFrom: '$block'
  203. } );
  204. editor.conversion.for( 'upcast' ).add( upcastElementToElement( { 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. 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. } );