upcasttable.js 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  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 from '../../src/converters/upcasttable';
  9. describe( 'upcastTable()', () => {
  10. let editor, model;
  11. beforeEach( () => {
  12. return VirtualTestEditor.create()
  13. .then( newEditor => {
  14. editor = newEditor;
  15. model = editor.model;
  16. const conversion = editor.conversion;
  17. const schema = model.schema;
  18. schema.register( 'table', {
  19. allowWhere: '$block',
  20. allowAttributes: [ 'headingRows', 'headingColumns' ],
  21. isObject: true
  22. } );
  23. schema.register( 'tableRow', { allowIn: 'table' } );
  24. schema.register( 'tableCell', {
  25. allowIn: 'tableRow',
  26. allowContentOf: '$block',
  27. allowAttributes: [ 'colspan', 'rowspan' ],
  28. isLimit: true
  29. } );
  30. conversion.for( 'upcast' ).add( upcastTable() );
  31. // Table row upcast only since downcast conversion is done in `downcastTable()`.
  32. conversion.for( 'upcast' ).add( upcastElementToElement( { model: 'tableRow', view: 'tr' } ) );
  33. // Table cell conversion.
  34. conversion.for( 'upcast' ).add( upcastElementToElement( { model: 'tableCell', view: 'td' } ) );
  35. conversion.for( 'upcast' ).add( upcastElementToElement( { model: 'tableCell', view: 'th' } ) );
  36. conversion.attributeToAttribute( { model: 'colspan', view: 'colspan' } );
  37. conversion.attributeToAttribute( { model: 'rowspan', view: 'rowspan' } );
  38. // Since this part of test tests only view->model conversion editing pipeline is not necessary
  39. // so defining model->view converters won't be necessary.
  40. editor.editing.destroy();
  41. } );
  42. } );
  43. function expectModel( data ) {
  44. expect( getModelData( model, { withoutSelection: true } ) ).to.equal( data );
  45. }
  46. it( 'should create table model from table without thead', () => {
  47. editor.setData(
  48. '<table>' +
  49. '<tr><td>1</td></tr>' +
  50. '</table>'
  51. );
  52. expectModel(
  53. '<table>' +
  54. '<tableRow><tableCell>1</tableCell></tableRow>' +
  55. '</table>'
  56. );
  57. } );
  58. it( 'should create table model from table with one thead with one row', () => {
  59. editor.setData(
  60. '<table>' +
  61. '<thead><tr><td>1</td></tr></thead>' +
  62. '</table>'
  63. );
  64. expectModel(
  65. '<table headingRows="1">' +
  66. '<tableRow><tableCell>1</tableCell></tableRow>' +
  67. '</table>'
  68. );
  69. } );
  70. it( 'should create table model from table with one thead with more then on row', () => {
  71. editor.setData(
  72. '<table>' +
  73. '<thead>' +
  74. '<tr><td>1</td></tr>' +
  75. '<tr><td>2</td></tr>' +
  76. '<tr><td>3</td></tr>' +
  77. '</thead>' +
  78. '</table>'
  79. );
  80. expectModel(
  81. '<table headingRows="3">' +
  82. '<tableRow><tableCell>1</tableCell></tableRow>' +
  83. '<tableRow><tableCell>2</tableCell></tableRow>' +
  84. '<tableRow><tableCell>3</tableCell></tableRow>' +
  85. '</table>'
  86. );
  87. } );
  88. it( 'should create table model from table with two theads with one row', () => {
  89. editor.setData(
  90. '<table>' +
  91. '<thead><tr><td>1</td></tr></thead>' +
  92. '<tbody><tr><td>2</td></tr></tbody>' +
  93. '<thead><tr><td>3</td></tr></thead>' +
  94. '</table>'
  95. );
  96. expectModel(
  97. '<table headingRows="1">' +
  98. '<tableRow><tableCell>1</tableCell></tableRow>' +
  99. '<tableRow><tableCell>2</tableCell></tableRow>' +
  100. '<tableRow><tableCell>3</tableCell></tableRow>' +
  101. '</table>'
  102. );
  103. } );
  104. it( 'should create table model from table with thead after the tbody', () => {
  105. editor.setData(
  106. '<table>' +
  107. '<tbody><tr><td>2</td></tr></tbody>' +
  108. '<thead><tr><td>1</td></tr></thead>' +
  109. '</table>'
  110. );
  111. expectModel(
  112. '<table headingRows="1">' +
  113. '<tableRow><tableCell>1</tableCell></tableRow>' +
  114. '<tableRow><tableCell>2</tableCell></tableRow>' +
  115. '</table>'
  116. );
  117. } );
  118. it( 'should create table model from table with one tfoot with one row', () => {
  119. editor.setData(
  120. '<table>' +
  121. '<tfoot><tr><td>1</td></tr></tfoot>' +
  122. '</table>'
  123. );
  124. expectModel(
  125. '<table>' +
  126. '<tableRow><tableCell>1</tableCell></tableRow>' +
  127. '</table>'
  128. );
  129. } );
  130. it( 'should create valid table model from empty table', () => {
  131. editor.setData(
  132. '<table>' +
  133. '</table>'
  134. );
  135. expectModel(
  136. '<table><tableRow><tableCell></tableCell></tableRow></table>'
  137. );
  138. } );
  139. it( 'should skip unknown table children', () => {
  140. editor.setData(
  141. '<table>' +
  142. '<caption>foo</caption>' +
  143. '<tr><td>bar</td></tr>' +
  144. '</table>'
  145. );
  146. expectModel(
  147. '<table><tableRow><tableCell>bar</tableCell></tableRow></table>'
  148. );
  149. } );
  150. it( 'should create table model from some broken table', () => {
  151. editor.setData(
  152. '<table><td><p>foo</p></td></table>'
  153. );
  154. expectModel(
  155. '<table><tableRow><tableCell>foo</tableCell></tableRow></table>'
  156. );
  157. } );
  158. it( 'should fix if inside other blocks', () => {
  159. // Using <div> instead of <p> as it breaks on Edge.
  160. editor.model.schema.register( 'div', {
  161. inheritAllFrom: '$block'
  162. } );
  163. editor.conversion.for( 'upcast' ).add( upcastElementToElement( { model: 'div', view: 'div' } ) );
  164. editor.setData(
  165. '<div>foo' +
  166. '<table>' +
  167. '<tbody><tr><td>2</td></tr></tbody>' +
  168. '<thead><tr><td>1</td></tr></thead>' +
  169. '</table>' +
  170. 'bar</div>'
  171. );
  172. expectModel(
  173. '<div>foo</div>' +
  174. '<table headingRows="1">' +
  175. '<tableRow><tableCell>1</tableCell></tableRow>' +
  176. '<tableRow><tableCell>2</tableCell></tableRow>' +
  177. '</table>' +
  178. '<div>bar</div>'
  179. );
  180. } );
  181. it( 'should be possible to overwrite table conversion', () => {
  182. editor.model.schema.register( 'fooTable', {
  183. allowWhere: '$block',
  184. allowAttributes: [ 'headingRows' ],
  185. isObject: true
  186. } );
  187. editor.conversion.elementToElement( { model: 'fooTable', view: 'table', converterPriority: 'high' } );
  188. editor.setData(
  189. '<table>' +
  190. '<thead><tr><td>foo</td></tr></thead>' +
  191. '</table>'
  192. );
  193. expectModel(
  194. '<fooTable></fooTable>'
  195. );
  196. } );
  197. describe( 'headingColumns', () => {
  198. it( 'should properly calculate heading columns', () => {
  199. editor.setData(
  200. '<table>' +
  201. '<tbody>' +
  202. // This row starts with 1 th (3 total).
  203. '<tr><th>21</th><td>22</td><th>23</th><th>24</th></tr>' +
  204. // This row starts with 2 th (2 total). This one has max number of heading columns: 2.
  205. '<tr><th>31</th><th>32</th><td>33</td><td>34</td></tr>' +
  206. // This row starts with 1 th (1 total).
  207. '<tr><th>41</th><td>42</td><td>43</td><td>44</td></tr>' +
  208. // This row starts with 0 th (3 total).
  209. '<tr><td>51</td><th>52</th><th>53</th><th>54</th></tr>' +
  210. '</tbody>' +
  211. '<thead>' +
  212. // This row has 4 ths but it is a thead.
  213. '<tr><th>11</th><th>12</th><th>13</th><th>14</th></tr>' +
  214. '</thead>' +
  215. '</table>'
  216. );
  217. expectModel(
  218. '<table headingColumns="2" headingRows="1">' +
  219. '<tableRow>' +
  220. '<tableCell>11</tableCell><tableCell>12</tableCell><tableCell>13</tableCell><tableCell>14</tableCell>' +
  221. '</tableRow>' +
  222. '<tableRow>' +
  223. '<tableCell>21</tableCell><tableCell>22</tableCell><tableCell>23</tableCell><tableCell>24</tableCell>' +
  224. '</tableRow>' +
  225. '<tableRow>' +
  226. '<tableCell>31</tableCell><tableCell>32</tableCell><tableCell>33</tableCell><tableCell>34</tableCell>' +
  227. '</tableRow>' +
  228. '<tableRow>' +
  229. '<tableCell>41</tableCell><tableCell>42</tableCell><tableCell>43</tableCell><tableCell>44</tableCell>' +
  230. '</tableRow>' +
  231. '<tableRow>' +
  232. '<tableCell>51</tableCell><tableCell>52</tableCell><tableCell>53</tableCell><tableCell>54</tableCell>' +
  233. '</tableRow>' +
  234. '</table>'
  235. );
  236. } );
  237. it( 'should calculate heading columns of cells with colspan', () => {
  238. editor.setData(
  239. '<table>' +
  240. '<tbody>' +
  241. // This row has colspan of 3 so it should be the whole table should have 3 heading columns.
  242. '<tr><th>21</th><th>22</th><td>23</td><td>24</td></tr>' +
  243. '<tr><th colspan="2">31</th><th>33</th><td>34</td></tr>' +
  244. '</tbody>' +
  245. '<thead>' +
  246. // This row has 4 ths but it is a thead.
  247. '<tr><th>11</th><th>12</th><th>13</th><th>14</th></tr>' +
  248. '</thead>' +
  249. '</table>'
  250. );
  251. expectModel(
  252. '<table headingColumns="3" headingRows="1">' +
  253. '<tableRow>' +
  254. '<tableCell>11</tableCell><tableCell>12</tableCell><tableCell>13</tableCell><tableCell>14</tableCell>' +
  255. '</tableRow>' +
  256. '<tableRow>' +
  257. '<tableCell>21</tableCell><tableCell>22</tableCell><tableCell>23</tableCell><tableCell>24</tableCell>' +
  258. '</tableRow>' +
  259. '<tableRow>' +
  260. '<tableCell colspan="2">31</tableCell><tableCell>33</tableCell><tableCell>34</tableCell>' +
  261. '</tableRow>' +
  262. '</table>'
  263. );
  264. } );
  265. } );
  266. } );