mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-20 08:32:03 +02:00
19.0 vanilla
This commit is contained in:
parent
d1963a3c3a
commit
2d3ee4855a
7430 changed files with 2687981 additions and 2965473 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,157 @@
|
|||
import { setSelection } from "@html_editor/../tests/_helpers/selection";
|
||||
import { insertText } from "@html_editor/../tests/_helpers/user_actions";
|
||||
import { expectElementCount } from "@html_editor/../tests/_helpers/ui_expectations";
|
||||
import { HtmlMailField } from "@mail/views/web/fields/html_mail_field/html_mail_field";
|
||||
import { after, before, beforeEach, expect, test } from "@odoo/hoot";
|
||||
import { press, queryOne } from "@odoo/hoot-dom";
|
||||
import { animationFrame, enableTransitions } from "@odoo/hoot-mock";
|
||||
import {
|
||||
clickSave,
|
||||
contains,
|
||||
defineModels,
|
||||
fields,
|
||||
models,
|
||||
mountView,
|
||||
onRpc,
|
||||
patchWithCleanup,
|
||||
} from "@web/../tests/web_test_helpers";
|
||||
import { mailModels } from "../mail_test_helpers";
|
||||
|
||||
function setSelectionInHtmlField(selector = "p", fieldName = "body") {
|
||||
const anchorNode = queryOne(`[name='${fieldName}'] .odoo-editor-editable ${selector}`);
|
||||
setSelection({ anchorNode, anchorOffset: 0 });
|
||||
return anchorNode;
|
||||
}
|
||||
|
||||
function useCustomStyleRules(rules = "") {
|
||||
let style;
|
||||
before(() => {
|
||||
style = document.createElement("STYLE");
|
||||
style.type = "text/css";
|
||||
style.append(document.createTextNode(rules));
|
||||
document.head.append(style);
|
||||
});
|
||||
after(() => {
|
||||
style.remove();
|
||||
});
|
||||
}
|
||||
|
||||
class CustomMessage extends models.Model {
|
||||
_name = "custom.message";
|
||||
|
||||
title = fields.Char();
|
||||
body = fields.Html();
|
||||
|
||||
_records = [
|
||||
{ id: 1, title: "first", body: "<p>first</p>" },
|
||||
{ id: 2, title: "second", body: "<p>second</p>" },
|
||||
];
|
||||
|
||||
_onChanges = {
|
||||
title(record) {
|
||||
record.body = `<p>${record.title}</p>`;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
defineModels({ ...mailModels, CustomMessage });
|
||||
|
||||
let htmlEditor;
|
||||
beforeEach(() => {
|
||||
patchWithCleanup(HtmlMailField.prototype, {
|
||||
onEditorLoad(editor) {
|
||||
htmlEditor = editor;
|
||||
return super.onEditorLoad(...arguments);
|
||||
},
|
||||
getConfig() {
|
||||
const config = super.getConfig();
|
||||
config.Plugins = config.Plugins.filter((Plugin) => Plugin.id !== "editorVersion");
|
||||
return config;
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("HtmlMail save inline html", async function () {
|
||||
enableTransitions();
|
||||
useCustomStyleRules(`.test-h1-inline .note-editable h1 { color: #111827 !important; }`);
|
||||
onRpc("web_save", ({ args }) => {
|
||||
expect(args[1].body.replace(/font-size: ?(\d+(\.\d+)?)px/, "font-size: []px")).toBe(
|
||||
`<h1 style="border-radius:0px;border-style:none;padding:0px;margin:0px 0 8px 0;box-sizing:border-box;border-left-color:#111827;border-bottom-color:#111827;border-right-color:#111827;border-top-color:#111827;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;border-top-width:0px;font-size: []px;color:#111827;line-height:1.2;font-weight:500;font-family:'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Ubuntu, 'Noto Sans', Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';">first</h1>`
|
||||
);
|
||||
expect.step("web_save");
|
||||
});
|
||||
await mountView({
|
||||
type: "form",
|
||||
resId: 1,
|
||||
resModel: "custom.message",
|
||||
arch: `
|
||||
<form>
|
||||
<field name="body" widget="html_mail" class="test-h1-inline"/>
|
||||
</form>`,
|
||||
});
|
||||
setSelectionInHtmlField();
|
||||
await insertText(htmlEditor, "/heading1");
|
||||
await press("enter");
|
||||
expect(".odoo-editor-editable").toHaveInnerHTML("<h1> first </h1>");
|
||||
|
||||
await clickSave();
|
||||
await expect.waitForSteps(["web_save"]);
|
||||
});
|
||||
|
||||
test("HtmlMail don't have access to column commands", async function () {
|
||||
await mountView({
|
||||
type: "form",
|
||||
resId: 1,
|
||||
resModel: "custom.message",
|
||||
arch: `
|
||||
<form>
|
||||
<field name="body" widget="html_mail"/>
|
||||
</form>`,
|
||||
});
|
||||
setSelectionInHtmlField();
|
||||
await insertText(htmlEditor, "/");
|
||||
await animationFrame();
|
||||
await expectElementCount(".o-we-powerbox", 1);
|
||||
|
||||
await insertText(htmlEditor, "column");
|
||||
await animationFrame();
|
||||
await expectElementCount(".o-we-powerbox", 0);
|
||||
});
|
||||
|
||||
test("HtmlMail add icon and save inline html", async function () {
|
||||
enableTransitions();
|
||||
useCustomStyleRules(
|
||||
`.test-icon-inline .note-editable .fa {
|
||||
color: rgb(55,65,81) !important;
|
||||
background-color: rgb(249,250,251) !important;
|
||||
}
|
||||
p, img {
|
||||
border-color: #ff0000 !important;
|
||||
}
|
||||
`
|
||||
);
|
||||
onRpc("web_save", ({ args }) => {
|
||||
expect(args[1].body).toBe(
|
||||
`<p style="border-radius:0px;border-style:none;padding:0px;margin:0px 0 16px 0;box-sizing:border-box;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;border-top-width:0px;border-left-color:#ff0000;border-bottom-color:#ff0000;border-right-color:#ff0000;border-top-color:#ff0000;"><span style="display: inline-block; width: 14px; height: 14px; vertical-align: text-bottom;" class="oe_unbreakable "><img width="14" height="14" src="/mail/font_to_img/61440/rgb(55%2C65%2C81)/rgb(249%2C250%2C251)/14x14" data-class="fa fa-glass" data-style="null" style="border-radius:0px;border-style:none;padding:0px;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;border-top-width:0px;border-left-color:#ff0000;border-bottom-color:#ff0000;border-right-color:#ff0000;border-top-color:#ff0000;box-sizing: border-box; line-height: 14px; width: 14px; height: 14px; vertical-align: unset; margin: 0px;"></span>first</p>`
|
||||
);
|
||||
expect.step("web_save");
|
||||
});
|
||||
await mountView({
|
||||
type: "form",
|
||||
resId: 1,
|
||||
resModel: "custom.message",
|
||||
arch: `
|
||||
<form>
|
||||
<field name="body" widget="html_mail" class="test-icon-inline"/>
|
||||
</form>`,
|
||||
});
|
||||
setSelectionInHtmlField();
|
||||
await insertText(htmlEditor, "/image");
|
||||
await press("enter");
|
||||
|
||||
await contains("a.nav-link:contains('Icons')").click();
|
||||
await contains("span.fa-glass").click();
|
||||
|
||||
await clickSave();
|
||||
await expect.waitForSteps(["web_save"]);
|
||||
});
|
||||
149
odoo-bringout-oca-ocb-mail/mail/static/tests/inline/utils.js
Normal file
149
odoo-bringout-oca-ocb-mail/mail/static/tests/inline/utils.js
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
import {
|
||||
TABLE_ATTRIBUTES,
|
||||
TABLE_STYLES,
|
||||
} from "@mail/views/web/fields/html_mail_field/convert_inline";
|
||||
|
||||
const tableAttributesString = Object.keys(TABLE_ATTRIBUTES)
|
||||
.map((key) => `${key}="${TABLE_ATTRIBUTES[key]}"`)
|
||||
.join(" ");
|
||||
const tableStylesString = Object.keys(TABLE_STYLES)
|
||||
.map((key) => `${key}: ${TABLE_STYLES[key]};`)
|
||||
.join(" ");
|
||||
/**
|
||||
* Take a matrix representing a grid and return an HTML string of the Bootstrap
|
||||
* grid. The matrix is an array of rows, with each row being an array of cells.
|
||||
* Each cell can be represented either by a 0 < number < 13 (col-#) or a falsy
|
||||
* value (col). Each cell has its coordinates `(row index, column index)` as
|
||||
* text content.
|
||||
* Eg: [ // <div class="container">
|
||||
* [ // <div class="row">
|
||||
* 1, // <div class="col-1">(0, 0)</div>
|
||||
* 11, // <div class="col-11">(0, 1)</div>
|
||||
* ], // </div>
|
||||
* [ // <div class="row">
|
||||
* false, // <div class="col">(1, 0)</div>
|
||||
* ], // </div>
|
||||
* ] // </div>
|
||||
*
|
||||
* @param {Array<Array<Number|null>>} matrix
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getGridHtml(matrix) {
|
||||
return (
|
||||
`<div class="container">` +
|
||||
matrix
|
||||
.map(
|
||||
(row, iRow) =>
|
||||
`<div class="row">` +
|
||||
row
|
||||
.map(
|
||||
(col, iCol) =>
|
||||
`<div class="${
|
||||
col ? "col-" + col : "col"
|
||||
}">(${iRow}, ${iCol})</div>`
|
||||
)
|
||||
.join("") +
|
||||
`</div>`
|
||||
)
|
||||
.join("") +
|
||||
`</div>`
|
||||
);
|
||||
}
|
||||
export function getTdHtml(colspan, text, containerWidth) {
|
||||
const style = containerWidth
|
||||
? ` style="max-width: ${Math.round(((containerWidth * colspan) / 12) * 100) / 100}px;"`
|
||||
: "";
|
||||
return `<td colspan="${colspan}"${style}>${text}</td>`;
|
||||
}
|
||||
/**
|
||||
* Take a matrix representing a table and return an HTML string of the table.
|
||||
* The matrix is an array of rows, with each row being an array of cells. Each
|
||||
* cell is represented by a tuple of numbers [colspan, width (in percent)]. A
|
||||
* cell can have a string as third value to represent its text content. The
|
||||
* default text content of each cell is its coordinates `(row index, column
|
||||
* index)`. If the cell has a number as third value, it will be used as the
|
||||
* max-width of the cell (in pixels).
|
||||
* Eg: [ // <table> (note: extra attrs and styles apply)
|
||||
* [ // <tr>
|
||||
* [1, 8], // <td colspan="1" width="8%">(0, 0)</td>
|
||||
* [11, 92] // <td colspan="11" width="92%">(0, 1)</td>
|
||||
* ], // </tr>
|
||||
* [ // <tr>
|
||||
* [2, 17, 'A'], // <td colspan="2" width="17%">A</td>
|
||||
* [10, 83], // <td colspan="10" width="83%">(1, 1)</td>
|
||||
* ], // </tr>
|
||||
* ] // </table>
|
||||
*
|
||||
* @param {Array<Array<Array<[Number, Number, string?, number?]>>>} matrix
|
||||
* @param {Number} [containerWidth]
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getTableHtml(matrix, containerWidth) {
|
||||
return (
|
||||
`<table ${tableAttributesString} style="width: 100% !important; ${tableStylesString}">` +
|
||||
matrix
|
||||
.map(
|
||||
(row, iRow) =>
|
||||
`<tr>` +
|
||||
row
|
||||
.map((col, iCol) =>
|
||||
getTdHtml(
|
||||
col[0],
|
||||
typeof col[2] === "string" ? col[2] : `(${iRow}, ${iCol})`,
|
||||
containerWidth
|
||||
)
|
||||
)
|
||||
.join("") +
|
||||
`</tr>`
|
||||
)
|
||||
.join("") +
|
||||
`</table>`
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Take a number of rows and a number of columns (or number of columns per
|
||||
* individual row) and return an HTML string of the corresponding grid. Every
|
||||
* column is a regular Bootstrap "col" (no col-#).
|
||||
* Eg: [2, 3] <=> getGridHtml([[false, false, false], [false, false, false]])
|
||||
* Eg: [2, [2, 1]] <=> getGridHtml([[false, false], [false]])
|
||||
*
|
||||
* @see getGridHtml
|
||||
* @param {Number} nRows
|
||||
* @param {Number|Number[]} nCols
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getRegularGridHtml(nRows, nCols) {
|
||||
const matrix = new Array(nRows)
|
||||
.fill()
|
||||
.map((_, iRow) => new Array(Array.isArray(nCols) ? nCols[iRow] : nCols).fill());
|
||||
return getGridHtml(matrix);
|
||||
}
|
||||
/**
|
||||
* Take a number of rows, a number of columns (or number of columns per
|
||||
* individual row), a colspan (or colspan per individual row) and a width (or
|
||||
* width per individual row, in percent), and return an HTML string of the
|
||||
* corresponding table. Every cell in a row has the same colspan/width.
|
||||
* Eg: [2, 2, 6, 50] <=> getTableHtml([[[6, 50], [6, 50]], [[6, 50], [6, 50]]])
|
||||
* Eg: [2, [2, 1], [6, 12], [50, 100]] <=> getTableHtml([[[6, 50], [6, 50]], [[12, 100]]])
|
||||
*
|
||||
* @see getTableHtml
|
||||
* @param {Number} nRows
|
||||
* @param {Number|Number[]} nCols
|
||||
* @param {Number|Number[]} colspan
|
||||
* @param {Number|Number[]} width
|
||||
* @param {Number} containerWidth
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getRegularTableHtml(nRows, nCols, colspan, width, containerWidth) {
|
||||
const matrix = new Array(nRows)
|
||||
.fill()
|
||||
.map((_, iRow) =>
|
||||
new Array(Array.isArray(nCols) ? nCols[iRow] : nCols)
|
||||
.fill()
|
||||
.map(() => [
|
||||
Array.isArray(colspan) ? colspan[iRow] : colspan,
|
||||
Array.isArray(width) ? width[iRow] : width,
|
||||
])
|
||||
);
|
||||
return getTableHtml(matrix, containerWidth);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue