',
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",
});
testConvertGrid({
before: '
',
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)",
});
});
});
describe("Normalize styles", () => {
beforeEach(() => {
editable = document.createElement("div");
});
// Test normalizeColors, normalizeRem and formatTables
test("convert rgb color to hexadecimal", async () => {
editable.innerHTML = `
`;
normalizeColors(editable);
expect(editable).toHaveInnerHTML(
`
`,
{ message: "should have converted several rgb colors to hexadecimal" }
);
});
test("convert rem sizes to px", async () => {
const testDom = `
`;
editable.innerHTML = testDom;
normalizeRem(editable);
expect(editable).toHaveInnerHTML(
`
`,
{
message: "should have converted several rem sizes to px using the default rem size",
}
);
editable.innerHTML = testDom;
normalizeRem(editable, 20);
expect(editable).toHaveInnerHTML(
`
`,
{
message: "should have converted several rem sizes to px using a set rem size",
}
);
});
test("move padding from snippet containers to cells", async () => {
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 =
`
` +
`` +
`` +
`(0, 0, 0) | ` + // TL
`(0, 1, 0) | ` + // T
`(0, 2, 0) | ` + // T
`(0, 3, 0) | ` + // T
`(0, 4, 0) | ` + // TR
`
` +
`` +
`` + // LR
`` +
`` +
`` +
`(0, 0, 1) | ` + // TBL
`(0, 1, 1) | ` + // TB
`(0, 2, 1) | ` + // TB
`(0, 3, 1) | ` + // TBR
` ` +
`` +
` ` +
` | ` +
`
` +
`` +
`(1, 0, 0) | ` + // BL
`(1, 1, 0) | ` + // B
`(1, 2, 0) | ` + // B
`(1, 3, 0) | ` + // B
`(1, 4, 0) | ` + // BR
`
` +
`` +
`
`;
// table.o_mail_snippet_general
editable.innerHTML = testTable;
formatTables(editable);
expect(editable).toHaveInnerHTML(expectedTable, {
message:
"should have moved the padding from table.o_mail_snippet_general and table in it to their respective cells",
});
});
test("add a tbody to any table that doesn't have one", async () => {
editable.innerHTML = `
`;
// unwrap tr (remove )
const tr = editable.querySelector("tr");
const body = tr.parentElement;
body.parentElement.appendChild(tr);
tr.parentElement.removeChild(body);
formatTables(editable);
expect(editable).toHaveInnerHTML(
`
`,
{ message: "should have added a tbody to a table that didn't have one" }
);
});
test("add number heights to parents of elements with percent heights", async () => {
editable.innerHTML = `
`;
formatTables(editable);
expect(editable).toHaveInnerHTML(
`
`,
{ message: "should have added a 0 height to the parent of a 100% height element" }
);
editable.innerHTML = `
`;
formatTables(editable);
expect(editable).toHaveInnerHTML(
`
`,
{ message: "should have added a 0 height to the parent of a 100% height element" }
);
editable.innerHTML = `
`;
formatTables(editable);
expect(editable).toHaveInnerHTML(
`
`,
{
message:
"should have changed the height of the grandparent of a 100% height element",
}
);
});
test("express align-self with vertical-align on table cells", async () => {
editable.innerHTML = `
`;
formatTables(editable);
expect(editable).toHaveInnerHTML(
`
`,
{ message: "should have added a top vertical alignment" }
);
editable.innerHTML = `
`;
formatTables(editable);
expect(editable).toHaveInnerHTML(
`
`,
{ message: "should have added a middle vertical alignment" }
);
editable.innerHTML = `
`;
formatTables(editable);
expect(editable).toHaveInnerHTML(
`
`,
{ message: "should have added a bottom vertical alignment" }
);
});
});
describe("Convert snippets and mailing bodies to tables", () => {
// Test addTables
beforeEach(() => {
editable = document.createElement("div");
});
test("convert snippets to tables", async () => {
editable.innerHTML = `
`;
addTables(editable);
expect(editable).toHaveInnerHTML(
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 "
)
),
{
message:
"should have converted .o_mail_snippet_general to a special table structure with a table in it",
}
);
editable.innerHTML = `
`;
addTables(editable);
expect(editable).toHaveInnerHTML(
getRegularTableHtml(1, 1, 12, 100)
.split("style=")
.join('class="o_mail_snippet_general" style=')
.replace(
/ | ]*>\(0, 0\)/,
" | "
),
{
message:
"should have converted .o_mail_snippet_general to a special table structure, keeping the table in it",
}
);
});
test("convert mailing bodies to tables", async () => {
editable.innerHTML = ``;
addTables(editable);
expect(editable).toHaveInnerHTML(
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 "
)
),
{
message:
"should have converted .o_layout to a special table structure with a table in it",
}
);
editable.innerHTML = `
`;
addTables(editable);
expect(editable).toHaveInnerHTML(
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\)/,
" | "
),
{
message:
"should have converted .o_layout to a special table structure, keeping the table in it",
}
);
});
});
describe("Convert classes to inline styles", () => {
// Test classToStyle
let styleEl, styleSheet;
beforeEach(() => {
editable = document.createElement("div");
styleEl = document.createElement("style");
styleEl.type = "text/css";
styleEl.title = "test-stylesheet";
document.head.appendChild(styleEl);
styleSheet = [...document.styleSheets].find((sheet) => sheet.title === "test-stylesheet");
});
afterEach(() => {
// @todo to adapt when hoot has a better way to remove it
document.head.removeChild(styleEl);
});
test("convert Bootstrap classes to inline styles", async () => {
editable.innerHTML = `
`;
getFixture().append(editable); // editable needs to be in the DOM to compute its dynamic styles.
classToStyle(editable, getCSSRules(editable.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%;`;
expect(editable).toHaveInnerHTML(
``,
{
message:
"should have converted the classes of a simple Bootstrap grid to inline styles",
}
);
});
test("simplify border/margin/padding styles", async () => {
// border-radius
styleSheet.insertRule(
`
.test-border-radius {
border-top-right-radius: 10%;
border-bottom-right-radius: 20%;
border-bottom-left-radius: 30%;
border-top-left-radius: 40%;
}
`,
0
);
editable.innerHTML = ``;
classToStyle(editable, getCSSRules(editable.ownerDocument));
expect(editable).toHaveInnerHTML(
``,
{
message:
"should have converted border-[position]-radius styles (from class) to border-radius",
}
);
styleSheet.deleteRule(0);
// convert all positional styles to a style in the form `property: a b c d`
styleSheet.insertRule(
`
.test-border {
border-top-style: dotted;
border-right-style: dashed;
border-left-style: solid;
}
`,
0
);
editable.innerHTML = ``;
classToStyle(editable, getCSSRules(editable.ownerDocument));
expect(editable).toHaveInnerHTML(
``,
{
message:
"should have converted border-[position]-style styles (from class) to border-style",
}
);
styleSheet.deleteRule(0);
styleSheet.insertRule(
`
.test-margin {
margin-right: 20px;
margin-bottom: 30px;
margin-left: 40px;
}
`,
0
);
editable.innerHTML = ``;
classToStyle(editable, getCSSRules(editable.ownerDocument));
expect(editable).toHaveInnerHTML(
``,
{ message: "should have converted margin-[position] styles (from class) to margin" }
);
styleSheet.deleteRule(0);
styleSheet.insertRule(
`
.test-padding {
padding-top: 10px;
padding-bottom: 30px;
padding-left: 40px;
}
`,
0
);
editable.innerHTML = ``;
classToStyle(editable, getCSSRules(editable.ownerDocument));
expect(editable).toHaveInnerHTML(
``,
{ message: "should have converted padding-[position] styles (from class) to padding" }
);
styleSheet.deleteRule(0);
// convert all positional styles to a style in the form `property: a`
styleSheet.insertRule(
`
.test-border-uniform {
border-top-style: dotted;
border-right-style: dotted;
border-bottom-style: dotted;
border-left-style: dotted;
}
`,
0
);
editable.innerHTML = ``;
classToStyle(editable, getCSSRules(editable.ownerDocument));
expect(editable).toHaveInnerHTML(
``,
{
message:
"should have converted uniform border-[position]-style styles (from class) to border-style",
}
);
styleSheet.deleteRule(0);
styleSheet.insertRule(
`
.test-margin-uniform {
margin-top: 10px;
margin-right: 10px;
margin-bottom: 10px;
margin-left: 10px;
}
`,
0
);
editable.innerHTML = ``;
classToStyle(editable, getCSSRules(editable.ownerDocument));
expect(editable).toHaveInnerHTML(
``,
{
message:
"should have converted uniform margin-[position] styles (from class) to margin",
}
);
styleSheet.deleteRule(0);
styleSheet.insertRule(
`
.test-padding-uniform {
padding-top: 10px;
padding-right: 10px;
padding-bottom: 10px;
padding-left: 10px;
}
`,
0
);
editable.innerHTML = ``;
classToStyle(editable, getCSSRules(editable.ownerDocument));
expect(editable).toHaveInnerHTML(
``,
{
message:
"should have converted uniform padding-[position] styles (from class) to padding",
}
);
styleSheet.deleteRule(0);
// do not convert positional styles that include an "inherit" value
styleSheet.insertRule(
`
.test-border-inherit {
border-top-style: dotted;
border-right-style: dashed;
border-bottom-style: inherit;
border-left-style: solid;
}
`,
0
);
editable.innerHTML = ``;
classToStyle(editable, getCSSRules(editable.ownerDocument));
expect(editable).toHaveInnerHTML(
``,
{
message:
"should not have converted border-[position]-style styles (from class) to border-style as they include an inherit",
}
);
styleSheet.deleteRule(0);
styleSheet.insertRule(
`
.test-margin-inherit {
margin-top: 10px;
margin-right: inherit;
margin-bottom: 30px;
margin-left: 40px;
}
`,
0
);
editable.innerHTML = ``;
classToStyle(editable, getCSSRules(editable.ownerDocument));
expect(editable).toHaveInnerHTML(
``,
{
message:
"should not have converted margin-[position] styles (from class) to margin as they include an inherit",
}
);
styleSheet.deleteRule(0);
styleSheet.insertRule(
`
.test-padding-inherit {
padding-top: 10px;
padding-right: 20px;
padding-bottom: inherit;
padding-left: 40px;
}
`,
0
);
editable.innerHTML = ``;
classToStyle(editable, getCSSRules(editable.ownerDocument));
expect(editable).toHaveInnerHTML(
``,
{
message:
"should have converted padding-[position] styles (from class) to padding as they include an inherit",
}
);
styleSheet.deleteRule(0);
// do not convert positional styles that include an "initial" value
// note: `border: initial` is automatically removed (tested in "remove
// unsupported styles")
styleSheet.insertRule(
`
.test-margin-initial {
margin-top: initial;
margin-right: 20px;
margin-bottom: 30px;
margin-left: 40px;
}
`,
0
);
editable.innerHTML = ``;
classToStyle(editable, getCSSRules(editable.ownerDocument));
expect(editable).toHaveInnerHTML(
``,
{
message:
"should not have converted margin-[position] styles (from class) to margin as they include an initial",
}
);
styleSheet.deleteRule(0);
styleSheet.insertRule(
`
.test-padding-initial {
padding-top: 10px;
padding-right: 20px;
padding-bottom: 30px;
padding-left: initial;
}
`,
0
);
editable.innerHTML = ``;
classToStyle(editable, getCSSRules(editable.ownerDocument));
expect(editable).toHaveInnerHTML(
``,
{
message:
"should not have converted padding-[position] styles (from class) to padding as they include an initial",
}
);
styleSheet.deleteRule(0);
// @todo to adapt when hoot has a better way to remove it
});
test("remove unsupported styles", async () => {
// text-decoration-[prop]
styleSheet.insertRule(
`
.test-decoration {
text-decoration-line: underline;
text-decoration-color: red;
text-decoration-style: solid;
text-decoration-thickness: 10px;
}
`,
0
);
editable.innerHTML = ``;
classToStyle(editable, getCSSRules(editable.ownerDocument));
expect(editable).toHaveInnerHTML(
``,
{
message:
"should have removed all text-decoration-[prop] styles (from class) and kept a simple text-decoration",
}
);
styleSheet.deleteRule(0);
// border[\w-]*: initial
styleSheet.insertRule(
`
.test-border-initial {
border-top-style: dotted;
border-right-style: dashed;
border-bottom-style: double;
border-left-style: initial;
}
`,
0
);
editable.innerHTML = ``;
classToStyle(editable, getCSSRules(editable.ownerDocument));
expect(editable).toHaveInnerHTML(
``,
{ message: "should have removed border initial" }
);
styleSheet.deleteRule(0);
// display: block
styleSheet.insertRule(
`
.test-block {
display: block;
}
`,
0
);
editable.innerHTML = ``;
classToStyle(editable, getCSSRules(editable.ownerDocument));
expect(editable).toHaveInnerHTML(
``,
{ message: "should have removed display block" }
);
styleSheet.deleteRule(0);
// !important
styleSheet.insertRule(
`
.test-unimportant-color {
color: blue;
}
`,
0
);
editable.innerHTML = ``;
classToStyle(editable, getCSSRules(editable.ownerDocument));
expect(editable).toHaveInnerHTML(
``,
{ message: "should have converted a simple color" }
);
styleSheet.insertRule(
`
.test-important-color {
color: red !important;
}
`,
0
);
editable.innerHTML = ``;
classToStyle(editable, getCSSRules(editable.ownerDocument));
expect(editable).toHaveInnerHTML(
``,
{ message: "should have converted an important color and removed the !important" }
);
styleSheet.deleteRule(0);
styleSheet.deleteRule(0);
// animation
styleSheet.insertRule(
`
.test-animation {
animation: example 5s linear 2s infinite alternate;
}
`,
0
);
editable.innerHTML = ``;
classToStyle(editable, getCSSRules(editable.ownerDocument));
expect(editable).toHaveInnerHTML(
``,
{ message: "should have removed animation style" }
);
styleSheet.deleteRule(0);
styleSheet.insertRule(
`
.test-animation-specific {
animation-name: example;
animation-duration: 5s;
animation-timing-function: linear;
animation-delay: 2s;
animation-iteration-count: infinite;
animation-direction: alternate;
}
`,
0
);
editable.innerHTML = ``;
classToStyle(editable, getCSSRules(editable.ownerDocument));
expect(editable).toHaveInnerHTML(
``,
{ message: "should have removed all specific animation styles" }
);
styleSheet.deleteRule(0);
// flex
styleSheet.insertRule(
`
.test-flex {
flex: 0 1 auto;
flex-flow: column wrap;
}
`,
0
);
editable.innerHTML = ``;
classToStyle(editable, getCSSRules(editable.ownerDocument));
expect(editable).toHaveInnerHTML(
``,
{ message: "should have removed all flex styles" }
);
styleSheet.deleteRule(0);
styleSheet.insertRule(
`
.test-flex-specific {
display: flex;
flex-direction: row;
flex-wrap: wrap;
flex-basis: auto;
flex-shrink: 3;
flex-grow: 4;
}
`,
0
);
editable.innerHTML = ``;
classToStyle(editable, getCSSRules(editable.ownerDocument));
expect(editable).toHaveInnerHTML(
``,
{ message: "should have removed all specific flex styles" }
);
styleSheet.deleteRule(0);
// @todo to adapt when hoot has a better way to remove it
});
test("give .o_layout the styles of the body", async () => {
const iframe = document.createElement("IFRAME");
getFixture().append(iframe);
const iframeEditable = document.createElement("div");
iframe.contentDocument.body.append(iframeEditable);
const styleEl = document.createElement("style");
styleEl.type = "text/css";
styleEl.title = "test-stylesheet";
iframe.contentDocument.head.appendChild(styleEl);
const styleSheet = [...iframe.contentDocument.styleSheets].find(
(sheet) => sheet.title === "test-stylesheet"
);
styleSheet.insertRule(
`
body {
background-color: red;
color: white;
font-size: 50px;
}
`,
0
);
iframeEditable.innerHTML = ``;
classToStyle(iframeEditable, getCSSRules(iframeEditable.ownerDocument));
expect(iframeEditable).toHaveInnerHTML(
``,
{ message: "should have given all styles of body to .o_layout" }
);
});
test("convert classes to styles, preserving specificity", async () => {
styleSheet.insertRule(
`
div.test-color {
color: green;
}
`,
0
);
styleSheet.insertRule(
`
.test-color {
color: red;
}
`,
1
);
styleSheet.insertRule(
`
.test-color {
color: blue;
}
`,
2
);
editable.innerHTML = ``;
classToStyle(editable, getCSSRules(editable.ownerDocument));
expect(editable).toHaveInnerHTML(
``,
{ message: "should have prioritized the last defined style" }
);
editable.innerHTML = ``;
classToStyle(editable, getCSSRules(editable.ownerDocument));
expect(editable).toHaveInnerHTML(
``,
{ message: "should have prioritized the more specific style" }
);
editable.innerHTML = ``;
classToStyle(editable, getCSSRules(editable.ownerDocument));
expect(editable).toHaveInnerHTML(
``,
{ message: "should have prioritized the inline style" }
);
styleSheet.insertRule(
`
.test-color {
color: black !important;
}
`,
0
);
editable.innerHTML = ``;
classToStyle(editable, getCSSRules(editable.ownerDocument));
expect(editable).toHaveInnerHTML(
``,
{ message: "should have prioritized the important style" }
);
// @todo to adapt when hoot has a better way to remove it
});
});
|