This rev. introduces robust helpers to use in the JS tests suite to
interact with DOM and components, and starts using them (almost)
everywhere.
All the helpers are exposed though testUtils.js.
There are 2 kinds of helpers:
1. Assertions
-------------
* assert.containsNone, containsN, containsOnce check that the DOM
(or a specific part of the DOM) contains a `selector`. It
generates a correct error message automatically.
ex: assert.strictEqual(form.$('.o_form_editable'), 1, "msg");
-> assert.containsOnce(form, '.o_form_editable');
* assert.isVisible, isNotVisible check that the DOM has an element
visible or not. They also check that the element is actually in
the DOM (before most tests didn't verify this).
* assert.hasClass, doesNotHaveClass, hasAttrVAlue, check specific
properties of a DOM element, and also validate that it is
applied on a single existing DOM element (before most tests
didn't verify this).
ex: assert.notOk(form.$('button').hasClass('btn-primary'));
-> assert.doesNotHaveClass(form.$('button'), 'btn-primary');
2. Utilities
------------
The goal of the utilities is to centralize the definition of many
standard components and interactions, ensuring that when we
refactor the JS framework, we do not need to change all the tests.
Existing mock utilities (addMockEnvironment, intercept, path,
patchDate, unpatch and fieldsViewGet) are moved to
'testUtils.mock.*'.
Existing DOM utilities are moved to 'testUtils.dom.*'.
New dom utilities are created for opendDatePicker, click,
clickFirst and clickLast. Helper `click` verifies that there is
exactly 1 element visible in the DOM you click on, `clickFirst`
and `clickLast` verify that there are more than one element on the
DOM.
ex: form.$('button').click();
-> testUtils.dom.click(form.$('button'));
New Form utilities: (testUtils.form.*)
clickEdit, clickSave, clickCreate, clickDiscard, all clicks on
the control panel buttons of the form.
`reload` reloads the form data.
New modal, graph, kanban and pivot utils (testUtils.pivot.*,
testUtils.kanban.*, etc.).
New fields utils: (testUtils.fields.*)
* editInput, editSelect: allow to change the value of a field,
using a selector to identify it. They validate that the input
exists and trigger the change event automatically.
* editAndTrigger: allow to modify a field and trigger specific
events after the value change
* many2one (testUtils.fields.many2one.*)
clickOpenDropdown, clickHighlightedItem, clickItem,
searchAndClickItem: use a field name instead of a selector and
do all the complex mechanism to open, filter and highlight
many2one fields.
Joint work with aab, dam, ged, mge, svs and vsc.
230 lines
7.0 KiB
JavaScript
230 lines
7.0 KiB
JavaScript
odoo.define('web.qunit_asserts', function (require) {
|
|
"use strict";
|
|
|
|
/**
|
|
* In this file, we extend QUnit by adding some specialized assertions. The goal
|
|
* of these new assertions is twofold:
|
|
* - ease of use: they should allow us to simplify some common complex assertions
|
|
* - safer: these assertions will fail when some preconditions are not met.
|
|
*
|
|
* For example, the assert.isVisible assertion will also check that the target
|
|
* matches exactly one element.
|
|
*/
|
|
|
|
var Widget = require('web.Widget');
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Private functions
|
|
//------------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Helper function, to check if a given element
|
|
* - is unique (if it is a jquery node set)
|
|
* - has (or has not) a css class
|
|
*
|
|
* @private
|
|
* @param {Widget|jQuery|HTMLElement} w
|
|
* @param {string} className
|
|
* @param {boolean} shouldHaveClass
|
|
* @param {string} [msg]
|
|
*/
|
|
function _checkClass(w, className, shouldHaveClass, msg) {
|
|
var $el = w instanceof Widget ? w.$el :
|
|
w instanceof HTMLElement ? $(w) :
|
|
w; // jquery element
|
|
|
|
if ($el.length !== 1) {
|
|
var descr = `${shouldHaveClass ? 'hasClass' : 'doesNotHaveClass'} ${className}`;
|
|
QUnit.assert.ok(false, `Assertion '${descr}' targets ${$el.length} elements instead of 1`);
|
|
} else {
|
|
msg = msg || `target ${$el.selector || ''} should ${shouldHaveClass ? '' : 'not'} have class ${className}`;
|
|
var hasClass = $el.hasClass(className);
|
|
var condition = shouldHaveClass ? hasClass : !hasClass;
|
|
QUnit.assert.ok(condition, msg);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper function, to check if a given element
|
|
* - is unique (if it is a jquery node set)
|
|
* - is (or not) visible
|
|
*
|
|
* @private
|
|
* @param {Widget|jQuery|HTMLElement} w
|
|
* @param {boolean} shouldBeVisible
|
|
* @param {string} [msg]
|
|
*/
|
|
function _checkVisible(w, shouldBeVisible, msg) {
|
|
var $el = w instanceof Widget ? w.$el :
|
|
w instanceof HTMLElement ? $(w) :
|
|
w; // jquery element
|
|
|
|
if ($el.length !== 1) {
|
|
var descr = `${shouldBeVisible ? 'isVisible' : 'isNotVisible'}`;
|
|
QUnit.assert.ok(false, `Assertion '${descr}' targets ${$el.length} elements instead of 1`);
|
|
} else {
|
|
msg = msg || `target ${$el.selector} should ${shouldBeVisible ? '' : 'not'} be visible`;
|
|
var isVisible = $el.is(':visible');
|
|
var condition = shouldBeVisible ? isVisible : !isVisible;
|
|
QUnit.assert.ok(condition, msg);
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Public functions
|
|
//------------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Checks that the target element (described by widget/jquery or html element)
|
|
* contains exactly n matches for the selector.
|
|
*
|
|
* Example: assert.containsN(document.body, '.modal', 0)
|
|
*
|
|
* @param {Widget|jQuery|HTMLElement} w
|
|
* @param {string} selector
|
|
* @param {number} n
|
|
* @param {string} [msg]
|
|
*/
|
|
function containsN(w, selector, n, msg) {
|
|
if (typeof n !== 'number') {
|
|
throw Error("containsN assert should be called with a number as third argument");
|
|
}
|
|
var $el = w instanceof Widget ? w.$el :
|
|
w instanceof HTMLElement ? $(w) :
|
|
w; // jquery element
|
|
|
|
var $matches = $el.find(selector);
|
|
if (!msg) {
|
|
msg = `Selector '${selector}' should have exactly ${n} matches`;
|
|
if ($el.selector) {
|
|
msg += ` (inside '${$el.selector}')`;
|
|
}
|
|
}
|
|
QUnit.assert.strictEqual($matches.length, n, msg);
|
|
}
|
|
|
|
/**
|
|
* Checks that the target element (described by widget/jquery or html element)
|
|
* contains exactly 1 match for the selector.
|
|
*
|
|
* @param {Widget|jQuery|HTMLElement} w
|
|
* @param {string} selector
|
|
* @param {string} [msg]
|
|
*/
|
|
function containsOnce(w, selector, msg) {
|
|
containsN(w, selector, 1, msg);
|
|
}
|
|
|
|
/**
|
|
* Checks that the target element (described by widget/jquery or html element)
|
|
* contains exactly 0 match for the selector.
|
|
*
|
|
* @param {Widget|jQuery|HTMLElement} w
|
|
* @param {string} selector
|
|
* @param {string} [msg]
|
|
*/
|
|
function containsNone(w, selector, msg) {
|
|
containsN(w, selector, 0, msg);
|
|
}
|
|
|
|
/**
|
|
* Checks that the target element (described by widget/jquery or html element)
|
|
* - exists
|
|
* - is unique
|
|
* - has the given class (specified by className)
|
|
*
|
|
* Note that it uses the hasClass jQuery method, so it can be used to check the
|
|
* presence of more than one class ('some-class other-class'), but it is a
|
|
* little brittle, because it depends on the order of these classes:
|
|
*
|
|
* div.a.b.c: has class 'a b c', but does not have class 'a c b'
|
|
*
|
|
* @param {Widget|jQuery|HTMLElement} w
|
|
* @param {string} className
|
|
* @param {string} [msg]
|
|
*/
|
|
function hasClass(w, className, msg) {
|
|
_checkClass(w, className, true, msg);
|
|
}
|
|
|
|
/**
|
|
* Checks that the target element (described by widget/jquery or html element)
|
|
* - exists
|
|
* - is unique
|
|
* - does not have the given class (specified by className)
|
|
*
|
|
* @param {Widget|jQuery|HTMLElement} w
|
|
* @param {string} className
|
|
* @param {string} [msg]
|
|
*/
|
|
function doesNotHaveClass(w, className, msg) {
|
|
_checkClass(w, className, false, msg);
|
|
}
|
|
|
|
/**
|
|
* Checks that the target element (described by widget/jquery or html element)
|
|
* - exists
|
|
* - is unique
|
|
* - has the given attribute with the proper value
|
|
*
|
|
* @param {Widget|jQuery|HTMLElement} w
|
|
* @param {string} attr
|
|
* @param {string} value
|
|
* @param {string} [msg]
|
|
*/
|
|
function hasAttrValue(w, attr, value, msg) {
|
|
var $el = w instanceof Widget ? w.$el :
|
|
w instanceof HTMLElement ? $(w) :
|
|
w; // jquery element
|
|
|
|
if ($el.length !== 1) {
|
|
var descr = `hasAttrValue (${attr}: ${value})`;
|
|
QUnit.assert.ok(false, `Assertion '${descr}' targets ${$el.length} elements instead of 1`);
|
|
} else {
|
|
msg = msg || `attribute '${attr}' of target ${$el.selector || ''} should be '${value}'`;
|
|
QUnit.assert.strictEqual($el.attr(attr), value, msg);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks that the target element (described by widget/jquery or html element)
|
|
* - exists
|
|
* - is visible (as far as jQuery can tell: not in display none, ...)
|
|
*
|
|
* @param {Widget|jQuery|HTMLElement} w
|
|
* @param {string} [msg]
|
|
*/
|
|
function isVisible(w, msg) {
|
|
_checkVisible(w, true, msg);
|
|
}
|
|
|
|
/**
|
|
* Checks that the target element (described by widget/jquery or html element)
|
|
* - exists
|
|
* - is not visible (as far as jQuery can tell: display none, ...)
|
|
*
|
|
* @param {Widget|jQuery|HTMLElement} w
|
|
* @param {string} [msg]
|
|
*/
|
|
function isNotVisible(w, msg) {
|
|
_checkVisible(w, false, msg);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Exposed API
|
|
//------------------------------------------------------------------------------
|
|
|
|
QUnit.assert.containsOnce = containsOnce;
|
|
QUnit.assert.containsNone = containsNone;
|
|
QUnit.assert.containsN = containsN;
|
|
|
|
QUnit.assert.hasClass = hasClass;
|
|
QUnit.assert.doesNotHaveClass = doesNotHaveClass;
|
|
|
|
QUnit.assert.hasAttrValue = hasAttrValue;
|
|
|
|
QUnit.assert.isVisible = isVisible;
|
|
QUnit.assert.isNotVisible = isNotVisible;
|
|
|
|
});
|