mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-20 12:32:02 +02:00
vanilla 19.0
This commit is contained in:
parent
991d2234ca
commit
d1963a3c3a
3066 changed files with 1651266 additions and 922560 deletions
|
|
@ -0,0 +1,624 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import { describe, expect, makeExpect, test } from "@odoo/hoot";
|
||||
import { check, manuallyDispatchProgrammaticEvent, tick, waitFor } from "@odoo/hoot-dom";
|
||||
import { Component, xml } from "@odoo/owl";
|
||||
import { mountForTest, parseUrl } from "../local_helpers";
|
||||
|
||||
import { Test } from "../../core/test";
|
||||
import { makeLabel } from "../../hoot_utils";
|
||||
|
||||
describe(parseUrl(import.meta.url), () => {
|
||||
test("makeExpect passing, without a test", () => {
|
||||
const [customExpect, hooks] = makeExpect({ headless: true });
|
||||
|
||||
expect(() => customExpect(true).toBe(true)).toThrow(
|
||||
"cannot call `expect()` outside of a test"
|
||||
);
|
||||
|
||||
hooks.before();
|
||||
|
||||
customExpect({ key: true }).toEqual({ key: true });
|
||||
customExpect("oui").toBe("oui");
|
||||
|
||||
const results = hooks.after();
|
||||
|
||||
expect(results.pass).toBe(true);
|
||||
expect(results.events).toHaveLength(2);
|
||||
});
|
||||
|
||||
test("makeExpect failing, without a test", () => {
|
||||
const [customExpect, hooks] = makeExpect({ headless: true });
|
||||
|
||||
hooks.before();
|
||||
|
||||
customExpect({ key: true }).toEqual({ key: true });
|
||||
customExpect("oui").toBe("non");
|
||||
|
||||
const results = hooks.after();
|
||||
|
||||
expect(results.pass).toBe(false);
|
||||
expect(results.events).toHaveLength(2);
|
||||
});
|
||||
|
||||
test("makeExpect with a test", async () => {
|
||||
const [customExpect, hooks] = makeExpect({ headless: true });
|
||||
const customTest = new Test(null, "test", {});
|
||||
customTest.setRunFn(() => {
|
||||
customExpect({ key: true }).toEqual({ key: true });
|
||||
customExpect("oui").toBe("non");
|
||||
});
|
||||
|
||||
hooks.before(customTest);
|
||||
|
||||
await customTest.run();
|
||||
|
||||
const results = hooks.after();
|
||||
|
||||
expect(customTest.lastResults).toBe(results);
|
||||
// Result is expected to have the same shape, no need for other assertions
|
||||
});
|
||||
|
||||
test("makeExpect with a test flagged with TODO", async () => {
|
||||
const [customExpect, hooks] = makeExpect({ headless: true });
|
||||
const customTest = new Test(null, "test", { todo: true });
|
||||
customTest.setRunFn(() => {
|
||||
customExpect(1).toBe(1);
|
||||
});
|
||||
|
||||
hooks.before(customTest);
|
||||
|
||||
await customTest.run();
|
||||
|
||||
const results = hooks.after();
|
||||
|
||||
expect(results.pass).toBe(false);
|
||||
expect(results.events[0].pass).toBe(true);
|
||||
});
|
||||
|
||||
test("makeExpect with no assertions & query events", async () => {
|
||||
await mountForTest(/* xml */ `<div>ABC</div>`);
|
||||
|
||||
const [, hooks] = makeExpect({ headless: true });
|
||||
|
||||
hooks.before();
|
||||
|
||||
await waitFor("div:contains(ABC)");
|
||||
|
||||
const results = hooks.after();
|
||||
|
||||
expect(results.pass).toBe(true);
|
||||
expect(results.events).toHaveLength(1);
|
||||
expect(results.events[0].label).toBe("waitFor");
|
||||
});
|
||||
|
||||
test("makeExpect with no assertions & no query events", () => {
|
||||
const [customExpect, hooks] = makeExpect({ headless: true });
|
||||
|
||||
hooks.before();
|
||||
|
||||
expect(() => customExpect.assertions(0)).toThrow(
|
||||
"expected assertions count should be more than 1"
|
||||
);
|
||||
|
||||
const results = hooks.after();
|
||||
|
||||
expect(results.pass).toBe(false);
|
||||
expect(results.events).toHaveLength(1);
|
||||
expect(results.events[0].message).toEqual([
|
||||
"expected at least",
|
||||
["1", "integer"],
|
||||
"assertion or query event, but none were run",
|
||||
]);
|
||||
});
|
||||
|
||||
test("makeExpect with unconsumed matchers", () => {
|
||||
const [customExpect, hooks] = makeExpect({ headless: true });
|
||||
|
||||
hooks.before();
|
||||
|
||||
expect(() => customExpect(true, true)).toThrow("`expect()` only accepts a single argument");
|
||||
customExpect(1).toBe(1);
|
||||
customExpect(true);
|
||||
|
||||
const results = hooks.after();
|
||||
|
||||
expect(results.pass).toBe(false);
|
||||
expect(results.events).toHaveLength(2);
|
||||
expect(results.events[1].message.join(" ")).toBe(
|
||||
"called once without calling any matchers"
|
||||
);
|
||||
});
|
||||
|
||||
test("makeExpect with unverified steps", () => {
|
||||
const [customExpect, hooks] = makeExpect({ headless: true });
|
||||
|
||||
hooks.before();
|
||||
|
||||
customExpect.step("oui");
|
||||
customExpect.verifySteps(["oui"]);
|
||||
customExpect.step("non");
|
||||
|
||||
const results = hooks.after();
|
||||
|
||||
expect(results.pass).toBe(false);
|
||||
expect(results.events).toHaveLength(2); // 1 'verifySteps' + 1 'unverified steps'
|
||||
expect(results.events.at(-1).message).toEqual(["unverified steps"]);
|
||||
});
|
||||
|
||||
test("makeExpect retains current values", () => {
|
||||
const [customExpect, hooks] = makeExpect({ headless: true });
|
||||
|
||||
hooks.before();
|
||||
|
||||
const object = { a: 1 };
|
||||
customExpect(object).toEqual({ b: 2 });
|
||||
object.b = 2;
|
||||
|
||||
const testResult = hooks.after();
|
||||
|
||||
const [assertion] = testResult.events;
|
||||
expect(assertion.pass).toBe(false);
|
||||
expect(assertion.failedDetails[1][1]).toEqual({ a: 1 });
|
||||
expect(object).toEqual({ a: 1, b: 2 });
|
||||
});
|
||||
|
||||
test("'expect' results contain the correct informations", async () => {
|
||||
await mountForTest(/* xml */ `
|
||||
<label style="color: #f00">
|
||||
Checkbox
|
||||
<input class="cb" type="checkbox" />
|
||||
</label>
|
||||
<input type="text" value="abc" />
|
||||
`);
|
||||
|
||||
await check("input[type=checkbox]");
|
||||
|
||||
const [customExpect, hooks] = makeExpect({ headless: true });
|
||||
|
||||
hooks.before();
|
||||
|
||||
const matchers = [
|
||||
// Standard
|
||||
["toBe", 1, 1],
|
||||
["toBeCloseTo", 1, 1],
|
||||
["toBeEmpty", []],
|
||||
["toBeGreaterThan", 1, 0],
|
||||
["toBeInstanceOf", {}, Object],
|
||||
["toBeLessThan", 0, 1],
|
||||
["toBeOfType", 1, "integer"],
|
||||
["toBeWithin", 1, 0, 2],
|
||||
["toEqual", [], []],
|
||||
["toHaveLength", [], 0],
|
||||
["toInclude", [1], 1],
|
||||
["toMatch", "a", "a"],
|
||||
["toMatchObject", { a: 1, b: { l: [1, 2] } }, { b: { l: [1, 2] } }],
|
||||
[
|
||||
"toThrow",
|
||||
() => {
|
||||
throw new Error("");
|
||||
},
|
||||
],
|
||||
// DOM
|
||||
["toBeChecked", ".cb"],
|
||||
["toBeDisplayed", ".cb"],
|
||||
["toBeEnabled", ".cb"],
|
||||
["toBeFocused", ".cb"],
|
||||
["toBeVisible", ".cb"],
|
||||
["toHaveAttribute", ".cb", "type", "checkbox"],
|
||||
["toHaveClass", ".cb", "cb"],
|
||||
["toHaveCount", ".cb", 1],
|
||||
["toHaveInnerHTML", ".cb", ""],
|
||||
["toHaveOuterHTML", ".cb", `<input class="cb" type="checkbox" />`],
|
||||
["toHaveProperty", ".cb", "checked", true],
|
||||
["toHaveRect", "label", { x: 0 }],
|
||||
["toHaveStyle", "label", { color: "rgb(255, 0, 0)" }],
|
||||
["toHaveText", "label", "Checkbox"],
|
||||
["toHaveValue", "input[type=text]", "abc"],
|
||||
];
|
||||
|
||||
for (const [name, ...args] of matchers) {
|
||||
customExpect(args.shift())[name](...args);
|
||||
}
|
||||
|
||||
const testResult = hooks.after();
|
||||
|
||||
expect(testResult.pass).toBe(true);
|
||||
expect(testResult.events).toHaveLength(matchers.length);
|
||||
expect(testResult.events.map(({ label }) => label)).toEqual(matchers.map(([name]) => name));
|
||||
});
|
||||
|
||||
test("assertions are prevented after an error", async () => {
|
||||
const [customExpect, hooks] = makeExpect({ headless: true });
|
||||
|
||||
hooks.before();
|
||||
|
||||
await customExpect(Promise.resolve(1)).resolves.toBe(1);
|
||||
hooks.error(new Error("boom"));
|
||||
customExpect(2).toBe(2);
|
||||
customExpect(Promise.resolve(3)).resolves.toBe(3);
|
||||
await tick();
|
||||
|
||||
const results = hooks.after();
|
||||
|
||||
expect(results.pass).toBe(false);
|
||||
expect(results.events).toHaveLength(3); // toBe + error + unverified errors
|
||||
});
|
||||
|
||||
describe("standard matchers", () => {
|
||||
test("toBe", () => {
|
||||
// Boolean
|
||||
expect(true).toBe(true);
|
||||
expect(true).not.toBe(false);
|
||||
|
||||
// Floats
|
||||
expect(1.1).toBe(1.1);
|
||||
expect(0.1 + 0.2).not.toBe(0.3); // floating point errors
|
||||
|
||||
// Integers
|
||||
expect(+0).toBe(-0);
|
||||
expect(1 + 2).toBe(3);
|
||||
expect(1).not.toBe(-1);
|
||||
expect(NaN).toBe(NaN);
|
||||
|
||||
// Strings
|
||||
expect("abc").toBe("abc");
|
||||
expect(new String("abc")).not.toBe(new String("abc"));
|
||||
|
||||
// Other primitives
|
||||
expect(undefined).toBe(undefined);
|
||||
expect(undefined).not.toBe(null);
|
||||
|
||||
// Symbols
|
||||
const symbol = Symbol("symbol");
|
||||
expect(symbol).toBe(symbol);
|
||||
expect(symbol).not.toBe(Symbol("symbol"));
|
||||
expect(Symbol.for("symbol")).toBe(Symbol.for("symbol"));
|
||||
|
||||
// Objects
|
||||
const object = { x: 1 };
|
||||
expect(object).toBe(object);
|
||||
expect([]).not.toBe([]);
|
||||
expect(object).not.toBe({ x: 1 });
|
||||
|
||||
// Dates
|
||||
const date = new Date(0);
|
||||
expect(date).toBe(date);
|
||||
expect(new Date(0)).not.toBe(new Date(0));
|
||||
|
||||
// Nodes
|
||||
expect(new Image()).not.toBe(new Image());
|
||||
expect(document.createElement("div")).not.toBe(document.createElement("div"));
|
||||
});
|
||||
|
||||
test("toBeCloseTo", () => {
|
||||
expect(0.2 + 0.1).toBeCloseTo(0.3);
|
||||
expect(0.2 + 0.1).toBeCloseTo(0.3, { margin: Number.EPSILON });
|
||||
expect(0.2 + 0.1).not.toBeCloseTo(0.3, { margin: 1e-17 });
|
||||
|
||||
expect(3.51).toBeCloseTo(3);
|
||||
expect(3.51).toBeCloseTo(3.52, { margin: 2e-2 });
|
||||
expect(3.502).not.toBeCloseTo(3.503, { margin: 1e-3 });
|
||||
|
||||
expect(3).toBeCloseTo(4 - 1e-15);
|
||||
expect(3 + 1e-15).toBeCloseTo(4);
|
||||
expect(3).not.toBeCloseTo(4);
|
||||
});
|
||||
|
||||
test("toEqual", () => {
|
||||
// Boolean
|
||||
expect(true).toEqual(true);
|
||||
expect(true).not.toEqual(false);
|
||||
|
||||
// Floats
|
||||
expect(1.1).toEqual(1.1);
|
||||
expect(0.1 + 0.2).not.toEqual(0.3); // floating point errors
|
||||
|
||||
// Integers
|
||||
expect(+0).toEqual(-0);
|
||||
expect(1 + 2).toEqual(3);
|
||||
expect(1).not.toEqual(-1);
|
||||
expect(NaN).toEqual(NaN);
|
||||
|
||||
// Strings
|
||||
expect("abc").toEqual("abc");
|
||||
expect(new String("abc")).toEqual(new String("abc"));
|
||||
|
||||
// Other primitives
|
||||
expect(undefined).toEqual(undefined);
|
||||
expect(undefined).not.toEqual(null);
|
||||
|
||||
// Symbols
|
||||
const symbol = Symbol("symbol");
|
||||
expect(symbol).toEqual(symbol);
|
||||
expect(symbol).not.toEqual(Symbol("symbol"));
|
||||
expect(Symbol.for("symbol")).toEqual(Symbol.for("symbol"));
|
||||
|
||||
// Objects
|
||||
const object = { x: 1 };
|
||||
expect(object).toEqual(object);
|
||||
expect([]).toEqual([]);
|
||||
expect(object).toEqual({ x: 1 });
|
||||
|
||||
// Iterables
|
||||
expect(new Set([1, 4, 6])).toEqual(new Set([1, 4, 6]));
|
||||
expect(new Set([1, 4, 6])).not.toEqual([1, 4, 6]);
|
||||
expect(new Map([[{}, "abc"]])).toEqual(new Map([[{}, "abc"]]));
|
||||
|
||||
// Dates
|
||||
const date = new Date(0);
|
||||
expect(date).toEqual(date);
|
||||
expect(new Date(0)).toEqual(new Date(0));
|
||||
|
||||
// Nodes
|
||||
expect(new Image()).toEqual(new Image());
|
||||
expect(document.createElement("div")).toEqual(document.createElement("div"));
|
||||
expect(document.createElement("div")).not.toEqual(document.createElement("span"));
|
||||
});
|
||||
|
||||
test("toMatch", () => {
|
||||
class Exception extends Error {}
|
||||
|
||||
expect("aaaa").toMatch(/^a{4}$/);
|
||||
expect("aaaa").toMatch("aa");
|
||||
expect("aaaa").not.toMatch("aaaaa");
|
||||
|
||||
// Matcher from a class
|
||||
expect(new Exception("oui")).toMatch(Error);
|
||||
expect(new Exception("oui")).toMatch(Exception);
|
||||
expect(new Exception("oui")).toMatch(new Error("oui"));
|
||||
});
|
||||
|
||||
test("toMatchObject", () => {
|
||||
expect({
|
||||
bath: true,
|
||||
bedrooms: 4,
|
||||
kitchen: {
|
||||
amenities: ["oven", "stove", "washer"],
|
||||
area: 20,
|
||||
wallColor: "white",
|
||||
},
|
||||
}).toMatchObject({
|
||||
bath: true,
|
||||
kitchen: {
|
||||
amenities: ["oven", "stove", "washer"],
|
||||
wallColor: "white",
|
||||
},
|
||||
});
|
||||
expect([{ tralalero: "tralala" }, { foo: 1 }]).toMatchObject([
|
||||
{ tralalero: "tralala" },
|
||||
{ foo: 1 },
|
||||
]);
|
||||
expect([{ tralalero: "tralala" }, { foo: 1, lirili: "larila" }]).toMatchObject([
|
||||
{ tralalero: "tralala" },
|
||||
{ foo: 1 },
|
||||
]);
|
||||
});
|
||||
|
||||
test("toThrow", async () => {
|
||||
const asyncBoom = async () => {
|
||||
throw new Error("rejection");
|
||||
};
|
||||
|
||||
const boom = () => {
|
||||
throw new Error("error");
|
||||
};
|
||||
|
||||
expect(boom).toThrow();
|
||||
expect(boom).toThrow("error");
|
||||
expect(boom).toThrow(new Error("error"));
|
||||
|
||||
await expect(asyncBoom()).rejects.toThrow();
|
||||
await expect(asyncBoom()).rejects.toThrow("rejection");
|
||||
await expect(asyncBoom()).rejects.toThrow(new Error("rejection"));
|
||||
});
|
||||
|
||||
test("verifyErrors", async () => {
|
||||
expect.assertions(1);
|
||||
expect.errors(3);
|
||||
|
||||
const boom = (msg) => {
|
||||
throw new Error(msg);
|
||||
};
|
||||
|
||||
// Timeout
|
||||
setTimeout(() => boom("timeout"));
|
||||
// Promise
|
||||
queueMicrotask(() => boom("promise"));
|
||||
// Event
|
||||
manuallyDispatchProgrammaticEvent(window, "error", { message: "event" });
|
||||
|
||||
await tick();
|
||||
|
||||
expect.verifyErrors(["event", "promise", "timeout"]);
|
||||
});
|
||||
|
||||
test("verifySteps", () => {
|
||||
expect.assertions(4);
|
||||
|
||||
expect.verifySteps([]);
|
||||
|
||||
expect.step("abc");
|
||||
expect.step("def");
|
||||
expect.verifySteps(["abc", "def"]);
|
||||
|
||||
expect.step({ property: "foo" });
|
||||
expect.step("ghi");
|
||||
|
||||
expect.verifySteps([{ property: "foo" }, "ghi"]);
|
||||
expect.verifySteps([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("DOM matchers", () => {
|
||||
test("toBeChecked", async () => {
|
||||
await mountForTest(/* xml */ `
|
||||
<input type="checkbox" />
|
||||
<input type="checkbox" checked="" />
|
||||
`);
|
||||
|
||||
expect("input:first").not.toBeChecked();
|
||||
expect("input:last").toBeChecked();
|
||||
});
|
||||
|
||||
test("toHaveAttribute", async () => {
|
||||
await mountForTest(/* xml */ `
|
||||
<input type="number" disabled="" />
|
||||
`);
|
||||
|
||||
expect("input").toHaveAttribute("disabled");
|
||||
expect("input").not.toHaveAttribute("readonly");
|
||||
expect("input").toHaveAttribute("type", "number");
|
||||
});
|
||||
|
||||
test("toHaveCount", async () => {
|
||||
await mountForTest(/* xml */ `
|
||||
<ul>
|
||||
<li>milk</li>
|
||||
<li>eggs</li>
|
||||
<li>milk</li>
|
||||
</ul>
|
||||
`);
|
||||
|
||||
expect("iframe").toHaveCount(0);
|
||||
expect("iframe").not.toHaveCount();
|
||||
expect("ul").toHaveCount(1);
|
||||
expect("ul").toHaveCount();
|
||||
expect("li").toHaveCount(3);
|
||||
expect("li").toHaveCount();
|
||||
expect("li:contains(milk)").toHaveCount(2);
|
||||
});
|
||||
|
||||
test("toHaveProperty", async () => {
|
||||
await mountForTest(/* xml */ `
|
||||
<input type="search" readonly="" />
|
||||
`);
|
||||
|
||||
expect("input").toHaveProperty("type", "search");
|
||||
expect("input").not.toHaveProperty("readonly");
|
||||
expect("input").toHaveProperty("readOnly", true);
|
||||
});
|
||||
|
||||
test("toHaveText", async () => {
|
||||
class TextComponent extends Component {
|
||||
static props = {};
|
||||
static template = xml`
|
||||
<div class="with">With<t t-esc="nbsp" />nbsp</div>
|
||||
<div class="without">Without nbsp</div>
|
||||
`;
|
||||
|
||||
nbsp = "\u00a0";
|
||||
}
|
||||
|
||||
await mountForTest(TextComponent);
|
||||
|
||||
expect(".with").toHaveText("With nbsp");
|
||||
expect(".with").toHaveText("With\u00a0nbsp", { raw: true });
|
||||
expect(".with").not.toHaveText("With\u00a0nbsp");
|
||||
|
||||
expect(".without").toHaveText("Without nbsp");
|
||||
expect(".without").not.toHaveText("Without\u00a0nbsp");
|
||||
expect(".without").not.toHaveText("Without\u00a0nbsp", { raw: true });
|
||||
});
|
||||
|
||||
test("toHaveInnerHTML", async () => {
|
||||
await mountForTest(/* xml */ `
|
||||
<div class="parent">
|
||||
<p>
|
||||
abc<strong>def</strong>ghi
|
||||
<br />
|
||||
<input type="text" />
|
||||
</p>
|
||||
</div>
|
||||
`);
|
||||
|
||||
expect(".parent").toHaveInnerHTML(/* xml */ `
|
||||
<p>abc<strong>def</strong>ghi<br><input type="text"></p>
|
||||
`);
|
||||
});
|
||||
|
||||
test("toHaveOuterHTML", async () => {
|
||||
await mountForTest(/* xml */ `
|
||||
<div class="parent">
|
||||
<p>
|
||||
abc<strong>def</strong>ghi
|
||||
<br />
|
||||
<input type="text" />
|
||||
</p>
|
||||
</div>
|
||||
`);
|
||||
|
||||
expect(".parent").toHaveOuterHTML(/* xml */ `
|
||||
<div class="parent">
|
||||
<p>abc<strong>def</strong>ghi<br><input type="text"></p>
|
||||
</div>
|
||||
`);
|
||||
});
|
||||
|
||||
test("toHaveStyle", async () => {
|
||||
const documentFontSize = parseFloat(
|
||||
getComputedStyle(document.documentElement).fontSize
|
||||
);
|
||||
await mountForTest(/* xml */ `
|
||||
<div class="div" style="width: 3rem; height: 26px" />
|
||||
`);
|
||||
|
||||
expect(".div").toHaveStyle({ width: `${3 * documentFontSize}px`, height: 26 });
|
||||
expect(".div").toHaveStyle({ display: "block" });
|
||||
expect(".div").toHaveStyle("border-top");
|
||||
expect(".div").not.toHaveStyle({ height: 50 });
|
||||
|
||||
expect(".div").toHaveStyle("height: 26px ; width : 3rem", { inline: true });
|
||||
expect(".div").not.toHaveStyle({ display: "block" }, { inline: true });
|
||||
expect(".div").not.toHaveStyle("border-top", { inline: true });
|
||||
});
|
||||
|
||||
test("no elements found messages", async () => {
|
||||
const [customExpect, hooks] = makeExpect({ headless: true });
|
||||
hooks.before();
|
||||
|
||||
await mountForTest(/* xml */ `
|
||||
<div />
|
||||
`);
|
||||
|
||||
const SELECTOR = "#brrbrrpatapim";
|
||||
const DOM_MATCHERS = [
|
||||
["toBeChecked"],
|
||||
["toBeDisplayed"],
|
||||
["toBeEnabled"],
|
||||
["toBeFocused"],
|
||||
["toBeVisible"],
|
||||
["toHaveAttribute", "attr"],
|
||||
["toHaveClass", "cls"],
|
||||
["toHaveInnerHTML", "<html></html>"],
|
||||
["toHaveOuterHTML", "<html></html>"],
|
||||
["toHaveProperty", "prop"],
|
||||
["toHaveRect", {}],
|
||||
["toHaveStyle", {}],
|
||||
["toHaveText", "abc"],
|
||||
["toHaveValue", "value"],
|
||||
];
|
||||
|
||||
for (const [matcher, arg] of DOM_MATCHERS) {
|
||||
customExpect(SELECTOR)[matcher](arg);
|
||||
}
|
||||
|
||||
const results = hooks.after();
|
||||
const assertions = results.getEvents("assertion");
|
||||
for (let i = 0; i < DOM_MATCHERS.length; i++) {
|
||||
const { label, message } = assertions[i];
|
||||
expect.step(label);
|
||||
expect(message).toEqual([
|
||||
"expected at least",
|
||||
makeLabel(1),
|
||||
"element and got",
|
||||
makeLabel(0),
|
||||
"elements matching",
|
||||
makeLabel(SELECTOR),
|
||||
]);
|
||||
}
|
||||
|
||||
expect.verifySteps(DOM_MATCHERS.map(([matcher]) => matcher));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import { after, defineTags, describe, expect, test } from "@odoo/hoot";
|
||||
import { parseUrl } from "../local_helpers";
|
||||
|
||||
import { Runner } from "../../core/runner";
|
||||
import { Suite } from "../../core/suite";
|
||||
import { undefineTags } from "../../core/tag";
|
||||
|
||||
const makeTestRunner = () => {
|
||||
const runner = new Runner();
|
||||
after(() => undefineTags(runner.tags.keys()));
|
||||
return runner;
|
||||
};
|
||||
|
||||
describe(parseUrl(import.meta.url), () => {
|
||||
test("can register suites", () => {
|
||||
const runner = makeTestRunner();
|
||||
runner.describe("a suite", () => {});
|
||||
runner.describe("another suite", () => {});
|
||||
|
||||
expect(runner.suites).toHaveLength(2);
|
||||
expect(runner.tests).toHaveLength(0);
|
||||
for (const suite of runner.suites.values()) {
|
||||
expect(suite).toMatch(Suite);
|
||||
}
|
||||
});
|
||||
|
||||
test("can register nested suites", () => {
|
||||
const runner = makeTestRunner();
|
||||
runner.describe(["a", "b", "c"], () => {});
|
||||
|
||||
expect([...runner.suites.values()].map((s) => s.name)).toEqual(["a", "b", "c"]);
|
||||
});
|
||||
|
||||
test("can register tests", () => {
|
||||
const runner = makeTestRunner();
|
||||
runner.describe("suite 1", () => {
|
||||
runner.test("test 1", () => {});
|
||||
});
|
||||
runner.describe("suite 2", () => {
|
||||
runner.test("test 2", () => {});
|
||||
runner.test("test 3", () => {});
|
||||
});
|
||||
|
||||
expect(runner.suites).toHaveLength(2);
|
||||
expect(runner.tests).toHaveLength(3);
|
||||
});
|
||||
|
||||
test("should not have duplicate suites", () => {
|
||||
const runner = makeTestRunner();
|
||||
runner.describe(["parent", "child a"], () => {});
|
||||
runner.describe(["parent", "child b"], () => {});
|
||||
|
||||
expect([...runner.suites.values()].map((suite) => suite.name)).toEqual([
|
||||
"parent",
|
||||
"child a",
|
||||
"child b",
|
||||
]);
|
||||
});
|
||||
|
||||
test("can refuse standalone tests", async () => {
|
||||
const runner = makeTestRunner();
|
||||
expect(() =>
|
||||
runner.test([], "standalone test", () => {
|
||||
expect(true).toBe(false);
|
||||
})
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
test("can register test tags", async () => {
|
||||
const runner = makeTestRunner();
|
||||
runner.describe("suite", () => {
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
// 10
|
||||
runner.test.tags(`Tag-${i}`);
|
||||
}
|
||||
|
||||
runner.test("tagged test", () => {});
|
||||
});
|
||||
|
||||
expect(runner.tags).toHaveLength(10);
|
||||
expect(runner.tests.values().next().value.tags).toHaveLength(10);
|
||||
});
|
||||
|
||||
test("can define exclusive test tags", async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
defineTags(
|
||||
{
|
||||
name: "a",
|
||||
exclude: ["b"],
|
||||
},
|
||||
{
|
||||
name: "b",
|
||||
exclude: ["a"],
|
||||
}
|
||||
);
|
||||
|
||||
const runner = makeTestRunner();
|
||||
runner.describe("suite", () => {
|
||||
runner.test.tags("a");
|
||||
runner.test("first test", () => {});
|
||||
|
||||
runner.test.tags("b");
|
||||
runner.test("second test", () => {});
|
||||
|
||||
runner.test.tags("a", "b");
|
||||
expect(() => runner.test("third test", () => {})).toThrow(`cannot apply tag "b"`);
|
||||
|
||||
runner.test.tags("a", "c");
|
||||
runner.test("fourth test", () => {});
|
||||
});
|
||||
|
||||
expect(runner.tests).toHaveLength(3);
|
||||
expect(runner.tags).toHaveLength(3);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import { describe, expect, test } from "@odoo/hoot";
|
||||
import { parseUrl } from "../local_helpers";
|
||||
|
||||
import { Suite } from "../../core/suite";
|
||||
|
||||
describe(parseUrl(import.meta.url), () => {
|
||||
test("should have a hashed id", () => {
|
||||
expect(new Suite(null, "a suite", []).id).toMatch(/^\w{8}$/);
|
||||
});
|
||||
|
||||
test("should describe its path in its name", () => {
|
||||
const a = new Suite(null, "a", []);
|
||||
const b = new Suite(a, "b", []);
|
||||
const c = new Suite(a, "c", []);
|
||||
const d = new Suite(b, "d", []);
|
||||
|
||||
expect(a.parent).toBe(null);
|
||||
expect(b.parent).toBe(a);
|
||||
expect(c.parent).toBe(a);
|
||||
expect(d.parent.parent).toBe(a);
|
||||
|
||||
expect(a.fullName).toBe("a");
|
||||
expect(b.fullName).toBe("a/b");
|
||||
expect(c.fullName).toBe("a/c");
|
||||
expect(d.fullName).toBe("a/b/d");
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import { describe, expect, test } from "@odoo/hoot";
|
||||
import { parseUrl } from "../local_helpers";
|
||||
|
||||
import { Suite } from "../../core/suite";
|
||||
import { Test } from "../../core/test";
|
||||
|
||||
function disableHighlighting() {
|
||||
if (!window.Prism) {
|
||||
return () => {};
|
||||
}
|
||||
const { highlight } = window.Prism;
|
||||
window.Prism.highlight = (text) => text;
|
||||
|
||||
return function restoreHighlighting() {
|
||||
window.Prism.highlight = highlight;
|
||||
};
|
||||
}
|
||||
|
||||
describe(parseUrl(import.meta.url), () => {
|
||||
test("should have a hashed id", () => {
|
||||
expect(new Test(null, "a test", {}).id).toMatch(/^\w{8}$/);
|
||||
});
|
||||
|
||||
test("should describe its path in its name", () => {
|
||||
const a = new Suite(null, "a", {});
|
||||
const b = new Suite(a, "b", {});
|
||||
const t1 = new Test(null, "t1", {});
|
||||
const t2 = new Test(a, "t2", {});
|
||||
const t3 = new Test(b, "t3", {});
|
||||
|
||||
expect(t1.fullName).toBe("t1");
|
||||
expect(t2.fullName).toBe("a/t2");
|
||||
expect(t3.fullName).toBe("a/b/t3");
|
||||
});
|
||||
|
||||
test("run is async and lazily formatted", () => {
|
||||
const restoreHighlighting = disableHighlighting();
|
||||
|
||||
const testName = "some test";
|
||||
const t = new Test(null, testName, {});
|
||||
const runFn = () => {
|
||||
// Synchronous
|
||||
expect(1).toBe(1);
|
||||
};
|
||||
|
||||
expect(t.run).toBe(null);
|
||||
expect(t.runFnString).toBe("");
|
||||
expect(t.formatted).toBe(false);
|
||||
|
||||
t.setRunFn(runFn);
|
||||
|
||||
expect(t.run()).toBeInstanceOf(Promise);
|
||||
expect(t.runFnString).toBe(runFn.toString());
|
||||
expect(t.formatted).toBe(false);
|
||||
|
||||
expect(String(t.code)).toBe(
|
||||
`
|
||||
test("${testName}", () => {
|
||||
// Synchronous
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`.trim()
|
||||
);
|
||||
expect(t.formatted).toBe(true);
|
||||
|
||||
restoreHighlighting();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,922 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import { describe, expect, getFixture, test } from "@odoo/hoot";
|
||||
import {
|
||||
animationFrame,
|
||||
click,
|
||||
formatXml,
|
||||
getActiveElement,
|
||||
getFocusableElements,
|
||||
getNextFocusableElement,
|
||||
getPreviousFocusableElement,
|
||||
isDisplayed,
|
||||
isEditable,
|
||||
isFocusable,
|
||||
isInDOM,
|
||||
isVisible,
|
||||
queryAll,
|
||||
queryAllRects,
|
||||
queryAllTexts,
|
||||
queryFirst,
|
||||
queryOne,
|
||||
queryRect,
|
||||
waitFor,
|
||||
waitForNone,
|
||||
} from "@odoo/hoot-dom";
|
||||
import { mockTouch } from "@odoo/hoot-mock";
|
||||
import { getParentFrame } from "@web/../lib/hoot-dom/helpers/dom";
|
||||
import { mountForTest, parseUrl } from "../local_helpers";
|
||||
|
||||
const $ = queryFirst;
|
||||
const $1 = queryOne;
|
||||
const $$ = queryAll;
|
||||
|
||||
/**
|
||||
* @param {...string} queryAllSelectors
|
||||
*/
|
||||
const expectSelector = (...queryAllSelectors) => {
|
||||
/**
|
||||
* @param {string} nativeSelector
|
||||
*/
|
||||
const toEqualNodes = (nativeSelector, options) => {
|
||||
if (typeof nativeSelector !== "string") {
|
||||
throw new Error(`Invalid selector: ${nativeSelector}`);
|
||||
}
|
||||
let root = options?.root || getFixture();
|
||||
if (typeof root === "string") {
|
||||
root = getFixture().querySelector(root);
|
||||
if (root.tagName === "IFRAME") {
|
||||
root = root.contentDocument;
|
||||
}
|
||||
}
|
||||
let nodes = nativeSelector ? [...root.querySelectorAll(nativeSelector)] : [];
|
||||
if (Number.isInteger(options?.index)) {
|
||||
nodes = [nodes.at(options.index)];
|
||||
}
|
||||
|
||||
const selector = queryAllSelectors.join(", ");
|
||||
const fnNodes = $$(selector);
|
||||
expect(fnNodes).toEqual($$`${selector}`, {
|
||||
message: `should return the same result from a tagged template literal`,
|
||||
});
|
||||
expect(fnNodes).toEqual(nodes, {
|
||||
message: `should match ${nodes.length} nodes`,
|
||||
});
|
||||
};
|
||||
|
||||
return { toEqualNodes };
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Document} document
|
||||
* @param {HTMLElement} [root]
|
||||
* @returns {Promise<HTMLIFrameElement>}
|
||||
*/
|
||||
const makeIframe = (document, root) =>
|
||||
new Promise((resolve) => {
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.addEventListener("load", () => resolve(iframe));
|
||||
iframe.srcdoc = "<body></body>";
|
||||
(root || document.body).appendChild(iframe);
|
||||
});
|
||||
|
||||
const FULL_HTML_TEMPLATE = /* html */ `
|
||||
<header>
|
||||
<h1 class="title">Title</h1>
|
||||
</header>
|
||||
<main id="custom-html">
|
||||
<h5 class="title">List header</h5>
|
||||
<ul colspan="1" class="overflow-auto" style="max-height: 80px">
|
||||
<li class="text highlighted">First item</li>
|
||||
<li class="text">Second item</li>
|
||||
<li class="text">Last item</li>
|
||||
</ul>
|
||||
<p colspan="2" class="text">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur justo
|
||||
velit, tristique vitae neque a, faucibus mollis dui. Aliquam iaculis
|
||||
sodales mi id posuere. Proin malesuada bibendum pellentesque. Phasellus
|
||||
mattis at massa quis gravida. Morbi luctus interdum mi, quis dapibus
|
||||
augue. Vivamus condimentum nunc mi, vitae suscipit turpis dictum nec.
|
||||
Sed varius diam dui, eget ultricies ante dictum ac.
|
||||
</p>
|
||||
<div class="hidden" style="display: none;">Invisible section</div>
|
||||
<svg></svg>
|
||||
<form class="overflow-auto" style="max-width: 100px">
|
||||
<h5 class="title">Form title</h5>
|
||||
<input name="name" type="text" value="John Doe (JOD)" />
|
||||
<input name="email" type="email" value="johndoe@sample.com" />
|
||||
<select name="title" value="mr">
|
||||
<option>Select an option</option>
|
||||
<option value="mr" selected="selected">Mr.</option>
|
||||
<option value="mrs">Mrs.</option>
|
||||
</select>
|
||||
<select name="job">
|
||||
<option selected="selected">Select an option</option>
|
||||
<option value="employer">Employer</option>
|
||||
<option value="employee">Employee</option>
|
||||
</select>
|
||||
<button type="submit">Submit</button>
|
||||
<button type="submit" disabled="disabled">Cancel</button>
|
||||
</form>
|
||||
<iframe srcdoc="<p>Iframe text content</p>"></iframe>
|
||||
</main>
|
||||
<footer>
|
||||
<em>Footer</em>
|
||||
<button type="button">Back to top</button>
|
||||
</footer>
|
||||
`;
|
||||
|
||||
customElements.define(
|
||||
"hoot-test-shadow-root",
|
||||
class ShadowRoot extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
const shadow = this.attachShadow({ mode: "open" });
|
||||
|
||||
const p = document.createElement("p");
|
||||
p.textContent = "Shadow content";
|
||||
|
||||
const input = document.createElement("input");
|
||||
|
||||
shadow.append(p, input);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
describe.tags("ui");
|
||||
describe(parseUrl(import.meta.url), () => {
|
||||
test("formatXml", () => {
|
||||
expect(formatXml("")).toBe("");
|
||||
expect(formatXml("<input />")).toBe("<input/>");
|
||||
expect(
|
||||
formatXml(/* xml */ `
|
||||
<div>
|
||||
A
|
||||
</div>
|
||||
`)
|
||||
).toBe(`<div>\n A\n</div>`);
|
||||
expect(formatXml(/* xml */ `<div>A</div>`)).toBe(`<div>\n A\n</div>`);
|
||||
|
||||
// Inline
|
||||
expect(
|
||||
formatXml(
|
||||
/* xml */ `
|
||||
<div>
|
||||
A
|
||||
</div>
|
||||
`,
|
||||
{ keepInlineTextNodes: true }
|
||||
)
|
||||
).toBe(`<div>\n A\n</div>`);
|
||||
expect(formatXml(/* xml */ `<div>A</div>`, { keepInlineTextNodes: true })).toBe(
|
||||
`<div>A</div>`
|
||||
);
|
||||
});
|
||||
|
||||
test("getActiveElement", async () => {
|
||||
await mountForTest(/* xml */ `<iframe srcdoc="<input >"></iframe>`);
|
||||
|
||||
expect(":iframe input").not.toBeFocused();
|
||||
|
||||
const input = $1(":iframe input");
|
||||
await click(input);
|
||||
|
||||
expect(":iframe input").toBeFocused();
|
||||
expect(getActiveElement()).toBe(input);
|
||||
});
|
||||
|
||||
test("getActiveElement: shadow dom", async () => {
|
||||
await mountForTest(/* xml */ `<hoot-test-shadow-root />`);
|
||||
|
||||
expect("hoot-test-shadow-root:shadow input").not.toBeFocused();
|
||||
|
||||
const input = $1("hoot-test-shadow-root:shadow input");
|
||||
await click(input);
|
||||
|
||||
expect("hoot-test-shadow-root:shadow input").toBeFocused();
|
||||
expect(getActiveElement()).toBe(input);
|
||||
});
|
||||
|
||||
test("getFocusableElements", async () => {
|
||||
await mountForTest(/* xml */ `
|
||||
<input class="input" />
|
||||
<div class="div" tabindex="0">aaa</div>
|
||||
<span class="span" tabindex="-1">aaa</span>
|
||||
<button class="disabled-button" disabled="disabled">Disabled button</button>
|
||||
<button class="button" tabindex="1">Button</button>
|
||||
`);
|
||||
|
||||
expect(getFocusableElements().map((el) => el.className)).toEqual([
|
||||
"button",
|
||||
"span",
|
||||
"input",
|
||||
"div",
|
||||
]);
|
||||
|
||||
expect(getFocusableElements({ tabbable: true }).map((el) => el.className)).toEqual([
|
||||
"button",
|
||||
"input",
|
||||
"div",
|
||||
]);
|
||||
});
|
||||
|
||||
test("getNextFocusableElement", async () => {
|
||||
await mountForTest(/* xml */ `
|
||||
<input class="input" />
|
||||
<div class="div" tabindex="0">aaa</div>
|
||||
<button class="disabled-button" disabled="disabled">Disabled button</button>
|
||||
<button class="button" tabindex="1">Button</button>
|
||||
`);
|
||||
|
||||
await click(".input");
|
||||
|
||||
expect(getNextFocusableElement()).toHaveClass("div");
|
||||
});
|
||||
|
||||
test("getParentFrame", async () => {
|
||||
await mountForTest(/* xml */ `
|
||||
<div class="root"></div>
|
||||
`);
|
||||
|
||||
const parent = await makeIframe(document, $1(".root"));
|
||||
const child = await makeIframe(parent.contentDocument);
|
||||
|
||||
const content = child.contentDocument.createElement("div");
|
||||
child.contentDocument.body.appendChild(content);
|
||||
|
||||
expect(getParentFrame(content)).toBe(child);
|
||||
expect(getParentFrame(child)).toBe(parent);
|
||||
expect(getParentFrame(parent)).toBe(null);
|
||||
});
|
||||
|
||||
test("getPreviousFocusableElement", async () => {
|
||||
await mountForTest(/* xml */ `
|
||||
<input class="input" />
|
||||
<div class="div" tabindex="0">aaa</div>
|
||||
<button class="disabled-button" disabled="disabled">Disabled button</button>
|
||||
<button class="button" tabindex="1">Button</button>
|
||||
`);
|
||||
|
||||
await click(".input");
|
||||
|
||||
expect(getPreviousFocusableElement()).toHaveClass("button");
|
||||
});
|
||||
|
||||
test("isEditable", async () => {
|
||||
expect(isEditable(document.createElement("input"))).toBe(true);
|
||||
expect(isEditable(document.createElement("textarea"))).toBe(true);
|
||||
expect(isEditable(document.createElement("select"))).toBe(false);
|
||||
|
||||
const editableDiv = document.createElement("div");
|
||||
expect(isEditable(editableDiv)).toBe(false);
|
||||
editableDiv.setAttribute("contenteditable", "true");
|
||||
expect(isEditable(editableDiv)).toBe(false); // not supported
|
||||
});
|
||||
|
||||
test("isFocusable", async () => {
|
||||
await mountForTest(FULL_HTML_TEMPLATE);
|
||||
|
||||
expect(isFocusable("input:first")).toBe(true);
|
||||
expect(isFocusable("li:first")).toBe(false);
|
||||
});
|
||||
|
||||
test("isInDom", async () => {
|
||||
await mountForTest(FULL_HTML_TEMPLATE);
|
||||
|
||||
expect(isInDOM(document)).toBe(true);
|
||||
expect(isInDOM(document.body)).toBe(true);
|
||||
expect(isInDOM(document.head)).toBe(true);
|
||||
expect(isInDOM(document.documentElement)).toBe(true);
|
||||
|
||||
const form = $1`form`;
|
||||
expect(isInDOM(form)).toBe(true);
|
||||
|
||||
form.remove();
|
||||
|
||||
expect(isInDOM(form)).toBe(false);
|
||||
|
||||
const paragraph = $1`:iframe p`;
|
||||
expect(isInDOM(paragraph)).toBe(true);
|
||||
|
||||
paragraph.remove();
|
||||
|
||||
expect(isInDOM(paragraph)).toBe(false);
|
||||
});
|
||||
|
||||
test("isDisplayed", async () => {
|
||||
await mountForTest(FULL_HTML_TEMPLATE);
|
||||
|
||||
expect(isDisplayed(document)).toBe(true);
|
||||
expect(isDisplayed(document.body)).toBe(true);
|
||||
expect(isDisplayed(document.head)).toBe(true);
|
||||
expect(isDisplayed(document.documentElement)).toBe(true);
|
||||
expect(isDisplayed("form")).toBe(true);
|
||||
|
||||
expect(isDisplayed(".hidden")).toBe(false);
|
||||
expect(isDisplayed("body")).toBe(false); // not available from fixture
|
||||
});
|
||||
|
||||
test("isVisible", async () => {
|
||||
await mountForTest(FULL_HTML_TEMPLATE + "<hoot-test-shadow-root />");
|
||||
|
||||
expect(isVisible(document)).toBe(true);
|
||||
expect(isVisible(document.body)).toBe(true);
|
||||
expect(isVisible(document.head)).toBe(false);
|
||||
expect(isVisible(document.documentElement)).toBe(true);
|
||||
expect(isVisible("form")).toBe(true);
|
||||
expect(isVisible("hoot-test-shadow-root:shadow input")).toBe(true);
|
||||
|
||||
expect(isVisible(".hidden")).toBe(false);
|
||||
expect(isVisible("body")).toBe(false); // not available from fixture
|
||||
});
|
||||
|
||||
test("matchMedia", async () => {
|
||||
// Invalid syntax
|
||||
expect(matchMedia("aaaa").matches).toBe(false);
|
||||
expect(matchMedia("display-mode: browser").matches).toBe(false);
|
||||
|
||||
// Does not exist
|
||||
expect(matchMedia("(a)").matches).toBe(false);
|
||||
expect(matchMedia("(a: b)").matches).toBe(false);
|
||||
|
||||
// Defaults
|
||||
expect(matchMedia("(display-mode:browser)").matches).toBe(true);
|
||||
expect(matchMedia("(display-mode: standalone)").matches).toBe(false);
|
||||
expect(matchMedia("not (display-mode: standalone)").matches).toBe(true);
|
||||
expect(matchMedia("(prefers-color-scheme :light)").matches).toBe(true);
|
||||
expect(matchMedia("(prefers-color-scheme : dark)").matches).toBe(false);
|
||||
expect(matchMedia("not (prefers-color-scheme: dark)").matches).toBe(true);
|
||||
expect(matchMedia("(prefers-reduced-motion: reduce)").matches).toBe(true);
|
||||
expect(matchMedia("(prefers-reduced-motion: no-preference)").matches).toBe(false);
|
||||
|
||||
// Touch feature
|
||||
expect(window.matchMedia("(pointer: coarse)").matches).toBe(false);
|
||||
expect(window.ontouchstart).toBe(undefined);
|
||||
|
||||
mockTouch(true);
|
||||
|
||||
expect(window.matchMedia("(pointer: coarse)").matches).toBe(true);
|
||||
expect(window.ontouchstart).not.toBe(undefined);
|
||||
});
|
||||
|
||||
test("waitFor: already in fixture", async () => {
|
||||
await mountForTest(FULL_HTML_TEMPLATE);
|
||||
|
||||
waitFor(".title").then((el) => {
|
||||
expect.step(el.className);
|
||||
return el;
|
||||
});
|
||||
|
||||
expect.verifySteps([]);
|
||||
|
||||
await animationFrame();
|
||||
|
||||
expect.verifySteps(["title"]);
|
||||
});
|
||||
|
||||
test("waitFor: rejects", async () => {
|
||||
await expect(waitFor("never", { timeout: 1 })).rejects.toThrow(
|
||||
`expected at least 1 element after 1ms and found 0 elements: 0 matching "never"`
|
||||
);
|
||||
});
|
||||
|
||||
test("waitFor: add new element", async () => {
|
||||
const el1 = document.createElement("div");
|
||||
el1.className = "new-element";
|
||||
|
||||
const el2 = document.createElement("div");
|
||||
el2.className = "new-element";
|
||||
|
||||
const promise = waitFor(".new-element").then((el) => {
|
||||
expect.step(el.className);
|
||||
return el;
|
||||
});
|
||||
|
||||
await animationFrame();
|
||||
|
||||
expect.verifySteps([]);
|
||||
|
||||
getFixture().append(el1, el2);
|
||||
|
||||
await expect(promise).resolves.toBe(el1);
|
||||
|
||||
expect.verifySteps(["new-element"]);
|
||||
});
|
||||
|
||||
test("waitForNone: DOM empty", async () => {
|
||||
waitForNone(".title").then(() => expect.step("none"));
|
||||
expect.verifySteps([]);
|
||||
|
||||
await animationFrame();
|
||||
|
||||
expect.verifySteps(["none"]);
|
||||
});
|
||||
|
||||
test("waitForNone: rejects", async () => {
|
||||
await mountForTest(FULL_HTML_TEMPLATE);
|
||||
|
||||
await expect(waitForNone(".title", { timeout: 1 })).rejects.toThrow();
|
||||
});
|
||||
|
||||
test("waitForNone: delete elements", async () => {
|
||||
await mountForTest(FULL_HTML_TEMPLATE);
|
||||
|
||||
waitForNone(".title").then(() => expect.step("none"));
|
||||
expect(".title").toHaveCount(3);
|
||||
|
||||
for (const title of $$(".title")) {
|
||||
expect.verifySteps([]);
|
||||
|
||||
title.remove();
|
||||
|
||||
await animationFrame();
|
||||
}
|
||||
|
||||
expect.verifySteps(["none"]);
|
||||
});
|
||||
|
||||
describe("query", () => {
|
||||
test("native selectors", async () => {
|
||||
await mountForTest(FULL_HTML_TEMPLATE);
|
||||
|
||||
expect($$()).toEqual([]);
|
||||
for (const selector of [
|
||||
"main",
|
||||
`.${"title"}`,
|
||||
`${"ul"}${" "}${`${"li"}`}`,
|
||||
".title",
|
||||
"ul > li",
|
||||
"form:has(.title:not(.haha)):not(.huhu) input[name='email']:enabled",
|
||||
"[colspan='1']",
|
||||
]) {
|
||||
expectSelector(selector).toEqualNodes(selector);
|
||||
}
|
||||
});
|
||||
|
||||
test("custom pseudo-classes", async () => {
|
||||
await mountForTest(FULL_HTML_TEMPLATE);
|
||||
|
||||
// :first, :last, :only & :eq
|
||||
expectSelector(".title:first").toEqualNodes(".title", { index: 0 });
|
||||
expectSelector(".title:last").toEqualNodes(".title", { index: -1 });
|
||||
expectSelector(".title:eq(-1)").toEqualNodes(".title", { index: -1 });
|
||||
expectSelector("main:only").toEqualNodes("main");
|
||||
expectSelector(".title:only").toEqualNodes("");
|
||||
expectSelector(".title:eq(1)").toEqualNodes(".title", { index: 1 });
|
||||
expectSelector(".title:eq('1')").toEqualNodes(".title", { index: 1 });
|
||||
expectSelector('.title:eq("1")').toEqualNodes(".title", { index: 1 });
|
||||
|
||||
// :contains (text)
|
||||
expectSelector("main > .text:contains(ipsum)").toEqualNodes("p");
|
||||
expectSelector(".text:contains(/\\bL\\w+\\b\\sipsum/)").toEqualNodes("p");
|
||||
expectSelector(".text:contains(item)").toEqualNodes("li");
|
||||
|
||||
// :contains (value)
|
||||
expectSelector("input:value(john)").toEqualNodes("[name=name],[name=email]");
|
||||
expectSelector("input:value(john doe)").toEqualNodes("[name=name]");
|
||||
expectSelector("input:value('John Doe (JOD)')").toEqualNodes("[name=name]");
|
||||
expectSelector(`input:value("(JOD)")`).toEqualNodes("[name=name]");
|
||||
expectSelector("input:value(johndoe)").toEqualNodes("[name=email]");
|
||||
expectSelector("select:value(mr)").toEqualNodes("[name=title]");
|
||||
expectSelector("select:value(unknown value)").toEqualNodes("");
|
||||
|
||||
// :selected
|
||||
expectSelector("option:selected").toEqualNodes(
|
||||
"select[name=title] option[value=mr],select[name=job] option:first-child"
|
||||
);
|
||||
|
||||
// :iframe
|
||||
expectSelector("iframe p:contains(iframe text content)").toEqualNodes("");
|
||||
expectSelector("div:iframe p").toEqualNodes("");
|
||||
expectSelector(":iframe p:contains(iframe text content)").toEqualNodes("p", {
|
||||
root: "iframe",
|
||||
});
|
||||
});
|
||||
|
||||
test("advanced use cases", async () => {
|
||||
await mountForTest(FULL_HTML_TEMPLATE);
|
||||
|
||||
// Comma-separated selectors
|
||||
expectSelector(":has(form:contains('Form title')),p:contains(ipsum)").toEqualNodes(
|
||||
"p,main"
|
||||
);
|
||||
|
||||
// :has & :not combinations with custom pseudo-classes
|
||||
expectSelector(`select:has(:contains(Employer))`).toEqualNodes("select[name=job]");
|
||||
expectSelector(`select:not(:has(:contains(Employer)))`).toEqualNodes(
|
||||
"select[name=title]"
|
||||
);
|
||||
expectSelector(
|
||||
`main:first-of-type:not(:has(:contains(This text does not exist))):contains('List header') > form:has([name="name"]):contains("Form title"):nth-child(6).overflow-auto:visible select[name=job] option:selected`
|
||||
).toEqualNodes("select[name=job] option:first-child");
|
||||
|
||||
// :contains & commas
|
||||
expectSelector(`p:contains(velit,)`).toEqualNodes("p");
|
||||
expectSelector(`p:contains('velit,')`).toEqualNodes("p");
|
||||
expectSelector(`p:contains(", tristique")`).toEqualNodes("p");
|
||||
expectSelector(`p:contains(/\\bvelit,/)`).toEqualNodes("p");
|
||||
});
|
||||
|
||||
// Whatever, at this point I'm just copying failing selectors and creating
|
||||
// fake contexts accordingly as I'm fixing them.
|
||||
|
||||
test("comma-separated long selector: no match", async () => {
|
||||
await mountForTest(/* xml */ `
|
||||
<div class="o_we_customize_panel">
|
||||
<we-customizeblock-option class="snippet-option-ImageTools">
|
||||
<div class="o_we_so_color_palette o_we_widget_opened">
|
||||
idk
|
||||
</div>
|
||||
<we-select data-name="shape_img_opt">
|
||||
<we-toggler></we-toggler>
|
||||
</we-select>
|
||||
</we-customizeblock-option>
|
||||
</div>
|
||||
`);
|
||||
expectSelector(
|
||||
`.o_we_customize_panel:not(:has(.o_we_so_color_palette.o_we_widget_opened)) we-customizeblock-option[class='snippet-option-ImageTools'] we-select[data-name="shape_img_opt"] we-toggler`,
|
||||
`.o_we_customize_panel:not(:has(.o_we_so_color_palette.o_we_widget_opened)) we-customizeblock-option[class='snippet-option-ImageTools'] [title='we-select[data-name="shape_img_opt"] we-toggler']`
|
||||
).toEqualNodes("");
|
||||
});
|
||||
|
||||
test("comma-separated long selector: match first", async () => {
|
||||
await mountForTest(/* xml */ `
|
||||
<div class="o_we_customize_panel">
|
||||
<we-customizeblock-option class="snippet-option-ImageTools">
|
||||
<we-select data-name="shape_img_opt">
|
||||
<we-toggler></we-toggler>
|
||||
</we-select>
|
||||
</we-customizeblock-option>
|
||||
</div>
|
||||
`);
|
||||
expectSelector(
|
||||
`.o_we_customize_panel:not(:has(.o_we_so_color_palette.o_we_widget_opened)) we-customizeblock-option[class='snippet-option-ImageTools'] we-select[data-name="shape_img_opt"] we-toggler`,
|
||||
`.o_we_customize_panel:not(:has(.o_we_so_color_palette.o_we_widget_opened)) we-customizeblock-option[class='snippet-option-ImageTools'] [title='we-select[data-name="shape_img_opt"] we-toggler']`
|
||||
).toEqualNodes("we-toggler");
|
||||
});
|
||||
|
||||
test("comma-separated long selector: match second", async () => {
|
||||
await mountForTest(/* xml */ `
|
||||
<div class="o_we_customize_panel">
|
||||
<we-customizeblock-option class="snippet-option-ImageTools">
|
||||
<div title='we-select[data-name="shape_img_opt"] we-toggler'>
|
||||
idk
|
||||
</div>
|
||||
</we-customizeblock-option>
|
||||
</div>
|
||||
`);
|
||||
expectSelector(
|
||||
`.o_we_customize_panel:not(:has(.o_we_so_color_palette.o_we_widget_opened)) we-customizeblock-option[class='snippet-option-ImageTools'] we-select[data-name="shape_img_opt"] we-toggler`,
|
||||
`.o_we_customize_panel:not(:has(.o_we_so_color_palette.o_we_widget_opened)) we-customizeblock-option[class='snippet-option-ImageTools'] [title='we-select[data-name="shape_img_opt"] we-toggler']`
|
||||
).toEqualNodes("div[title]");
|
||||
});
|
||||
|
||||
test("comma-separated :contains", async () => {
|
||||
await mountForTest(/* xml */ `
|
||||
<div class="o_menu_sections">
|
||||
<a class="dropdown-item">Products</a>
|
||||
</div>
|
||||
<nav class="o_burger_menu_content">
|
||||
<ul>
|
||||
<li data-menu-xmlid="sale.menu_product_template_action">
|
||||
Products
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
`);
|
||||
expectSelector(
|
||||
`.o_menu_sections .dropdown-item:contains('Products'), nav.o_burger_menu_content li[data-menu-xmlid='sale.menu_product_template_action']`
|
||||
).toEqualNodes(".dropdown-item,li");
|
||||
});
|
||||
|
||||
test(":contains with line return", async () => {
|
||||
await mountForTest(/* xml */ `
|
||||
<span>
|
||||
<div>Matrix (PAV11, PAV22, PAV31)</div>
|
||||
<div>PA4: PAV41</div>
|
||||
</span>
|
||||
`);
|
||||
expectSelector(
|
||||
`span:contains("Matrix (PAV11, PAV22, PAV31)\nPA4: PAV41")`
|
||||
).toEqualNodes("span");
|
||||
});
|
||||
|
||||
test(":has(...):first", async () => {
|
||||
await mountForTest(/* xml */ `
|
||||
<a href="/web/event/1"></a>
|
||||
<a target="" href="/web/event/2">
|
||||
<span>Conference for Architects TEST</span>
|
||||
</a>
|
||||
`);
|
||||
|
||||
expectSelector(
|
||||
`a[href*="/event"]:contains("Conference for Architects TEST")`
|
||||
).toEqualNodes("[target]");
|
||||
expectSelector(
|
||||
`a[href*="/event"]:contains("Conference for Architects TEST"):first`
|
||||
).toEqualNodes("[target]");
|
||||
});
|
||||
|
||||
test(":eq", async () => {
|
||||
await mountForTest(/* xml */ `
|
||||
<ul>
|
||||
<li>a</li>
|
||||
<li>b</li>
|
||||
<li>c</li>
|
||||
</ul>
|
||||
`);
|
||||
|
||||
expectSelector(`li:first:contains(a)`).toEqualNodes("li:nth-child(1)");
|
||||
expectSelector(`li:contains(a):first`).toEqualNodes("li:nth-child(1)");
|
||||
expectSelector(`li:first:contains(b)`).toEqualNodes("");
|
||||
expectSelector(`li:contains(b):first`).toEqualNodes("li:nth-child(2)");
|
||||
});
|
||||
|
||||
test(":empty", async () => {
|
||||
await mountForTest(/* xml */ `
|
||||
<input class="empty" />
|
||||
<input class="value" value="value" />
|
||||
`);
|
||||
|
||||
expectSelector(`input:empty`).toEqualNodes(".empty");
|
||||
expectSelector(`input:not(:empty)`).toEqualNodes(".value");
|
||||
});
|
||||
|
||||
test("regular :contains", async () => {
|
||||
await mountForTest(/* xml */ `
|
||||
<div class="website_links_click_chart">
|
||||
<div class="title">
|
||||
0 clicks
|
||||
</div>
|
||||
<div class="title">
|
||||
1 clicks
|
||||
</div>
|
||||
<div class="title">
|
||||
2 clicks
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
expectSelector(`.website_links_click_chart .title:contains("1 clicks")`).toEqualNodes(
|
||||
".title:nth-child(2)"
|
||||
);
|
||||
});
|
||||
|
||||
test("other regular :contains", async () => {
|
||||
await mountForTest(/* xml */ `
|
||||
<ul
|
||||
class="o-autocomplete--dropdown-menu ui-widget show dropdown-menu ui-autocomplete"
|
||||
style="position: fixed; top: 283.75px; left: 168.938px"
|
||||
>
|
||||
<li class="o-autocomplete--dropdown-item ui-menu-item block">
|
||||
<a
|
||||
href="#"
|
||||
class="dropdown-item ui-menu-item-wrapper truncate ui-state-active"
|
||||
>Account Tax Group Partner</a
|
||||
>
|
||||
</li>
|
||||
<li
|
||||
class="o-autocomplete--dropdown-item ui-menu-item block o_m2o_dropdown_option o_m2o_dropdown_option_search_more"
|
||||
>
|
||||
<a href="#" class="dropdown-item ui-menu-item-wrapper truncate"
|
||||
>Search More...</a
|
||||
>
|
||||
</li>
|
||||
<li
|
||||
class="o-autocomplete--dropdown-item ui-menu-item block o_m2o_dropdown_option o_m2o_dropdown_option_create_edit"
|
||||
>
|
||||
<a href="#" class="dropdown-item ui-menu-item-wrapper truncate"
|
||||
>Create and edit...</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
`);
|
||||
|
||||
expectSelector(`.ui-menu-item a:contains("Account Tax Group Partner")`).toEqualNodes(
|
||||
"ul li:first-child a"
|
||||
);
|
||||
});
|
||||
|
||||
test(":iframe", async () => {
|
||||
await mountForTest(/* xml */ `
|
||||
<iframe srcdoc="<p>Iframe text content</p>"></iframe>
|
||||
`);
|
||||
|
||||
expectSelector(`:iframe html`).toEqualNodes("html", { root: "iframe" });
|
||||
expectSelector(`:iframe body`).toEqualNodes("body", { root: "iframe" });
|
||||
expectSelector(`:iframe head`).toEqualNodes("head", { root: "iframe" });
|
||||
});
|
||||
|
||||
test(":contains with brackets", async () => {
|
||||
await mountForTest(/* xml */ `
|
||||
<div class="o_content">
|
||||
<div class="o_field_widget" name="messages">
|
||||
<table class="o_list_view table table-sm table-hover table-striped o_list_view_ungrouped">
|
||||
<tbody>
|
||||
<tr class="o_data_row">
|
||||
<td class="o_list_record_selector">
|
||||
bbb
|
||||
</td>
|
||||
<td class="o_data_cell o_required_modifier">
|
||||
<span>
|
||||
[test_trigger] Mitchell Admin
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
expectSelector(
|
||||
`.o_content:has(.o_field_widget[name=messages]):has(td:contains(/^bbb$/)):has(td:contains(/^\\[test_trigger\\] Mitchell Admin$/))`
|
||||
).toEqualNodes(".o_content");
|
||||
});
|
||||
|
||||
test(":eq in the middle of a selector", async () => {
|
||||
await mountForTest(/* xml */ `
|
||||
<ul>
|
||||
<li class="oe_overlay o_draggable"></li>
|
||||
<li class="oe_overlay o_draggable"></li>
|
||||
<li class="oe_overlay o_draggable oe_active"></li>
|
||||
<li class="oe_overlay o_draggable"></li>
|
||||
</ul>
|
||||
`);
|
||||
expectSelector(`.oe_overlay.o_draggable:eq(2).oe_active`).toEqualNodes(
|
||||
"li:nth-child(3)"
|
||||
);
|
||||
});
|
||||
|
||||
test("combinator +", async () => {
|
||||
await mountForTest(/* xml */ `
|
||||
<form class="js_attributes">
|
||||
<input type="checkbox" />
|
||||
<label>Steel - Test</label>
|
||||
</form>
|
||||
`);
|
||||
|
||||
expectSelector(
|
||||
`form.js_attributes input:not(:checked) + label:contains(Steel - Test)`
|
||||
).toEqualNodes("label");
|
||||
});
|
||||
|
||||
test("multiple + combinators", async () => {
|
||||
await mountForTest(/* xml */ `
|
||||
<div class="s_cover">
|
||||
<span class="o_text_highlight">
|
||||
<span class="o_text_highlight_item">
|
||||
<span class="o_text_highlight_path_underline" />
|
||||
</span>
|
||||
<br />
|
||||
<span class="o_text_highlight_item">
|
||||
<span class="o_text_highlight_path_underline" />
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
`);
|
||||
|
||||
expectSelector(`
|
||||
.s_cover span.o_text_highlight:has(
|
||||
.o_text_highlight_item
|
||||
+ br
|
||||
+ .o_text_highlight_item
|
||||
)
|
||||
`).toEqualNodes(".o_text_highlight");
|
||||
});
|
||||
|
||||
test(":last", async () => {
|
||||
await mountForTest(/* xml */ `
|
||||
<div class="o_field_widget" name="messages">
|
||||
<table class="o_list_view table table-sm table-hover table-striped o_list_view_ungrouped">
|
||||
<tbody>
|
||||
<tr class="o_data_row">
|
||||
<td class="o_list_record_remove">
|
||||
<button class="btn">Remove</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="o_data_row">
|
||||
<td class="o_list_record_remove">
|
||||
<button class="btn">Remove</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`);
|
||||
expectSelector(
|
||||
`.o_field_widget[name=messages] .o_data_row td.o_list_record_remove button:visible:last`
|
||||
).toEqualNodes(".o_data_row:last-child button");
|
||||
});
|
||||
|
||||
test("select :contains & :value", async () => {
|
||||
await mountForTest(/* xml */ `
|
||||
<select class="configurator_select form-select form-select-lg">
|
||||
<option value="217" selected="">Metal</option>
|
||||
<option value="218">Wood</option>
|
||||
</select>
|
||||
`);
|
||||
expectSelector(`.configurator_select:has(option:contains(Metal))`).toEqualNodes(
|
||||
"select"
|
||||
);
|
||||
expectSelector(`.configurator_select:has(option:value(217))`).toEqualNodes("select");
|
||||
expectSelector(`.configurator_select:has(option:value(218))`).toEqualNodes("select");
|
||||
expectSelector(`.configurator_select:value(217)`).toEqualNodes("select");
|
||||
expectSelector(`.configurator_select:value(218)`).toEqualNodes("");
|
||||
expectSelector(`.configurator_select:value(Metal)`).toEqualNodes("");
|
||||
});
|
||||
|
||||
test("invalid selectors", async () => {
|
||||
await mountForTest(FULL_HTML_TEMPLATE);
|
||||
|
||||
expect(() => $$`[colspan=1]`).toThrow(); // missing quotes
|
||||
expect(() => $$`[href=/]`).toThrow(); // missing quotes
|
||||
expect(
|
||||
() =>
|
||||
$$`_o_wblog_posts_loop:has(span:has(i.fa-calendar-o):has(a[href="/blog?search=a"])):has(span:has(i.fa-search):has(a[href^="/blog?date_begin"]))`
|
||||
).toThrow(); // nested :has statements
|
||||
});
|
||||
|
||||
test("queryAllRects", async () => {
|
||||
await mountForTest(/* xml */ `
|
||||
<div style="width: 40px; height: 60px;" />
|
||||
<div style="width: 20px; height: 10px;" />
|
||||
`);
|
||||
|
||||
expect(queryAllRects("div")).toEqual($$("div").map((el) => el.getBoundingClientRect()));
|
||||
expect(queryAllRects("div:first")).toEqual([new DOMRect({ width: 40, height: 60 })]);
|
||||
expect(queryAllRects("div:last")).toEqual([new DOMRect({ width: 20, height: 10 })]);
|
||||
});
|
||||
|
||||
test("queryAllTexts", async () => {
|
||||
await mountForTest(FULL_HTML_TEMPLATE);
|
||||
|
||||
expect(queryAllTexts(".title")).toEqual(["Title", "List header", "Form title"]);
|
||||
expect(queryAllTexts("footer")).toEqual(["FooterBack to top"]);
|
||||
});
|
||||
|
||||
test("queryOne", async () => {
|
||||
await mountForTest(FULL_HTML_TEMPLATE);
|
||||
|
||||
expect($1(".title:first")).toBe(getFixture().querySelector("header .title"));
|
||||
|
||||
expect(() => $1(".title")).toThrow();
|
||||
expect(() => $1(".title", { exact: 2 })).toThrow();
|
||||
});
|
||||
|
||||
test("queryRect", async () => {
|
||||
await mountForTest(/* xml */ `
|
||||
<div class="container">
|
||||
<div class="rect" style="width: 40px; height: 60px;" />
|
||||
</div>
|
||||
`);
|
||||
|
||||
expect(".rect").toHaveRect(".container"); // same rect as parent
|
||||
expect(".rect").toHaveRect({ width: 40, height: 60 });
|
||||
expect(queryRect(".rect")).toEqual($1(".rect").getBoundingClientRect());
|
||||
expect(queryRect(".rect")).toEqual(new DOMRect({ width: 40, height: 60 }));
|
||||
});
|
||||
|
||||
test("queryRect with trimPadding", async () => {
|
||||
await mountForTest(/* xml */ `
|
||||
<div style="width: 50px; height: 70px; padding: 5px; margin: 6px" />
|
||||
`);
|
||||
|
||||
expect("div").toHaveRect({ width: 50, height: 70 }); // with padding
|
||||
expect("div").toHaveRect({ width: 40, height: 60 }, { trimPadding: true });
|
||||
});
|
||||
|
||||
test("not found messages", async () => {
|
||||
await mountForTest(/* xml */ `
|
||||
<div class="tralalero">
|
||||
Tralala
|
||||
</div>
|
||||
`);
|
||||
|
||||
expect(() => $("invalid:pseudo-selector")).toThrow();
|
||||
// Perform in-between valid query with custom pseudo selectors
|
||||
expect($`.modal:visible:contains('Tung Tung Tung Sahur')`).toBe(null);
|
||||
|
||||
// queryOne error messages
|
||||
expect(() => $1()).toThrow(`found 0 elements instead of 1`);
|
||||
expect(() => $$([], { exact: 18 })).toThrow(`found 0 elements instead of 18`);
|
||||
expect(() => $1("")).toThrow(`found 0 elements instead of 1: 0 matching ""`);
|
||||
expect(() => $$(".tralalero", { exact: -20 })).toThrow(
|
||||
`found 1 element instead of -20: 1 matching ".tralalero"`
|
||||
);
|
||||
expect(() => $1`.tralalero:contains(Tralala):visible:scrollable:first`).toThrow(
|
||||
`found 0 elements instead of 1: 0 matching ".tralalero:contains(Tralala):visible:scrollable:first" (1 element with text "Tralala" > 1 visible element > 0 scrollable elements)`
|
||||
);
|
||||
expect(() =>
|
||||
$1(".tralalero", {
|
||||
contains: "Tralala",
|
||||
visible: true,
|
||||
scrollable: true,
|
||||
first: true,
|
||||
})
|
||||
).toThrow(
|
||||
`found 0 elements instead of 1: 1 matching ".tralalero", including 1 element with text "Tralala", including 1 visible element, including 0 scrollable elements`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,132 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import { describe, expect, test } from "@odoo/hoot";
|
||||
import {
|
||||
Deferred,
|
||||
advanceTime,
|
||||
animationFrame,
|
||||
microTick,
|
||||
runAllTimers,
|
||||
tick,
|
||||
waitUntil,
|
||||
} from "@odoo/hoot-dom";
|
||||
import { parseUrl } from "../local_helpers";
|
||||
|
||||
// timeout of 1 second to ensure all timeouts are actually mocked
|
||||
describe.timeout(1_000);
|
||||
describe(parseUrl(import.meta.url), () => {
|
||||
test("advanceTime", async () => {
|
||||
expect.assertions(8);
|
||||
|
||||
await advanceTime(5_000);
|
||||
|
||||
const timeoutId = window.setTimeout(() => expect.step("timeout"), "2000");
|
||||
const intervalId = window.setInterval(() => expect.step("interval"), 3_000);
|
||||
const animationHandle = window.requestAnimationFrame((delta) => {
|
||||
expect(delta).toBeGreaterThan(5_000);
|
||||
expect.step("animation");
|
||||
});
|
||||
|
||||
expect(timeoutId).toBeGreaterThan(0);
|
||||
expect(intervalId).toBeGreaterThan(0);
|
||||
expect(animationHandle).toBeGreaterThan(0);
|
||||
expect.verifySteps([]);
|
||||
|
||||
await advanceTime(10_000); // 10 seconds
|
||||
|
||||
expect.verifySteps(["animation", "timeout", "interval", "interval", "interval"]);
|
||||
|
||||
await advanceTime(10_000);
|
||||
|
||||
expect.verifySteps(["interval", "interval", "interval"]);
|
||||
|
||||
window.clearInterval(intervalId);
|
||||
|
||||
await advanceTime(10_000);
|
||||
|
||||
expect.verifySteps([]);
|
||||
});
|
||||
|
||||
test("Deferred", async () => {
|
||||
const def = new Deferred();
|
||||
|
||||
def.then(() => expect.step("resolved"));
|
||||
|
||||
expect.step("before");
|
||||
|
||||
def.resolve(14);
|
||||
|
||||
expect.step("after");
|
||||
|
||||
await expect(def).resolves.toBe(14);
|
||||
|
||||
expect.verifySteps(["before", "after", "resolved"]);
|
||||
});
|
||||
|
||||
test("tick", async () => {
|
||||
let count = 0;
|
||||
|
||||
const nextTickPromise = tick().then(() => ++count);
|
||||
|
||||
expect(count).toBe(0);
|
||||
|
||||
await expect(nextTickPromise).resolves.toBe(1);
|
||||
|
||||
expect(count).toBe(1);
|
||||
});
|
||||
|
||||
test("runAllTimers", async () => {
|
||||
expect.assertions(4);
|
||||
|
||||
window.setTimeout(() => expect.step("timeout"), 1e6);
|
||||
window.requestAnimationFrame((delta) => {
|
||||
expect(delta).toBeGreaterThan(1);
|
||||
expect.step("animation");
|
||||
});
|
||||
|
||||
expect.verifySteps([]);
|
||||
|
||||
const ms = await runAllTimers();
|
||||
|
||||
expect(ms).toBeCloseTo(1e6, { margin: 10 });
|
||||
expect.verifySteps(["animation", "timeout"]);
|
||||
});
|
||||
|
||||
test("waitUntil: already true", async () => {
|
||||
const promise = waitUntil(() => "some value").then((value) => {
|
||||
expect.step("resolved");
|
||||
return value;
|
||||
});
|
||||
|
||||
expect.verifySteps([]);
|
||||
expect(promise).toBeInstanceOf(Promise);
|
||||
|
||||
await microTick();
|
||||
|
||||
expect.verifySteps(["resolved"]);
|
||||
await expect(promise).resolves.toBe("some value");
|
||||
});
|
||||
|
||||
test("waitUntil: rejects", async () => {
|
||||
await expect(waitUntil(() => false, { timeout: 0 })).rejects.toThrow();
|
||||
});
|
||||
|
||||
test("waitUntil: lazy", async () => {
|
||||
let returnValue = "";
|
||||
const promise = waitUntil(() => returnValue).then((v) => expect.step(v));
|
||||
|
||||
expect.verifySteps([]);
|
||||
expect(promise).toBeInstanceOf(Promise);
|
||||
|
||||
await animationFrame();
|
||||
await animationFrame();
|
||||
|
||||
expect.verifySteps([]);
|
||||
|
||||
returnValue = "test";
|
||||
|
||||
await animationFrame();
|
||||
|
||||
expect.verifySteps(["test"]);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
const _owl = window.owl;
|
||||
delete window.owl;
|
||||
|
||||
export const App = _owl.App;
|
||||
export const Component = _owl.Component;
|
||||
export const EventBus = _owl.EventBus;
|
||||
export const OwlError = _owl.OwlError;
|
||||
export const __info__ = _owl.__info__;
|
||||
export const blockDom = _owl.blockDom;
|
||||
export const loadFile = _owl.loadFile;
|
||||
export const markRaw = _owl.markRaw;
|
||||
export const markup = _owl.markup;
|
||||
export const mount = _owl.mount;
|
||||
export const onError = _owl.onError;
|
||||
export const onMounted = _owl.onMounted;
|
||||
export const onPatched = _owl.onPatched;
|
||||
export const onRendered = _owl.onRendered;
|
||||
export const onWillDestroy = _owl.onWillDestroy;
|
||||
export const onWillPatch = _owl.onWillPatch;
|
||||
export const onWillRender = _owl.onWillRender;
|
||||
export const onWillStart = _owl.onWillStart;
|
||||
export const onWillUnmount = _owl.onWillUnmount;
|
||||
export const onWillUpdateProps = _owl.onWillUpdateProps;
|
||||
export const reactive = _owl.reactive;
|
||||
export const status = _owl.status;
|
||||
export const toRaw = _owl.toRaw;
|
||||
export const useChildSubEnv = _owl.useChildSubEnv;
|
||||
export const useComponent = _owl.useComponent;
|
||||
export const useEffect = _owl.useEffect;
|
||||
export const useEnv = _owl.useEnv;
|
||||
export const useExternalListener = _owl.useExternalListener;
|
||||
export const useRef = _owl.useRef;
|
||||
export const useState = _owl.useState;
|
||||
export const useSubEnv = _owl.useSubEnv;
|
||||
export const validate = _owl.validate;
|
||||
export const validateType = _owl.validateType;
|
||||
export const whenReady = _owl.whenReady;
|
||||
export const xml = _owl.xml;
|
||||
|
|
@ -0,0 +1,354 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import { describe, expect, test } from "@odoo/hoot";
|
||||
import { queryOne } from "@odoo/hoot-dom";
|
||||
import { isInstanceOf, isIterable } from "@web/../lib/hoot-dom/hoot_dom_utils";
|
||||
import {
|
||||
deepEqual,
|
||||
formatHumanReadable,
|
||||
formatTechnical,
|
||||
generateHash,
|
||||
levenshtein,
|
||||
lookup,
|
||||
match,
|
||||
parseQuery,
|
||||
title,
|
||||
toExplicitString,
|
||||
} from "../hoot_utils";
|
||||
import { mountForTest, parseUrl } from "./local_helpers";
|
||||
|
||||
describe(parseUrl(import.meta.url), () => {
|
||||
test("deepEqual", () => {
|
||||
const recursive = {};
|
||||
recursive.self = recursive;
|
||||
|
||||
const TRUTHY_CASES = [
|
||||
[true, true],
|
||||
[false, false],
|
||||
[null, null],
|
||||
[recursive, recursive],
|
||||
[new Date(0), new Date(0)],
|
||||
[
|
||||
{ b: 2, a: 1 },
|
||||
{ a: 1, b: 2 },
|
||||
],
|
||||
[{ o: { a: [{ b: 1 }] } }, { o: { a: [{ b: 1 }] } }],
|
||||
[Symbol.for("a"), Symbol.for("a")],
|
||||
[document.createElement("div"), document.createElement("div")],
|
||||
[
|
||||
[1, 2, 3],
|
||||
[1, 2, 3],
|
||||
],
|
||||
];
|
||||
const FALSY_CASES = [
|
||||
[true, false],
|
||||
[null, undefined],
|
||||
[recursive, { ...recursive, a: 1 }],
|
||||
[
|
||||
[1, 2, 3],
|
||||
[3, 1, 2],
|
||||
],
|
||||
[new Date(0), new Date(1_000)],
|
||||
[{ a: new Date(0) }, { a: 0 }],
|
||||
[document.createElement("a"), document.createElement("div")],
|
||||
[{ [Symbol("a")]: 1 }, { [Symbol("a")]: 1 }],
|
||||
];
|
||||
const TRUTHY_IF_UNORDERED_CASES = [
|
||||
[
|
||||
[1, "2", 3],
|
||||
["2", 3, 1],
|
||||
],
|
||||
[
|
||||
[1, { a: [4, 2] }, "3"],
|
||||
[{ a: [2, 4] }, "3", 1],
|
||||
],
|
||||
[
|
||||
new Set([
|
||||
"abc",
|
||||
new Map([
|
||||
["b", 2],
|
||||
["a", 1],
|
||||
]),
|
||||
]),
|
||||
new Set([
|
||||
new Map([
|
||||
["a", 1],
|
||||
["b", 2],
|
||||
]),
|
||||
"abc",
|
||||
]),
|
||||
],
|
||||
];
|
||||
|
||||
expect.assertions(
|
||||
TRUTHY_CASES.length + FALSY_CASES.length + TRUTHY_IF_UNORDERED_CASES.length * 2
|
||||
);
|
||||
|
||||
for (const [a, b] of TRUTHY_CASES) {
|
||||
expect(deepEqual(a, b)).toBe(true, {
|
||||
message: [a, `==`, b],
|
||||
});
|
||||
}
|
||||
for (const [a, b] of FALSY_CASES) {
|
||||
expect(deepEqual(a, b)).toBe(false, {
|
||||
message: [a, `!=`, b],
|
||||
});
|
||||
}
|
||||
for (const [a, b] of TRUTHY_IF_UNORDERED_CASES) {
|
||||
expect(deepEqual(a, b)).toBe(false, {
|
||||
message: [a, `!=`, b],
|
||||
});
|
||||
expect(deepEqual(a, b, { ignoreOrder: true })).toBe(true, {
|
||||
message: [a, `==`, b, `(unordered))`],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test("formatHumanReadable", () => {
|
||||
// Strings
|
||||
expect(formatHumanReadable("abc")).toBe(`"abc"`);
|
||||
expect(formatHumanReadable("a".repeat(300))).toBe(`"${"a".repeat(80)}…"`);
|
||||
expect(formatHumanReadable(`with "double quotes"`)).toBe(`'with "double quotes"'`);
|
||||
expect(formatHumanReadable(`with "double quotes" and 'single quote'`)).toBe(
|
||||
`\`with "double quotes" and 'single quote'\``
|
||||
);
|
||||
// Numbers
|
||||
expect(formatHumanReadable(1)).toBe(`1`);
|
||||
// Other primitives
|
||||
expect(formatHumanReadable(true)).toBe(`true`);
|
||||
expect(formatHumanReadable(null)).toBe(`null`);
|
||||
// Functions & classes
|
||||
expect(formatHumanReadable(async function oui() {})).toBe(`async function oui() { … }`);
|
||||
expect(formatHumanReadable(class Oui {})).toBe(`class Oui { … }`);
|
||||
// Iterators
|
||||
expect(formatHumanReadable([1, 2, 3])).toBe(`[1, 2, 3]`);
|
||||
expect(formatHumanReadable(new Set([1, 2, 3]))).toBe(`Set [1, 2, 3]`);
|
||||
expect(
|
||||
formatHumanReadable(
|
||||
new Map([
|
||||
["a", 1],
|
||||
["b", 2],
|
||||
])
|
||||
)
|
||||
).toBe(`Map [["a", 1], ["b", 2]]`);
|
||||
// Objects
|
||||
expect(formatHumanReadable(/ab(c)d/gi)).toBe(`/ab(c)d/gi`);
|
||||
expect(formatHumanReadable(new Date("1997-01-09T12:30:00.000Z"))).toBe(
|
||||
`1997-01-09T12:30:00.000Z`
|
||||
);
|
||||
expect(formatHumanReadable({})).toBe(`{ }`);
|
||||
expect(formatHumanReadable({ a: { b: 1 } })).toBe(`{ a: { b: 1 } }`);
|
||||
expect(
|
||||
formatHumanReadable(
|
||||
new Proxy(
|
||||
{
|
||||
allowed: true,
|
||||
get forbidden() {
|
||||
throw new Error("Cannot access!");
|
||||
},
|
||||
},
|
||||
{}
|
||||
)
|
||||
)
|
||||
).toBe(`{ allowed: true }`);
|
||||
expect(formatHumanReadable(window)).toBe(`Window { }`);
|
||||
// Nodes
|
||||
expect(formatHumanReadable(document.createElement("div"))).toBe("<div>");
|
||||
expect(formatHumanReadable(document.createTextNode("some text"))).toBe("#text");
|
||||
expect(formatHumanReadable(document)).toBe("#document");
|
||||
});
|
||||
|
||||
test("formatTechnical", () => {
|
||||
expect(
|
||||
formatTechnical({
|
||||
b: 2,
|
||||
[Symbol("s")]: "value",
|
||||
a: true,
|
||||
})
|
||||
).toBe(
|
||||
`{
|
||||
a: true,
|
||||
b: 2,
|
||||
Symbol(s): "value",
|
||||
}`.trim()
|
||||
);
|
||||
|
||||
expect(formatTechnical(["a", "b"])).toBe(
|
||||
`[
|
||||
"a",
|
||||
"b",
|
||||
]`.trim()
|
||||
);
|
||||
|
||||
class List extends Array {}
|
||||
|
||||
expect(formatTechnical(new List("a", "b"))).toBe(
|
||||
`List [
|
||||
"a",
|
||||
"b",
|
||||
]`.trim()
|
||||
);
|
||||
|
||||
function toArguments() {
|
||||
return arguments;
|
||||
}
|
||||
|
||||
expect(formatTechnical(toArguments("a", "b"))).toBe(
|
||||
`Arguments [
|
||||
"a",
|
||||
"b",
|
||||
]`.trim()
|
||||
);
|
||||
});
|
||||
|
||||
test("generateHash", () => {
|
||||
expect(generateHash("abc")).toHaveLength(8);
|
||||
expect(generateHash("abcdef")).toHaveLength(8);
|
||||
expect(generateHash("abc")).toBe(generateHash("abc"));
|
||||
|
||||
expect(generateHash("abc")).not.toBe(generateHash("def"));
|
||||
});
|
||||
|
||||
test("isInstanceOf", async () => {
|
||||
await mountForTest(/* xml */ `
|
||||
<iframe srcdoc="" />
|
||||
`);
|
||||
|
||||
expect(() => isInstanceOf()).toThrow(TypeError);
|
||||
expect(() => isInstanceOf("a")).toThrow(TypeError);
|
||||
|
||||
expect(isInstanceOf(null, null)).toBe(false);
|
||||
expect(isInstanceOf(undefined, undefined)).toBe(false);
|
||||
expect(isInstanceOf("", String)).toBe(false);
|
||||
expect(isInstanceOf(24, Number)).toBe(false);
|
||||
expect(isInstanceOf(true, Boolean)).toBe(false);
|
||||
|
||||
class List extends Array {}
|
||||
|
||||
class A {}
|
||||
class B extends A {}
|
||||
|
||||
expect(isInstanceOf([], Array)).toBe(true);
|
||||
expect(isInstanceOf(new List(), Array)).toBe(true);
|
||||
expect(isInstanceOf(new B(), B)).toBe(true);
|
||||
expect(isInstanceOf(new B(), A)).toBe(true);
|
||||
expect(isInstanceOf(new Error("error"), Error)).toBe(true);
|
||||
expect(isInstanceOf(/a/, RegExp, Date)).toBe(true);
|
||||
expect(isInstanceOf(new Date(), RegExp, Date)).toBe(true);
|
||||
|
||||
const { contentDocument, contentWindow } = queryOne("iframe");
|
||||
|
||||
expect(isInstanceOf(queryOne("iframe"), HTMLIFrameElement)).toBe(true);
|
||||
expect(contentWindow instanceof Window).toBe(false);
|
||||
expect(isInstanceOf(contentWindow, Window)).toBe(true);
|
||||
expect(contentDocument.body instanceof HTMLBodyElement).toBe(false);
|
||||
expect(isInstanceOf(contentDocument.body, HTMLBodyElement)).toBe(true);
|
||||
});
|
||||
|
||||
test("isIterable", () => {
|
||||
expect(isIterable([1, 2, 3])).toBe(true);
|
||||
expect(isIterable(new Set([1, 2, 3]))).toBe(true);
|
||||
|
||||
expect(isIterable(null)).toBe(false);
|
||||
expect(isIterable("abc")).toBe(false);
|
||||
expect(isIterable({})).toBe(false);
|
||||
});
|
||||
|
||||
test("levenshtein", () => {
|
||||
expect(levenshtein("abc", "abc")).toBe(0);
|
||||
expect(levenshtein("abc", "àbc ")).toBe(2);
|
||||
expect(levenshtein("abc", "def")).toBe(3);
|
||||
expect(levenshtein("abc", "adc")).toBe(1);
|
||||
});
|
||||
|
||||
test("parseQuery & lookup", () => {
|
||||
/**
|
||||
* @param {string} query
|
||||
* @param {string[]} itemsList
|
||||
* @param {string} [property]
|
||||
*/
|
||||
const expectQuery = (query, itemsList, property = "key") => {
|
||||
const keyedItems = itemsList.map((item) => ({ [property]: item }));
|
||||
const result = lookup(parseQuery(query), keyedItems);
|
||||
return {
|
||||
/**
|
||||
* @param {string[]} expected
|
||||
*/
|
||||
toEqual: (expected) =>
|
||||
expect(result).toEqual(
|
||||
expected.map((item) => ({ [property]: item })),
|
||||
{ message: `query ${query} should match ${expected}` }
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
const list = [
|
||||
"Frodo",
|
||||
"Sam",
|
||||
"Merry",
|
||||
"Pippin",
|
||||
"Frodo Sam",
|
||||
"Merry Pippin",
|
||||
"Frodo Sam Merry Pippin",
|
||||
];
|
||||
|
||||
// Error handling
|
||||
expect(() => parseQuery()).toThrow();
|
||||
expect(() => lookup()).toThrow();
|
||||
expect(() => lookup("a", [{ key: "a" }])).toThrow();
|
||||
expect(() => lookup(parseQuery("a"))).toThrow();
|
||||
|
||||
// Empty query and/or empty lists
|
||||
expectQuery("", []).toEqual([]);
|
||||
expectQuery("", ["bababa", "baaab", "cccbccb"]).toEqual(["bababa", "baaab", "cccbccb"]);
|
||||
expectQuery("aaa", []).toEqual([]);
|
||||
|
||||
// Regex
|
||||
expectQuery(`/.b$/`, ["bababa", "baaab", "cccbccB"]).toEqual(["baaab"]);
|
||||
expectQuery(`/.b$/i`, ["bababa", "baaab", "cccbccB"]).toEqual(["baaab", "cccbccB"]);
|
||||
|
||||
// Exact match
|
||||
expectQuery(`"aaa"`, ["bababa", "baaab", "cccbccb"]).toEqual(["baaab"]);
|
||||
expectQuery(`"sam"`, list).toEqual([]);
|
||||
expectQuery(`"Sam"`, list).toEqual(["Sam", "Frodo Sam", "Frodo Sam Merry Pippin"]);
|
||||
expectQuery(`"Sam" "Frodo"`, list).toEqual(["Frodo Sam", "Frodo Sam Merry Pippin"]);
|
||||
expectQuery(`"Frodo Sam"`, list).toEqual(["Frodo Sam", "Frodo Sam Merry Pippin"]);
|
||||
expectQuery(`"FrodoSam"`, list).toEqual([]);
|
||||
expectQuery(`"Frodo Sam"`, list).toEqual([]);
|
||||
expectQuery(`"Sam" -"Frodo"`, list).toEqual(["Sam"]);
|
||||
|
||||
// Partial (fuzzy) match
|
||||
expectQuery(`aaa`, ["bababa", "baaab", "cccbccb"]).toEqual(["baaab", "bababa"]);
|
||||
expectQuery(`aaa -bbb`, ["bababa", "baaab", "cccbccb"]).toEqual(["baaab"]);
|
||||
expectQuery(`-aaa`, ["bababa", "baaab", "cccbccb"]).toEqual(["cccbccb"]);
|
||||
expectQuery(`frosapip`, list).toEqual(["Frodo Sam Merry Pippin"]);
|
||||
expectQuery(`-s fro`, list).toEqual(["Frodo"]);
|
||||
expectQuery(` FR SAPI `, list).toEqual(["Frodo Sam Merry Pippin"]);
|
||||
|
||||
// Mixed queries
|
||||
expectQuery(`"Sam" fro pip`, list).toEqual(["Frodo Sam Merry Pippin"]);
|
||||
expectQuery(`fro"Sam"pip`, list).toEqual(["Frodo Sam Merry Pippin"]);
|
||||
expectQuery(`-"Frodo" s`, list).toEqual(["Sam"]);
|
||||
expectQuery(`"Merry" -p`, list).toEqual(["Merry"]);
|
||||
expectQuery(`"rry" -s`, list).toEqual(["Merry", "Merry Pippin"]);
|
||||
});
|
||||
|
||||
test("match", () => {
|
||||
expect(match("abc", /^abcd?/)).toBe(true);
|
||||
expect(match(new Error("error message"), "message")).toBe(true);
|
||||
});
|
||||
|
||||
test("title", () => {
|
||||
expect(title("abcDef")).toBe("AbcDef");
|
||||
});
|
||||
|
||||
test("toExplicitString", () => {
|
||||
expect(toExplicitString("\n")).toBe(`\\n`);
|
||||
expect(toExplicitString("\t")).toBe(`\\t`);
|
||||
|
||||
expect(toExplicitString(" \n")).toBe(` \n`);
|
||||
expect(toExplicitString("\t ")).toBe(`\t `);
|
||||
|
||||
expect(toExplicitString("Abc\u200BDef")).toBe(`Abc\\u200bDef`);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>HOOT internal tests</title>
|
||||
|
||||
<!-- Source map -->
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
"@odoo/hoot-dom": "/web/static/lib/hoot-dom/hoot-dom.js",
|
||||
"@odoo/hoot-mock": "/web/static/lib/hoot/hoot-mock.js",
|
||||
"@odoo/hoot": "/web/static/lib/hoot/hoot.js",
|
||||
"@odoo/owl": "/web/static/lib/hoot/tests/hoot-owl-module.js",
|
||||
"@web/../lib/hoot-dom/helpers/dom": "/web/static/lib/hoot-dom/helpers/dom.js",
|
||||
"@web/../lib/hoot-dom/helpers/events": "/web/static/lib/hoot-dom/helpers/events.js",
|
||||
"@web/../lib/hoot-dom/helpers/time": "/web/static/lib/hoot-dom/helpers/time.js",
|
||||
"@web/../lib/hoot-dom/hoot_dom_utils": "/web/static/lib/hoot-dom/hoot_dom_utils.js",
|
||||
"/web/static/lib/hoot-dom/helpers/dom": "/web/static/lib/hoot-dom/helpers/dom.js",
|
||||
"/web/static/lib/hoot-dom/helpers/events": "/web/static/lib/hoot-dom/helpers/events.js",
|
||||
"/web/static/lib/hoot-dom/helpers/time": "/web/static/lib/hoot-dom/helpers/time.js",
|
||||
"/web/static/lib/hoot-dom/hoot_dom_utils": "/web/static/lib/hoot-dom/hoot_dom_utils.js",
|
||||
"/web/static/lib/hoot-dom/hoot-dom": "/web/static/lib/hoot-dom/hoot-dom.js",
|
||||
"/web/static/lib/hoot/core/cleanup": "/web/static/lib/hoot/core/cleanup.js",
|
||||
"/web/static/lib/hoot/core/config": "/web/static/lib/hoot/core/config.js",
|
||||
"/web/static/lib/hoot/core/expect": "/web/static/lib/hoot/core/expect.js",
|
||||
"/web/static/lib/hoot/core/fixture": "/web/static/lib/hoot/core/fixture.js",
|
||||
"/web/static/lib/hoot/core/job": "/web/static/lib/hoot/core/job.js",
|
||||
"/web/static/lib/hoot/core/logger": "/web/static/lib/hoot/core/logger.js",
|
||||
"/web/static/lib/hoot/core/runner": "/web/static/lib/hoot/core/runner.js",
|
||||
"/web/static/lib/hoot/core/suite": "/web/static/lib/hoot/core/suite.js",
|
||||
"/web/static/lib/hoot/core/tag": "/web/static/lib/hoot/core/tag.js",
|
||||
"/web/static/lib/hoot/core/test": "/web/static/lib/hoot/core/test.js",
|
||||
"/web/static/lib/hoot/core/url": "/web/static/lib/hoot/core/url.js",
|
||||
"/web/static/lib/hoot/hoot_utils": "/web/static/lib/hoot/hoot_utils.js",
|
||||
"/web/static/lib/hoot/hoot-mock": "/web/static/lib/hoot/hoot-mock.js",
|
||||
"/web/static/lib/hoot/hoot": "/web/static/lib/hoot/hoot.js",
|
||||
"/web/static/lib/hoot/lib/diff_match_patch": "/web/static/lib/hoot/lib/diff_match_patch.js",
|
||||
"/web/static/lib/hoot/main_runner": "/web/static/lib/hoot/main_runner.js",
|
||||
"/web/static/lib/hoot/mock/animation": "/web/static/lib/hoot/mock/animation.js",
|
||||
"/web/static/lib/hoot/mock/console": "/web/static/lib/hoot/mock/console.js",
|
||||
"/web/static/lib/hoot/mock/crypto": "/web/static/lib/hoot/mock/crypto.js",
|
||||
"/web/static/lib/hoot/mock/date": "/web/static/lib/hoot/mock/date.js",
|
||||
"/web/static/lib/hoot/mock/math": "/web/static/lib/hoot/mock/math.js",
|
||||
"/web/static/lib/hoot/mock/navigator": "/web/static/lib/hoot/mock/navigator.js",
|
||||
"/web/static/lib/hoot/mock/network": "/web/static/lib/hoot/mock/network.js",
|
||||
"/web/static/lib/hoot/mock/notification": "/web/static/lib/hoot/mock/notification.js",
|
||||
"/web/static/lib/hoot/mock/storage": "/web/static/lib/hoot/mock/storage.js",
|
||||
"/web/static/lib/hoot/mock/sync_values": "/web/static/lib/hoot/mock/sync_values.js",
|
||||
"/web/static/lib/hoot/mock/window": "/web/static/lib/hoot/mock/window.js",
|
||||
"/web/static/lib/hoot/tests/local_helpers": "/web/static/lib/hoot/tests/local_helpers.js",
|
||||
"/web/static/lib/hoot/ui/hoot_buttons": "/web/static/lib/hoot/ui/hoot_buttons.js",
|
||||
"/web/static/lib/hoot/ui/hoot_colors": "/web/static/lib/hoot/ui/hoot_colors.js",
|
||||
"/web/static/lib/hoot/ui/hoot_config_menu": "/web/static/lib/hoot/ui/hoot_config_menu.js",
|
||||
"/web/static/lib/hoot/ui/hoot_copy_button": "/web/static/lib/hoot/ui/hoot_copy_button.js",
|
||||
"/web/static/lib/hoot/ui/hoot_debug_toolbar": "/web/static/lib/hoot/ui/hoot_debug_toolbar.js",
|
||||
"/web/static/lib/hoot/ui/hoot_dropdown": "/web/static/lib/hoot/ui/hoot_dropdown.js",
|
||||
"/web/static/lib/hoot/ui/hoot_job_buttons": "/web/static/lib/hoot/ui/hoot_job_buttons.js",
|
||||
"/web/static/lib/hoot/ui/hoot_link": "/web/static/lib/hoot/ui/hoot_link.js",
|
||||
"/web/static/lib/hoot/ui/hoot_log_counters": "/web/static/lib/hoot/ui/hoot_log_counters.js",
|
||||
"/web/static/lib/hoot/ui/hoot_main": "/web/static/lib/hoot/ui/hoot_main.js",
|
||||
"/web/static/lib/hoot/ui/hoot_reporting": "/web/static/lib/hoot/ui/hoot_reporting.js",
|
||||
"/web/static/lib/hoot/ui/hoot_search": "/web/static/lib/hoot/ui/hoot_search.js",
|
||||
"/web/static/lib/hoot/ui/hoot_side_bar": "/web/static/lib/hoot/ui/hoot_side_bar.js",
|
||||
"/web/static/lib/hoot/ui/hoot_status_panel": "/web/static/lib/hoot/ui/hoot_status_panel.js",
|
||||
"/web/static/lib/hoot/ui/hoot_tag_button": "/web/static/lib/hoot/ui/hoot_tag_button.js",
|
||||
"/web/static/lib/hoot/ui/hoot_technical_value": "/web/static/lib/hoot/ui/hoot_technical_value.js",
|
||||
"/web/static/lib/hoot/ui/hoot_test_path": "/web/static/lib/hoot/ui/hoot_test_path.js",
|
||||
"/web/static/lib/hoot/ui/hoot_test_result": "/web/static/lib/hoot/ui/hoot_test_result.js",
|
||||
"/web/static/lib/hoot/ui/setup_hoot_ui": "/web/static/lib/hoot/ui/setup_hoot_ui.js"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Test assets -->
|
||||
<script src="/web/static/lib/owl/owl.js"></script>
|
||||
<script src="../hoot.js" type="module" defer></script>
|
||||
<link rel="stylesheet" href="/web/static/lib/hoot/ui/hoot_style.css" />
|
||||
<link rel="stylesheet" href="/web/static/src/libs/fontawesome/css/font-awesome.css" />
|
||||
|
||||
<!-- Test suites -->
|
||||
<script src="./index.js" type="module" defer></script>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
17
odoo-bringout-oca-ocb-web/web/static/lib/hoot/tests/index.js
Normal file
17
odoo-bringout-oca-ocb-web/web/static/lib/hoot/tests/index.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { isHootReady, start } from "@odoo/hoot";
|
||||
|
||||
import "./core/expect.test.js";
|
||||
import "./core/runner.test.js";
|
||||
import "./core/suite.test.js";
|
||||
import "./core/test.test.js";
|
||||
import "./hoot-dom/dom.test.js";
|
||||
import "./hoot-dom/events.test.js";
|
||||
import "./hoot-dom/time.test.js";
|
||||
import "./hoot_utils.test.js";
|
||||
import "./mock/navigator.test.js";
|
||||
import "./mock/network.test.js";
|
||||
import "./mock/window.test.js";
|
||||
import "./ui/hoot_technical_value.test.js";
|
||||
import "./ui/hoot_test_result.test.js";
|
||||
|
||||
isHootReady.then(start);
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import { after, destroy, getFixture } from "@odoo/hoot";
|
||||
import { App, Component, xml } from "@odoo/owl";
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Exports
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @param {import("@odoo/owl").ComponentConstructor} ComponentClass
|
||||
* @param {ConstructorParameters<typeof App>[1]} [config]
|
||||
*/
|
||||
export async function mountForTest(ComponentClass, config) {
|
||||
if (typeof ComponentClass === "string") {
|
||||
ComponentClass = class extends Component {
|
||||
static name = "anonymous component";
|
||||
static props = {};
|
||||
static template = xml`${ComponentClass}`;
|
||||
};
|
||||
}
|
||||
|
||||
const app = new App(ComponentClass, {
|
||||
name: "TEST",
|
||||
test: true,
|
||||
warnIfNoStaticProps: true,
|
||||
...config,
|
||||
});
|
||||
const fixture = getFixture();
|
||||
|
||||
after(() => destroy(app));
|
||||
|
||||
fixture.style.backgroundColor = "#fff";
|
||||
await app.mount(fixture);
|
||||
if (fixture.hasIframes) {
|
||||
await fixture.waitForIframes();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} url
|
||||
*/
|
||||
export function parseUrl(url) {
|
||||
return url.replace(/^.*hoot\/tests/, "@hoot").replace(/(\.test)?\.js$/, "");
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import { describe, expect, test } from "@odoo/hoot";
|
||||
import { mockSendBeacon, mockTouch, mockVibrate } from "@odoo/hoot-mock";
|
||||
import { parseUrl } from "../local_helpers";
|
||||
|
||||
/**
|
||||
* @param {Promise<any>} promise
|
||||
*/
|
||||
const ensureResolvesImmediatly = (promise) =>
|
||||
Promise.race([
|
||||
promise,
|
||||
new Promise((resolve, reject) => reject("failed to resolve in a single micro tick")),
|
||||
]);
|
||||
|
||||
describe(parseUrl(import.meta.url), () => {
|
||||
describe("clipboard", () => {
|
||||
test.tags("secure");
|
||||
test("read/write calls are resolved immediatly", async () => {
|
||||
navigator.clipboard.write([
|
||||
new ClipboardItem({
|
||||
"text/plain": new Blob(["some text"], { type: "text/plain" }),
|
||||
}),
|
||||
]);
|
||||
|
||||
const items = await ensureResolvesImmediatly(navigator.clipboard.read());
|
||||
|
||||
expect(items).toHaveLength(1);
|
||||
expect(items[0]).toBeInstanceOf(ClipboardItem);
|
||||
|
||||
const blob = await ensureResolvesImmediatly(items[0].getType("text/plain"));
|
||||
|
||||
expect(blob).toBeInstanceOf(Blob);
|
||||
|
||||
const value = await ensureResolvesImmediatly(blob.text());
|
||||
|
||||
expect(value).toBe("some text");
|
||||
});
|
||||
});
|
||||
|
||||
test("maxTouchPoints", () => {
|
||||
mockTouch(false);
|
||||
|
||||
expect(navigator.maxTouchPoints).toBe(0);
|
||||
|
||||
mockTouch(true);
|
||||
|
||||
expect(navigator.maxTouchPoints).toBe(1);
|
||||
});
|
||||
|
||||
test("sendBeacon", () => {
|
||||
expect(() => navigator.sendBeacon("/route", new Blob([]))).toThrow(/sendBeacon/);
|
||||
|
||||
mockSendBeacon(expect.step);
|
||||
|
||||
expect.verifySteps([]);
|
||||
|
||||
navigator.sendBeacon("/route", new Blob([]));
|
||||
|
||||
expect.verifySteps(["/route"]);
|
||||
});
|
||||
|
||||
test("vibrate", () => {
|
||||
expect(() => navigator.vibrate(100)).toThrow(/vibrate/);
|
||||
|
||||
mockVibrate(expect.step);
|
||||
|
||||
expect.verifySteps([]);
|
||||
|
||||
navigator.vibrate(100);
|
||||
|
||||
expect.verifySteps([100]);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import { describe, expect, test } from "@odoo/hoot";
|
||||
import { mockFetch } from "@odoo/hoot-mock";
|
||||
import { parseUrl } from "../local_helpers";
|
||||
|
||||
describe(parseUrl(import.meta.url), () => {
|
||||
test("setup network values", async () => {
|
||||
expect(document.cookie).toBe("");
|
||||
|
||||
document.cookie = "cids=4";
|
||||
document.title = "kek";
|
||||
|
||||
expect(document.cookie).toBe("cids=4");
|
||||
expect(document.title).toBe("kek");
|
||||
});
|
||||
|
||||
test("values are reset between test", async () => {
|
||||
expect(document.cookie).toBe("");
|
||||
expect(document.title).toBe("");
|
||||
});
|
||||
|
||||
test("fetch should not mock internal URLs", async () => {
|
||||
mockFetch(expect.step);
|
||||
|
||||
await fetch("http://some.url");
|
||||
await fetch("/odoo");
|
||||
await fetch(URL.createObjectURL(new Blob([""])));
|
||||
|
||||
expect.verifySteps(["http://some.url", "/odoo"]);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import { after, describe, expect, test } from "@odoo/hoot";
|
||||
import { queryOne } from "@odoo/hoot-dom";
|
||||
import { EventBus } from "@odoo/owl";
|
||||
import { mountForTest, parseUrl } from "../local_helpers";
|
||||
import { watchListeners } from "@odoo/hoot-mock";
|
||||
|
||||
describe(parseUrl(import.meta.url), () => {
|
||||
class TestBus extends EventBus {
|
||||
addEventListener(type) {
|
||||
expect.step(`addEventListener:${type}`);
|
||||
return super.addEventListener(...arguments);
|
||||
}
|
||||
|
||||
removeEventListener() {
|
||||
throw new Error("Cannot remove event listeners");
|
||||
}
|
||||
}
|
||||
|
||||
let testBus;
|
||||
|
||||
test("elementFromPoint and elementsFromPoint should be mocked", async () => {
|
||||
await mountForTest(/* xml */ `
|
||||
<div class="oui" style="position: absolute; left: 10px; top: 10px; width: 250px; height: 250px;">
|
||||
Oui
|
||||
</div>
|
||||
`);
|
||||
|
||||
expect(".oui").toHaveRect({
|
||||
x: 10,
|
||||
y: 10,
|
||||
width: 250,
|
||||
height: 250,
|
||||
});
|
||||
|
||||
const div = queryOne(".oui");
|
||||
expect(document.elementFromPoint(11, 11)).toBe(div);
|
||||
expect(document.elementsFromPoint(11, 11)).toEqual([
|
||||
div,
|
||||
document.body,
|
||||
document.documentElement,
|
||||
]);
|
||||
|
||||
expect(document.elementFromPoint(9, 9)).toBe(document.body);
|
||||
expect(document.elementsFromPoint(9, 9)).toEqual([document.body, document.documentElement]);
|
||||
});
|
||||
|
||||
// ! WARNING: the following 2 tests need to be run sequentially to work, as they
|
||||
// ! attempt to test the in-between-tests event listeners cleanup.
|
||||
test("event listeners are properly removed: setup", async () => {
|
||||
const callback = () => expect.step("callback");
|
||||
|
||||
testBus = new TestBus();
|
||||
|
||||
expect.verifySteps([]);
|
||||
|
||||
after(watchListeners());
|
||||
|
||||
testBus.addEventListener("some-event", callback);
|
||||
testBus.trigger("some-event");
|
||||
|
||||
expect.verifySteps(["addEventListener:some-event", "callback"]);
|
||||
});
|
||||
test("event listeners are properly removed: check", async () => {
|
||||
testBus.trigger("some-event");
|
||||
|
||||
expect.verifySteps([]);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import { after, describe, expect, test } from "@odoo/hoot";
|
||||
import { animationFrame, click, Deferred } from "@odoo/hoot-dom";
|
||||
import { Component, reactive, useState, xml } from "@odoo/owl";
|
||||
import { mountForTest, parseUrl } from "../local_helpers";
|
||||
|
||||
import { logger } from "../../core/logger";
|
||||
import { HootTechnicalValue } from "../../ui/hoot_technical_value";
|
||||
|
||||
const mountTechnicalValue = async (defaultValue) => {
|
||||
const updateValue = async (value) => {
|
||||
state.value = value;
|
||||
await animationFrame();
|
||||
};
|
||||
|
||||
const state = reactive({ value: defaultValue });
|
||||
|
||||
class TechnicalValueParent extends Component {
|
||||
static components = { HootTechnicalValue };
|
||||
static props = {};
|
||||
static template = xml`<HootTechnicalValue value="state.value" />`;
|
||||
|
||||
setup() {
|
||||
this.state = useState(state);
|
||||
}
|
||||
}
|
||||
|
||||
await mountForTest(TechnicalValueParent);
|
||||
|
||||
return updateValue;
|
||||
};
|
||||
|
||||
describe(parseUrl(import.meta.url), () => {
|
||||
test("technical value with primitive values", async () => {
|
||||
const updateValue = await mountTechnicalValue("oui");
|
||||
expect(".hoot-string").toHaveText(`"oui"`);
|
||||
|
||||
await updateValue(`"stringified"`);
|
||||
expect(".hoot-string").toHaveText(`'"stringified"'`);
|
||||
|
||||
await updateValue(3);
|
||||
expect(".hoot-integer").toHaveText(`3`);
|
||||
|
||||
await updateValue(undefined);
|
||||
expect(".hoot-undefined").toHaveText(`undefined`);
|
||||
|
||||
await updateValue(null);
|
||||
expect(".hoot-null").toHaveText(`null`);
|
||||
});
|
||||
|
||||
test("technical value with objects", async () => {
|
||||
const logDebug = logger.debug;
|
||||
logger.debug = expect.step;
|
||||
after(() => (logger.debug = logDebug));
|
||||
|
||||
const updateValue = await mountTechnicalValue({});
|
||||
expect(".hoot-technical").toHaveText(`Object(0)`);
|
||||
|
||||
await updateValue([1, 2, "3"]);
|
||||
|
||||
expect(".hoot-technical").toHaveText(`Array(3)`);
|
||||
expect.verifySteps([]);
|
||||
|
||||
await click(".hoot-object");
|
||||
await animationFrame();
|
||||
|
||||
expect(".hoot-technical").toHaveText(`Array(3)[\n1\n,\n2\n,\n"3"\n,\n]`);
|
||||
expect.verifySteps([[1, 2, "3"]]);
|
||||
|
||||
await updateValue({ a: true });
|
||||
expect(".hoot-technical").toHaveText(`Object(1)`);
|
||||
|
||||
await click(".hoot-object");
|
||||
await animationFrame();
|
||||
|
||||
expect(".hoot-technical").toHaveText(`Object(1){\na\n:\ntrue\n,\n}`);
|
||||
|
||||
await updateValue({
|
||||
a: true,
|
||||
sub: {
|
||||
key: "oui",
|
||||
},
|
||||
});
|
||||
expect(".hoot-technical").toHaveText(`Object(2)`);
|
||||
|
||||
await click(".hoot-object:first");
|
||||
await animationFrame();
|
||||
|
||||
expect(".hoot-technical:first").toHaveText(
|
||||
`Object(2){\na\n:\ntrue\n,\nsub\n:\nObject(1)\n}`
|
||||
);
|
||||
expect.verifySteps([]);
|
||||
|
||||
await click(".hoot-object:last");
|
||||
await animationFrame();
|
||||
|
||||
expect(".hoot-technical:first").toHaveText(
|
||||
`Object(2){\na\n:\ntrue\n,\nsub\n:\nObject(1){\nkey\n:\n"oui"\n,\n}\n}`
|
||||
);
|
||||
expect.verifySteps([{ key: "oui" }]);
|
||||
});
|
||||
|
||||
test("technical value with special cases", async () => {
|
||||
const updateValue = await mountTechnicalValue(new Date(0));
|
||||
expect(".hoot-technical").toHaveText(`1970-01-01T00:00:00.000Z`);
|
||||
|
||||
await updateValue(/ab[c]/gi);
|
||||
expect(".hoot-technical").toHaveText(`/ab[c]/gi`);
|
||||
|
||||
const def = new Deferred(() => {});
|
||||
await updateValue(def);
|
||||
expect(".hoot-technical").toHaveText(`Deferred<\npending\n>`);
|
||||
|
||||
def.resolve("oui");
|
||||
await animationFrame();
|
||||
expect(".hoot-technical").toHaveText(`Deferred<\nfulfilled\n:\n"oui"\n>`);
|
||||
});
|
||||
|
||||
test("evaluation of unsafe value does not crash", async () => {
|
||||
const logDebug = logger.debug;
|
||||
logger.debug = () => expect.step("debug");
|
||||
after(() => (logger.debug = logDebug));
|
||||
|
||||
class UnsafeString extends String {
|
||||
toString() {
|
||||
return this.valueOf();
|
||||
}
|
||||
valueOf() {
|
||||
throw new Error("UNSAFE");
|
||||
}
|
||||
}
|
||||
|
||||
await mountTechnicalValue(new UnsafeString("some value"));
|
||||
await click(".hoot-object");
|
||||
|
||||
expect(".hoot-object").toHaveText("UnsafeString(0)", {
|
||||
message: "size is 0 because it couldn't be evaluated",
|
||||
});
|
||||
|
||||
expect.verifySteps(["debug"]);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import { describe, expect, makeExpect, test } from "@odoo/hoot";
|
||||
import { mountForTest, parseUrl } from "../local_helpers";
|
||||
|
||||
import { animationFrame, click } from "@odoo/hoot-dom";
|
||||
import { Component, xml } from "@odoo/owl";
|
||||
import { Runner } from "../../core/runner";
|
||||
import { Test } from "../../core/test";
|
||||
import { HootTestResult } from "../../ui/hoot_test_result";
|
||||
import { makeUiState } from "../../ui/setup_hoot_ui";
|
||||
|
||||
/**
|
||||
* @param {(mockExpect: typeof expect) => any} callback
|
||||
*/
|
||||
const mountTestResults = async (testFn, props) => {
|
||||
const runner = new Runner();
|
||||
const ui = makeUiState();
|
||||
const mockTest = new Test(null, "test", {});
|
||||
const [mockExpect, { after, before }] = makeExpect({});
|
||||
|
||||
class Parent extends Component {
|
||||
static components = { HootTestResult };
|
||||
static props = { test: Test, open: [Boolean, { value: "always" }] };
|
||||
static template = xml`
|
||||
<HootTestResult test="props.test" open="props.open">
|
||||
Toggle
|
||||
</HootTestResult>
|
||||
`;
|
||||
|
||||
mockTest = mockTest;
|
||||
}
|
||||
|
||||
before(mockTest);
|
||||
testFn(mockExpect);
|
||||
after(runner);
|
||||
|
||||
await mountForTest(Parent, {
|
||||
env: { runner, ui },
|
||||
props: {
|
||||
test: mockTest,
|
||||
open: "always",
|
||||
...props,
|
||||
},
|
||||
});
|
||||
|
||||
return mockTest;
|
||||
};
|
||||
|
||||
const CLS_PASS = "text-emerald";
|
||||
const CLS_FAIL = "text-rose";
|
||||
|
||||
describe(parseUrl(import.meta.url), () => {
|
||||
test("test results: toBe and basic interactions", async () => {
|
||||
const mockTest = await mountTestResults(
|
||||
(mockExpect) => {
|
||||
mockExpect(true).toBe(true);
|
||||
mockExpect(true).toBe(false);
|
||||
},
|
||||
{ open: false }
|
||||
);
|
||||
|
||||
expect(".HootTestResult button:only").toHaveText("Toggle");
|
||||
expect(".hoot-result-detail").not.toHaveCount();
|
||||
expect(mockTest.lastResults.pass).toBe(false);
|
||||
|
||||
await click(".HootTestResult button");
|
||||
await animationFrame();
|
||||
|
||||
expect(".hoot-result-detail").toHaveCount(1);
|
||||
|
||||
// First assertion: pass
|
||||
expect(`.hoot-result-detail > .${CLS_PASS}`).toHaveText(
|
||||
/received value is strictly equal to true/,
|
||||
{ inline: true }
|
||||
);
|
||||
|
||||
// Second assertion: fail
|
||||
expect(`.hoot-result-detail > .${CLS_FAIL}`).toHaveText(
|
||||
/expected values to be strictly equal/,
|
||||
{ inline: true }
|
||||
);
|
||||
expect(`.hoot-info .${CLS_PASS}:contains(Expected)`).toHaveCount(1);
|
||||
expect(`.hoot-info .${CLS_FAIL}:contains(Received)`).toHaveCount(1);
|
||||
});
|
||||
test("test results: toEqual", async () => {
|
||||
await mountTestResults((mockExpect) => {
|
||||
mockExpect([1, 2, { a: true }]).toEqual([1, 2, { a: true }]);
|
||||
mockExpect([1, { a: false }, 3]).toEqual([1, { a: true }, 3]);
|
||||
});
|
||||
|
||||
expect(".hoot-result-detail").toHaveCount(1);
|
||||
|
||||
// First assertion: pass
|
||||
expect(`.hoot-result-detail > .${CLS_PASS}`).toHaveText(
|
||||
/received value is deeply equal to \[1, 2, { a: true }\]/,
|
||||
{ inline: true }
|
||||
);
|
||||
|
||||
// Second assertion: fail
|
||||
expect(`.hoot-result-detail > .${CLS_FAIL}`).toHaveText(
|
||||
/expected values to be deeply equal/,
|
||||
{ inline: true }
|
||||
);
|
||||
expect(`.hoot-info .${CLS_PASS}:contains(Expected)`).toHaveCount(1);
|
||||
expect(`.hoot-info .${CLS_FAIL}:contains(Received)`).toHaveCount(1);
|
||||
});
|
||||
|
||||
test("test results: toHaveCount", async () => {
|
||||
await mountForTest(/* xml */ `
|
||||
<span class="text" >abc</span>
|
||||
<span class="text" >bcd</span>
|
||||
`);
|
||||
await mountTestResults((mockExpect) => {
|
||||
mockExpect(".text").toHaveCount(2);
|
||||
mockExpect(".text").toHaveCount(1);
|
||||
});
|
||||
|
||||
expect(".hoot-result-detail").toHaveCount(1);
|
||||
|
||||
// First assertion: pass
|
||||
expect(`.hoot-result-detail > .${CLS_PASS}`).toHaveText(
|
||||
/found 2 elements matching ".text"/,
|
||||
{ inline: true }
|
||||
);
|
||||
|
||||
// Second assertion: fail
|
||||
expect(`.hoot-result-detail > .${CLS_FAIL}`).toHaveText(
|
||||
/found 2 elements matching ".text"/,
|
||||
{ inline: true }
|
||||
);
|
||||
expect(`.hoot-info .${CLS_PASS}:contains(Expected)`).toHaveCount(1);
|
||||
expect(`.hoot-info .${CLS_FAIL}:contains(Received)`).toHaveCount(1);
|
||||
});
|
||||
|
||||
test("multiple test results: toHaveText", async () => {
|
||||
await mountForTest(/* xml */ `
|
||||
<span class="text" >abc</span>
|
||||
<span class="text" >bcd</span>
|
||||
`);
|
||||
await mountTestResults((mockExpect) => {
|
||||
mockExpect(".text:first").toHaveText("abc");
|
||||
mockExpect(".text").toHaveText("abc");
|
||||
mockExpect(".text").not.toHaveText("abc");
|
||||
});
|
||||
|
||||
expect(".hoot-result-detail").toHaveCount(1);
|
||||
|
||||
// First assertion: pass
|
||||
expect(`.hoot-result-detail > .${CLS_PASS}`).toHaveText(
|
||||
/1 element matching ".text:first" has text "abc"/,
|
||||
{ inline: true }
|
||||
);
|
||||
|
||||
// Second assertion: fail
|
||||
expect(`.hoot-result-detail > .${CLS_FAIL}:eq(0)`).toHaveText(
|
||||
/expected 2 elements matching ".text" to have the given text/,
|
||||
{ inline: true }
|
||||
);
|
||||
expect(".hoot-info:eq(0) .hoot-html").toHaveCount(2);
|
||||
expect(".hoot-info:eq(0) .hoot-html").toHaveText("<span.text/>");
|
||||
expect(`.hoot-info:eq(0) .${CLS_PASS}:contains(Received)`).toHaveCount(1);
|
||||
expect(`.hoot-info:eq(0) .${CLS_PASS}:contains(Expected)`).toHaveCount(1);
|
||||
expect(`.hoot-info:eq(0) .${CLS_FAIL}:contains(Received)`).toHaveCount(1);
|
||||
|
||||
// Third assertion: fail
|
||||
expect(`.hoot-result-detail > .${CLS_FAIL}:eq(1)`).toHaveText(
|
||||
/expected 2 elements matching ".text" not to have the given text/,
|
||||
{ inline: true }
|
||||
);
|
||||
expect(".hoot-info:eq(1) .hoot-html").toHaveCount(2);
|
||||
expect(".hoot-info:eq(1) .hoot-html").toHaveText("<span.text/>");
|
||||
expect(`.hoot-info:eq(1) .${CLS_PASS}:contains(Received)`).toHaveCount(1);
|
||||
expect(`.hoot-info:eq(1) .${CLS_PASS}:contains(Expected)`).toHaveCount(1);
|
||||
expect(`.hoot-info:eq(1) .${CLS_FAIL}:contains(Received)`).toHaveCount(1);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue