HOOT¶
Overview¶
HOOT is a testing framework written with Owl whose key features are:
to register and run tests and test suites;
to display an intuitive interface to view and filter test results;
to provide ways to interact with the DOM to simulate user actions;
to provide low-level helpers allowing to mock various global objects.
As such, it has been integrated as a lib/ in the Odoo codebase and exports 2 main modules:
@odoo/hoot-dom: (can be used in tours) helpers to:query elements from the DOM, such as
queryAll()andwaitFor();
@odoo/hoot: (only to be used in unit tests) all the test framework features:test,describeandexpecttest hooks like
afterandafterEachfixture handling with
getFixturedate and time handling like
mockDateoradvanceTimemocking network responses through
mockFetch()ormockWebSocket()every helper exported by
@odoo/hoot-dom
Nota
This section of the documentation is not meant to list all helpers available in Hoot (the full list can be found in the @odoo/hoot module itself). The goal here is to showcase the most used helpers and to justify some of the decisions that have led to the current shape of the testing framework.
Running tests¶
In Odoo, frontend unit tests can be run by going to the /web/tests URL. Most of
the setup for calling the test runner is already in place:
the
web.assets_unit_testsbundle is already defined, and picks up all tests defined in most addons;a
start.hoot.jsfile takes care of calling the test runner with its exportedstartentry point function.
When going to the test page, tests will be run sequentially and the results will
be displayed in the console and in the GUI (if not running in headless mode).
Runner options¶
The runner can be configured either:
through the interface (with the configuration dropdown and the search bar);
or through the URL query parameters (e.g.
?headlessto run in headless mode).
Here is the list of available options for the runner:
bailAmount of failed tests after which the test runner will be stopped. A falsy value (including 0) means that the runner should never be aborted. (default:
0)
debugTestSame as the
FILTER_SCHEMA.testfilter, while also putting the test runner in «debug» mode. SeeTestRunner.debugfor more info. (default:false)
fpsSets the value of frames per seconds (this will be transformed to milliseconds and used in
advanceFrame)
filterSearch string that will filter matching tests/suites, based on their full name (including their parent suite(s)) and their tags. (default:
"")
frameRateEstimated amount of frames rendered per second, used when mocking animation frames. (default:
60fps)
funLightens the mood. (default:
false)
headlessWhether to render the test runner user interface. (default:
false)
idIDs of the suites OR tests to run exclusively. The ID of a job is generated deterministically based on its full name.
loglevelLog level used by the test runner. The higher the level, the more logs will be displayed:
0: only runner logs are displayed (default)1: all suite results are also logged2: all test results are also logged3: debug information for each test is also logged
manualWhether the test runner must be manually started after page load (defaults to starting automatically). (default:
false)
notrycatchRemoves the safety of
try .. catchstatements around each test’s run function to let errors bubble to the browser. (default:false)
orderDetermines the order of test execution:
"fifo": tests will be run sequentially as declared in the file system;"lifo": tests will be run sequentially in the reverse order;"random": shuffles tests and suites within their parent suite.
presetEnvironment in which the test runner is running. This parameter is used to determine the default value of other features, namely:
the user agent;
touch support;
expected size of the viewport.
showdetailDetermines how the failed tests must be unfolded in the UI. (default:
"first-fail")
tagTag names of tests and suites to run exclusively (case insensitive). (default: empty)
timeoutDuration (in milliseconds) at the end of which a test will automatically fail. (default:
5seconds)
Nota
When selecting tests and suites to run, an implicit OR is applied between
the including filters. This means that adding more inclusive filters will
result in more tests being run. This applies to the filter, id and tag
filters (excluding filters however will remove matching tests from the list
of tests to run).
Writing tests¶
Test¶
Writing a test can be very straightforward, as it is just a matter of calling the test function
with a name and a function that will contain the test logic.
Here is a simple example:
import { expect, test } from "@odoo/hoot";
test("My first test", () => {
expect(2 + 2).toBe(4);
});
Describe¶
Most of the time, tests are not that simple. They often require some setup and teardown,
and sometimes they need to be grouped together in a suite. This is where the describe
function comes into play.
Here is how you would declare a suite and a test within it:
import { describe, expect, test } from "@odoo/hoot";
describe("My first suite", () => {
test("My first test", () => {
expect(2 + 2).toBe(4);
});
});
Importante
In Odoo, all test files are run in an isolated environment and are wrapped within a global
describe block (with the name of the suite being the path of the test file).
With that in mind you should not need to declare a suite in your test files, although you can still declare sub-suites in the same file if you still want to split the file’s suite, for organization or tagging purposes.
Expect¶
The expect function is the main assertion function of the framework. It is used
to assert that a value or an object is what it is expected to be or in the state
it is supposed to be. To do so, it provides a few modifiers and a wide range of
matchers.
Modifiers¶
An expect modifier is a getter that returns another set of altered matchers that will behave in
a specific way.
notInverts the result of the following matcher: it will succeed if the matcher fails.
expect(true).not.toBe(false);
resolvesWaits for the value (
Promise) to be «resolved» before running the following matcher with the resolved value.await expect(Promise.resolve(42)).resolves.toBe(42);
rejectsWaits for the value (
Promise) to be «rejected» before running the following matcher with the rejected reason.await expect(Promise.reject("error")).rejects.toBe("error");
Nota
The resolves and rejects modifiers are only available when the value is
a promise, and will return a promise that will resolve once the assertion is
done.
Regular matchers¶
The matchers dictate what to do on the value being tested. Some will take that value as-is, while others will transform that value before performing the assertion on it (i.e. DOM matchers).
Note that the last argument parameter of all matchers is an optional dictionary with additional
options, in which a custom assertion message can be given for added context/specificity.
The first list of matchers are primitive or object based and are the most common ones:
- toBe(expected[, options])¶
Expects the received value to be strictly equal to the
expectedvalue.Parameters
expected:anyoptions:{ message?: string }
Examples
expect("foo").toBe("foo"); expect({ foo: 1 }).not.toBe({ foo: 1 });
- toBeCloseTo(expected[, options])¶
Expects the received value to be close to the
expectedvalue up to a given amount of digits (default is 2).Parameters
expected:anyoptions:{ message?: string, digits?: number }
Examples
expect(0.2 + 0.1).toBeCloseTo(0.3); expect(3.51).toBeCloseTo(3.5, { digits: 1 });
- toBeEmpty([options])¶
Expects the received value to be empty:
iterable: no itemsobject: no keysnode: no content (i.e. no value or text)anything else: falsy value (
false,0,"",null,undefined)
Parameters
options:{ message?: string }
Examples
expect({}).toBeEmpty(); expect(["a", "b"]).not.toBeEmpty(); expect(queryOne("input")).toBeEmpty();
- toBeGreaterThan(min[, options])¶
Expects the received value to be strictly greater than
min.Parameters
min:numberoptions:{ message?: string }
Examples
expect(5).toBeGreaterThan(-1); expect(4 + 2).toBeGreaterThan(5);
- toBeInstanceOf(cls[, options])¶
Expects the received value to be an instance of the given
cls.Parameters
cls:Functionoptions:{ message?: string }
Examples
expect({ foo: 1 }).not.toBeInstanceOf(Object); expect(document.createElement("div")).toBeInstanceOf(HTMLElement);
- toBeLessThan(max[, options])¶
Expects the received value to be strictly less than
max.Parameters
max:numberoptions:{ message?: string }
Examples
expect(5).toBeLessThan(10); expect(8 - 6).toBeLessThan(3);
- toBeOfType(type[, options])¶
Expects the received value to be of the given
type.Parameters
type:stringoptions:{ message?: string }
Examples
expect("foo").toBeOfType("string"); expect({ foo: 1 }).toBeOfType("object");
- toBeWithin(min, max[, options])¶
Expects the received value to be between
minandmax(both inclusive).Parameters
min:numbermax:numberoptions:{ message?: string }
Examples
expect(3).toBeWithin(3, 9); expect(-8.5).toBeWithin(-20, 0); expect(100).toBeWithin(50, 100);
- toEqual(expected[, options])¶
Expects the received value to be deeply equal to the
expectedvalue.Parameters
expected:anyoptions:{ message?: string }
Examples
expect(["foo"]).toEqual(["foo"]); expect({ foo: 1 }).toEqual({ foo: 1 });
- toHaveLength(length[, options])¶
Expects the received value to have a length of the given
length. Received value can be anyIterableorObject.Parameters
length:numberoptions:{ message?: string }
Examples
expect("foo").toHaveLength(3); expect([1, 2, 3]).toHaveLength(3); expect({ foo: 1, bar: 2 }).toHaveLength(2); expect(new Set([1, 2])).toHaveLength(2);
- toInclude(item[, options])¶
Expects the received value to include an
itemof a given shape.Received value can be an iterable or an object (in case it is an object, the
itemshould be a key or a tuple representing an entry in that object).Note that it is NOT a strict comparison: the item will be matched for deep equality against each item of the iterable.
Parameters
item:anyoptions:{ message?: string }
Examples
expect([1, 2, 3]).toInclude(2); expect({ foo: 1, bar: 2 }).toInclude("foo"); expect({ foo: 1, bar: 2 }).toInclude(["foo", 1]); expect(new Set([{ foo: 1 }, { bar: 2 }])).toInclude({ bar: 2 });
- toMatch(matcher[, options])¶
Expects the received value to match the given
matcher.Parameters
matcher:string | number | RegExpoptions:{ message?: string }
Examples
expect(new Error("foo")).toMatch("foo"); expect("a foo value").toMatch(/fo.*ue/);
- toThrow(matcher[, options])¶
Expects the received
Functionto throw an error after being called.Parameters
matcher:string | number | RegExpoptions:{ message?: string }
Examples
expect(() => { throw new Error("Woops!") }).toThrow(/woops/i); await expect(Promise.reject("foo")).rejects.toThrow("foo");
DOM matchers¶
This next list of matchers are node-based and are used to assert the state of a
node or a list of nodes. They generally take a custom selector
as the argument of the expect function (although a Node or an iterable of Node
is also accepted).
- toBeChecked([options])¶
Expects the received
Targetto be"checked", or to be"indeterminate"if the homonymous option is set totrue.Parameters
options:{ message?: string, indeterminate?: boolean }
Examples
expect("input[type=checkbox]").toBeChecked();
- toBeDisplayed([options])¶
Expects the received
Targetto be «displayed», meaning that:it has a bounding box;
it is contained in the root document.
Parameters
options:{ message?: string }
Examples
expect(document.body).toBeDisplayed(); expect(document.createElement("div")).not.toBeDisplayed();
- toBeEnabled([options])¶
Expects the received
Targetto be «enabled», meaning that it matches the:enabledpseudo-selector.Parameters
options:{ message?: string }
Examples
expect("button").toBeEnabled(); expect("input[type=radio]").not.toBeEnabled();
- toBeFocused([options])¶
Expects the received
Targetto be «focused» in its owner document.Parameters
options:{ message?: string }
- toBeVisible([options])¶
Expects the received
Targetto be «visible», meaning that:it has a bounding box;
it is contained in the root document;
it is not hidden by CSS properties.
Parameters
options:{ message?: string }
Examples
expect(document.body).toBeVisible(); expect("[style='opacity: 0']").not.toBeVisible();
- toHaveAttribute(attribute, value[, options])¶
Expects the received
Targetto have the given attribute set, and for that attribute value to match the givenvalueif any.Parameters
attribute:stringvalue:string | number | RegExpoptions:{ message?: string }
Examples
expect("a").toHaveAttribute("href"); expect("script").toHaveAttribute("src", "./index.js");
- toHaveClass(className[, options])¶
Expects the received
Targetto have the given class name(s).Parameters
className:string | string[]options:{ message?: string }
Examples
expect("button").toHaveClass("btn btn-primary"); expect("body").toHaveClass(["o_webclient", "o_dark"]);
- toHaveCount(amount[, options])¶
Expects the received
Targetto contain exactlyamountelement(s). Note that theamountparameter can be omitted, in which case the function will expect at least one element.Parameters
amount:numberoptions:{ message?: string }
Examples
expect(".o_webclient").toHaveCount(1); expect(".o_form_view .o_field_widget").toHaveCount(); expect("ul > li").toHaveCount(4);
- toHaveInnerHTML(expected[, options])¶
Expects the
innerHTMLof the receivedTargetto match theexpectedvalue (upon formatting).Parameters
expected:string | RegExpoptions:{ message?: string, type?: "html" | "xml", tabSize?: number, keepInlineTextNodes?: boolean }
Examples
expect(".my_element").toHaveInnerHTML(` Some <strong>text</strong> `);
- toHaveOuterHTML(expected[, options])¶
Expects the
outerHTMLof the receivedTargetto match theexpectedvalue (upon formatting).Parameters
expected:string | RegExpoptions:{ message?: string, type?: "html" | "xml", tabSize?: number, keepInlineTextNodes?: boolean }
Examples
expect(".my_element").toHaveOuterHTML(` <div class="my_element"> Some <strong>text</strong> </div> `);
- toHaveProperty(property, value[, options])¶
Expects the received
Targetto have its given property value match the givenvalue. If no value is given: the matcher will instead check that the given property exists on the target.Parameters
property:stringvalue:anyoptions:{ message?: string }
Examples
expect("button").toHaveProperty("tabIndex", 0); expect("input").toHaveProperty("ontouchstart"); expect("script").toHaveProperty("src", "./index.js");
- toHaveRect(rect[, options])¶
Expects the
DOMRectof the receivedTargetto match the givenrectobject. Therectobject can either be:a
DOMRectobject;a CSS selector string (to get the rect of the only matching element);
a node.
If the resulting
rectvalue is a node, then both nodes” rects will be compared.Parameters
rect:Partial<DOMRect> | Targetoptions:{ message?: string, trimPadding?: boolean }
Examples
expect("button").toHaveRect({ x: 20, width: 100, height: 50 }); expect("button").toHaveRect(".container");
- toHaveStyle(style[, options])¶
Expects the received
Targetto match the given style properties.Parameters
style:string | Record<string, string | RegExp>options:{ message?: string }
Examples
expect("button").toHaveStyle({ color: "red" }); expect("p").toHaveStyle("text-align: center");
- toHaveText(text[, options])¶
Expects the
textcontent of the receivedTargetto either:be strictly equal to a given string;
match a given regular expression.
Note:
innerHTMLis used to retrieve the text content to take CSS visibility into account. This also means that text values from child elements will be joined using a line-break as separator.Parameters
text:string | RegExpoptions:{ message?: string, raw?: boolean }
Examples
expect("p").toHaveText("lorem ipsum dolor sit amet"); expect("header h1").toHaveText(/odoo/i);
- toHaveValue(value[, options])¶
Expects the value of the received
Targetto either:be strictly equal to a given string or number;
match a given regular expression;
contain file objects matching the given
fileslist.
Parameters
value:anyoptions:{ message?: string }
Examples
expect("input[type=email]").toHaveValue("john@doe.com"); expect("input[type=file]").toHaveValue(new File(["foo"], "foo.txt")); expect("select[multiple]").toHaveValue(["foo", "bar"]);
Static methods¶
The expect helper function also contains static methods that can be used to run
through a detached testing flow that isn’t bound to one specific value at a certain
moment.
These methods are mainly used to register steps or errors in the scope of the current test, and to evaluate them later on.
- expect.assertions(expected)¶
- Argumentos
expected (
number()) –
Expects the current test to have the
expectedamount of assertions. This number cannot be less than 1.Nota
It is generally preferred to use
expect.step()andexpect.verifySteps()instead as it is more reliable and allows to test more extensively.
- expect.errors(expected)¶
- Argumentos
expected (
number()) –
Expects the current test to have the
expectedamount of errors.This also means that from the moment this function is called, the test will accept that amount of errors before being considered as failed.
- expect.step(value)¶
- Argumentos
value (
unknown()) –
Registers a step for the current test, that can be consumed by
expect.verifySteps(). Unconsumed steps will fail the test.
- expect.verifyErrors(errors[, options])¶
- Argumentos
errors (
unknown[]()) –options (
{ message?: string }()) –
- Devuelve
boolean
Expects the received matchers to match the errors thrown since the start of the test or the last call to
expect.verifyErrors(). Calling this matcher will reset the list of current errors.expect.verifyErrors([/RPCError/, /Invalid domain AST/]);
- expect.verifySteps(steps[, options])¶
- Argumentos
steps (
unknown[]()) –options (
{ ignoreOrder?: boolean, message?: string, partial?: boolean }()) –
- Devuelve
boolean
Expects the received steps to be equal to the steps emitted since the start of the test or the last call to
expect.verifySteps(). Calling this matcher will reset the list of current steps.expect.step("web_read_group"); expect.step([1, 2]); expect.verifySteps(["web_read_group", [1, 2]]);
- expect.waitForErrors(errors[, options])¶
- Argumentos
errors (
unknown[]()) –options (
{ message?: string }()) –
- Devuelve
Promise<boolean>
Same as
expect.verifyErrors(), but will not immediatly fail if errors are not caught yet, and will instead wait for a certain timeout (default: 2000ms) to allow errors to be caught later.Checks are performed initially, at the end of the timeout, and each time an error is detected.
fetch("invalid/url"); await expect.waitForErrors([/RPCError/]);
- expect.waitForSteps(steps[, options])¶
- Argumentos
steps (
unknown[]()) –options (
{ ignoreOrder?: boolean, message?: string, partial?: boolean }()) –
- Devuelve
Promise<boolean>
Same as
expect.verifySteps(), but will not immediatly fail if steps have not been registered yet, and will instead wait for a certain timeout (default: 2000ms) to allow steps to be registered later.Checks are performed initially, at the end of the timeout, and each time a step is registered.
// ... step on each 'web_read_group' call fetch(".../call_kw/web_read_group"); await expect.waitForSteps(["web_read_group"]);
DOM: queries¶
Custom DOM selectors¶
Here’s a brief section on DOM selectors in Hoot, as they support additional pseudo-classes that can be used to target elements based on non-standard features, such as their text content or their global position in the document.
:contains(text)matches nodes whose text content matches the given
textgiven text supports regular expression syntax (e.g.
:contains(/^foo.+/)) and is case-insensitive (unless using theiflag at the end of the regex)
:displayedmatches nodes that are «displayed» (see
isDisplayed)
:emptymatches nodes that have an empty content (value or text content)
:eq(n)returns the nth node based on its global position (0-based index);
:firstreturns the first node matching the selector (in the whole document)
:focusablematches nodes that can be «focused» (see
isFocusable)
:hiddenmatches nodes that are not «visible» (see
isVisible)
:iframematches nodes that are
<iframe>elements, and returns theirbodyif it is ready
:lastreturns the last node matching the selector (in the whole document)
:selectedmatches nodes that are selected (e.g.
<option>elements)
:shadowmatches nodes that have shadow roots, and returns their shadow root
:scrollablematches nodes that are scrollable (see
isScrollable)
:value(text)matches nodes whose value matches the given
textgiven text supports regular expression syntax (e.g.
:value(/^foo.+/)) and is case-insensitive (unless using theiflag at the end of the regex)
:visiblematches nodes that are «visible» (see
isVisible)
Query & node properties helpers¶
Hoot provides helpers to query nodes and some of their properties in a streamlined
and elegant way. This can mainly be done through the use of queryX helpers:
- queryAll(target[, options])¶
Returns a list of nodes matching the given
Target. This function can either be used as a template literal tag (only supports string selector without options) or invoked the usual way.The target can be:
a
Node(or an iterable of nodes), orWindowobject;a
Documentobject (which will be converted to its body);a string representing a custom selector (which will be queried from the
rootoption).
An
optionsobject can be specified to filter 1 the results:count: the exact number of nodes to match (throws an error if the number of nodes doesn’t match);displayed: whether the nodes must be «displayed» (seeisDisplayed);focusable: whether the nodes must be «focusable» (seeisFocusable);root: the root node to query the selector in (defaults to the current fixture);visible: whether the nodes must be «visible» (seeisVisible). * This option impliesdisplayed
- 1
these filters (except for
countandroot) achieve the same result as using their homonym pseudo-classes on the final group of the given selector string, e.g.:// These 2 will return the same result queryAll`ul > li:visible`; queryAll("ul > li", { visible: true });
- Devuelve
Node[]
- queryAllAttributes(target, attribute[, options])¶
Performs a
queryAll()on the giventargetand returns a list of attribute values.- Devuelve
string[]list of attribute values
- queryAllProperties(target, property[, options])¶
Performs a
queryAll()on the giventargetand returns a list of property values.- Devuelve
unknown[]list of property values
- queryAllTexts(target[, options])¶
Performs a
queryAll()on the giventargetand returns a list of text contents.- Devuelve
string[]list of text contents
- queryAllValues(target[, options])¶
Performs a
queryAll()on the giventargetand returns a list of values.- Devuelve
string[]a list of values
- queryAttribute(target, attribute[, options])¶
Performs a
queryOne()with the given arguments and returns the value of the givenattributeof the matching node.- Devuelve
stringthe attribute value
- queryFirst(target[, options])¶
Performs a
queryAll()with the given arguments and returns the first result ornull.- Devuelve
Node|nullthe first matching node
- queryOne(target[, options])¶
Performs a
queryAll()with the given arguments, along with a forcedcount: 1option to ensure only one node matches the givenTarget.The returned value is a single node instead of a list of nodes.
- Devuelve
Nodea single node
- queryText(target[, options])¶
Performs a
queryOne()with the given arguments and returns the text of the matching node.- Devuelve
stringtext of the matching node
- queryValue(target[, options])¶
Performs a
queryOne()with the given arguments and returns the value of the matching node.- Devuelve
stringvalue of the matching node
All of the above helpers are synchronous, meaning that they will attempt to query nodes instantly. Although some use cases require the element to be awaited for an arbitrary amount of time, unknown in advance due to UI fetching and rendering complexity.
Hoot provides 2 methods to wait for an element to appear / disappear within a certain
time frame (by default: 200 milliseconds) for such cases:
- waitFor(target[, options])¶
Combination of
queryAll()andwaitUntil(): waits for a given target to match elements in the DOM and returns the first matching node when it appears (or immediately if it is already present).- Devuelve
Promise<Node>containing the first matching node
DOM: interaction helpers¶
Along with querying elements, it is often required to interact with them. As such, Hoot provides helpers to simulate various user interactions on elements.
These can be split into 2 types based on their parameters: pointer-based interaction helpers, and the other ones.
Pointer interaction helpers:¶
Pointer interaction helpers (such as click() or drag()) will simulate
actual pointer movements and events on the given target, and on any previous element
the pointer was supposed to have been.
- check(target[, options])¶
Ensures that the given
Targetis checked.If it is not checked, a
click()is simulated on the input. If the input is still not checked after the click, an error is thrown.- Devuelve
Promise<Event[]>
check("input[type=checkbox]"); // Checks the first <input> checkbox element
- click(target[, options])¶
Performs a click sequence on the given
Target.The event sequence is as follows:
pointerdown[desktop]
mousedown[touch]
touchstart[target is not active element]
blur[target is focusable]
focuspointerup[desktop]
mouseup[touch]
touchendclickdblclickif click is not prevented & current click count is even
- Devuelve
Promise<Event[]>
click("button"); // Clicks on the first <button> element
- dblclick(target[, options])¶
Performs two
click()sequences on the givenTarget.- Devuelve
Promise<Event[]>
dblclick("button"); // Double-clicks on the first <button> element
- drag(target[, options])¶
Starts a drag sequence on the given
Target.Returns a set of helper functions to direct the sequence:
moveTo: moves the pointer to the given target;drop: drops the dragged element on the given target (if any);cancel: cancels the drag sequence.
- Devuelve
Promise<DragHelpers>
drag(".card:first").drop(".card:last"); // Drags the first card onto the last one drag(".card:first").moveTo(".card:last").drop(); // Same as above const { cancel, moveTo } = await drag(".card:first"); // Starts the drag sequence moveTo(".card:eq(3)"); // Moves the dragged card to the 4th card cancel(); // Cancels the drag sequence
- hover(target[, options])¶
Performs a hover sequence on the given
Target.The event sequence is as follows:
pointerover[desktop]
mouseoverpointerenter[desktop]
mouseenterpointermove[desktop]
mousemove[touch]
touchmove
- Devuelve
Promise<Event[]>
hover("button"); // Hovers the first <button> element
- pointerDown(target[, options])¶
Performs a pointer down on the given
Target.The event sequence is as follows:
pointerdown[desktop]
mousedown[touch]
touchstart[target is not active element]
blur[target is focusable]
focus
- Devuelve
Promise<Event[]>
pointerDown("button"); // Focuses to the first <button> element
- pointerUp(target[, options])¶
Performs a pointer up on the given
Target.The event sequence is as follows:
pointerup[desktop]
mouseup[touch]
touchend
- Devuelve
Promise<Event[]>
pointerUp("body"); // Triggers a pointer up on the <body> element
- scroll(target, position[, options])¶
Performs a scroll event sequence on the given
Target.The event sequence is as follows:
[desktop]
wheelscroll
- Devuelve
Promise<Event[]>
scroll("body", { y: 0 }); // Scrolls to the top of <body>
- setInputRange(target, value[, options])¶
Sets the given value to the current «input[type=range]»
Target.The event sequence is as follows:
pointerdowninputchangepointerup
- Devuelve
Promise<Event[]>
Other interaction helpers:¶
Other interaction helpers will not have a target parameter. It is not needed,
since pressing keys on a keyboard (for example) is done on the current active element.
- clear([options])¶
Clears the value of the current active element.
This is done using the following sequence:
pressing
"Control"&"A"to select the whole value;pressing
"Backspace"to delete the value;(optional) triggering a
"change"event by pressing"Enter".
- Devuelve
Promise<Event[]>
clear(); // Clears the value of the current active element
- edit(value[, options])¶
Combination of
clear()andfill():first, clears the input value (if any)
then fills the input with the given value
- Devuelve
Promise<Event[]>
fill("foo"); // Types "foo" in the active element edit("Hello World"); // Replaces "foo" by "Hello World"
- fill(value[, options])¶
Fills the current active element with the given
value. This helper is intended for<input>and<textarea>elements, with the exception of"checkbox"and"radio"types, which should be selected using thecheckhelper.If the target is an editable input, its string
valuewill be input one character at a time, each generating its corresponding keyboard event sequence. This behavior can be overridden by passing theinstantlyoption, which will instead simulate acontrol+vkeyboard sequence, resulting in the whole text being pasted.Note that the given value is appended to the current value of the element.
If the active element is a
<input type="file"/>, thevalueshould be aFile/list ofFileobject(s).- Devuelve
Promise<Event[]>
fill("Hello World"); // Types "Hello World" in the active element fill("Hello World", { instantly: true }); // Pastes "Hello World" in the active element fill(new File(["Hello World"], "hello.txt")); // Uploads a file named "hello.txt" with "Hello World" as content
- keyDown(keyStrokes[, options])¶
Performs a key down sequence on the current active element.
The event sequence is as follows:
keydown
Additional actions will be performed depending on the key pressed:
Tab: focus next (or previous withshift) focusable element;c: copy current selection to clipboard;v: paste current clipboard content to current element;Enter: submit the form if the target is a<button type="button">or a<form>element, or trigger achangeevent on the target if it is an<input>element;Space: trigger aclickevent on the target if it is an<input type="checkbox">element.
- Devuelve
Promise<Event[]>
keyDown(" "); // Space key
- keyUp(keyStrokes[, options])¶
Performs a key up sequence on the current active element.
The event sequence is as follows:
keyup
- Devuelve
Promise<Event[]>
keyUp("Enter");
- leave([options])¶
Performs a leave sequence on the current
Window.The event sequence is as follows:
pointermove[desktop]
mousemove[touch]
touchmovepointerout[desktop]
mouseoutpointerleave[desktop]
mouseleave
- Devuelve
Promise<Event[]>
leave("button"); // Moves out of <button>
- press(keyStrokes[, options])¶
Performs a keyboard event sequence on the current active element.
The event sequence is as follows:
keydownkeyup
- Devuelve
Promise<Event[]>
pointerDown("button[type=submit]"); // Moves focus to <button> keyDown("Enter"); // Submits the form keyDown("Shift+Tab"); // Focuses previous focusable element keyDown(["ctrl", "v"]); // Pastes current clipboard content
- resize([dimensions[, options]])¶
Performs a resize event sequence on the current
Window.The event sequence is as follows:
resize
The target will be resized to the given dimensions, enforced by
!importantstyle attributes.- Devuelve
Promise<Event[]>
resize("body", { width: 1000, height: 500 }); // Resizes <body> to 1000x500
- select(value[, options])¶
Performs a selection event sequence on the current active element. This helper is intended for
<select>elements only.The event sequence is as follows:
change
- Devuelve
Promise<Event[]>
click("select[name=country]"); // Focuses <select> element select("belgium"); // Selects the <option value="belgium"> element
- setInputFiles(files[, options])¶
Gives the given
Filelist to the current file input. This helper only works if a file input has been previously interacted with (by clicking on it).- Devuelve
Promise<Event[]>
- unload([options])¶
Triggers a «beforeunload» event on the current
Window.- Devuelve
Promise<Event[]>
Mocks¶
By default, a lot of low-level features are mocked by Hoot: clipboard, fetch, localStorage,
etc. These mocks are intended to not produce any side-effect that would disturb the test runner
or the context of other tests, while still providing the same interface to allow tests to rely
on these features seamlessly.
There is also a need (most of the time) to force actions on these features or change their behavior for a test, so there exist helpers to interact with these mocked features. The following sections will list the main mocked features and the means to interact with them.
Time¶
Most asynchronous features are mocked: «timers» (setTimeout, setInterval and
requestAnimationFrame), Date and performance all behave normally, but can be canceled or
sped-up manually to considerably shorten the actual duration of tests. For example: all «timers»
are canceled at the end of each test to avoid side-effects for the next one.
Importante
There are 2 main timing behaviors that are NOT mocked:
Promiseobjects and related API;OWL’s timer functions: to wait for OWL rendering functions, you’ll have to resort to the
animationFramehelper.
Network¶
In general, we don’t want to perform actual network calls in tests. To ensure this, all calls
to fetch and XMLHttpRequest have been re-routed to a function given to
mockFetch().
Nota
In Odoo, this is generally implicitly handled by a MockServer which is spawned by the mock environment, i.e. any time a component is rendered using the mountWithCleanup helper.
Related helpers¶
- mockFetch([fetchFn])¶
Mocks the fetch function by replacing it with a given
fetchFn.The return value of
fetchFnis used as the response of the mocked fetch, or wrapped in aMockResponseobject if it does not meet the required format.mockFetch((input, init) => { if (input === "/../web_search_read") { return { records: [{ id: 3, name: "john" }] }; } // ... }); mockFetch((input, init) => { if (input === "/translations") { const translations = { "Hello, world!": "Bonjour, monde !", // ... }; return new Response(JSON.stringify(translations)); } });
- mockWebSocket([onWebSocketConnected])¶
Activates mock WebSocket classe:
websocket connections will be handled by
window.fetch(seemockFetch());the
onWebSocketConnectedcallback will be called after a websocket has been created.
- mockWorker([onWorkerConnected])¶
Activates mock
WorkerandSharedWorkerclasses:actual code fetched by worker URLs will then be handled by
window.fetch(seemockFetch());the
onWorkerConnectedcallback will be called after a worker has been created.
Notable global features¶
The following features may not have any specific mocked feature added, but they do work as expected without changing the actual properties they were meant to:
-
Both
titleandcookiecan be set and read without changing the actual properties of the current document. -
The
historyAPI is mocked and bound to themockLocationobject to return the same values and provide consistency. -
Hoot returns a
mockLocationobject to use instead ofwindow.location, but this relies on the use of an indirection in the actual production code.Importante
This feature will only work if an indirection is set between production code and calls to
window.location. In Odoo, it works because the@web/core/browsermodule provides such an indirection, and that module is mocked in test environments to redirect to themockLocationobject. -
Most used navigator features, such as the
clipboardAPI anduserAgent, have been mocked to hijack their actual behaviors. Itspermissionsobject has been bound to a global mock of the permissions API. -
Notifications have been mocked, with the «notification» permissions bound to the global mocked permissions API.
-
Permissions can enable or disable other APIs by being given the
"granted"or"denied"statuses. This can be done through themockPermissionhelper. -
localStorageandsessionStorageboth point to «virtual» storages. -
Touch features can be force-activated or deactivated globally for a given test/suite using the
mockTouch()helper. It will mock both the presence of touch handlers likeontouchstarton window, as well as the"pointer"media being set tofineorcoarse.
Related helpers¶
- mockPermission(name[, value])¶
Sets the given value for the given permission. This allows to enable or prevent certain APIs (see the Permissions API).
// Prevents the whole notification API from working mockPermission("notifications", "denied");
- mockTouch(setTouch)¶
Toggles touch features on or off in the current
Window.