/** @odoo-module **/ import convertInline from '@web_editor/js/backend/convert_inline'; import {getGridHtml, getTableHtml, getRegularGridHtml, getRegularTableHtml, getTdHtml, removeComments} from 'web_editor.test_utils'; const TEST_WIDTH = 800; const TEST_HEIGHT = 600; QUnit.module('web_editor', {}, function () { QUnit.module('convert_inline', {}, function () { QUnit.module('Convert Bootstrap grids to tables', { beforeEach: function (assert) { this.editable = document.createElement('div'); this.editable.style.setProperty('width', TEST_WIDTH + 'px'); this.editable.style.setProperty('height', TEST_HEIGHT + 'px'); document.querySelector('#qunit-fixture').append(this.editable); this.testConvertGrid = ({ before, after, title, stepFunction }) => { this.editable.innerHTML = before; (stepFunction || convertInline.bootstrapToTable)(this.editable); // Remove class that is added by `bootstrapToTable` for use in // further methods of `toInline`, and removed at the end of it. this.editable.querySelectorAll('.o_converted_col').forEach(node => { node.classList.remove('o_converted_col'); if (!node.classList.length) { node.removeAttribute('class'); } }); assert.strictEqual(removeComments(this.editable.innerHTML), after, title); } } }); // Test bootstrapToTable, cardToTable and listGroupToTable QUnit.test('convert a single-row regular grid', async function (assert) { assert.expect(4); // 1x1 this.testConvertGrid({ before: getRegularGridHtml(1, 1), after: getRegularTableHtml(1, 1, 12, 100, TEST_WIDTH), title: "should have converted a 1x1 grid to an equivalent table", }); // 1x2 this.testConvertGrid({ before: getRegularGridHtml(1, 2), after: getRegularTableHtml(1, 2, 6, 50, TEST_WIDTH), title: "should have converted a 1x2 grid to an equivalent table", }); // 1x3 this.testConvertGrid({ before: getRegularGridHtml(1, 3), after: getRegularTableHtml(1, 3, 4, 33.33, TEST_WIDTH), title: "should have converted a 1x3 grid to an equivalent table", }); // 1x12 this.testConvertGrid({ before: getRegularGridHtml(1, 12), after: getRegularTableHtml(1, 12, 1, 8.33, TEST_WIDTH), title: "should have converted a 1x12 grid to an equivalent table", }); }); QUnit.test('convert a single-row regular overflowing grid', async function (assert) { assert.expect(4); // 1x13 this.testConvertGrid({ before: getRegularGridHtml(1, 13), after: getRegularTableHtml(1, 12, 1, 8.33, TEST_WIDTH).slice(0, -8) + `` + getTdHtml(1, '(0, 12)', TEST_WIDTH) + getTdHtml(11, '', TEST_WIDTH) + ``, title: "should have converted a 1x13 grid to an equivalent table (overflowing)", }); // 1x14 this.testConvertGrid({ before: getRegularGridHtml(1, 14), after: getRegularTableHtml(1, 12, 1, 8.33, TEST_WIDTH).slice(0, -8) + `` + getTdHtml(1, '(0, 12)', TEST_WIDTH) + getTdHtml(1, '(0, 13)', TEST_WIDTH) + getTdHtml(10, '', TEST_WIDTH) + ``, title: "should have converted a 1x14 grid to an equivalent table (overflowing)", }); // 1x25 this.testConvertGrid({ before: getRegularGridHtml(1, 25), after: getRegularTableHtml(1, 12, 1, 8.33, TEST_WIDTH).slice(0, -8) + getRegularTableHtml(1, 12, 1, 8.33, TEST_WIDTH).replace(/\(0, (\d+)\)/g, (s, c) => `(0, ${+c + 12})`) .replace(/^/, '').slice(0, -8) + `` + getTdHtml(1, '(0, 24)', TEST_WIDTH) + getTdHtml(11, '', TEST_WIDTH) + ``, title: "should have converted a 1x25 grid to an equivalent table (overflowing)", }); // 1x26 this.testConvertGrid({ before: getRegularGridHtml(1, 26), after: getRegularTableHtml(1, 12, 1, 8.33, TEST_WIDTH).slice(0, -8) + getRegularTableHtml(1, 12, 1, 8.33, TEST_WIDTH).replace(/\(0, (\d+)\)/g, (s, c) => `(0, ${+c + 12})`) .replace(/^/, '').slice(0, -8) + `` + getTdHtml(1, '(0, 24)', TEST_WIDTH) + getTdHtml(1, '(0, 25)', TEST_WIDTH) + getTdHtml(10, '', TEST_WIDTH) + ``, title: "should have converted a 1x26 grid to an equivalent table (overflowing)", }); }); QUnit.test('convert a multi-row regular grid', async function (assert) { assert.expect(4); // 2x1 this.testConvertGrid({ before: getRegularGridHtml(2, 1), after: getRegularTableHtml(2, 1, 12, 100, TEST_WIDTH), title: "should have converted a 2x1 grid to an equivalent table", }); // 2x[1,2] this.testConvertGrid({ before: getRegularGridHtml(2, [1, 2]), after: getRegularTableHtml(2, [1, 2], [12, 6], [100, 50], TEST_WIDTH), title: "should have converted a 2x[1,2] grid to an equivalent table", }); // 3x3 this.testConvertGrid({ before: getRegularGridHtml(3, 3), after: getRegularTableHtml(3, 3, 4, 33.33, TEST_WIDTH), title: "should have converted a 3x3 grid to an equivalent table", }); // 3x[3,2,1] this.testConvertGrid({ before: getRegularGridHtml(3, [3,2,1]), after: getRegularTableHtml(3, [3, 2, 1], [4, 6, 12], [33.33, 50, 100], TEST_WIDTH), title: "should have converted a 3x[3,2,1] grid to an equivalent table", }); }); QUnit.test('convert a multi-row regular overflowing grid', async function (assert) { assert.expect(4); // 2x[13,1] this.testConvertGrid({ before: getRegularGridHtml(2, [13, 1]), after: getRegularTableHtml(1, 12, 1, 8.33, TEST_WIDTH).slice(0, -8) + `` + getTdHtml(1, '(0, 12)', TEST_WIDTH) + getTdHtml(11, '', TEST_WIDTH) + // 13 overflowed the row by 1 -> fill up `` + `${getTdHtml(12, '(1, 0)', TEST_WIDTH)}`, // 1 col with no size == col-12 title: "should have converted a 2x[13,1] grid to an equivalent table (overflowing)", }); // 2x[1,13] this.testConvertGrid({ before: getRegularGridHtml(2, [1, 13]), after: getRegularTableHtml(2, [1, 12], [12, 1], [100, 8.33], TEST_WIDTH).slice(0, -8) + `` + getTdHtml(1, '(1, 12)', TEST_WIDTH) + getTdHtml(11, '', TEST_WIDTH) + // 13 overflowed the row by 1 -> fill up ``, title: "should have converted a 2x[1,13] grid to an equivalent table (overflowing)", }); // 3x[1,13,6] this.testConvertGrid({ before: getRegularGridHtml(3, [1, 13, 6]), after: getRegularTableHtml(2, [1, 12], [12, 1], [100, 8.33], TEST_WIDTH).slice(0, -8) + `` + getTdHtml(1, '(1, 12)', TEST_WIDTH) + getTdHtml(11, '', TEST_WIDTH) + // 13 overflowed the row by 1 -> fill up `` + getRegularTableHtml(1, 6, 2, 16.67, TEST_WIDTH).replace(/\(0,/g, `(2,`).replace(/^/, ''), title: "should have converted a 3x[1,13,6] grid to an equivalent table (overflowing)", }); // 3x[1,6,13] this.testConvertGrid({ before: getRegularGridHtml(3, [1, 6, 13]), after: getRegularTableHtml(3, [1, 6, 12], [12, 2, 1], [100, 16.67, 8.33], TEST_WIDTH).slice(0, -8) + `` + getTdHtml(1, '(2, 12)', TEST_WIDTH) + getTdHtml(11, '', TEST_WIDTH) + // 13 overflowed the row by 1 -> fill up ``, title: "should have converted a 3x[1,6,13] grid to an equivalent table (overflowing)", }); }); QUnit.test('convert a single-row irregular grid', async function (assert) { assert.expect(2); // 1x2 this.testConvertGrid({ before: getGridHtml([[8, 4]]), after: getTableHtml([[[8, 66.67], [4, 33.33]]], TEST_WIDTH), title: "should have converted a 1x2 irregular grid to an equivalent table", }); // 1x3 this.testConvertGrid({ before: getGridHtml([[2, 3, 7]]), after: getTableHtml([[[2, 16.67], [3, 25], [7, 58.33]]], TEST_WIDTH), title: "should have converted a 1x3 grid to an equivalent table", }); }); QUnit.test('convert a single-row irregular overflowing grid', async function (assert) { assert.expect(2); // 1x2 this.testConvertGrid({ before: getGridHtml([[8, 5]]), after: getTableHtml([ [[8, 66.67], [4, 33.33, '']], [[5, 41.67, '(0, 1)'], [7, 58.33, '']], ], TEST_WIDTH), title: "should have converted a 1x2 irregular overflowing grid to an equivalent table", }); // 1x3 this.testConvertGrid({ before: getGridHtml([[7, 6, 9]]), after: getTableHtml([ [[7, 58.33], [5, 41.67, '']], [[6, 50, '(0, 1)'], [6, 50, '']], [[9, 75, '(0, 2)'], [3, 25, '']], ], TEST_WIDTH), title: "should have converted a 1x3 irregular overflowing grid to an equivalent table", }); }); QUnit.test('convert a multi-row irregular grid', async function (assert) { assert.expect(2); // 2x2 this.testConvertGrid({ before: getGridHtml([[1, 11], [2, 10]]), after: getTableHtml([[[1, 8.33], [11, 91.67]], [[2, 16.67], [10, 83.33]]], TEST_WIDTH), title: "should have converted a 2x2 irregular grid to an equivalent table", }); // 2x[2,3] this.testConvertGrid({ before: getGridHtml([[3, 9], [4, 6, 2]]), after: getTableHtml([[[3, 25], [9, 75]], [[4, 33.33], [6, 50], [2, 16.67]]], TEST_WIDTH), title: "should have converted a 2x[2,3] irregular grid to an equivalent table", }); }); QUnit.test('convert a multi-row irregular overflowing grid', async function (assert) { assert.expect(3); // 2x2 (both rows overflow) this.testConvertGrid({ before: getGridHtml([[6, 8], [7, 9]]), after: getTableHtml([ [[6, 50], [6, 50, '']], [[8, 66.67, '(0, 1)'], [4, 33.33, '']], [[7, 58.33, '(1, 0)'], [5, 41.67, '']], [[9, 75, '(1, 1)'], [3, 25, '']], ], TEST_WIDTH), title: "should have converted a 2x[1,13] irregular grid to an equivalent table (both rows overflowing)", }); // 2x[2,3] (first row overflows) this.testConvertGrid({ before: getGridHtml([[5, 8], [4, 2, 6]]), after: getTableHtml([ [[5, 41.67], [7, 58.33, '']], [[8, 66.67, '(0, 1)'], [4, 33.33, '']], [[4, 33.33, '(1, 0)'], [2, 16.67, '(1, 1)'], [6, 50, '(1, 2)']], ], TEST_WIDTH), title: "should have converted a 2x[2,3] irregular grid to an equivalent table (first row overflowing)", }); // 2x[3,2] (second row overflows) this.testConvertGrid({ before: getGridHtml([[4, 2, 6], [5, 8]]), after: getTableHtml([ [[4, 33.33], [2, 16.67], [6, 50]], [[5, 41.67], [7, 58.33, '']], [[8, 66.67, '(1, 1)'], [4, 33.33, '']], ], TEST_WIDTH), title: "should have converted a 2x[3,2] irregular grid to an equivalent table (second row overflowing)", }); }); QUnit.test('convert a card to a table', async function (assert) { assert.expect(1); this.testConvertGrid({ title: "should have converted a card structure into a table", before: `
` + `
` + `HEADER` + `
` + `
` + `

TITLE

` + `BODY ` + `
` + `` + `
`, stepFunction: convertInline.cardToTable, after: getRegularTableHtml(3, 1, 12, 100) .replace('role=\"presentation\"', 'role=\"presentation\" class=\"card\"') .replace(/]*>\(0, 0\)<\/td>/, `` + `` + `` + `
HEADER
`) .replace(/]*>\(1, 0\)<\/td>/, `` + `` + `` + `

TITLE

BODY
`) .replace(/]*>\(2, 0\)<\/td>/, `` + `` + `` + `
`), }); }); QUnit.test('convert a list group to a table', async function (assert) { assert.expect(1); this.testConvertGrid({ title: "should have converted a list group structure into a table", before: `
    ` + `
  • ` + `(0, 0)` + `
  • ` + `
  • ` + `(1, 0)` + `
  • ` + `
  • ` + `
  • ` + `(2, 0)` + `
  • ` + `
`, stepFunction: convertInline.listGroupToTable, after: getRegularTableHtml(3, 1, 12, 100) .split('style="').join('class="list-group-flush" style="') .replace(/]*>(\(0, 0\))<\/td>/, '$1') .replace(/]*>(\(1, 0\))<\/td>/, '$1') .replace(/]*>(\(2, 0\))<\/td>/, '$1'), }); }); QUnit.test('convert a grid with offsets to a table', async function (assert) { assert.expect(2); this.testConvertGrid({ before: '
(0, 0)
', after: getTableHtml([[[4, 33.33, ''], [6, 50, '(0, 0)'], [2, 16.67, '']]], TEST_WIDTH), title: "should have converted a column with an offset to two columns, then completed the column", }); this.testConvertGrid({ before: '
(0, 0)
(0, 1)
', after: getTableHtml([ [[4, 33.33, ''], [6, 50, '(0, 0)'], [1, 8.33, ''], [1, 8.33, '']], [[6, 50, '(0, 1)'], [6, 50, '']] ], TEST_WIDTH), title: "should have converted a column with an offset to two columns, then completed the column (overflowing)", }); }); QUnit.module('Normalize styles'); // Test normalizeColors, normalizeRem and formatTables QUnit.test('convert rgb color to hexadecimal', async function (assert) { assert.expect(1); const $editable = $( `
` + `
` + `

Test

` + `
` + `
` ); convertInline.normalizeColors($editable); assert.strictEqual($editable.html(), `
` + `
` + `

Test

` + `
` + `
`, "should have converted several rgb colors to hexadecimal" ); }); QUnit.test('convert rem sizes to px', async function (assert) { assert.expect(2); const testDom = `
` + `
` + `

Test

` + `
` + `
`; let $editable = $(`
${testDom}
`); document.body.append($editable[0]); convertInline.normalizeRem($editable); assert.strictEqual($editable.html(), `
` + `
` + `

Test

` + `
` + `
`, "should have converted several rem sizes to px using the default rem size" ); $editable.remove(); $editable = $(`
${testDom}
`); document.body.append($editable[0]); convertInline.normalizeRem($editable, 20); assert.strictEqual($editable.html(), `
` + `
` + `

Test

` + `
` + `
`, "should have converted several rem sizes to px using a set rem size" ); $editable.remove(); }); QUnit.test('move padding from snippet containers to cells', async function (assert) { assert.expect(1); const testTable = `` + `` + `` + `` + `` + `` + `` + `` + `` + `` + `` + `` + `` + `` + `` + `` + `` + `` + `` + `` + `
(0, 0, 0)(0, 1, 0)(0, 2, 0)(0, 3, 0)(0, 4, 0)
` + `` + `` + `` + `` + `` + `` + `` + `` + `` + `
(0, 0, 1)(0, 1, 1)(0, 2, 1)(0, 3, 1)
` + `
(1, 0, 0)(1, 1, 0)(1, 2, 0)(1, 3, 0)(1, 4, 0)
`; const expectedTable = `` + `` + `` + `` + // TL `` + // T `` + // T `` + // T `` + // TR `` + `` + `` + `` + `` + `` + // BL `` + // B `` + // B `` + // B `` + // BR `` + `` + `
(0, 0, 0)(0, 1, 0)(0, 2, 0)(0, 3, 0)(0, 4, 0)
` + // LR `` + `` + `` + `` + // TBL `` + // TB `` + // TB `` + // TBR `` + `` + `
(0, 0, 1)(0, 1, 1)(0, 2, 1)(0, 3, 1)
` + `
(1, 0, 0)(1, 1, 0)(1, 2, 0)(1, 3, 0)(1, 4, 0)
`; // table.o_mail_snippet_general const $editable = $(`
${testTable}
`); convertInline.formatTables($editable); assert.strictEqual($editable.html(), expectedTable, "should have moved the padding from table.o_mail_snippet_general and table in it to their respective cells" ); }); QUnit.test('add a tbody to any table that doesn\'t have one', async function (assert) { assert.expect(1); const $editable = $(`
${`
I don't have a body :'(
`}
`); $editable.find('tr').unwrap(); convertInline.formatTables($editable); assert.strictEqual($editable.html(), `
I don't have a body :'(
`, "should have added a tbody to a table that didn't have one" ); }); QUnit.test('add number heights to parents of elements with percent heights', async function (assert) { assert.expect(3); let $editable = $(`
${`
yup
`}
`); convertInline.formatTables($editable); assert.strictEqual($editable.html(), `
yup
`, "should have added a 0 height to the parent of a 100% height element" ); $editable = $(`
${`
yup
`}
`); convertInline.formatTables($editable); assert.strictEqual($editable.html(), `
yup
`, "should not have changed the height of the parent of a 100% height element" ); $editable = $(`
${`
yup
`}
`); convertInline.formatTables($editable); assert.strictEqual($editable.html(), `
yup
`, "should have changed the height of the grandparent of a 100% height element" ); }); QUnit.test('express align-self with vertical-align on table cells', async function (assert) { assert.expect(3); let $editable = $(`
yup
`); convertInline.formatTables($editable); assert.strictEqual($editable.html(), `
yup
`, "should have added a top vertical alignment" ); $editable = $(`
yup
`); convertInline.formatTables($editable); assert.strictEqual($editable.html(), `
yup
`, "should have added a middle vertical alignment" ); $editable = $(`
yup
`); convertInline.formatTables($editable); assert.strictEqual($editable.html(), `
yup
`, "should have added a bottom vertical alignment" ); }); QUnit.module('Convert snippets and mailing bodies to tables'); // Test addTables QUnit.test('convert snippets to tables', async function (assert) { assert.expect(2); let $editable = $( `
` + `
Snippet
` + `
` ); convertInline.addTables($editable); assert.strictEqual($editable.html(), getRegularTableHtml(1, 1, 12, 100) .split('style=').join('class="o_mail_snippet_general" style=') .replace(/]*>\(0, 0\)/, '' + getRegularTableHtml(1, 1, 12, 100).replace(/]*>\(0, 0\)/, '
Snippet
')), "should have converted .o_mail_snippet_general to a special table structure with a table in it" ); $editable = $( `
` + `
Snippet
` + `
` ); convertInline.addTables($editable); assert.strictEqual($editable.html(), getRegularTableHtml(1, 1, 12, 100) .split('style=').join('class="o_mail_snippet_general" style=') .replace(/]*>\(0, 0\)/, '
Snippet
'), "should have converted .o_mail_snippet_general to a special table structure, keeping the table in it" ); }); QUnit.test('convert mailing bodies to tables', async function (assert) { assert.expect(2); let $editable = $( `
` + `
Mailing
` + `
` ); convertInline.addTables($editable); assert.strictEqual($editable.html(), getRegularTableHtml(1, 1, 12, 100) .split('style=').join('class="o_layout" style=') .replace(' font-size: unset; line-height: inherit;', '') // o_layout keeps those default values .replace(/]*>\(0, 0\)/, '' + getRegularTableHtml(1, 1, 12, 100).replace(/]*>\(0, 0\)/, '
Mailing
')), "should have converted .o_layout to a special table structure with a table in it" ); $editable = $( `
` + `
Mailing
` + `
` ); convertInline.addTables($editable); assert.strictEqual($editable.html(), getRegularTableHtml(1, 1, 12, 100) .split('style=').join('class="o_layout" style=') .replace(' font-size: unset; line-height: inherit;', '') // o_layout keeps those default values .replace(/]*>\(0, 0\)/, '
Mailing
'), "should have converted .o_layout to a special table structure, keeping the table in it" ); }); QUnit.module('Convert classes to inline styles'); // Test classToStyle QUnit.test('convert Bootstrap classes to inline styles', async function (assert) { assert.expect(1); const $editable = $(`
Hello
`); $(document.body).append($editable); // editable needs to be in the DOM to compute its dynamic styles. convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument)); // Some positional properties (eg., padding-right, margin-left) are not // concatenated (eg., as padding, margin) because they were defined with // variables (var) or calculated (calc). const containerStyle = `margin: 0px auto; box-sizing: border-box; max-width: 1320px; padding-left: 16px; padding-right: 16px; width: 100%;`; const rowStyle = `box-sizing: border-box; margin-left: -16px; margin-right: -16px; margin-top: 0px;`; const colStyle = `box-sizing: border-box; margin-top: 0px; padding-left: 16px; padding-right: 16px; max-width: 100%; width: 100%;`; assert.strictEqual($editable.html(), `
` + `
` + `
Hello
`, "should have converted the classes of a simple Bootstrap grid to inline styles" ); $editable.remove(); }); QUnit.test('simplify border/margin/padding styles', async function (assert) { assert.expect(12); const $styleSheet = $('