replace stale web_editor with html_editor and html_builder for 19.0

web_editor was removed in Odoo 19.0 and replaced by html_editor
and html_builder. The old web_editor was incorrectly included in
the 19.0 vanilla import.

🤖 assisted by claude
This commit is contained in:
Ernad Husremovic 2026-03-09 15:31:13 +01:00
parent 4b94f0abc5
commit f866779561
1513 changed files with 396049 additions and 358525 deletions

View file

@ -0,0 +1,201 @@
import {
setupHTMLBuilder,
getDragHelper,
waitForEndOfOperation,
} from "@html_builder/../tests/helpers";
import { BuilderOptionsPlugin } from "@html_builder/core/builder_options_plugin";
import { Operation } from "@html_builder/core/operation";
import { describe, expect, test } from "@odoo/hoot";
import {
animationFrame,
click,
Deferred,
queryAll,
queryAllTexts,
queryOne,
waitFor,
} from "@odoo/hoot-dom";
import { contains, patchWithCleanup } from "@web/../tests/web_test_helpers";
import { loadBundle } from "@web/core/assets";
describe.current.tags("desktop");
const snippetContent = [
`<div name="Button A" data-oe-thumbnail="buttonA.svg" data-oe-snippet-id="123">
<a class="btn btn-primary" href="#" data-snippet="s_button">Button A</a>
</div>`,
`<div name="Button B" data-oe-thumbnail="buttonB.svg" data-oe-snippet-id="123">
<a class="btn btn-primary" href="#" data-snippet="s_button">Button B</a>
</div>`,
];
const dropzoneSelectors = [
{
selector: "*",
dropNear: "p",
},
];
test("Display inner content snippet", async () => {
await setupHTMLBuilder("<div><p>Text</p></div>", {
snippetContent,
dropzoneSelectors,
});
const snippetInnerContentSelector = ".o-snippets-menu #snippet_content .o_snippet";
expect(snippetInnerContentSelector).toHaveCount(2);
expect(queryAllTexts(snippetInnerContentSelector)).toEqual(["Button A", "Button B"]);
const thumbnailImgUrls = queryAll(
`${snippetInnerContentSelector} .o_snippet_thumbnail_img`
).map((thumbnail) => thumbnail.style.backgroundImage);
expect(thumbnailImgUrls).toEqual(['url("buttonA.svg")', 'url("buttonB.svg")']);
});
test("Drag & drop inner content block", async () => {
const { contentEl } = await setupHTMLBuilder("<div><p>Text</p></div>", {
snippetContent,
dropzoneSelectors,
});
expect(contentEl).toHaveInnerHTML(`<div><p>Text</p></div>`);
expect(".o-website-builder_sidebar .fa-undo").not.toBeEnabled();
const { moveTo, drop } = await contains(
".o-website-builder_sidebar [name='Button A'] .o_snippet_thumbnail"
).drag();
expect(":iframe .oe_drop_zone:nth-child(1)").toHaveCount(1);
expect(":iframe .oe_drop_zone:nth-child(3)").toHaveCount(1);
expect(".o-website-builder_sidebar .fa-undo").not.toBeEnabled();
await moveTo(":iframe .oe_drop_zone");
expect(":iframe .oe_drop_zone.invisible:nth-child(1)").toHaveCount(1);
expect(".o-website-builder_sidebar .fa-undo").not.toBeEnabled();
await drop(getDragHelper());
await waitForEndOfOperation();
expect(contentEl).toHaveInnerHTML(
`<div>\ufeff<a class="btn btn-primary" href="#" data-snippet="s_button" data-name="Button A">\ufeffButton A\ufeff</a>\ufeff<p>Text</p></div>`
);
expect(".o-website-builder_sidebar .fa-undo").toBeEnabled();
});
test("Drag & drop inner content block + undo/redo", async () => {
const { contentEl } = await setupHTMLBuilder("<div><p>Text</p></div>", {
snippetContent,
dropzoneSelectors,
});
expect(contentEl).toHaveInnerHTML(`<div><p>Text</p></div>`);
expect(".o-website-builder_sidebar .fa-undo").not.toBeEnabled();
expect(".o-website-builder_sidebar .fa-repeat").not.toBeEnabled();
await click(".o-website-builder_sidebar .fa-undo");
const { moveTo, drop } = await contains(
".o-website-builder_sidebar [name='Button A'] .o_snippet_thumbnail"
).drag();
await moveTo(":iframe .oe_drop_zone");
await drop(getDragHelper());
await waitForEndOfOperation();
expect(contentEl).toHaveInnerHTML(
`<div>\ufeff<a class="btn btn-primary" href="#" data-snippet="s_button" data-name="Button A">\ufeffButton A\ufeff</a>\ufeff<p>Text</p></div>`
);
expect(".o-website-builder_sidebar .fa-undo").toBeEnabled();
expect(".o-website-builder_sidebar .fa-repeat").not.toBeEnabled();
await click(".o-website-builder_sidebar .fa-undo");
await animationFrame();
expect(contentEl).toHaveInnerHTML(`<div><p>Text</p></div>`);
expect(".o-website-builder_sidebar .fa-undo").not.toBeEnabled();
expect(".o-website-builder_sidebar .fa-repeat").toBeEnabled();
});
test("Drag inner content and drop it outside of a dropzone", async () => {
const { contentEl, builderEl } = await setupHTMLBuilder("<div><p>Text</p></div>", {
snippetContent,
dropzoneSelectors,
});
expect(contentEl).toHaveInnerHTML(`<div><p>Text</p></div>`);
const { moveTo, drop } = await contains(
".o-website-builder_sidebar [name='Button A'] .o_snippet_thumbnail"
).drag();
expect(":iframe .oe_drop_zone:nth-child(1)").toHaveCount(1);
expect(":iframe .oe_drop_zone:nth-child(3)").toHaveCount(1);
await moveTo(builderEl);
await drop(getDragHelper());
await waitForEndOfOperation();
expect(contentEl).toHaveInnerHTML(`<div><p>Text</p></div>`);
});
test("A snippet should appear disabled if there is nowhere to drop it", async () => {
const { contentEl } = await setupHTMLBuilder("", {
snippetContent,
dropzoneSelectors,
});
expect(contentEl).toHaveInnerHTML("");
expect(".o_block_tab .o_snippet.o_disabled").toHaveCount(2);
});
test.tags("desktop");
test("click just after drop is redispatched in next operation", async () => {
const nextDef = new Deferred();
patchWithCleanup(Operation.prototype, {
next(fn, ...args) {
const originalFn = fn;
fn = async () => {
await originalFn();
nextDef.resolve();
};
expect.step(`next${args[0]?.shouldInterceptClick ? " should intercept" : ""}`);
const res = super.next(fn, ...args);
return res;
},
});
patchWithCleanup(BuilderOptionsPlugin.prototype, {
async onClick(ev) {
expect.step("onClick");
super.onClick(ev);
},
updateContainers(...args) {
expect.step("updateContainers");
super.updateContainers(...args);
},
});
await setupHTMLBuilder("", {
styleContent: /*css*/ `
.o_loading_screen {
position: absolute;
inset: 0;
}
section {
height: 100%; /* to easily target */
}`,
});
// TODO: the next lines replicate website's `insertCategorySnippet` helper.
// It should be moved to html_builder.
await contains(".o-snippets-menu #snippet_groups .o_snippet_thumbnail_area").click();
await animationFrame();
await loadBundle("html_builder.iframe_add_dialog", {
targetDoc: queryOne("iframe.o_add_snippet_iframe").contentDocument,
js: false,
});
await waitFor(".o_add_snippet_dialog iframe.show.o_add_snippet_iframe");
await contains(
".o_add_snippet_dialog .o_add_snippet_iframe:iframe .o_snippet_preview_wrap"
).click();
await animationFrame();
expect.verifySteps(["next should intercept"]); // On snippet selected
await waitFor(":iframe .o_loading_screen");
await click(":iframe", { position: { x: 200, y: 50 }, relative: true });
expect.verifySteps(["next"]); // On click
await nextDef;
expect.verifySteps(["updateContainers"]); // End of drop, on addStep()
await animationFrame();
expect.verifySteps(["onClick", "next", "updateContainers"]); // On click redispatched
await animationFrame();
expect(".o-snippets-tabs .o-hb-tab.active").toHaveText("Style");
});

View file

@ -0,0 +1,533 @@
import {
addBuilderPlugin,
addDropZoneSelector,
createTestSnippets,
getDragHelper,
getSnippetStructure,
setupHTMLBuilder,
setupHTMLBuilderWithDummySnippet,
waitForEndOfOperation,
waitForSnippetDialog,
} from "@html_builder/../tests/helpers";
import { Builder } from "@html_builder/builder";
import { Plugin } from "@html_editor/plugin";
import { beforeEach, expect, test, describe } from "@odoo/hoot";
import { animationFrame, click, queryAll, queryAllTexts, queryFirst } from "@odoo/hoot-dom";
import { contains, onRpc, patchWithCleanup } from "@web/../tests/web_test_helpers";
describe.current.tags("desktop");
let snippets;
beforeEach(() => {
snippets = {
snippet_groups: [
'<div name="A" data-o-image-preview="" data-oe-thumbnail="a.svg" data-oe-snippet-id="123" data-oe-keywords="" data-o-snippet-group="a"><section class="s_snippet_group" data-snippet="s_snippet_group"></section></div>',
'<div name="B" data-o-image-preview="" data-oe-thumbnail="b.svg" data-oe-snippet-id="123" data-oe-keywords="" data-o-snippet-group="b"><section class="s_snippet_group" data-snippet="s_snippet_group"></section></div>',
'<div name="C" data-o-image-preview="" data-oe-thumbnail="c.svg" data-oe-snippet-id="123" data-oe-keywords="" data-o-snippet-group="c"><section class="s_snippet_group" data-snippet="s_snippet_group"></section></div>',
],
};
addDropZoneSelector({
selector: "*",
dropNear: "section",
});
});
test("display group snippet", async () => {
await setupHTMLBuilder("<div><p>Text</p></div>", {
snippets,
});
const snippetGroupsSelector = ".o-snippets-menu #snippet_groups .o_snippet";
expect(snippetGroupsSelector).toHaveCount(3);
expect(queryAllTexts(snippetGroupsSelector)).toEqual(["A", "B", "C"]);
const thumbnailImgUrls = queryAll(`${snippetGroupsSelector} .o_snippet_thumbnail_img`).map(
(thumbnail) => thumbnail.style.backgroundImage
);
expect(thumbnailImgUrls).toEqual(['url("a.svg")', 'url("b.svg")', 'url("c.svg")']);
});
test("install an app from snippet group", async () => {
patchWithCleanup(Builder.prototype, {
setup() {
this.props.installSnippetModule = ({ moduleId }) => {
expect(moduleId).toEqual("111");
expect.step(`button_immediate_install`);
};
super.setup(...arguments);
},
});
await setupHTMLBuilder("<div><p>Text</p></div>", {
snippets: {
snippet_groups: [
'<div name="A" data-module-id="111" data-module-display-name="module_A" data-oe-thumbnail="a.svg"><section class="s_snippet_group" data-snippet="s_snippet_group"></section></div>',
],
},
});
await click(`.o-snippets-menu #snippet_groups .o_snippet .btn.o_install_btn`);
await animationFrame();
expect(".modal").toHaveCount(1);
expect(".modal-body").toHaveText(
"Do you want to install module_A App?\nMore info about this app."
);
await contains(".modal .btn-primary:contains('Save and Install')").click();
expect.verifySteps([`button_immediate_install`]);
});
test("install an app from snippet structure", async () => {
patchWithCleanup(Builder.prototype, {
setup() {
this.props.installSnippetModule = ({ moduleId }) => {
expect(moduleId).toEqual("111");
expect.step(`button_immediate_install`);
};
super.setup(...arguments);
},
});
const snippetsDescription = createTestSnippets({
snippets: [
{
name: "Test 1",
moduleDisplayName: "Test 1 module",
groupName: "a",
innerHTML: "Yop",
moduleId: 111,
},
{
name: "Test 2",
moduleDisplayName: "Test 2 module",
groupName: "a",
innerHTML: "Hello",
},
],
});
await setupHTMLBuilder("<div><p>Text</p></div>", {
snippets: {
snippet_groups: [
'<div name="A" data-oe-thumbnail="a.svg" data-oe-snippet-id="123" data-o-snippet-group="a"><section data-snippet="s_snippet_group"></section></div>',
],
snippet_structure: snippetsDescription.map((snippetDesc) =>
getSnippetStructure(snippetDesc)
),
},
});
await click(".o-snippets-menu #snippet_groups .o_snippet_thumbnail .o_snippet_thumbnail_area");
await waitForSnippetDialog();
expect(
".o_add_snippet_dialog .o_add_snippet_iframe:iframe .o_snippet_preview_wrap"
).toHaveCount(2);
expect(
".o_add_snippet_dialog .o_add_snippet_iframe:iframe .o_snippet_preview_wrap .o_snippet_preview_install_btn"
).toHaveCount(1);
expect(
".o_add_snippet_dialog .o_add_snippet_iframe:iframe .o_snippet_preview_wrap:has(.o_snippet_preview_install_btn) .s_test"
).toHaveText("Yop");
await click(
".o_add_snippet_dialog .o_add_snippet_iframe:iframe .o_snippet_preview_wrap .o_snippet_preview_install_btn"
);
await animationFrame();
expect(".o_dialog:not(:has(.o_inactive_modal)) .modal-body").toHaveText(
"Do you want to install Test 1 module App?\nMore info about this app."
);
await contains(
".o_dialog:not(:has(.o_inactive_modal)) .btn-primary:contains('Save and Install')"
).click();
expect.verifySteps([`button_immediate_install`]);
});
test("open add snippet dialog + switch snippet category", async () => {
const snippets = [
{ name: "Test", groupName: "a", innerHTML: "Yop" },
{ name: "Test", groupName: "a", innerHTML: "Hello" },
{ name: "Test", groupName: "b", innerHTML: "Nice" },
];
await setupHTMLBuilder("<div><p>Text</p></div>", {
snippets: {
snippet_groups: [
'<div name="A" data-oe-thumbnail="a.svg" data-oe-snippet-id="123" data-o-snippet-group="a"><section data-snippet="s_snippet_group"></section></div>',
'<div name="B" data-oe-thumbnail="b.svg" data-oe-snippet-id="123" data-o-snippet-group="b"><section data-snippet="s_snippet_group"></section></div>',
],
snippet_structure: createTestSnippets({ snippets, withName: false }).map(
(snippetDesc) => getSnippetStructure(snippetDesc)
),
},
});
expect(queryAllTexts(".o-snippets-menu #snippet_groups .o_snippet")).toEqual(["A", "B"]);
await click(".o-snippets-menu #snippet_groups .o_snippet_thumbnail .o_snippet_thumbnail_area");
await waitForSnippetDialog();
expect(queryAllTexts(".o_add_snippet_dialog aside .list-group .list-group-item")).toEqual([
"A",
"B",
]);
expect(".o_add_snippet_dialog aside .list-group .list-group-item.active").toHaveText("A");
expect(
".o_add_snippet_dialog .o_add_snippet_iframe:iframe .o_snippet_preview_wrap"
).toHaveCount(2);
expect(
queryAll(
".o_add_snippet_dialog .o_add_snippet_iframe:iframe .o_snippet_preview_wrap > div"
).map((el) => el.innerHTML)
).toEqual(
createTestSnippets({ snippets, withName: true })
.filter((s) => s.groupName === "a")
.map((s) => s.content)
);
await click(".o_add_snippet_dialog aside .list-group .list-group-item:contains('B')");
await animationFrame();
expect(".o_add_snippet_dialog aside .list-group .list-group-item.active").toHaveText("B");
expect(
queryAll(
".o_add_snippet_dialog .o_add_snippet_iframe:iframe .o_snippet_preview_wrap > div"
).map((el) => el.innerHTML)
).toEqual(
createTestSnippets({ snippets, withName: true })
.filter((s) => s.groupName === "b")
.map((s) => s.content)
);
});
test("search snippet in add snippet dialog", async () => {
const snippets = [
{ name: "gravy", groupName: "a", innerHTML: "content 1", keywords: ["jumper"] },
{ name: "bandage", groupName: "a", innerHTML: "content 2", keywords: ["order"] },
{
name: "banana",
groupName: "b",
innerHTML: "content 3",
keywords: ["grape", "orange"],
},
];
await setupHTMLBuilder("<div><p>Text</p></div>", {
snippets: {
snippet_groups: [
'<div name="A" data-oe-thumbnail="a.svg" data-oe-snippet-id="123" data-o-snippet-group="a"><section data-snippet="s_snippet_group"></section></div>',
'<div name="B" data-oe-thumbnail="b.svg" data-oe-snippet-id="123" data-o-snippet-group="b"><section data-snippet="s_snippet_group"></section></div>',
],
snippet_structure: createTestSnippets({ snippets, withName: false }).map(
(snippetDesc) => getSnippetStructure(snippetDesc)
),
},
});
await click(".o-snippets-menu #snippet_groups .o_snippet_thumbnail .o_snippet_thumbnail_area");
await waitForSnippetDialog();
expect("aside .list-group .list-group-item").toHaveCount(2);
const snippetsDescriptionProcessed = createTestSnippets({ snippets, withName: true });
expect(
queryAll(
".o_add_snippet_dialog .o_add_snippet_iframe:iframe .o_snippet_preview_wrap > div"
).map((el) => el.innerHTML)
).toEqual(
snippetsDescriptionProcessed.filter((s) => s.groupName === "a").map((s) => s.content)
);
// Search base on snippet name
await contains(".o_add_snippet_dialog aside input[type='search']").edit("Ban");
expect("aside .list-group .list-group-item").toHaveCount(0);
expect(
queryAll(
".o_add_snippet_dialog .o_add_snippet_iframe:iframe .o_snippet_preview_wrap > div"
).map((el) => el.innerHTML)
).toEqual(
[snippetsDescriptionProcessed[1], snippetsDescriptionProcessed[2]].map((s) => s.content)
);
// Search base on snippet name and keywords
await contains(".o_add_snippet_dialog aside input[type='search']").edit("gra");
expect("aside .list-group .list-group-item").toHaveCount(0);
expect(
queryAll(
".o_add_snippet_dialog .o_add_snippet_iframe:iframe .o_snippet_preview_wrap > div"
).map((el) => el.innerHTML)
).toEqual(
[snippetsDescriptionProcessed[0], snippetsDescriptionProcessed[2]].map((s) => s.content)
);
// Search base on keywords
await contains(".o_add_snippet_dialog aside input[type='search']").edit("or");
expect("aside .list-group .list-group-item").toHaveCount(0);
expect(
queryAll(
".o_add_snippet_dialog .o_add_snippet_iframe:iframe .o_snippet_preview_wrap > div"
).map((el) => el.innerHTML)
).toEqual(
[snippetsDescriptionProcessed[1], snippetsDescriptionProcessed[2]].map((s) => s.content)
);
});
test("search snippet by class", async () => {
const snippets = [
{
name: "foo_bar",
groupName: "a",
innerHTML: "content 1",
snippet: "s_foo_bar",
additionalClassOnRoot: "s_additional_class",
keywords: [],
},
// The second snippet includes a child class selector using innerHTML.
// Using createTestSnippets with withName toggles the data-name automatically.
{
name: "foo",
groupName: "a",
innerHTML: `<div class="s_class_on_child">content 2</div>`,
snippet: "s_foo",
keywords: [],
},
{
name: "bar",
groupName: "a",
innerHTML: "content 3",
snippet: "s_bar",
keywords: [],
},
];
const snippetsForSetup = createTestSnippets({ snippets, withName: false });
const snippetsDescriptionProcessed = createTestSnippets({ snippets, withName: true });
await setupHTMLBuilder("<div><p>Text</p></div>", {
snippets: {
snippet_groups: [
'<div name="A" data-oe-thumbnail="a.svg" data-oe-snippet-id="123" data-o-snippet-group="a"><section data-snippet="s_snippet_group"></section></div>',
],
snippet_structure: snippetsForSetup.map((snippetDesc) =>
getSnippetStructure(snippetDesc)
),
},
});
await click(".o-snippets-menu #snippet_groups .o_snippet_thumbnail .o_snippet_thumbnail_area");
await waitForSnippetDialog();
// Search among classes of root node
await contains(".o_add_snippet_dialog aside input[type='search']").edit("s_bar");
expect(
queryFirst(
".o_add_snippet_dialog .o_add_snippet_iframe:iframe .o_snippet_preview_wrap > div"
).innerHTML
).toEqual(snippetsDescriptionProcessed[2].content);
await contains(".o_add_snippet_dialog aside input[type='search']").edit("s_additional_class");
expect(
queryFirst(
".o_add_snippet_dialog .o_add_snippet_iframe:iframe .o_snippet_preview_wrap > div"
).innerHTML
).toEqual(snippetsDescriptionProcessed[0].content);
// Search among classes of child nodes
await contains(".o_add_snippet_dialog aside input[type='search']").edit("s_class_on_child");
expect(
queryFirst(
".o_add_snippet_dialog .o_add_snippet_iframe:iframe .o_snippet_preview_wrap > div"
).innerHTML
).toEqual(snippetsDescriptionProcessed[1].content);
});
test("add snippet dialog with imagePreview", async () => {
const snippets = [
{ name: "gravy", groupName: "a", innerHTML: "content 1" },
{ name: "banana", groupName: "a", innerHTML: "content 2", imagePreview: "banana.png" },
];
await setupHTMLBuilder("<div><p>Text</p></div>", {
snippets: {
snippet_groups: [
'<div name="A" data-oe-thumbnail="a.svg" data-oe-snippet-id="123" data-o-snippet-group="a"><section data-snippet="s_snippet_group"></section></div>',
'<div name="B" data-oe-thumbnail="b.svg" data-oe-snippet-id="123" data-o-snippet-group="b"><section data-snippet="s_snippet_group"></section></div>',
],
snippet_structure: createTestSnippets({ snippets, withName: false }).map(
(snippetDesc) => getSnippetStructure(snippetDesc)
),
},
});
await click(".o-snippets-menu #snippet_groups .o_snippet_thumbnail .o_snippet_thumbnail_area");
const previewSnippetIframeSelector =
".o_add_snippet_dialog .o_add_snippet_iframe:iframe .o_snippet_preview_wrap";
await waitForSnippetDialog();
expect(`${previewSnippetIframeSelector}`).toHaveCount(2);
const snippetsDescriptionProcessed = createTestSnippets({ snippets, withName: true });
expect(`${previewSnippetIframeSelector}:first > div`).toHaveInnerHTML(
snippetsDescriptionProcessed[0].content
);
expect(
`${previewSnippetIframeSelector}:nth-child(1) .s_dialog_preview_image img`
).toHaveAttribute("data-src", snippetsDescriptionProcessed[1].imagePreview);
});
test("insert snippet structure", async () => {
const snippets = [{ name: "Test", groupName: "a", innerHTML: "Yop" }];
const { contentEl } = await setupHTMLBuilder("<section><p>Text</p></section>", {
snippets: {
snippet_groups: [
'<div name="A" data-oe-thumbnail="a.svg" data-oe-snippet-id="123" data-o-snippet-group="a"><section data-snippet="s_snippet_group"></section></div>',
],
snippet_structure: createTestSnippets({ snippets, withName: false }).map(
(snippetDesc) => getSnippetStructure(snippetDesc)
),
},
});
expect(contentEl).toHaveInnerHTML(`<section><p>Text</p></section>`);
await click(".o-snippets-menu #snippet_groups .o_snippet_thumbnail .o_snippet_thumbnail_area");
await waitForSnippetDialog();
const previewSelector =
".o_add_snippet_dialog .o_add_snippet_iframe:iframe .o_snippet_preview_wrap";
expect(previewSelector).toHaveCount(1);
await contains(previewSelector).click();
expect(".o_add_snippet_dialog").toHaveCount(0);
await waitForEndOfOperation();
expect(contentEl).toHaveInnerHTML(
`<section><p>Text</p></section>${
createTestSnippets({ snippets, withName: true })[0].content
}`
);
});
test("Drag & drop snippet structure", async () => {
const snippets = [{ name: "Test", groupName: "a", innerHTML: "Yop" }];
const { contentEl } = await setupHTMLBuilder("<section><p>Text</p></section>", {
snippets: {
snippet_groups: [
'<div name="A" data-oe-thumbnail="a.svg" data-oe-snippet-id="123" data-o-snippet-group="a"><section data-snippet="s_snippet_group"></section></div>',
],
snippet_structure: createTestSnippets({ snippets, withName: false }).map(
(snippetDesc) => getSnippetStructure(snippetDesc)
),
},
});
expect(contentEl).toHaveInnerHTML(`<section><p>Text</p></section>`);
const { moveTo, drop } = await contains(
".o-snippets-menu #snippet_groups .o_snippet_thumbnail"
).drag();
expect(":iframe .oe_drop_zone:nth-child(1)").toHaveCount(1);
expect(":iframe .oe_drop_zone:nth-child(3)").toHaveCount(1);
await moveTo(":iframe .oe_drop_zone");
expect(":iframe .oe_drop_zone.o_dropzone_highlighted:nth-child(1)").toHaveCount(1);
await drop(getDragHelper());
expect(":iframe section[data-snippet='s_snippet_group']:nth-child(1)").toHaveCount(1);
expect(".o_add_snippet_dialog").toHaveCount(1);
await waitForSnippetDialog();
const previewSelector =
".o_add_snippet_dialog .o_add_snippet_iframe:iframe .o_snippet_preview_wrap";
expect(previewSelector).toHaveCount(1);
await contains(previewSelector).click();
expect(".o_add_snippet_dialog").toHaveCount(0);
await waitForEndOfOperation();
expect(contentEl).toHaveInnerHTML(
`${
createTestSnippets({ snippets, withName: true })[0].content
}<section><p>Text</p></section>`
);
});
test("Cancel snippet drag & drop over sidebar", async () => {
const { contentEl } = await setupHTMLBuilderWithDummySnippet();
const { moveTo, drop } = await contains(
".o-snippets-menu #snippet_groups .o_snippet_thumbnail"
).drag();
expect(":iframe .oe_drop_zone").toHaveCount(1);
// Specifying an explicit target should not be needed, but the test
// sometimes fails, probably because the snippet is partially touching the
// iframe. We drop on the "mobile" button to be as far as possible from the
// iframe.
await moveTo(".o-website-builder_sidebar button[data-action=mobile]");
await drop(getDragHelper());
expect(".o_add_snippet_dialog").toHaveCount(0);
await waitForEndOfOperation();
expect(contentEl).toHaveInnerHTML("");
});
test("Renaming custom snippets don't make an orm call", async () => {
class TestSetupEditorPlugin extends Plugin {
static id = "test.setup_editor_plugin";
resources = {
snippet_preview_dialog_bundles: ["web.assets_frontend"],
};
}
addBuilderPlugin(TestSetupEditorPlugin);
// Stub rename_snippet RPC to succeed if it is called
onRpc("ir.ui.view", "rename_snippet", ({ args }) => true);
const customSnippets = createTestSnippets({
snippets: [
{
name: "Dummy Section",
groupName: "custom",
keywords: ["dummy"],
content: `
<section data-snippet="s_dummy">
<div class="container">
<div class="row">
<div class="col-lg-7">
<p>TEST</p>
</div>
</div>
</div>
</section>
`,
},
],
withName: true,
});
const snippets = {
snippet_groups: [
'<div name="Custom" data-oe-snippet-id="123" data-o-snippet-group="custom"><section data-snippet="s_snippet_group"></section></div>',
],
snippet_structure: customSnippets.map((snippetDesc) => getSnippetStructure(snippetDesc)),
snippet_custom: customSnippets.map((snippetDesc) => getSnippetStructure(snippetDesc)),
};
await setupHTMLBuilder(
`<section data-name="Dummy Section" data-snippet="s_dummy">
<div class="container">
<div class="row">
<div class="col-lg-7">
<p>TEST</p>
<p><a class="btn">BUTTON</a></p>
</div>
</div>
</div>
</section>`,
{ snippets }
);
await contains(
".o-website-builder_sidebar .o_snippets_container .o_snippet[name='Custom'] button"
).click();
await animationFrame();
// Throw if any render_public_asset RPC happens during rename
onRpc("ir.ui.view", "render_public_asset", () => {
throw new Error("shouldn't make an rpc call on snippet rename");
});
await contains(
".o_add_snippet_dialog .o_add_snippet_iframe:iframe .o_custom_snippet_edit button > .fa-pencil"
).click();
expect(".o-overlay-item .modal-dialog:contains('Rename the block')").toHaveCount(1);
await contains(".o-overlay-item .modal-dialog input#inputConfirmation").fill("new custom name");
await contains(".o-overlay-item .modal-dialog footer>button:contains('Save')").click();
expect(
".o_add_snippet_dialog .o_add_snippet_iframe:iframe .o_custom_snippet_edit>span:contains('new custom name')"
).toHaveCount(1);
});