summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js/tools/test_menus.js
diff options
context:
space:
mode:
authorChristophe Monniez <moc@odoo.com>2018-07-04 16:32:05 +0200
committerChristophe Monniez <moc@odoo.com>2018-08-29 16:57:27 +0200
commit3f0ebca1924afb538c6f7960a42aba725654c643 (patch)
tree1bcdb20706658bdde4487c38abad1d4b5204c5bc /addons/web/static/src/js/tools/test_menus.js
parent19cc218d03c9e2eb32ce354db96668b4c5280854 (diff)
[IMP] web: New test to test Odoo menus
This commit adds a new js test in web that enters each app and click on each view switcher, each menu and each search filter. This test is tagged '-standard' and 'click_all' as it could last a long time, that way it's not executed by default. It could also be started from the browser console with 'clickEverywhere();'.
Diffstat (limited to 'addons/web/static/src/js/tools/test_menus.js')
-rw-r--r--addons/web/static/src/js/tools/test_menus.js278
1 files changed, 278 insertions, 0 deletions
diff --git a/addons/web/static/src/js/tools/test_menus.js b/addons/web/static/src/js/tools/test_menus.js
new file mode 100644
index 00000000000..6a8c9d2118b
--- /dev/null
+++ b/addons/web/static/src/js/tools/test_menus.js
@@ -0,0 +1,278 @@
+(function (exports) {
+ /**
+ * The purpose of this test is to click on every installed App and then
+ * open each view. On each view, click on each filter.
+ */
+ "use strict";
+ var clientActionCount = 0;
+ var viewUpdateCount = 0;
+ var testedApps;
+ var testedMenus;
+
+ function createWebClientHooks() {
+ var AbstractController = odoo.__DEBUG__.services['web.AbstractController'];
+ var Discuss = odoo.__DEBUG__.services['mail.Discuss'];
+ var WebClient = odoo.__DEBUG__.services["web.WebClient"];
+
+ WebClient.include({
+ current_action_updated : function (action, controller) {
+ this._super(action, controller);
+ clientActionCount++;
+ },
+ });
+
+ AbstractController.include({
+ start: function(){
+ this.$el.attr('data-view-type', this.viewType);
+ return this._super();
+ },
+ update: function(params, options) {
+ return this._super(params, options).then(function (){
+ viewUpdateCount++;
+ });
+ },
+ });
+
+ if (Discuss) {
+ Discuss.include({
+ _fetchAndRenderThread: function() {
+ return this._super().then(function (){
+ viewUpdateCount++;
+ });
+ },
+ });
+ }
+ }
+
+ // Main function that starts orchestration of tests
+ function clickEverywhere(){
+ console.log("Starting ClickEverywhere test");
+ var startTime = performance.now()
+ createWebClientHooks();
+ testedApps = [];
+ testedMenus = [];
+ var isEnterprise = odoo.session_info.server_version_info[5] === 'e';
+ // finding applications menus
+ var $listOfAppMenuItems;
+ if (isEnterprise) {
+ console.log("Odoo flavor: Enterprise")
+ var $homeMenu = $("nav.o_main_navbar > a.o_menu_toggle.fa-th");
+ $homeMenu.click()
+ $listOfAppMenuItems = $(".o_app, .o_menuitem")
+ } else {
+ console.log("Odoo flavor: Community")
+ $listOfAppMenuItems = $('#oe_main_menu_navbar a.oe_menu_leaf, #oe_main_menu_navbar a.oe_menu_toggler');
+ }
+ console.log('Found ', $listOfAppMenuItems.length, 'apps to test');
+
+ var testDef = $.when();
+ testDef = chainDeferred($listOfAppMenuItems, testDef, testApp);
+ return testDef.then(function() {
+ console.log("Successfully tested ", testedApps.length, " apps");
+ console.log("Successfully tested ", testedMenus.length - testedApps.length, " menus");
+ console.log("ok");
+ }).always(function() {
+ console.log("Test took ", (performance.now() - startTime)/1000, " seconds");
+ }).fail(function () {
+ console.error("Error !")
+ });
+ }
+
+
+ /**
+ * Test an "App" menu item by orchestrating the following actions:
+ * 1 - clicking on its menuItem
+ * 2 - clicking on each view
+ * 3 - clicking on each menu
+ * 3.1 - clicking on each view
+ * @param {DomElement} element: the App menu item
+ * @returns {Deferred}
+ */
+ function testApp(element){
+ console.log("Testing app menu:", element.dataset.menuXmlid);
+ if (testedApps.indexOf(element.dataset.menuXmlid) >= 0) return $.Deferred().resolve(); // Another infinite loop protection
+ testedApps.push(element.dataset.menuXmlid);
+ return testMenuItem(element).then(function () {
+ var $subMenuItems;
+ var isEnterprise = odoo.session_info.server_version_info[5] === 'e';
+ if (isEnterprise) {
+ $subMenuItems = $('.o_menu_entry_lvl_1, .o_menu_entry_lvl_2, .o_menu_entry_lvl_3, .o_menu_entry_lvl_4');
+ } else {
+ $subMenuItems = $('div.o_sub_menu_content .oe_menu_leaf');
+ }
+ var testMenuDef = $.when();
+ testMenuDef = chainDeferred($subMenuItems, testMenuDef, testMenuItem);
+ return testMenuDef;
+ }).then(function(){
+ // no effect in community
+ var $homeMenu = $("nav.o_main_navbar > a.o_menu_toggle.fa-th");
+ $homeMenu.click();
+ });
+ }
+
+
+ /**
+ * Test a menu item by:
+ * 1 - clikcing on the menuItem
+ * 2 - Orchestrate the view switch
+ *
+ * @param {DomElement} element: the menu item
+ * @returns {Deferred}
+ */
+ function testMenuItem(element){
+ if (testedMenus.indexOf(element.dataset.menuXmlid) >= 0) return $.Deferred().resolve(); // Avoid infinite loop
+ console.log("Testing menu", element.innerText.trim(), " ", element.dataset.menuXmlid);
+ testedMenus.push(element.dataset.menuXmlid);
+ var startActionCount = clientActionCount;
+ element.click();
+ var isModal = false;
+ return waitForCondition(function() {
+ // sometimes, the app is just a modal that needs to be closed
+ var $modal = $('.modal[role="dialog"][open="open"]');
+ if ($modal.length > 0) {
+ $modal.modal('hide');
+ isModal = true;
+ return true;
+ };
+ return startActionCount != clientActionCount;
+ }).then(function() {
+ if (!isModal) {
+ return testFilters();
+ }
+ }).then(function () {
+ if (!isModal) {
+ return testViews();
+ }
+ }).fail(function() {
+ console.error("Error while testing", element);
+ });
+ };
+
+
+ /**
+ * Orchestrate the test of views
+ * This function finds the buttons that permit to switch views and orchestrate
+ * the click on each of them
+ */
+ function testViews() {
+ var $switches = $("nav.o_cp_switch_buttons > button:not(.active):visible");
+ var testSwitchDef = $.when();
+ // chainDeferred($switches, testSwitchDef, testViewSwitch # FIXME
+ _.each($switches, function(switchButton) {
+ testSwitchDef = testSwitchDef.then(function () {
+ // get the view view-type data attribute
+ return testViewSwitch(switchButton.dataset.viewType);
+ });
+ });
+ return testSwitchDef;
+ }
+
+ /**
+ * Test a view button
+ * @param {string} viewType: a string for the view type to test (list, kanban ...)
+ * @returns {Deferred} a deferred that wait for the view to be loaded
+ */
+ function testViewSwitch(viewType){
+ console.log("Testing view switch: ", viewType);
+ // timeout to avoid click debounce
+ setTimeout(function() {
+ var $element = $("nav.o_cp_switch_buttons > button[data-view-type=" + viewType + "]");
+ console.log('Clicking on: ', $element[0].dataset.viewType, ' view switcher');
+ $element.click();
+ },250);
+ var waitViewSwitch = waitForCondition(function(){
+ return $('.o_content > .o_view_controller').data('view-type') === viewType;
+ });
+ return waitViewSwitch.then(function() {
+ return testFilters();
+ });
+ }
+
+ /**
+ * Test filters
+ * Click on each filter in the control pannel
+ */
+ function testFilters() {
+ var filterDef = $.when();
+ // var $filters = $('div.o_control_panel div.btn-group.o_dropdown > ul.o_filters_menu > li:not(.o_add_custom_filter)');
+ var $filters = $('.o_filters_menu > .o_menu_item')
+ console.log("Testing " + $filters.length + " filters");
+ var filter_ids = _.compact(_.map($filters, function(f) { return f.dataset.id}));
+ _.each(filter_ids, function(filter_id){
+ filterDef = filterDef.then(function(){
+ var currentViewCount = viewUpdateCount;
+ var $filter = $('.o_menu_item[data-id="' + filter_id + '"] a');
+ // with some customized search views, the filter cannot be found
+ if ($filter[0] === undefined) {
+ console.warn('Filter with ID ', filter_id , 'cannot be found');
+ return $.Deferred().resolve();
+ };
+ console.log('Clicking on filter "', $filter.text().trim(), '"');
+ $filter[0].click();
+ setTimeout(function() {
+ var $filterOption = $('.o_menu_item .o_item_option[data-item_id="' + filter_id + '"]:not(.selected) a');
+ // In case the filter is a date filter, we need to click on the first filter option (like 'today','This week' ...)
+ if ($filterOption.length > 0) {
+ console.log('Clicking on filter option "', $filterOption[0], '"');
+ $filterOption[0].click();
+ console.log('And now on filter again');
+ $filter = $('.o_menu_item[data-id="' + filter_id + '"] a');
+ $filter[0].click(); // To avoid that the next view fold the options
+ }
+ }, 250);
+ return waitForCondition(function() {
+ return currentViewCount !== viewUpdateCount;
+ });
+ });
+ });
+ return filterDef;
+ }
+
+ // utility functions
+ /**
+ * Wait a certain amount of time for a condition to occur
+ * @param stopCondition: a function that returns a boolean
+ * @returns {Deferred} that is rejected if the timeout is exceeded
+ */
+ function waitForCondition(stopCondition) {
+ var def = $.Deferred();
+ var interval = 250;
+ var timeLimit = 15000;
+ function checkCondition() {
+ if (stopCondition()) {
+ def.resolve();
+ } else {
+ timeLimit -= interval;
+ if (timeLimit > 0) {
+ // recursive call until the resolve or the timeout
+ setTimeout(checkCondition, interval);
+ } else {
+ console.error("Timeout exceeded", stopCondition);
+ def.reject();
+ }
+ }
+ }
+ setTimeout(checkCondition, interval);
+ return def;
+ };
+
+
+ /**
+ * chain deferred actions
+ * @param $elements: a list of jquery elements to be passed as arg to the function
+ * @param deferred: the deferred on which other deferreds will be chained
+ * @param f: the function to be deferred
+ * @returns : the chained deferred
+ */
+ function chainDeferred($elements, deferred, f) {
+ _.each($elements, function($el) {
+ deferred = deferred.then(function () {
+ return f($el);
+ });
+ });
+ return deferred;
+ }
+
+
+ exports.clickEverywhere = clickEverywhere;
+})(window);