convert_inline_tests.js 51 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072
  1. /** @odoo-module **/
  2. import convertInline from '@web_editor/js/backend/convert_inline';
  3. import {getGridHtml, getTableHtml, getRegularGridHtml, getRegularTableHtml, getTdHtml, removeComments} from 'web_editor.test_utils';
  4. const TEST_WIDTH = 800;
  5. const TEST_HEIGHT = 600;
  6. QUnit.module('web_editor', {}, function () {
  7. QUnit.module('convert_inline', {}, function () {
  8. QUnit.module('Convert Bootstrap grids to tables', {
  9. beforeEach: function (assert) {
  10. this.editable = document.createElement('div');
  11. this.editable.style.setProperty('width', TEST_WIDTH + 'px');
  12. this.editable.style.setProperty('height', TEST_HEIGHT + 'px');
  13. document.querySelector('#qunit-fixture').append(this.editable);
  14. this.testConvertGrid = ({ before, after, title, stepFunction }) => {
  15. this.editable.innerHTML = before;
  16. (stepFunction || convertInline.bootstrapToTable)(this.editable);
  17. // Remove class that is added by `bootstrapToTable` for use in
  18. // further methods of `toInline`, and removed at the end of it.
  19. this.editable.querySelectorAll('.o_converted_col').forEach(node => {
  20. node.classList.remove('o_converted_col');
  21. if (!node.classList.length) {
  22. node.removeAttribute('class');
  23. }
  24. });
  25. assert.strictEqual(removeComments(this.editable.innerHTML), after, title);
  26. }
  27. }
  28. });
  29. // Test bootstrapToTable, cardToTable and listGroupToTable
  30. QUnit.test('convert a single-row regular grid', async function (assert) {
  31. assert.expect(4);
  32. // 1x1
  33. this.testConvertGrid({
  34. before: getRegularGridHtml(1, 1),
  35. after: getRegularTableHtml(1, 1, 12, 100, TEST_WIDTH),
  36. title: "should have converted a 1x1 grid to an equivalent table",
  37. });
  38. // 1x2
  39. this.testConvertGrid({
  40. before: getRegularGridHtml(1, 2),
  41. after: getRegularTableHtml(1, 2, 6, 50, TEST_WIDTH),
  42. title: "should have converted a 1x2 grid to an equivalent table",
  43. });
  44. // 1x3
  45. this.testConvertGrid({
  46. before: getRegularGridHtml(1, 3),
  47. after: getRegularTableHtml(1, 3, 4, 33.33, TEST_WIDTH),
  48. title: "should have converted a 1x3 grid to an equivalent table",
  49. });
  50. // 1x12
  51. this.testConvertGrid({
  52. before: getRegularGridHtml(1, 12),
  53. after: getRegularTableHtml(1, 12, 1, 8.33, TEST_WIDTH),
  54. title: "should have converted a 1x12 grid to an equivalent table",
  55. });
  56. });
  57. QUnit.test('convert a single-row regular overflowing grid', async function (assert) {
  58. assert.expect(4);
  59. // 1x13
  60. this.testConvertGrid({
  61. before: getRegularGridHtml(1, 13),
  62. after: getRegularTableHtml(1, 12, 1, 8.33, TEST_WIDTH).slice(0, -8) +
  63. `<tr>` +
  64. getTdHtml(1, '(0, 12)', TEST_WIDTH) + getTdHtml(11, '', TEST_WIDTH) +
  65. `</tr></table>`,
  66. title: "should have converted a 1x13 grid to an equivalent table (overflowing)",
  67. });
  68. // 1x14
  69. this.testConvertGrid({
  70. before: getRegularGridHtml(1, 14),
  71. after: getRegularTableHtml(1, 12, 1, 8.33, TEST_WIDTH).slice(0, -8) +
  72. `<tr>` +
  73. getTdHtml(1, '(0, 12)', TEST_WIDTH) + getTdHtml(1, '(0, 13)', TEST_WIDTH) + getTdHtml(10, '', TEST_WIDTH) +
  74. `</tr></table>`,
  75. title: "should have converted a 1x14 grid to an equivalent table (overflowing)",
  76. });
  77. // 1x25
  78. this.testConvertGrid({
  79. before: getRegularGridHtml(1, 25),
  80. after: getRegularTableHtml(1, 12, 1, 8.33, TEST_WIDTH).slice(0, -8) +
  81. getRegularTableHtml(1, 12, 1, 8.33, TEST_WIDTH).replace(/\(0, (\d+)\)/g, (s, c) => `(0, ${+c + 12})`)
  82. .replace(/^<table[^<]*>/, '').slice(0, -8) +
  83. `<tr>` +
  84. getTdHtml(1, '(0, 24)', TEST_WIDTH) + getTdHtml(11, '', TEST_WIDTH) +
  85. `</tr></table>`,
  86. title: "should have converted a 1x25 grid to an equivalent table (overflowing)",
  87. });
  88. // 1x26
  89. this.testConvertGrid({
  90. before: getRegularGridHtml(1, 26),
  91. after: getRegularTableHtml(1, 12, 1, 8.33, TEST_WIDTH).slice(0, -8) +
  92. getRegularTableHtml(1, 12, 1, 8.33, TEST_WIDTH).replace(/\(0, (\d+)\)/g, (s, c) => `(0, ${+c + 12})`)
  93. .replace(/^<table[^<]*>/, '').slice(0, -8) +
  94. `<tr>` +
  95. getTdHtml(1, '(0, 24)', TEST_WIDTH) + getTdHtml(1, '(0, 25)', TEST_WIDTH) + getTdHtml(10, '', TEST_WIDTH) +
  96. `</tr></table>`,
  97. title: "should have converted a 1x26 grid to an equivalent table (overflowing)",
  98. });
  99. });
  100. QUnit.test('convert a multi-row regular grid', async function (assert) {
  101. assert.expect(4);
  102. // 2x1
  103. this.testConvertGrid({
  104. before: getRegularGridHtml(2, 1),
  105. after: getRegularTableHtml(2, 1, 12, 100, TEST_WIDTH),
  106. title: "should have converted a 2x1 grid to an equivalent table",
  107. });
  108. // 2x[1,2]
  109. this.testConvertGrid({
  110. before: getRegularGridHtml(2, [1, 2]),
  111. after: getRegularTableHtml(2, [1, 2], [12, 6], [100, 50], TEST_WIDTH),
  112. title: "should have converted a 2x[1,2] grid to an equivalent table",
  113. });
  114. // 3x3
  115. this.testConvertGrid({
  116. before: getRegularGridHtml(3, 3),
  117. after: getRegularTableHtml(3, 3, 4, 33.33, TEST_WIDTH),
  118. title: "should have converted a 3x3 grid to an equivalent table",
  119. });
  120. // 3x[3,2,1]
  121. this.testConvertGrid({
  122. before: getRegularGridHtml(3, [3,2,1]),
  123. after: getRegularTableHtml(3, [3, 2, 1], [4, 6, 12], [33.33, 50, 100], TEST_WIDTH),
  124. title: "should have converted a 3x[3,2,1] grid to an equivalent table",
  125. });
  126. });
  127. QUnit.test('convert a multi-row regular overflowing grid', async function (assert) {
  128. assert.expect(4);
  129. // 2x[13,1]
  130. this.testConvertGrid({
  131. before: getRegularGridHtml(2, [13, 1]),
  132. after: getRegularTableHtml(1, 12, 1, 8.33, TEST_WIDTH).slice(0, -8) +
  133. `<tr>` +
  134. getTdHtml(1, '(0, 12)', TEST_WIDTH) +
  135. getTdHtml(11, '', TEST_WIDTH) + // 13 overflowed the row by 1 -> fill up
  136. `</tr>` +
  137. `<tr>${getTdHtml(12, '(1, 0)', TEST_WIDTH)}</tr></table>`, // 1 col with no size == col-12
  138. title: "should have converted a 2x[13,1] grid to an equivalent table (overflowing)",
  139. });
  140. // 2x[1,13]
  141. this.testConvertGrid({
  142. before: getRegularGridHtml(2, [1, 13]),
  143. after: getRegularTableHtml(2, [1, 12], [12, 1], [100, 8.33], TEST_WIDTH).slice(0, -8) +
  144. `<tr>` +
  145. getTdHtml(1, '(1, 12)', TEST_WIDTH) +
  146. getTdHtml(11, '', TEST_WIDTH) + // 13 overflowed the row by 1 -> fill up
  147. `</tr></table>`,
  148. title: "should have converted a 2x[1,13] grid to an equivalent table (overflowing)",
  149. });
  150. // 3x[1,13,6]
  151. this.testConvertGrid({
  152. before: getRegularGridHtml(3, [1, 13, 6]),
  153. after: getRegularTableHtml(2, [1, 12], [12, 1], [100, 8.33], TEST_WIDTH).slice(0, -8) +
  154. `<tr>` +
  155. getTdHtml(1, '(1, 12)', TEST_WIDTH) +
  156. getTdHtml(11, '', TEST_WIDTH) + // 13 overflowed the row by 1 -> fill up
  157. `</tr>` +
  158. getRegularTableHtml(1, 6, 2, 16.67, TEST_WIDTH).replace(/\(0,/g, `(2,`).replace(/^<table[^<]*>/, ''),
  159. title: "should have converted a 3x[1,13,6] grid to an equivalent table (overflowing)",
  160. });
  161. // 3x[1,6,13]
  162. this.testConvertGrid({
  163. before: getRegularGridHtml(3, [1, 6, 13]),
  164. after: getRegularTableHtml(3, [1, 6, 12], [12, 2, 1], [100, 16.67, 8.33], TEST_WIDTH).slice(0, -8) +
  165. `<tr>` +
  166. getTdHtml(1, '(2, 12)', TEST_WIDTH) +
  167. getTdHtml(11, '', TEST_WIDTH) + // 13 overflowed the row by 1 -> fill up
  168. `</tr></table>`,
  169. title: "should have converted a 3x[1,6,13] grid to an equivalent table (overflowing)",
  170. });
  171. });
  172. QUnit.test('convert a single-row irregular grid', async function (assert) {
  173. assert.expect(2);
  174. // 1x2
  175. this.testConvertGrid({
  176. before: getGridHtml([[8, 4]]),
  177. after: getTableHtml([[[8, 66.67], [4, 33.33]]], TEST_WIDTH),
  178. title: "should have converted a 1x2 irregular grid to an equivalent table",
  179. });
  180. // 1x3
  181. this.testConvertGrid({
  182. before: getGridHtml([[2, 3, 7]]),
  183. after: getTableHtml([[[2, 16.67], [3, 25], [7, 58.33]]], TEST_WIDTH),
  184. title: "should have converted a 1x3 grid to an equivalent table",
  185. });
  186. });
  187. QUnit.test('convert a single-row irregular overflowing grid', async function (assert) {
  188. assert.expect(2);
  189. // 1x2
  190. this.testConvertGrid({
  191. before: getGridHtml([[8, 5]]),
  192. after: getTableHtml([
  193. [[8, 66.67], [4, 33.33, '']],
  194. [[5, 41.67, '(0, 1)'], [7, 58.33, '']],
  195. ], TEST_WIDTH),
  196. title: "should have converted a 1x2 irregular overflowing grid to an equivalent table",
  197. });
  198. // 1x3
  199. this.testConvertGrid({
  200. before: getGridHtml([[7, 6, 9]]),
  201. after: getTableHtml([
  202. [[7, 58.33], [5, 41.67, '']],
  203. [[6, 50, '(0, 1)'], [6, 50, '']],
  204. [[9, 75, '(0, 2)'], [3, 25, '']],
  205. ], TEST_WIDTH),
  206. title: "should have converted a 1x3 irregular overflowing grid to an equivalent table",
  207. });
  208. });
  209. QUnit.test('convert a multi-row irregular grid', async function (assert) {
  210. assert.expect(2);
  211. // 2x2
  212. this.testConvertGrid({
  213. before: getGridHtml([[1, 11], [2, 10]]),
  214. after: getTableHtml([[[1, 8.33], [11, 91.67]], [[2, 16.67], [10, 83.33]]], TEST_WIDTH),
  215. title: "should have converted a 2x2 irregular grid to an equivalent table",
  216. });
  217. // 2x[2,3]
  218. this.testConvertGrid({
  219. before: getGridHtml([[3, 9], [4, 6, 2]]),
  220. after: getTableHtml([[[3, 25], [9, 75]], [[4, 33.33], [6, 50], [2, 16.67]]], TEST_WIDTH),
  221. title: "should have converted a 2x[2,3] irregular grid to an equivalent table",
  222. });
  223. });
  224. QUnit.test('convert a multi-row irregular overflowing grid', async function (assert) {
  225. assert.expect(3);
  226. // 2x2 (both rows overflow)
  227. this.testConvertGrid({
  228. before: getGridHtml([[6, 8], [7, 9]]),
  229. after: getTableHtml([
  230. [[6, 50], [6, 50, '']],
  231. [[8, 66.67, '(0, 1)'], [4, 33.33, '']],
  232. [[7, 58.33, '(1, 0)'], [5, 41.67, '']],
  233. [[9, 75, '(1, 1)'], [3, 25, '']],
  234. ], TEST_WIDTH),
  235. title: "should have converted a 2x[1,13] irregular grid to an equivalent table (both rows overflowing)",
  236. });
  237. // 2x[2,3] (first row overflows)
  238. this.testConvertGrid({
  239. before: getGridHtml([[5, 8], [4, 2, 6]]),
  240. after: getTableHtml([
  241. [[5, 41.67], [7, 58.33, '']],
  242. [[8, 66.67, '(0, 1)'], [4, 33.33, '']],
  243. [[4, 33.33, '(1, 0)'], [2, 16.67, '(1, 1)'], [6, 50, '(1, 2)']],
  244. ], TEST_WIDTH),
  245. title: "should have converted a 2x[2,3] irregular grid to an equivalent table (first row overflowing)",
  246. });
  247. // 2x[3,2] (second row overflows)
  248. this.testConvertGrid({
  249. before: getGridHtml([[4, 2, 6], [5, 8]]),
  250. after: getTableHtml([
  251. [[4, 33.33], [2, 16.67], [6, 50]],
  252. [[5, 41.67], [7, 58.33, '']],
  253. [[8, 66.67, '(1, 1)'], [4, 33.33, '']],
  254. ], TEST_WIDTH),
  255. title: "should have converted a 2x[3,2] irregular grid to an equivalent table (second row overflowing)",
  256. });
  257. });
  258. QUnit.test('convert a card to a table', async function (assert) {
  259. assert.expect(1);
  260. this.testConvertGrid({
  261. title: "should have converted a card structure into a table",
  262. before:
  263. `<div class="card">` +
  264. `<div class="card-header">` +
  265. `<span>HEADER</span>` +
  266. `</div>` +
  267. `<div class="card-body">` +
  268. `<h2 class="card-title">TITLE</h2>` +
  269. `<small>BODY <img></small>` +
  270. `</div>` +
  271. `<div class="card-footer">` +
  272. `<a href="#" class="btn">FOOTER</a>` +
  273. `</div>` +
  274. `</div>`,
  275. stepFunction: convertInline.cardToTable,
  276. after: getRegularTableHtml(3, 1, 12, 100)
  277. .replace('role=\"presentation\"', 'role=\"presentation\" class=\"card\"')
  278. .replace(/<td[^>]*>\(0, 0\)<\/td>/,
  279. `<td>` +
  280. `<table cellspacing=\"0\" cellpadding=\"0\" border=\"0\" width=\"100%\" align=\"center\" ` +
  281. `role=\"presentation\" style=\"width: 100% !important; border-collapse: collapse; text-align: inherit; ` +
  282. `font-size: unset; line-height: inherit;\"><tr>` +
  283. `<td class="card-header"><span>HEADER</span></td>` +
  284. `</tr></table></td>`)
  285. .replace(/<td[^>]*>\(1, 0\)<\/td>/,
  286. `<td>` +
  287. `<table cellspacing=\"0\" cellpadding=\"0\" border=\"0\" width=\"100%\" align=\"center\" ` +
  288. `role=\"presentation\" style=\"width: 100% !important; border-collapse: collapse; text-align: inherit; ` +
  289. `font-size: unset; line-height: inherit;\"><tr>` +
  290. `<td class="card-body"><h2 class="card-title">TITLE</h2><small>BODY <img></small></td>` +
  291. `</tr></table></td>`)
  292. .replace(/<td[^>]*>\(2, 0\)<\/td>/,
  293. `<td>` +
  294. `<table cellspacing=\"0\" cellpadding=\"0\" border=\"0\" width=\"100%\" align=\"center\" ` +
  295. `role=\"presentation\" style=\"width: 100% !important; border-collapse: collapse; text-align: inherit; ` +
  296. `font-size: unset; line-height: inherit;\"><tr>` +
  297. `<td class="card-footer"><a href="#" class="btn">FOOTER</a></td>` +
  298. `</tr></table></td>`),
  299. });
  300. });
  301. QUnit.test('convert a list group to a table', async function (assert) {
  302. assert.expect(1);
  303. this.testConvertGrid({
  304. title: "should have converted a list group structure into a table",
  305. before:
  306. `<ul class="list-group list-group-flush">` +
  307. `<li class="list-group-item">` +
  308. `<strong>(0, 0)</strong>` +
  309. `</li>` +
  310. `<li class="list-group-item a">` +
  311. `(1, 0)` +
  312. `</li>` +
  313. `<li><img></li>` +
  314. `<li class="list-group-item">` +
  315. `<strong class="b">(2, 0)</strong>` +
  316. `</li>` +
  317. `</ul>`,
  318. stepFunction: convertInline.listGroupToTable,
  319. after: getRegularTableHtml(3, 1, 12, 100)
  320. .split('style="').join('class="list-group-flush" style="')
  321. .replace(/<td[^>]*>(\(0, 0\))<\/td>/, '<td><strong>$1</strong></td>')
  322. .replace(/<td[^>]*>(\(1, 0\))<\/td>/, '<td class="a">$1</td>')
  323. .replace(/<tr><td[^>]*>(\(2, 0\))<\/td>/, '<img><tr><td><strong class="b">$1</strong></td>'),
  324. });
  325. });
  326. QUnit.test('convert a grid with offsets to a table', async function (assert) {
  327. assert.expect(2);
  328. this.testConvertGrid({
  329. before: '<div class="container"><div class="row"><div class="col-6 offset-4">(0, 0)</div></div>',
  330. after: getTableHtml([[[4, 33.33, ''], [6, 50, '(0, 0)'], [2, 16.67, '']]], TEST_WIDTH),
  331. title: "should have converted a column with an offset to two columns, then completed the column",
  332. });
  333. this.testConvertGrid({
  334. before: '<div class="container"><div class="row"><div class="col-6 offset-4">(0, 0)</div><div class="col-6 offset-1">(0, 1)</div></div>',
  335. after: getTableHtml([
  336. [[4, 33.33, ''], [6, 50, '(0, 0)'], [1, 8.33, ''], [1, 8.33, '']],
  337. [[6, 50, '(0, 1)'], [6, 50, '']]
  338. ], TEST_WIDTH),
  339. title: "should have converted a column with an offset to two columns, then completed the column (overflowing)",
  340. });
  341. });
  342. QUnit.module('Normalize styles');
  343. // Test normalizeColors, normalizeRem and formatTables
  344. QUnit.test('convert rgb color to hexadecimal', async function (assert) {
  345. assert.expect(1);
  346. const $editable = $(
  347. `<div><div style="color: rgb(0, 0, 0);">` +
  348. `<div class="a" style="padding: 0; background-color:rgb(255,255,255)" width="100%">` +
  349. `<p style="border: 1px rgb(50, 100,200 ) solid; color: rgb(35, 134, 54);">Test</p>` +
  350. `</div>` +
  351. `</div></div>`
  352. );
  353. convertInline.normalizeColors($editable);
  354. assert.strictEqual($editable.html(),
  355. `<div style="color: #000000;">` +
  356. `<div class="a" style="padding: 0; background-color:#ffffff" width="100%">` +
  357. `<p style="border: 1px #3264c8 solid; color: #238636;">Test</p>` +
  358. `</div>` +
  359. `</div>`,
  360. "should have converted several rgb colors to hexadecimal"
  361. );
  362. });
  363. QUnit.test('convert rem sizes to px', async function (assert) {
  364. assert.expect(2);
  365. const testDom = `<div style="font-size: 2rem;">` +
  366. `<div class="a" style="color: #000000; padding: 2.5 rem" width="100%">` +
  367. `<p style="border: 1.2rem #aaaaaa solid; margin: 3.79rem;">Test</p>` +
  368. `</div>` +
  369. `</div>`;
  370. let $editable = $(`<div>${testDom}</div>`);
  371. document.body.append($editable[0]);
  372. convertInline.normalizeRem($editable);
  373. assert.strictEqual($editable.html(),
  374. `<div style="font-size: 32px;">` +
  375. `<div class="a" style="color: #000000; padding: 40px" width="100%">` +
  376. `<p style="border: 19.2px #aaaaaa solid; margin: 60.64px;">Test</p>` +
  377. `</div>` +
  378. `</div>`,
  379. "should have converted several rem sizes to px using the default rem size"
  380. );
  381. $editable.remove();
  382. $editable = $(`<div>${testDom}</div>`);
  383. document.body.append($editable[0]);
  384. convertInline.normalizeRem($editable, 20);
  385. assert.strictEqual($editable.html(),
  386. `<div style="font-size: 40px;">` +
  387. `<div class="a" style="color: #000000; padding: 50px" width="100%">` +
  388. `<p style="border: 24px #aaaaaa solid; margin: 75.8px;">Test</p>` +
  389. `</div>` +
  390. `</div>`,
  391. "should have converted several rem sizes to px using a set rem size"
  392. );
  393. $editable.remove();
  394. });
  395. QUnit.test('move padding from snippet containers to cells', async function (assert) {
  396. assert.expect(1);
  397. const testTable = `<table class="o_mail_snippet_general" style="padding: 10px 20px 30px 40px;">` +
  398. `<tbody>` +
  399. `<tr>` +
  400. `<td style="padding-top: 1px; padding-right: 2px;">(0, 0, 0)</td>` +
  401. `<td style="padding: 3px 4px 5px 6px;">(0, 1, 0)</td>` +
  402. `<td style="padding: 7px;">(0, 2, 0)</td>` +
  403. `<td style="padding: 8px 9px;">(0, 3, 0)</td>` +
  404. `<td style="padding-right: 9.1px;">(0, 4, 0)</td>` +
  405. `</tr>` +
  406. `<tr>` +
  407. `<td>` +
  408. `<table style="padding: 50px 60px 70px 80px;">` +
  409. `<tbody>` +
  410. `<tr>` +
  411. `<td style="padding: 1px 2px 3px 4px;">(0, 0, 1)</td>` +
  412. `<td style="padding: 5px;">(0, 1, 1)</td>` +
  413. `<td style="padding: 6px 7px;">(0, 2, 1)</td>` +
  414. `<td style="padding-top: 8px; padding-right: 9px;">(0, 3, 1)</td>` +
  415. `</tr>` +
  416. `</tbody>` +
  417. `</table>` +
  418. `</td>` +
  419. `</tr>` +
  420. `<tr>` +
  421. `<td style="padding-left: 9.1px;">(1, 0, 0)</td>` +
  422. `<td style="padding: 9px 8px 7px 6px;">(1, 1, 0)</td>` +
  423. `<td style="padding: 5px;">(1, 2, 0)</td>` +
  424. `<td style="padding: 4px 3px;">(1, 3, 0)</td>` +
  425. `<td style="padding-bottom: 2px; padding-right: 1px;">(1, 4, 0)</td>` +
  426. `</tr>` +
  427. `</tbody>` +
  428. `</table>`;
  429. const expectedTable = `<table class="o_mail_snippet_general" style="">` +
  430. `<tbody>` +
  431. `<tr>` +
  432. `<td style="padding-top: 11px; padding-right: 2px; padding-left: 40px;">(0, 0, 0)</td>` + // TL
  433. `<td style="padding: 13px 4px 5px 6px;">(0, 1, 0)</td>` + // T
  434. `<td style="padding: 17px 7px 7px;">(0, 2, 0)</td>` + // T
  435. `<td style="padding: 18px 9px 8px;">(0, 3, 0)</td>` + // T
  436. `<td style="padding-right: 29.1px; padding-top: 10px;">(0, 4, 0)</td>` + // TR
  437. `</tr>` +
  438. `<tr>` +
  439. `<td style="padding-right: 20px; padding-left: 40px;">` + // LR
  440. `<table style="">` +
  441. `<tbody>` +
  442. `<tr>` +
  443. `<td style="padding: 51px 2px 73px 84px;">(0, 0, 1)</td>` + // TBL
  444. `<td style="padding: 55px 5px 75px;">(0, 1, 1)</td>` + // TB
  445. `<td style="padding: 56px 7px 76px;">(0, 2, 1)</td>` + // TB
  446. `<td style="padding-top: 58px; padding-right: 69px; padding-bottom: 70px;">(0, 3, 1)</td>` + // TBR
  447. `</tr>` +
  448. `</tbody>` +
  449. `</table>` +
  450. `</td>` +
  451. `</tr>` +
  452. `<tr>` +
  453. `<td style="padding-left: 49.1px; padding-bottom: 30px;">(1, 0, 0)</td>` + // BL
  454. `<td style="padding: 9px 8px 37px 6px;">(1, 1, 0)</td>` + // B
  455. `<td style="padding: 5px 5px 35px;">(1, 2, 0)</td>` + // B
  456. `<td style="padding: 4px 3px 34px;">(1, 3, 0)</td>` + // B
  457. `<td style="padding-bottom: 32px; padding-right: 21px;">(1, 4, 0)</td>` + // BR
  458. `</tr>` +
  459. `</tbody>` +
  460. `</table>`;
  461. // table.o_mail_snippet_general
  462. const $editable = $(`<div>${testTable}</div>`);
  463. convertInline.formatTables($editable);
  464. assert.strictEqual($editable.html(), expectedTable,
  465. "should have moved the padding from table.o_mail_snippet_general and table in it to their respective cells"
  466. );
  467. });
  468. QUnit.test('add a tbody to any table that doesn\'t have one', async function (assert) {
  469. assert.expect(1);
  470. const $editable = $(`<div>${`<table><tr><td>I don't have a body :'(</td></tr></table>`}</div>`);
  471. $editable.find('tr').unwrap();
  472. convertInline.formatTables($editable);
  473. assert.strictEqual($editable.html(), `<table><tbody style="vertical-align: top;"><tr><td>I don't have a body :'(</td></tr></tbody></table>`,
  474. "should have added a tbody to a table that didn't have one"
  475. );
  476. });
  477. QUnit.test('add number heights to parents of elements with percent heights', async function (assert) {
  478. assert.expect(3);
  479. let $editable = $(`<div>${`<table><tbody><tr style="height: 100%;"><td>yup</td></tr></tbody></table>`}</div>`);
  480. convertInline.formatTables($editable);
  481. assert.strictEqual($editable.html(), `<table><tbody style="height: 0px;"><tr style="height: 100%;"><td>yup</td></tr></tbody></table>`,
  482. "should have added a 0 height to the parent of a 100% height element"
  483. );
  484. $editable = $(`<div>${`<table><tbody style="height: 200px;"><tr style="height: 100%;"><td>yup</td></tr></tbody></table>`}</div>`);
  485. convertInline.formatTables($editable);
  486. assert.strictEqual($editable.html(), `<table><tbody style="height: 200px;"><tr style="height: 100%;"><td>yup</td></tr></tbody></table>`,
  487. "should not have changed the height of the parent of a 100% height element"
  488. );
  489. $editable = $(`<div>${`<table><tbody style="height: 50%;"><tr style="height: 100%;"><td>yup</td></tr></tbody></table>`}</div>`);
  490. convertInline.formatTables($editable);
  491. assert.strictEqual($editable.html(), `<table style="height: 0px;"><tbody style="height: 50%;"><tr style="height: 100%;"><td>yup</td></tr></tbody></table>`,
  492. "should have changed the height of the grandparent of a 100% height element"
  493. );
  494. });
  495. QUnit.test('express align-self with vertical-align on table cells', async function (assert) {
  496. assert.expect(3);
  497. let $editable = $(`<div><table><tbody><tr><td style="align-self: start;">yup</td></tr></tbody></table></div>`);
  498. convertInline.formatTables($editable);
  499. assert.strictEqual($editable.html(), `<table><tbody><tr><td style="align-self: start; vertical-align: top;">yup</td></tr></tbody></table>`,
  500. "should have added a top vertical alignment"
  501. );
  502. $editable = $(`<div><table><tbody><tr><td style="align-self: center;">yup</td></tr></tbody></table></div>`);
  503. convertInline.formatTables($editable);
  504. assert.strictEqual($editable.html(), `<table><tbody><tr><td style="align-self: center; vertical-align: middle;">yup</td></tr></tbody></table>`,
  505. "should have added a middle vertical alignment"
  506. );
  507. $editable = $(`<div><table><tbody><tr><td style="align-self: end;">yup</td></tr></tbody></table></div>`);
  508. convertInline.formatTables($editable);
  509. assert.strictEqual($editable.html(), `<table><tbody><tr><td style="align-self: end; vertical-align: bottom;">yup</td></tr></tbody></table>`,
  510. "should have added a bottom vertical alignment"
  511. );
  512. });
  513. QUnit.module('Convert snippets and mailing bodies to tables');
  514. // Test addTables
  515. QUnit.test('convert snippets to tables', async function (assert) {
  516. assert.expect(2);
  517. let $editable = $(
  518. `<div><div class="o_mail_snippet_general">` +
  519. `<div>Snippet</div>` +
  520. `</div></div>`
  521. );
  522. convertInline.addTables($editable);
  523. assert.strictEqual($editable.html(),
  524. getRegularTableHtml(1, 1, 12, 100)
  525. .split('style=').join('class="o_mail_snippet_general" style=')
  526. .replace(/<td[^>]*>\(0, 0\)/, '<td>' + getRegularTableHtml(1, 1, 12, 100).replace(/<td[^>]*>\(0, 0\)/, '<td><div>Snippet</div>')),
  527. "should have converted .o_mail_snippet_general to a special table structure with a table in it"
  528. );
  529. $editable = $(
  530. `<div><div class="o_mail_snippet_general">` +
  531. `<table><tbody><tr><td>Snippet</td></tr></tbody></table>` +
  532. `</div></div>`
  533. );
  534. convertInline.addTables($editable);
  535. assert.strictEqual($editable.html(),
  536. getRegularTableHtml(1, 1, 12, 100)
  537. .split('style=').join('class="o_mail_snippet_general" style=')
  538. .replace(/<td[^>]*>\(0, 0\)/, '<td><table><tbody><tr><td>Snippet</td></tr></tbody></table>'),
  539. "should have converted .o_mail_snippet_general to a special table structure, keeping the table in it"
  540. );
  541. });
  542. QUnit.test('convert mailing bodies to tables', async function (assert) {
  543. assert.expect(2);
  544. let $editable = $(
  545. `<div><div class="o_layout">` +
  546. `<div>Mailing</div>` +
  547. `</div></div>`
  548. );
  549. convertInline.addTables($editable);
  550. assert.strictEqual($editable.html(),
  551. getRegularTableHtml(1, 1, 12, 100)
  552. .split('style=').join('class="o_layout" style=')
  553. .replace(' font-size: unset; line-height: inherit;', '') // o_layout keeps those default values
  554. .replace(/<td[^>]*>\(0, 0\)/, '<td>' + getRegularTableHtml(1, 1, 12, 100).replace(/<td[^>]*>\(0, 0\)/, '<td><div>Mailing</div>')),
  555. "should have converted .o_layout to a special table structure with a table in it"
  556. );
  557. $editable = $(
  558. `<div><div class="o_layout">` +
  559. `<table><tbody><tr><td>Mailing</td></tr></tbody></table>` +
  560. `</div></div>`
  561. );
  562. convertInline.addTables($editable);
  563. assert.strictEqual($editable.html(),
  564. getRegularTableHtml(1, 1, 12, 100)
  565. .split('style=').join('class="o_layout" style=')
  566. .replace(' font-size: unset; line-height: inherit;', '') // o_layout keeps those default values
  567. .replace(/<td[^>]*>\(0, 0\)/, '<td><table><tbody><tr><td>Mailing</td></tr></tbody></table>'),
  568. "should have converted .o_layout to a special table structure, keeping the table in it"
  569. );
  570. });
  571. QUnit.module('Convert classes to inline styles');
  572. // Test classToStyle
  573. QUnit.test('convert Bootstrap classes to inline styles', async function (assert) {
  574. assert.expect(1);
  575. const $editable = $(`<div><div class="container"><div class="row"><div class="col">Hello</div></div></div></div>`);
  576. $(document.body).append($editable); // editable needs to be in the DOM to compute its dynamic styles.
  577. convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
  578. // Some positional properties (eg., padding-right, margin-left) are not
  579. // concatenated (eg., as padding, margin) because they were defined with
  580. // variables (var) or calculated (calc).
  581. const containerStyle = `margin: 0px auto; box-sizing: border-box; max-width: 1320px; padding-left: 16px; padding-right: 16px; width: 100%;`;
  582. const rowStyle = `box-sizing: border-box; margin-left: -16px; margin-right: -16px; margin-top: 0px;`;
  583. const colStyle = `box-sizing: border-box; margin-top: 0px; padding-left: 16px; padding-right: 16px; max-width: 100%; width: 100%;`;
  584. assert.strictEqual($editable.html(),
  585. `<div class="container" style="${containerStyle}" width="100%">` +
  586. `<div class="row" style="${rowStyle}">` +
  587. `<div class="col" style="${colStyle}" width="100%">Hello</div></div></div>`,
  588. "should have converted the classes of a simple Bootstrap grid to inline styles"
  589. );
  590. $editable.remove();
  591. });
  592. QUnit.test('simplify border/margin/padding styles', async function (assert) {
  593. assert.expect(12);
  594. const $styleSheet = $('<style type="text/css" title="test-stylesheet"/>');
  595. document.head.appendChild($styleSheet[0])
  596. const styleSheet = [...document.styleSheets].find(sheet => sheet.title === 'test-stylesheet');
  597. // border-radius
  598. styleSheet.insertRule(`
  599. .test-border-radius {
  600. border-top-right-radius: 10%;
  601. border-bottom-right-radius: 20%;
  602. border-bottom-left-radius: 30%;
  603. border-top-left-radius: 40%;
  604. }
  605. `, 0);
  606. let $editable = $(`<div>${`<div class="test-border-radius"></div>`}</div>`);
  607. convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
  608. assert.strictEqual($editable.html(),
  609. `<div class="test-border-radius" style="border-radius:30%;box-sizing:border-box;"></div>`,
  610. "should have converted border-[position]-radius styles (from class) to border-radius");
  611. styleSheet.deleteRule(0);
  612. // convert all positional styles to a style in the form `property: a b c d`
  613. styleSheet.insertRule(`
  614. .test-border {
  615. border-top-style: dotted;
  616. border-right-style: dashed;
  617. border-left-style: solid;
  618. }
  619. `, 0);
  620. $editable = $(`<div>${`<div class="test-border"></div>`}</div>`);
  621. convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
  622. assert.strictEqual($editable.html(),
  623. `<div class="test-border" style="border-style:dotted dashed none solid;box-sizing:border-box;"></div>`,
  624. "should have converted border-[position]-style styles (from class) to border-style");
  625. styleSheet.deleteRule(0);
  626. styleSheet.insertRule(`
  627. .test-margin {
  628. margin-right: 20px;
  629. margin-bottom: 30px;
  630. margin-left: 40px;
  631. }
  632. `, 0);
  633. $editable = $(`<div>${`<div class="test-margin"></div>`}</div>`);
  634. convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
  635. assert.strictEqual($editable.html(),
  636. `<div class="test-margin" style="margin:0 20px 30px 40px;box-sizing:border-box;"></div>`,
  637. "should have converted margin-[position] styles (from class) to margin");
  638. styleSheet.deleteRule(0);
  639. styleSheet.insertRule(`
  640. .test-padding {
  641. padding-top: 10px;
  642. padding-bottom: 30px;
  643. padding-left: 40px;
  644. }
  645. `, 0);
  646. $editable = $(`<div>${`<div class="test-padding"></div>`}</div>`);
  647. convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
  648. assert.strictEqual($editable.html(),
  649. `<div class="test-padding" style="padding:10px 0 30px 40px;box-sizing:border-box;"></div>`,
  650. "should have converted padding-[position] styles (from class) to padding");
  651. styleSheet.deleteRule(0);
  652. // convert all positional styles to a style in the form `property: a`
  653. styleSheet.insertRule(`
  654. .test-border-uniform {
  655. border-top-style: dotted;
  656. border-right-style: dotted;
  657. border-bottom-style: dotted;
  658. border-left-style: dotted;
  659. }
  660. `, 0);
  661. $editable = $(`<div>${`<div class="test-border-uniform"></div>`}</div>`);
  662. convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
  663. assert.strictEqual($editable.html(),
  664. `<div class="test-border-uniform" style="border-style:dotted;box-sizing:border-box;"></div>`,
  665. "should have converted uniform border-[position]-style styles (from class) to border-style");
  666. styleSheet.deleteRule(0);
  667. styleSheet.insertRule(`
  668. .test-margin-uniform {
  669. margin-top: 10px;
  670. margin-right: 10px;
  671. margin-bottom: 10px;
  672. margin-left: 10px;
  673. }
  674. `, 0);
  675. $editable = $(`<div>${`<div class="test-margin-uniform"></div>`}</div>`);
  676. convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
  677. assert.strictEqual($editable.html(),
  678. `<div class="test-margin-uniform" style="margin:10px;box-sizing:border-box;"></div>`,
  679. "should have converted uniform margin-[position] styles (from class) to margin");
  680. styleSheet.deleteRule(0);
  681. styleSheet.insertRule(`
  682. .test-padding-uniform {
  683. padding-top: 10px;
  684. padding-right: 10px;
  685. padding-bottom: 10px;
  686. padding-left: 10px;
  687. }
  688. `, 0);
  689. $editable = $(`<div>${`<div class="test-padding-uniform"></div>`}</div>`);
  690. convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
  691. assert.strictEqual($editable.html(),
  692. `<div class="test-padding-uniform" style="padding:10px;box-sizing:border-box;"></div>`,
  693. "should have converted uniform padding-[position] styles (from class) to padding");
  694. styleSheet.deleteRule(0);
  695. // do not convert positional styles that include an "inherit" value
  696. styleSheet.insertRule(`
  697. .test-border-inherit {
  698. border-top-style: dotted;
  699. border-right-style: dashed;
  700. border-bottom-style: inherit;
  701. border-left-style: solid;
  702. }
  703. `, 0);
  704. $editable = $(`<div>${`<div class="test-border-inherit"></div>`}</div>`);
  705. convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
  706. assert.strictEqual($editable.html(),
  707. `<div class="test-border-inherit" style="box-sizing:border-box;border-left-style:solid;border-bottom-style:inherit;border-right-style:dashed;border-top-style:dotted;"></div>`,
  708. "should not have converted border-[position]-style styles (from class) to border-style as they include an inherit");
  709. styleSheet.deleteRule(0);
  710. styleSheet.insertRule(`
  711. .test-margin-inherit {
  712. margin-top: 10px;
  713. margin-right: inherit;
  714. margin-bottom: 30px;
  715. margin-left: 40px;
  716. }
  717. `, 0);
  718. $editable = $(`<div>${`<div class="test-margin-inherit"></div>`}</div>`);
  719. convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
  720. assert.strictEqual($editable.html(),
  721. `<div class="test-margin-inherit" style="box-sizing:border-box;margin-left:40px;margin-bottom:30px;margin-right:inherit;margin-top:10px;"></div>`,
  722. "should not have converted margin-[position] styles (from class) to margin as they include an inherit");
  723. styleSheet.deleteRule(0);
  724. styleSheet.insertRule(`
  725. .test-padding-inherit {
  726. padding-top: 10px;
  727. padding-right: 20px;
  728. padding-bottom: inherit;
  729. padding-left: 40px;
  730. }
  731. `, 0);
  732. $editable = $(`<div>${`<div class="test-padding-inherit"></div>`}</div>`);
  733. convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
  734. assert.strictEqual($editable.html(),
  735. `<div class="test-padding-inherit" style="box-sizing:border-box;padding-left:40px;padding-bottom:inherit;padding-right:20px;padding-top:10px;"></div>`,
  736. "should have converted padding-[position] styles (from class) to padding as they include an inherit");
  737. styleSheet.deleteRule(0);
  738. // do not convert positional styles that include an "initial" value
  739. // note: `border: initial` is automatically removed (tested in "remove
  740. // unsupported styles")
  741. styleSheet.insertRule(`
  742. .test-margin-initial {
  743. margin-top: initial;
  744. margin-right: 20px;
  745. margin-bottom: 30px;
  746. margin-left: 40px;
  747. }
  748. `, 0);
  749. $editable = $(`<div>${`<div class="test-margin-initial"></div>`}</div>`);
  750. convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
  751. assert.strictEqual($editable.html(),
  752. `<div class="test-margin-initial" style="box-sizing:border-box;margin-left:40px;margin-bottom:30px;margin-right:20px;margin-top:initial;"></div>`,
  753. "should not have converted margin-[position] styles (from class) to margin as they include an initial");
  754. styleSheet.deleteRule(0);
  755. styleSheet.insertRule(`
  756. .test-padding-initial {
  757. padding-top: 10px;
  758. padding-right: 20px;
  759. padding-bottom: 30px;
  760. padding-left: initial;
  761. }
  762. `, 0);
  763. $editable = $(`<div>${`<div class="test-padding-initial"></div>`}</div>`);
  764. convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
  765. assert.strictEqual($editable.html(),
  766. `<div class="test-padding-initial" style="box-sizing:border-box;padding-left:initial;padding-bottom:30px;padding-right:20px;padding-top:10px;"></div>`,
  767. "should not have converted padding-[position] styles (from class) to padding as they include an initial");
  768. styleSheet.deleteRule(0);
  769. $styleSheet.remove();
  770. });
  771. QUnit.test('remove unsupported styles', async function (assert) {
  772. assert.expect(9);
  773. const $styleSheet = $('<style type="text/css" title="test-stylesheet"/>');
  774. document.head.appendChild($styleSheet[0])
  775. const styleSheet = [...document.styleSheets].find(sheet => sheet.title === 'test-stylesheet');
  776. // text-decoration-[prop]
  777. styleSheet.insertRule(`
  778. .test-decoration {
  779. text-decoration-line: underline;
  780. text-decoration-color: red;
  781. text-decoration-style: solid;
  782. text-decoration-thickness: 10px;
  783. }
  784. `, 0);
  785. let $editable = $(`<div>${`<div class="test-decoration"></div>`}</div>`);
  786. convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
  787. assert.strictEqual($editable.html(),
  788. `<div class="test-decoration" style="text-decoration:underline;box-sizing:border-box;"></div>`,
  789. "should have removed all text-decoration-[prop] styles (from class) and kept a simple text-decoration");
  790. styleSheet.deleteRule(0);
  791. // border[\w-]*: initial
  792. styleSheet.insertRule(`
  793. .test-border-initial {
  794. border-top-style: dotted;
  795. border-right-style: dashed;
  796. border-bottom-style: double;
  797. border-left-style: initial;
  798. }
  799. `, 0);
  800. $editable = $(`<div>${`<div class="test-border-initial"></div>`}</div>`);
  801. convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
  802. assert.strictEqual($editable.html(),
  803. `<div class="test-border-initial" style="box-sizing:border-box;border-bottom-style:double;border-right-style:dashed;border-top-style:dotted;"></div>`,
  804. "should have removed border initial");
  805. styleSheet.deleteRule(0);
  806. // display: block
  807. styleSheet.insertRule(`
  808. .test-block {
  809. display: block;
  810. }
  811. `, 0);
  812. $editable = $(`<div>${`<div class="test-block"></div>`}</div>`);
  813. convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
  814. assert.strictEqual($editable.html(),
  815. `<div class="test-block" style="box-sizing:border-box;"></div>`,
  816. "should have removed display block");
  817. styleSheet.deleteRule(0);
  818. // !important
  819. styleSheet.insertRule(`
  820. .test-unimportant-color {
  821. color: blue;
  822. }
  823. `, 0);
  824. $editable = $(`<div>${`<div class="test-unimportant-color"></div>`}</div>`);
  825. convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
  826. assert.strictEqual($editable.html(),
  827. `<div class="test-unimportant-color" style="box-sizing:border-box;color:blue;"></div>`,
  828. "should have converted a simple color");
  829. styleSheet.insertRule(`
  830. .test-important-color {
  831. color: red !important;
  832. }
  833. `, 0);
  834. $editable = $(`<div>${`<div class="test-important-color test-unimportant-color"></div>`}</div>`);
  835. convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
  836. assert.strictEqual($editable.html(),
  837. `<div class="test-important-color test-unimportant-color" style="box-sizing:border-box;color:red;"></div>`,
  838. "should have converted an important color and removed the !important");
  839. styleSheet.deleteRule(0);
  840. styleSheet.deleteRule(0);
  841. // animation
  842. styleSheet.insertRule(`
  843. .test-animation {
  844. animation: example 5s linear 2s infinite alternate;
  845. }
  846. `, 0);
  847. $editable = $(`<div>${`<div class="test-animation"></div>`}</div>`);
  848. convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
  849. assert.strictEqual($editable.html(),
  850. `<div class="test-animation" style="box-sizing:border-box;"></div>`,
  851. "should have removed animation style");
  852. styleSheet.deleteRule(0);
  853. styleSheet.insertRule(`
  854. .test-animation-specific {
  855. animation-name: example;
  856. animation-duration: 5s;
  857. animation-timing-function: linear;
  858. animation-delay: 2s;
  859. animation-iteration-count: infinite;
  860. animation-direction: alternate;
  861. }
  862. `, 0);
  863. $editable = $(`<div>${`<div class="test-animation-specific"></div>`}</div>`);
  864. convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
  865. assert.strictEqual($editable.html(),
  866. `<div class="test-animation-specific" style="box-sizing:border-box;"></div>`,
  867. "should have removed all specific animation styles");
  868. styleSheet.deleteRule(0);
  869. // flex
  870. styleSheet.insertRule(`
  871. .test-flex {
  872. flex: 0 1 auto;
  873. flex-flow: column wrap;
  874. }
  875. `, 0);
  876. $editable = $(`<div>${`<div class="test-flex"></div>`}</div>`);
  877. convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
  878. assert.strictEqual($editable.html(),
  879. `<div class="test-flex" style="box-sizing:border-box;"></div>`,
  880. "should have removed all flex styles");
  881. styleSheet.deleteRule(0);
  882. styleSheet.insertRule(`
  883. .test-flex-specific {
  884. display: flex;
  885. flex-direction: row;
  886. flex-wrap: wrap;
  887. flex-basis: auto;
  888. flex-shrink: 3;
  889. flex-grow: 4;
  890. }
  891. `, 0);
  892. $editable = $(`<div>${`<div class="test-flex-specific"></div>`}</div>`);
  893. convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
  894. assert.strictEqual($editable.html(),
  895. `<div class="test-flex-specific" style="box-sizing:border-box;"></div>`,
  896. "should have removed all specific flex styles");
  897. styleSheet.deleteRule(0);
  898. $styleSheet.remove();
  899. });
  900. QUnit.test('give .o_layout the styles of the body', async function (assert) {
  901. assert.expect(1);
  902. const $iframe = $('<iframe></iframe>');
  903. $(document.body).append($iframe);
  904. const $iframeEditable = $('<div/>');
  905. $iframe.contents().find('body').append($iframeEditable);
  906. const $styleSheet = $('<style type="text/css" title="test-stylesheet"/>');
  907. $iframe.contents().find('head').append($styleSheet);
  908. const styleSheet = [...$iframe.contents()[0].styleSheets].find(sheet => sheet.title === 'test-stylesheet');
  909. styleSheet.insertRule(`
  910. body {
  911. background-color: red;
  912. color: white;
  913. font-size: 50px;
  914. }
  915. `, 0);
  916. $iframeEditable.append(`<div class="o_layout" style="padding: 50px;"></div>`);
  917. convertInline.classToStyle($iframeEditable, convertInline.getCSSRules($iframeEditable[0].ownerDocument));
  918. assert.strictEqual($iframeEditable.html(),
  919. `<div class="o_layout" style="box-sizing:border-box;font-size:50px;color:white;background-color:red;padding: 50px;"></div>`,
  920. "should have given all styles of body to .o_layout");
  921. styleSheet.deleteRule(0);
  922. $iframe.remove();
  923. });
  924. QUnit.test('convert classes to styles, preserving specificity', async function (assert) {
  925. assert.expect(4);
  926. const $styleSheet = $('<style type="text/css" title="test-stylesheet"/>');
  927. document.head.appendChild($styleSheet[0])
  928. const styleSheet = [...document.styleSheets].find(sheet => sheet.title === 'test-stylesheet');
  929. styleSheet.insertRule(`
  930. div.test-color {
  931. color: green;
  932. }
  933. `, 0);
  934. styleSheet.insertRule(`
  935. .test-color {
  936. color: red;
  937. }
  938. `, 1);
  939. styleSheet.insertRule(`
  940. .test-color {
  941. color: blue;
  942. }
  943. `, 2);
  944. let $editable = $(`<div><span class="test-color"></span></div>`);
  945. convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
  946. assert.strictEqual($editable.html(),
  947. `<span class="test-color" style="box-sizing:border-box;color:blue;"></span>`,
  948. "should have prioritized the last defined style");
  949. $editable = $(`<div><div class="test-color"></div></div>`);
  950. convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
  951. assert.strictEqual($editable.html(),
  952. `<div class="test-color" style="box-sizing:border-box;color:green;"></div>`,
  953. "should have prioritized the more specific style");
  954. $editable = $(`<div><div class="test-color" style="color: yellow;"></div></div>`);
  955. convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
  956. assert.strictEqual($editable.html(),
  957. `<div class="test-color" style="box-sizing:border-box;color: yellow;"></div>`,
  958. "should have prioritized the inline style");
  959. styleSheet.insertRule(`
  960. .test-color {
  961. color: black !important;
  962. }
  963. `, 0);
  964. $editable = $(`<div><div class="test-color"></div></div>`);
  965. convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
  966. assert.strictEqual($editable.html(),
  967. `<div class="test-color" style="box-sizing:border-box;color:black;"></div>`,
  968. "should have prioritized the important style");
  969. styleSheet.deleteRule(0);
  970. styleSheet.deleteRule(0);
  971. styleSheet.deleteRule(0);
  972. styleSheet.deleteRule(0);
  973. $styleSheet.remove();
  974. });
  975. });
  976. });