Odoo18-Base/addons/html_editor/static/tests/file.test.js
2025-03-04 12:23:19 +07:00

191 lines
8.5 KiB
JavaScript

import { beforeEach, describe, expect, test } from "@odoo/hoot";
import { setupEditor } from "./_helpers/editor";
import { EMBEDDED_COMPONENT_PLUGINS, MAIN_PLUGINS } from "@html_editor/plugin_sets";
import { insertText } from "./_helpers/user_actions";
import { onRpc, patchWithCleanup } from "@web/../tests/web_test_helpers";
import { animationFrame, click, press, queryOne, waitFor } from "@odoo/hoot-dom";
import { execCommand } from "./_helpers/userCommands";
import { MAIN_EMBEDDINGS } from "@html_editor/others/embedded_components/embedding_sets";
import { getContent } from "./_helpers/selection";
import { isZwnbsp } from "@html_editor/utils/dom_info";
import { EmbeddedFilePlugin } from "@html_editor/others/embedded_components/plugins/embedded_file_plugin/embedded_file_plugin";
const configWithEmbeddedFile = {
Plugins: [
...MAIN_PLUGINS.filter((P) => P.id !== "file"),
EmbeddedFilePlugin,
...EMBEDDED_COMPONENT_PLUGINS,
],
resources: { embedded_components: MAIN_EMBEDDINGS },
};
const patchUpload = (editor) => {
const mockedUploadPromise = new Promise((resolve) => {
patchWithCleanup(editor.services.uploadLocalFiles, {
async upload() {
resolve();
return [{ id: 1, name: "file.txt" }];
},
});
});
return mockedUploadPromise;
};
describe("file command", () => {
test("/file uploads a file via the system's selector, skipping the media dialog", async () => {
const { editor } = await setupEditor("<p>[]<br></p>");
const mockedUpload = patchUpload(editor);
// Open powerbox.
await insertText(editor, "/file");
await animationFrame();
// Select first command ("Upload a file")
await press("Enter");
await animationFrame();
// Check that there's no media dialog.
expect(".o_select_media_dialog").toHaveCount(0);
await mockedUpload;
// Check that file card (embedded component) was inserted in the editable.
expect(".odoo-editor-editable .o_file_box").toHaveCount(1);
});
test("file card should have inline display, BS alert-info style and no download button", async () => {
const { editor } = await setupEditor("<p>[]<br></p>");
patchUpload(editor);
execCommand(editor, "uploadFile");
// wait for the embedded component to be mounted
await waitFor('.o_file_box .o_file_name_container:contains("file.txt")');
// Check that file card has inline display, with alert style.
const fileCard = queryOne(".o_file_box");
expect(fileCard).toHaveStyle({ display: "inline-block" });
expect(fileCard.firstElementChild).toHaveClass(["alert", "alert-info"]);
// No download button in file card.
expect(".o_file_box .fa-download").toHaveCount(0);
});
});
describe("document tab in media dialog", () => {
beforeEach(() =>
onRpc("/web/dataset/call_kw/ir.attachment/search_read", () => [
{
id: 1,
name: "file.txt",
mimetype: "text/plain",
public: true,
image_src: "",
},
])
);
describe("without File nor EmbeddedFile plugin", () => {
test("Document tab is not available by default", async () => {
const { editor } = await setupEditor("<p>[]<br></p>", {
config: { Plugins: MAIN_PLUGINS.filter((p) => p.id !== "file") },
});
execCommand(editor, "insertMedia");
await animationFrame();
expect(".nav-link:contains('Documents')").toHaveCount(0);
});
});
describe("with EmbeddedFile plugin", () => {
test("file upload via media dialog inserts a file card in the editable", async () => {
const { editor } = await setupEditor("<p>[]<br></p>", {
config: configWithEmbeddedFile,
});
execCommand(editor, "insertMedia");
await animationFrame();
await click(".nav-link:contains('Documents')");
await animationFrame();
await click(".o_we_attachment_highlight");
// wait for the embedded component to be mounted
await waitFor('[data-embedded="file"] .o_file_name:contains("file.txt")');
expect('[data-embedded="file"]').toHaveCount(1);
});
});
describe("with File plugin (no embedded component)", () => {
test("file upload via media dialog inserts a link in the editable", async () => {
const { editor } = await setupEditor("<p>[]<br></p>");
execCommand(editor, "insertMedia");
await animationFrame();
await click(".nav-link:contains('Documents')");
await animationFrame();
await click(".o_we_attachment_highlight");
expect(".odoo-editor-editable .o_file_box a:contains('file.txt')").toHaveCount(1);
});
});
});
describe("powerbutton", () => {
test("file powerbutton uploads a file directly via the system's selector", async () => {
const { editor } = await setupEditor("<p>[]<br></p>");
const mockedUpload = patchUpload(editor);
// Click on the upload powerbutton.
await click(".power_button.fa-upload");
await animationFrame();
// Check that there's no media dialog.
expect(".o_select_media_dialog").toHaveCount(0);
await mockedUpload;
// Check that file card (embedded component) was inserted in the editable.
expect(".odoo-editor-editable .o_file_box").toHaveCount(1);
});
test("file powerbutton uploads a file directly via the system's selector (embedded component)", async () => {
const { editor } = await setupEditor("<p>[]<br></p>", { config: configWithEmbeddedFile });
const mockedUpload = patchUpload(editor);
// Click on the upload powerbutton.
await click(".power_button.fa-upload");
await animationFrame();
// Check that there's no media dialog.
expect(".o_select_media_dialog").toHaveCount(0);
await mockedUpload;
// Check that file card (embedded component) was inserted in the editable.
expect('.odoo-editor-editable [data-embedded="file"]').toHaveCount(1);
});
});
describe("zero width no-break space", () => {
test("file card should be padded with zero-width no-break spaces", async () => {
const { editor } = await setupEditor("<p>[]<br></p>");
patchUpload(editor);
execCommand(editor, "uploadFile");
// wait for the the file to be uploaded and the card rendered
await waitFor('.o_file_box a:contains("file.txt")');
// Check that file card is padded with ZWNBSP on both sides.
const fileCard = queryOne(".o_file_box");
expect(isZwnbsp(fileCard.previousSibling)).toBe(true);
expect(isZwnbsp(fileCard.nextSibling)).toBe(true);
});
test("should not add two contiguous ZWNBSP between two file cards", async () => {
const { editor, el } = await setupEditor("<p>[]<br></p>", {
config: { ...configWithEmbeddedFile, resources: {} }, // disable embedded component rendering
});
let mockUpload = patchUpload(editor);
execCommand(editor, "uploadFile");
await mockUpload;
// patch again to get new Promise
mockUpload = patchUpload(editor);
execCommand(editor, "uploadFile");
await mockUpload;
let content = getContent(el);
// replace embedded component root with a <FILE/> placeholder for readability
content = content.replace(/<span data-embedded="file".*?<\/span>/g, "<FILE/>");
expect(content).toBe("<p>\ufeff<FILE/>\ufeff<FILE/>\ufeff[]</p>");
});
test("should not add two contiguous ZWNBSP between two file cards (2)", async () => {
const { el } = await setupEditor(
'<p>abc<span data-embedded="file" class="o_file_box"></span>x[]<span data-embedded="file" class="o_file_box"></span></p>',
{ config: { ...configWithEmbeddedFile, resources: {} } } // disable embedded component rendering
);
expect(getContent(el)).toBe(
'<p>abc\ufeff<span data-embedded="file" class="o_file_box"></span>\ufeffx[]\ufeff<span data-embedded="file" class="o_file_box"></span>\ufeff</p>'
);
press("Backspace");
expect(getContent(el)).toBe(
'<p>abc\ufeff<span data-embedded="file" class="o_file_box"></span>\ufeff[]<span data-embedded="file" class="o_file_box"></span>\ufeff</p>'
);
});
});