import { beforeEach, expect, test } from "@odoo/hoot"; import { advanceTime, animationFrame, click, edit, queryOne } from "@odoo/hoot-dom"; import { Component, useState, xml } from "@odoo/owl"; import { mountWithCleanup, patchWithCleanup } from "@web/../tests/web_test_helpers"; import { Macro } from "@web/core/macro"; let macro; async function waitForMacro() { for (let i = 0; i < 50; i++) { await animationFrame(); await advanceTime(265); if (macro.isComplete) { return; } } if (!macro.isComplete) { throw new Error(`Macro is not complete`); } } beforeEach(() => { patchWithCleanup(Macro.prototype, { start() { super.start(...arguments); macro = this; }, }); }); function onTextChange(element, callback) { const observer = new MutationObserver((mutations) => { for (const mutation of mutations) { if (mutation.type === "characterData" || mutation.type === "childList") { callback(element.textContent); } } }); observer.observe(element, { characterData: true, childList: true, subtree: true, }); return observer; } class TestComponent extends Component { static template = xml`

`; static props = ["*"]; setup() { this.state = useState({ value: 0 }); } } test("simple use", async () => { await mountWithCleanup(TestComponent); new Macro({ name: "test", steps: [ { trigger: "button.inc", async action(trigger) { await click(trigger); }, }, ], }).start(queryOne(".counter")); const span = queryOne("span.value"); expect(span).toHaveText("0"); onTextChange(span, expect.step); await waitForMacro(); expect.verifySteps(["1"]); }); test("multiple steps", async () => { await mountWithCleanup(TestComponent); const span = queryOne("span.value"); expect(span).toHaveText("0"); new Macro({ name: "test", steps: [ { trigger: "button.inc", async action(trigger) { await click(trigger); }, }, { trigger: () => (span.textContent === "1" ? span : null), }, { trigger: "button.inc", async action(trigger) { await click(trigger); }, }, ], }).start(queryOne(".counter")); onTextChange(span, expect.step); await waitForMacro(); expect.verifySteps(["1", "2"]); }); test("can input values", async () => { await mountWithCleanup(TestComponent); const input = queryOne("input"); new Macro({ name: "test", steps: [ { trigger: "div.counter input", async action(trigger) { await click(trigger); await edit("aaron", { confirm: "blur" }); }, }, ], }).start(queryOne(".counter")); expect(input).toHaveValue(""); await waitForMacro(); expect(input).toHaveValue("aaron"); }); test("a step can have no trigger", async () => { await mountWithCleanup(TestComponent); const input = queryOne("input"); new Macro({ name: "test", steps: [ { action: () => expect.step("1") }, { action: () => expect.step("2") }, { trigger: "div.counter input", async action(trigger) { await click(trigger); await edit("aaron", { confirm: "blur" }); }, }, { action: () => expect.step("3") }, ], }).start(queryOne(".counter")); expect(input).toHaveValue(""); await waitForMacro(); expect(input).toHaveValue("aaron"); expect.verifySteps(["1", "2", "3"]); }); test("onStep function is called at each step", async () => { await mountWithCleanup(TestComponent); const span = queryOne("span.value"); expect(span).toHaveText("0"); new Macro({ name: "test", onStep: (el, step, index) => { expect.step(index); }, steps: [ { action: () => { console.log("brol"); }, }, { trigger: "button.inc", async action(trigger) { await click(trigger); }, }, ], }).start(queryOne(".counter")); await waitForMacro(); expect(span).toHaveText("1"); expect.verifySteps([0, 1]); }); test("trigger can be a function returning an htmlelement", async () => { await mountWithCleanup(TestComponent); const span = queryOne("span.value"); expect(span).toHaveText("0"); new Macro({ name: "test", steps: [ { trigger: () => queryOne("button.inc"), async action(trigger) { await click(trigger); }, }, ], }).start(queryOne(".counter")); expect(span).toHaveText("0"); await waitForMacro(); expect(span).toHaveText("1"); }); test("macro wait element is visible to do action", async () => { await mountWithCleanup(TestComponent); const span = queryOne("span.value"); const button = queryOne("button.inc"); button.classList.add("d-none"); expect(span).toHaveText("0"); new Macro({ name: "test", timeout: 1000, steps: [ { trigger: "button.inc", action: () => { expect.step("element is now visible"); }, }, ], onError: (error) => { expect.step(error); }, }).start(queryOne(".counter")); advanceTime(500); button.classList.remove("d-none"); await waitForMacro(); expect.verifySteps(["element is now visible"]); }); test("macro timeout if element is not visible", async () => { await mountWithCleanup(TestComponent); const span = queryOne("span.value"); const button = queryOne("button.inc"); button.classList.add("d-none"); expect(span).toHaveText("0"); const macro = new Macro({ name: "test", timeout: 1000, steps: [ { trigger: "button.inc", action: () => { expect.step("element is now visible"); }, }, ], onError: (error) => { expect.step(error.message); }, }); macro.start(queryOne(".counter")); await waitForMacro(); expect.verifySteps(["TIMEOUT step failed to complete within 1000 ms."]); });