Odoo18-Base/addons/html_editor/static/tests/_helpers/tabs.js
2025-01-06 10:57:38 +07:00

135 lines
4.1 KiB
JavaScript

import { expect } from "@odoo/hoot";
import { testEditor } from "./editor";
export const TAB_WIDTH = 40;
let charWidths = undefined;
let indentWidths = undefined;
function setWidths() {
if (charWidths && indentWidths) {
// charWidths and indentWidths are global variables that need to be set
// only once.
return;
}
charWidths = {};
indentWidths = {};
const rootDiv = document.createElement("div");
rootDiv.classList.add("odoo-editor-editable");
rootDiv.contentEditable = true;
document.body.append(rootDiv);
const referenceBlock = document.createElement("p");
rootDiv.append(referenceBlock);
const referenceLeft = referenceBlock.getBoundingClientRect().left;
const range = new Range();
const tags = ["p", "h1", "blockquote", "li"];
const chars = ["a", "b", "c", "d", "e", "f"];
for (const tag of tags) {
let element;
if (tag === "li") {
const ul = document.createElement("ul");
element = document.createElement("li");
ul.append(element);
rootDiv.append(ul);
} else {
element = document.createElement(tag);
rootDiv.append(element);
}
// Calculate the base indentation (result of margin, padding and border)
// for the given block.
element.textContent = "|";
range.selectNodeContents(element);
const indentWidth = range.getBoundingClientRect().left - referenceLeft;
indentWidths[tag] = indentWidth;
// Calculate the width of each char in the given block.
charWidths[tag] = {};
for (const char of chars) {
element.textContent = char;
range.selectNodeContents(element);
const width = range.getBoundingClientRect().width;
charWidths[tag][char] = width;
}
}
rootDiv.remove();
}
export function getCharWidth(tag, char) {
if (!charWidths) {
setWidths();
}
return charWidths[tag][char];
}
export function getIndentWidth(tag) {
if (!indentWidths) {
setWidths();
}
return indentWidths[tag];
}
export function oeTab(size, contenteditable = true) {
return (
`<span class="oe-tabs"` +
(contenteditable ? "" : ' contenteditable="false"') +
(size ? ` style="width: ${size.toFixed(1)}px;"` : "") +
`>\u0009</span>\u200B`
);
}
/**
* Extracts the style.width values from the given content and replaces them with a placeholder.
* @param {string} content
* @returns {Object} - { text: string, widths: number[] }
*/
function extractWidth(content) {
const regex = /width: ([\d.]+)px;/g;
const widths = [];
const text = content.replaceAll(regex, (_, w) => {
widths.push(parseFloat(w));
return `width: _px;`;
});
return { text, widths };
}
/**
* Compares the two contents with hoot expect.
* Style.width values are allowed to differ by a margin of tolerance.
*
* @param {string} contentEl
* @param {string} contentSpec
* @param {"contentAfterEdit"|"contentAfter"} mode
*/
function compare(contentEl, contentSpec, mode) {
const maxDiff = 0.5;
const { text: receivedContent, widths: receivedWidths } = extractWidth(contentEl);
const { text: expectedContent, widths: expectedWidths } = extractWidth(contentSpec);
expect(receivedContent).toBe(expectedContent, {
message: `(testEditor) ${mode} is strictly equal to %actual%`,
});
const diffs = expectedWidths.map((width, i) => Math.abs(width - receivedWidths[i]));
expect(Math.max(...diffs)).toBeLessThan(maxDiff, {
message:
`(testEditor) (${mode}) tab widths differ by less than ${maxDiff} pixel\n` +
diffs
.map(
(diff, i) =>
`tab[${i}] ` +
`received: ${receivedWidths[i]}, ` +
`expected: ${expectedWidths[i]}, ` +
`diff: ${diff.toFixed(1)}`
)
.join("\n"),
});
}
export function testTabulation(params) {
return testEditor({ ...params, compareFunction: compare });
}