import { before, beforeEach, describe, expect, test } from "@odoo/hoot";
import {
animationFrame,
click,
dblclick,
queryAll,
queryFirst,
queryOne,
freezeTime,
} from "@odoo/hoot-dom";
import { advanceTime, Deferred } from "@odoo/hoot-mock";
import { Component, onWillDestroy, markup, xml } from "@odoo/owl";
import { clearRegistry, patchWithCleanup } from "@web/../tests/web_test_helpers";
import { registry } from "@web/core/registry";
import { patch } from "@web/core/utils/patch";
import { Colibri } from "@web/public/colibri";
import { Interaction } from "@web/public/interaction";
import { patchDynamicContent } from "@web/public/utils";
import { startInteraction, startInteractions } from "./helpers";
describe.current.tags("interaction_dev");
const TemplateBase = `
coucou
`;
const TemplateTest = `
coucou
`;
const TemplateTestDoubleSpan = `
span1
span2
`;
const TemplateTestDoubleButton = `
button1
button2
`;
const getTemplateWithAttribute = function (attribute) {
return `
coucou
`;
};
function installProtect() {
patchWithCleanup(Colibri.prototype, {
updateContent() {
expect.step("updateContent");
super.updateContent();
},
protectSyncAfterAsync(interaction, name, fn) {
fn = super.protectSyncAfterAsync(interaction, name, fn);
return (...args) => {
expect.step("protect");
fn(...args);
expect.step("unprotect");
};
},
});
}
describe("adding listeners", () => {
test("can add a listener on a single element", async () => {
let clicked = 0;
class Test extends Interaction {
static selector = ".test";
dynamicContent = {
span: { "t-on-click": () => clicked++ },
};
}
await startInteraction(Test, TemplateTest);
expect(clicked).toBe(0);
await click("span");
expect(clicked).toBe(1);
});
test("can add a listener on multiple elements", async () => {
let clicked = 0;
class Test extends Interaction {
static selector = ".test";
dynamicContent = {
span: { "t-on-click": () => clicked++ },
};
}
await startInteraction(Test, TemplateTestDoubleSpan);
expect(clicked).toBe(0);
const spans = queryAll("span");
await click(spans[0]);
await click(spans[1]);
expect(clicked).toBe(2);
});
test.tags("desktop");
test("can add multiple listeners on an element", async () => {
let clicked = 0;
class Test extends Interaction {
static selector = ".test";
dynamicContent = {
span: {
"t-on-click": () => clicked++,
"t-on-dblclick": () => clicked++,
},
};
}
await startInteraction(Test, TemplateTest);
expect(clicked).toBe(0);
await dblclick("span");
expect(clicked).toBe(3); // event dblclick = click + click + dblclick
});
test("can use addListener on HTMLCollection", async () => {
let clicked = 0;
class Test extends Interaction {
static selector = ".test";
start() {
this.addListener(this.el.querySelectorAll("span"), "click", () => clicked++);
}
}
await startInteraction(Test, TemplateTestDoubleSpan);
expect(clicked).toBe(0);
const spans = queryAll("span");
await click(spans[0]);
await click(spans[1]);
expect(clicked).toBe(2);
});
test("listener is added between willstart and start", async () => {
class Test extends Interaction {
static selector = ".test";
dynamicContent = {
span: { "t-on-click": () => expect.step("click") },
};
setup() {
expect.step("setup");
}
async willStart() {
await click("span");
expect.step("willStart");
}
start() {
expect.step("start");
}
}
await startInteraction(Test, TemplateTest);
await click("span");
expect.verifySteps(["setup", "willStart", "start", "click"]);
});
test("listener is added on iframe single element", async () => {
class Test extends Interaction {
static selector = "iframe";
start() {
const spanEl = this.el.contentDocument.createElement("span");
spanEl.textContent = "abc";
this.el.contentDocument.body.appendChild(spanEl);
this.addListener(spanEl, "click", () => expect.step("click"));
spanEl.click();
}
}
await startInteraction(Test, ``);
expect.verifySteps(["click"]);
});
test("listener is added on iframe elements", async () => {
class Test extends Interaction {
static selector = "iframe";
start() {
const spanEl = this.el.contentDocument.createElement("span");
spanEl.textContent = "abc";
this.el.contentDocument.body.appendChild(spanEl);
const spanEls = this.el.contentDocument.querySelectorAll("span");
this.addListener(spanEls, "click", () => expect.step("click"));
spanEl.click();
}
}
await startInteraction(Test, ``);
expect.verifySteps(["click"]);
});
test("updateContent after async listener", async () => {
const def = new Deferred();
let clicked = 0;
class Test extends Interaction {
static selector = ".test";
dynamicContent = {
span: {
"t-on-click": async () => {
await def;
clicked++;
},
"t-att-x": () => clicked.toString(),
},
};
}
await startInteraction(Test, TemplateTest);
expect(clicked).toBe(0);
expect("span").toHaveAttribute("x", "0");
await click("span");
expect(clicked).toBe(0);
expect("span").toHaveAttribute("x", "0");
def.resolve();
await animationFrame();
expect(clicked).toBe(1);
expect("span").toHaveAttribute("x", "1");
});
});
describe("using selectors", () => {
test("can add a listener on root element", async () => {
let clicked = 0;
class Test extends Interaction {
static selector = ".test";
dynamicContent = {
_root: { "t-on-click": () => clicked++ },
};
}
await startInteraction(Test, TemplateTest);
expect(clicked).toBe(0);
await click(".test");
expect(clicked).toBe(1);
});
test("can add a listener on body element", async () => {
let clicked = 0;
class Test extends Interaction {
static selector = ".test";
dynamicContent = {
_body: { "t-on-click": () => clicked++ },
};
}
await startInteraction(Test, TemplateTest);
expect(clicked).toBe(0);
await click(document.body);
expect(clicked).toBe(1);
});
test("can add a listener on window element", async () => {
let clicked = 0;
class Test extends Interaction {
static selector = ".test";
dynamicContent = {
_window: { "t-on-event": () => clicked++ },
};
}
await startInteraction(Test, TemplateTest);
expect(clicked).toBe(0);
await window.dispatchEvent(new Event("event"));
expect(clicked).toBe(1);
});
test("can add a listener on document ", async () => {
let clicked = 0;
class Test extends Interaction {
static selector = ".test";
dynamicContent = {
_document: { "t-on-event": () => clicked++ },
};
}
await startInteraction(Test, TemplateTest);
expect(clicked).toBe(0);
await window.document.dispatchEvent(new Event("event"));
expect(clicked).toBe(1);
});
test("can add a listener on modal element, if any", async () => {
let clicked = 0;
class Test extends Interaction {
static selector = ".test";
dynamicSelectors = {
_modal: () => this.el.closest(".modal"),
};
dynamicContent = {
_modal: { "t-on-click": () => clicked++ },
};
}
await startInteraction(Test, `${TemplateTest}
`);
expect(clicked).toBe(0);
await click(".modal");
expect(clicked).toBe(1);
});
test("can refresh nodes", async () => {
let clicked = 0;
class Test extends Interaction {
static selector = ".test";
dynamicContent = {
".me": {
"t-on-click": (ev) => {
clicked++;
ev.currentTarget.parentElement
.querySelectorAll("span:not(.me)")
.forEach((el) => el.classList.add("me"));
ev.currentTarget.classList.remove("me");
},
},
};
}
await startInteraction(
Test,
`
span1
span2
span3
`
);
async function clickAll() {
for (const el of queryAll(".me")) {
await click(el);
}
}
expect(clicked).toBe(0);
await clickAll();
expect(clicked).toBe(1);
await clickAll();
expect(clicked).toBe(3);
});
test("does not crash if no modal is found", async () => {
let clicked = 0;
class Test extends Interaction {
static selector = ".test";
dynamicSelectors = {
_modal: () => {
expect.step("check");
return null;
},
};
dynamicContent = {
_modal: { "t-on-click": () => clicked++ },
};
}
await startInteraction(Test, TemplateTest);
expect.verifySteps(["check"]);
expect(clicked).toBe(0);
});
test("allow pseudo-classes in inline format in dynamicContent", async () => {
class Test extends Interaction {
static selector = ".test";
dynamicContent = {
".btn:not(.off)": { "t-on-click": () => expect.step("doStuff") },
};
}
await startInteraction(
Test,
`
`
);
expect.verifySteps([]);
await click(".btn:not(.off)");
expect.verifySteps(["doStuff"]);
await click(".btn.off");
expect.verifySteps([]);
});
test("allow customized special selector", async () => {
class Test extends Interaction {
static selector = ".test";
dynamicSelectors = {
_myselector: () => this.el.querySelector(".my-selector"),
};
dynamicContent = {
_myselector: { "t-att-animal": () => "colibri" },
};
}
await startInteraction(
Test,
`
coucou
`
);
expect("span").toHaveAttribute("animal", "colibri");
});
test("dynamic selector can return multiple nodes", async () => {
class Test extends Interaction {
static selector = ".test";
dynamicSelectors = {
_myselector: () => this.el.querySelectorAll(".my-selector"),
};
dynamicContent = {
_myselector: { "t-att-animal": () => "colibri" },
};
}
await startInteraction(
Test,
`
coucou
coucou
coucou
`
);
expect(queryAll("span")).toHaveAttribute("animal", "colibri");
});
test("dynamicSelector on form element is applied on form, not on controls", async () => {
//