summaryrefslogtreecommitdiff
path: root/addons/web/static/tests/helpers/qunit_config.js
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/web/static/tests/helpers/qunit_config.js
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/web/static/tests/helpers/qunit_config.js')
-rw-r--r--addons/web/static/tests/helpers/qunit_config.js249
1 files changed, 249 insertions, 0 deletions
diff --git a/addons/web/static/tests/helpers/qunit_config.js b/addons/web/static/tests/helpers/qunit_config.js
new file mode 100644
index 00000000..575c8022
--- /dev/null
+++ b/addons/web/static/tests/helpers/qunit_config.js
@@ -0,0 +1,249 @@
+(function() {
+"use strict";
+
+/**
+ * QUnit Config
+ *
+ * The Odoo javascript test framework is based on QUnit (http://qunitjs.com/).
+ * This file is necessary to setup Qunit and to prepare its interactions with
+ * Odoo. It has to be loaded before any tests are defined.
+ *
+ * Note that it is not an Odoo module, because we want this code to be executed
+ * as soon as possible, not whenever the Odoo module system feels like it.
+ */
+
+
+/**
+ * This configuration variable is not strictly necessary, but it ensures more
+ * safety for asynchronous tests. With it, each test has to explicitely tell
+ * QUnit how many assertion it expects, otherwise the test will fail.
+ */
+QUnit.config.requireExpects = true;
+
+/**
+ * not important in normal mode, but in debug=assets, the files are loaded
+ * asynchroneously, which can lead to various issues with QUnit... Notice that
+ * this is done outside of odoo modules, otherwise the setting would not take
+ * effect on time.
+ */
+QUnit.config.autostart = false;
+
+/**
+ * A test timeout of 1 min, before an async test is considered failed.
+ */
+QUnit.config.testTimeout = 1 * 60 * 1000;
+
+/**
+ * Hide passed tests by default in the QUnit page
+ */
+QUnit.config.hidepassed = (window.location.href.match(/[?&]testId=/) === null);
+
+var sortButtonAppended = false;
+
+/**
+ * If we want to log several errors, we have to log all of them at once, as
+ * browser_js is closed as soon as an error is logged.
+ */
+const errorMessages = [];
+/**
+ * List of elements tolerated in the body after a test. The property "keep"
+ * prevents the element from being removed (typically: qunit suite elements).
+ */
+const validElements = [
+ // always in the body:
+ { tagName: 'DIV', attr: 'id', value: 'qunit', keep: true },
+ { tagName: 'DIV', attr: 'id', value: 'qunit-fixture', keep: true },
+ // shouldn't be in the body after a test but are tolerated:
+ { tagName: 'SCRIPT', attr: 'id', value: '' },
+ { tagName: 'DIV', attr: 'className', value: 'o_notification_manager' },
+ { tagName: 'DIV', attr: 'className', value: 'tooltip fade bs-tooltip-auto' },
+ { tagName: 'DIV', attr: 'className', value: 'tooltip fade bs-tooltip-auto show' },
+ { tagName: 'SPAN', attr: 'className', value: 'select2-hidden-accessible' },
+ // Due to a Document Kanban bug (already present in 12.0)
+ { tagName: 'DIV', attr: 'className', value: 'ui-helper-hidden-accessible' },
+ { tagName: 'UL', attr: 'className', value: 'ui-menu ui-widget ui-widget-content ui-autocomplete ui-front' },
+];
+
+/**
+ * Waits for the module system to end processing the JS modules, so that we can
+ * make the suite fail if some modules couldn't be loaded (e.g. because of a
+ * missing dependency).
+ *
+ * @returns {Promise<boolean>}
+ */
+async function checkModules() {
+ // do not mark the suite as successful already, as we still need to ensure
+ // that all modules have been correctly loaded
+ $('#qunit-banner').removeClass('qunit-pass');
+ const $modulesAlert = $('<div>')
+ .addClass('alert alert-info')
+ .text('Waiting for modules check...');
+ $modulesAlert.appendTo('#qunit');
+
+ // wait for the module system to end processing the JS modules
+ await odoo.__DEBUG__.didLogInfo;
+
+ const info = odoo.__DEBUG__.jsModules;
+ if (info.missing.length || info.failed.length) {
+ $('#qunit-banner').addClass('qunit-fail');
+ $modulesAlert.toggleClass('alert-info alert-danger');
+ const failingModules = info.missing.concat(info.failed);
+ const error = `Some modules couldn't be started: ${failingModules.join(', ')}.`;
+ $modulesAlert.text(error);
+ errorMessages.unshift(error);
+ return false;
+ } else {
+ $modulesAlert.toggleClass('alert-info alert-success');
+ $modulesAlert.text('All modules have been correctly loaded.');
+ $('#qunit-banner').addClass('qunit-pass');
+ return true;
+ }
+}
+
+/**
+ * This is the way the testing framework knows that tests passed or failed. It
+ * only look in the phantomJS console and check if there is a ok or an error.
+ *
+ * Someday, we should devise a safer strategy...
+ */
+QUnit.done(async function (result) {
+ const allModulesLoaded = await checkModules();
+ if (result.failed) {
+ errorMessages.push(`${result.failed} / ${result.total} tests failed.`);
+ }
+ if (!result.failed && allModulesLoaded) {
+ console.log('test successful');
+ } else {
+ console.error(errorMessages.join('\n'));
+ }
+
+ if (!sortButtonAppended) {
+ _addSortButton();
+ }
+});
+
+/**
+ * This logs various data in the console, which will be available in the log
+ * .txt file generated by the runbot.
+ */
+QUnit.log(function (result) {
+ if (!result.result) {
+ var info = '"QUnit test failed: "' + result.module + ' > ' + result.name + '"';
+ info += ' [message: "' + result.message + '"';
+ if (result.actual !== null) {
+ info += ', actual: "' + result.actual + '"';
+ }
+ if (result.expected !== null) {
+ info += ', expected: "' + result.expected + '"';
+ }
+ info += ']';
+ errorMessages.push(info);
+ }
+});
+
+/**
+ * This is done mostly for the .txt log file generated by the runbot.
+ */
+QUnit.moduleDone(function(result) {
+ if (!result.failed) {
+ console.log('"' + result.name + '"', "passed", result.total, "tests.");
+ } else {
+ console.log('"' + result.name + '"',
+ "failed", result.failed,
+ "tests out of", result.total, ".");
+ }
+
+});
+
+/**
+ * After each test, we check that there is no leftover in the DOM.
+ *
+ * Note: this event is not QUnit standard, we added it for this specific use case.
+ * As a payload, an object with keys 'moduleName' and 'testName' is provided. It
+ * is used to indicate the test that left elements in the DOM, when it happens.
+ */
+QUnit.on('OdooAfterTestHook', function (info) {
+ const toRemove = [];
+ // check for leftover elements in the body
+ for (const bodyChild of document.body.children) {
+ const tolerated = validElements.find((e) =>
+ e.tagName === bodyChild.tagName && bodyChild[e.attr] === e.value
+ );
+ if (!tolerated) {
+ console.error(`Test ${info.moduleName} > ${info.testName}`);
+ console.error('Body still contains undesirable elements:' +
+ '\nInvalid element:\n' + bodyChild.outerHTML);
+ QUnit.pushFailure(`Body still contains undesirable elements`);
+ }
+ if (!tolerated || !tolerated.keep) {
+ toRemove.push(bodyChild);
+ }
+ }
+
+ // check for leftovers in #qunit-fixture
+ const qunitFixture = document.getElementById('qunit-fixture');
+ if (qunitFixture.children.length) {
+ console.error(`Test ${info.moduleName} > ${info.testName}`);
+ console.error('#qunit-fixture still contains elements:' +
+ '\n#qunit-fixture HTML:\n' + qunitFixture.outerHTML);
+ QUnit.pushFailure(`#qunit-fixture still contains elements`);
+ toRemove.push(...qunitFixture.children);
+ }
+
+ // remove unwanted elements if not in debug
+ if (!document.body.classList.contains('debug')) {
+ for (const el of toRemove) {
+ el.remove();
+ }
+ }
+});
+
+/**
+ * Add a sort button on top of the QUnit result page, so we can see which tests
+ * take the most time.
+ */
+function _addSortButton() {
+ sortButtonAppended = true;
+ var $sort = $('<label> sort by time (desc)</label>').css({float: 'right'});
+ $('h2#qunit-userAgent').append($sort);
+ $sort.click(function() {
+ var $ol = $('ol#qunit-tests');
+ var $results = $ol.children('li').get();
+ $results.sort(function (a, b) {
+ var timeA = Number($(a).find('span.runtime').first().text().split(" ")[0]);
+ var timeB = Number($(b).find('span.runtime').first().text().split(" ")[0]);
+ if (timeA < timeB) {
+ return 1;
+ } else if (timeA > timeB) {
+ return -1;
+ } else {
+ return 0;
+ }
+ });
+ $.each($results, function(idx, $itm) { $ol.append($itm); });
+
+ });
+}
+
+/**
+ * We add here a 'fail fast' feature: we often want to stop the test suite after
+ * the first failed test. This is also useful for the runbot test suites.
+ */
+
+QUnit.config.urlConfig.push({
+ id: "failfast",
+ label: "Fail Fast",
+ tooltip: "Stop the test suite immediately after the first failed test."
+});
+
+QUnit.begin(function() {
+ if (QUnit.config.failfast) {
+ QUnit.testDone(function(details) {
+ if (details.failed > 0) {
+ QUnit.config.queue.length = 0;
+ }
+ });
+ }
+});
+
+})();