From fa2f25154c4a924eeae43ae6db87a1d2b51c746d Mon Sep 17 00:00:00 2001 From: Romeo Fragomeli Date: Mon, 3 Feb 2020 14:46:09 +0000 Subject: [PATCH 01/10] [IMP] web_tour: activate tour service for mobile Before this commit, when you tried to run a tour by clicking on "Start Tour" in the debug menu, nothing happened and a warning message appeared in console: "debug_manager.js:53 No handler for start_tour". Now it will work on mobile too (debug mode only). We had to do this to be able to run the main flow tour on mobile. Note that another commit (in enterprise) will automatically consume all tours to avoid to display it for demonstration purposes. Task ID: 2149402 --- addons/web_tour/static/src/js/tour_service.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/addons/web_tour/static/src/js/tour_service.js b/addons/web_tour/static/src/js/tour_service.js index 87cf69b7d83..af4c8db0dc5 100644 --- a/addons/web_tour/static/src/js/tour_service.js +++ b/addons/web_tour/static/src/js/tour_service.js @@ -7,9 +7,6 @@ var rpc = require('web.rpc'); var session = require('web.session'); var TourManager = require('web_tour.TourManager'); -if (config.device.isMobile) { - return Promise.reject(); -} /** * @namespace * @property {Object} active_tooltips From 10a47cf4015b622c88242f569c8a6fa2950d8e66 Mon Sep 17 00:00:00 2001 From: Romeo Fragomeli Date: Mon, 3 Feb 2020 14:38:34 +0000 Subject: [PATCH 02/10] [FIX] web_tour: tip not visible anymore on notebook header Since commit: odoo/enterprise@e662ef36296afb2787eb694a33b4c6616a5e1326 a 'div' wrapper was added around the notebook header. The tip was not visible due to it's location in the wrapper with 'overflow: hidden'. After this commit, the tip is appended to the ancestor of the wrapper and so it's visible again. --- addons/web_tour/static/src/js/tip.js | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/web_tour/static/src/js/tip.js b/addons/web_tour/static/src/js/tip.js index 3bc950ba8f8..babb50b8c5c 100644 --- a/addons/web_tour/static/src/js/tip.js +++ b/addons/web_tour/static/src/js/tip.js @@ -135,6 +135,7 @@ var Tip = Widget.extend({ p = $location.css("position"); } while ( $location.hasClass('dropdown-menu') || + $location.hasClass('o_notebook_headers') || ( (o === "visible" || o === "hidden") && p !== "fixed" && From 3a191d053a3aa7b817d8081c7643a58e05c5a27d Mon Sep 17 00:00:00 2001 From: Romeo Fragomeli Date: Mon, 3 Feb 2020 14:43:25 +0000 Subject: [PATCH 03/10] [FIX] web_tour: tip not consumed with the right event on mobile Before this commit, the tip for a many2X field was consumed with a input event. But on mobile we doesn't have the same behaviour; a kanban modal is opened when we click on the many2X field input instead of writing in it (auto-complete) like on desktop. After this commit, the tip is consumed with a click event for many2X on mobile. --- addons/web_tour/static/src/js/tip.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/addons/web_tour/static/src/js/tip.js b/addons/web_tour/static/src/js/tip.js index babb50b8c5c..d9446341de7 100644 --- a/addons/web_tour/static/src/js/tip.js +++ b/addons/web_tour/static/src/js/tip.js @@ -320,6 +320,10 @@ Tip.getConsumeEventType = function ($element) { if ($element.hasClass("o_field_date_range")) { return "apply.daterangepicker input"; } + if (config.device.isMobile && + $element.closest('.o_field_widget').is('.o_field_many2one, .o_field_many2many')) { + return "click"; + } return "input"; } else if ($element.hasClass('ui-draggable-handle')) { return "drag"; From d2fcdd50122c89c0a91654b2780aa29381e7d776 Mon Sep 17 00:00:00 2001 From: Romeo Fragomeli Date: Fri, 7 Feb 2020 09:41:09 +0000 Subject: [PATCH 04/10] [REF] test_main_flows: fix linter warnings --- .../static/tests/tours/main_flow.js | 102 +++++++++--------- 1 file changed, 49 insertions(+), 53 deletions(-) diff --git a/odoo/addons/test_main_flows/static/tests/tours/main_flow.js b/odoo/addons/test_main_flows/static/tests/tours/main_flow.js index e32a70ace08..892f22d37f2 100644 --- a/odoo/addons/test_main_flows/static/tests/tours/main_flow.js +++ b/odoo/addons/test_main_flows/static/tests/tours/main_flow.js @@ -1,10 +1,10 @@ -odoo.define('test_main_flows.tour', function(require) { +odoo.define('test_main_flows.tour', function (require) { "use strict"; -var core = require('web.core'); -var tour = require('web_tour.tour'); +const core = require('web.core'); +const tour = require('web_tour.tour'); -var _t = core._t; +const _t = core._t; tour.register('main_flow_tour', { test: true, @@ -23,26 +23,26 @@ tour.register('main_flow_tour', { // Add Stockable product trigger: ".o_menu_sections a:contains('Products')", extra_trigger: '.o_main_navbar', - content: _t("Let\'s create products."), + content: _t("Let's create products."), position: "bottom", }, { trigger: ".o_menu_sections a:has(span:contains('Products'))", - content: _t("Let\'s create products."), + content: _t("Let's create products."), position: "bottom" }, { trigger: '.o-kanban-button-new', extra_trigger: '.o_kanban_view', - content: _t('Let\'s create your first product.'), + content: _t("Let's create your first product."), position: 'bottom', }, { trigger: 'input[name=name]', extra_trigger: '.o_form_sheet', - content: _t('Let\'s enter the name.'), + content: _t("Let's enter the name."), position: 'left', run: 'text the_flow.product', }, { - trigger: "select[name=type]", - content: _t('Let\'s enter the product type'), + trigger: "select[name=type]", + content: _t("Let's enter the product type"), position: 'left', run: 'text "product"', }, { @@ -70,7 +70,7 @@ tour.register('main_flow_tour', { content: _t("Focus on customer taxes field."), run: function (actions) { actions.click(); - var $e = $('.ui-menu-item:not(.o_m2o_dropdown_option) > a.ui-state-active'); + const $e = $('.ui-menu-item:not(.o_m2o_dropdown_option) > a.ui-state-active'); if ($e.length) { actions.click($e); } else { @@ -79,7 +79,7 @@ tour.register('main_flow_tour', { }, }, { trigger: '.o_form_button_save', - content: _t('Save this product and the modifications you\'ve made to it.'), + content: _t("Save this product and the modifications you've made to it."), position: 'bottom', }, { trigger: ".oe_button_box", @@ -87,12 +87,12 @@ tour.register('main_flow_tour', { auto: true, run: function (actions) { // auto expand "More" buttons - var $more = $(".oe_button_box .o_button_more"); + const $more = $(".oe_button_box .o_button_more"); if ($more.length) { actions.click($more); } }, -},{ +}, { trigger: ".oe_button_box .oe_stat_button:has(div[name=bom_count])", extra_trigger: '.o_form_readonly', content: _t('See Bill of material'), @@ -133,11 +133,11 @@ tour.register('main_flow_tour', { content: _t('Go to purchase tab'), position: 'top', }, { - trigger: ".o_field_widget[name=seller_ids] .o_field_x2many_list_row_add > a", - content: _t('Let\'s enter the cost price'), + trigger: ".o_field_widget[name=seller_ids] .o_field_x2many_list_row_add > a", + content: _t("Let's enter the cost price"), position: 'right', }, { - trigger: ".o_field_widget[name=name] input", + trigger: ".o_field_widget[name=name] input", extra_trigger: ".modal-dialog", content: _t('Select a seller'), position: 'top', @@ -146,7 +146,7 @@ tour.register('main_flow_tour', { trigger: ".ui-menu-item > a:contains('the_flow.vendor')", in_modal: false, }, { - trigger: ".o_field_widget[name=price]", + trigger: ".o_field_widget[name=price]", content: _t('Set the cost price'), position: 'right', run: "text 1", @@ -154,27 +154,23 @@ tour.register('main_flow_tour', { trigger: 'label:contains("Vendor Taxes")', extra_trigger: ".o_field_widget[name=name] > .o_external_button", // Wait name_create // click somewhere else to exit cell focus -}, -{ +}, { trigger: ".modal-footer .btn-primary", content: _t('Save'), position: 'bottom', -}, - { +}, { // Add second component trigger: ".o_field_x2many_list_row_add > a", extra_trigger: "body:not(:has(table.o_list_table div.o_field_widget[name='product_id'] input))", content: _t("Click here to add some lines."), position: "bottom", -}, -{ +}, { trigger: ".o_selected_row .o_required_modifier[name=product_id] input", extra_trigger: '.o_field_widget[name=bom_line_ids] .o_data_row:nth(1).o_selected_row', content: _t("Select a product, or create a new one on the fly."), position: "right", run: "text the_flow.component2", -}, -{ +}, { trigger: ".ui-menu-item > a:contains('the_flow.component2')", auto: true, }, { @@ -187,11 +183,11 @@ tour.register('main_flow_tour', { content: _t('Go to purchase tab'), position: 'top', }, { - trigger: ".o_field_widget[name=seller_ids] .o_field_x2many_list_row_add > a", - content: _t('Let\'s enter the cost price'), + trigger: ".o_field_widget[name=seller_ids] .o_field_x2many_list_row_add > a", + content: _t("Let's enter the cost price"), position: 'right', }, { - trigger: ".o_field_widget[name=name] input", + trigger: ".o_field_widget[name=name] input", extra_trigger: ".modal-dialog", content: _t('Select a seller'), position: 'top', @@ -201,14 +197,14 @@ tour.register('main_flow_tour', { auto: true, in_modal: false, }, { - trigger: ".o_field_widget[name=price]", + trigger: ".o_field_widget[name=price]", content: _t('Set the cost price'), position: 'right', run: "text 1", }, { trigger: 'label:contains("Vendor Taxes")', // click somewhere else to exit cell focus -},{ +}, { trigger: ".modal-footer .btn-primary", extra_trigger: ".o_field_widget[name=seller_ids] .o_data_row td:nth-child(2):contains('the_flow.vendor')", content: _t('Save'), @@ -226,12 +222,12 @@ tour.register('main_flow_tour', { // Add service product trigger: '.o-kanban-button-new', extra_trigger: '.o_kanban_view', - content: _t('Let\'s create your second product.'), + content: _t("Let's create your second product."), position: 'bottom', }, { trigger: 'input[name=name]', extra_trigger: '.o_form_sheet', - content: _t('Let\'s enter the name.'), + content: _t("Let's enter the name."), position: 'left', run: 'text the_flow.service', }, { @@ -244,7 +240,7 @@ tour.register('main_flow_tour', { content: _t("Focus on customer taxes field."), run: function (actions) { actions.click(); - var $e = $('.ui-menu-item:not(.o_m2o_dropdown_option) > a.ui-state-active'); + const $e = $('.ui-menu-item:not(.o_m2o_dropdown_option) > a.ui-state-active'); if ($e.length) { actions.click($e); } else { @@ -274,7 +270,7 @@ tour.register('main_flow_tour', { }, { trigger: '.o_form_button_save', extra_trigger: '.o_field_widget[name=project_id] > .o_external_button', // Wait name_create - content: _t('Save this product and the modifications you\'ve made to it.'), + content: _t("Save this product and the modifications you've made to it."), position: 'bottom', }, { // Create an opportunity @@ -345,13 +341,13 @@ tour.register('main_flow_tour', { run: function (actions) { actions.text("the_flow.product", this.$anchor); // fake keydown to trigger search - var keyDownEvent = jQuery.Event("keydown"); + const keyDownEvent = $.Event("keydown"); keyDownEvent.which = 42; this.$anchor.trigger(keyDownEvent); - var $descriptionElement = $('.o_form_editable textarea[name="name"]'); + const $descriptionElement = $('.o_form_editable textarea[name="name"]'); // when description changes, we know the product has been loaded - $descriptionElement.change(function () { - if ($(this).val().indexOf('the_flow.product') !== -1){ + $descriptionElement.on('change', function () { + if ($(this).val().indexOf('the_flow.product') !== -1) { $(this).addClass('product_loading_success'); } }); @@ -378,13 +374,13 @@ tour.register('main_flow_tour', { run: function (actions) { actions.text("the_flow.service", this.$anchor); // fake keydown to trigger search - var keyDownEvent = jQuery.Event("keydown"); + const keyDownEvent = $.Event("keydown"); keyDownEvent.which = 42; this.$anchor.trigger(keyDownEvent); - var $descriptionElement = $('.o_form_editable textarea[name="name"]'); + const $descriptionElement = $('.o_form_editable textarea[name="name"]'); // when description changes, we know the product has been loaded - $descriptionElement.change(function () { - if ($(this).val().indexOf('the_flow.service') !== -1){ + $descriptionElement.on('change', function () { + if ($(this).val().indexOf('the_flow.service') !== -1) { $(this).addClass('product_service_loading_success'); } }); @@ -407,11 +403,11 @@ tour.register('main_flow_tour', { content: _t("Enter an email address"), position: "right", run: "text test@the_flow.com", -},{ +}, { trigger: ".modal-footer .btn-primary", content: _t("Save your changes"), position: "bottom", -}, { +}, { trigger: ".modal-footer .btn-primary span:contains('Send')", content: _t("Try to send it to email"), position: "bottom", @@ -479,7 +475,7 @@ tour.register('main_flow_tour', { trigger: ".o_menu_sections a:contains('Operations')", content: _t("Go to Run Schedulers"), position: "bottom" -},{ +}, { trigger: ".o_menu_sections a[data-menu-xmlid='stock.menu_procurement_compute']", content: _t("Click on schedulers"), position: "bottom" @@ -529,7 +525,7 @@ tour.register('main_flow_tour', { trigger: ".o_back_button a, .breadcrumb-item:not('.active'):last", content: _t('go back to the purchase order'), position: 'bottom', - }, { +}, { trigger: ".o_statusbar_buttons > button:enabled:contains('Create Bill')", content: _t('go to Vendor Bills'), position: 'bottom', @@ -581,7 +577,7 @@ tour.register('main_flow_tour', { content: _t("Produce"), position: "bottom", }, { - trigger: ".modal-footer .btn-primary:nth-child(3)", + trigger: ".modal-footer .btn-primary:nth-child(3)", content: _t('Record Production'), position: 'bottom', }, { @@ -718,18 +714,18 @@ tour.register('main_flow_tour', { }, { edition: "enterprise", trigger: 'input[name=name]', - content: _t('Let\'s enter the reference.'), + content: _t("Let's enter the reference."), position: 'left', run: 'text the_flow.statement', }, { edition: "enterprise", - trigger: ".o_field_widget[name=balance_end_real] input", - content: _t('Let\'s calculate the ending balance.'), + trigger: ".o_field_widget[name=balance_end_real] input", + content: _t("Let's calculate the ending balance."), position: 'right', run: 'text 9010.85', // + 12.65 }, { edition: "enterprise", - trigger: ".o_field_widget[name=line_ids] .o_field_x2many_list_row_add > a", + trigger: ".o_field_widget[name=line_ids] .o_field_x2many_list_row_add > a", content: _t("Click here to add some lines."), position: "bottom", }, { @@ -752,7 +748,7 @@ tour.register('main_flow_tour', { }, { edition: "enterprise", trigger: ".o_selected_row .o_field_widget[name=name]", - content: _t('Let\'s enter a name.'), + content: _t("Let's enter a name."), position: "bottom", run: "text the_flow.statement.line", }, { From 6f7349c91aa5a9f81147621f46adbd5c51197d28 Mon Sep 17 00:00:00 2001 From: Romeo Fragomeli Date: Wed, 12 Feb 2020 12:00:33 +0000 Subject: [PATCH 05/10] [REF] test_main_flows: use destructuring assignment --- odoo/addons/test_main_flows/static/tests/tours/main_flow.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/odoo/addons/test_main_flows/static/tests/tours/main_flow.js b/odoo/addons/test_main_flows/static/tests/tours/main_flow.js index 892f22d37f2..dbe510467ac 100644 --- a/odoo/addons/test_main_flows/static/tests/tours/main_flow.js +++ b/odoo/addons/test_main_flows/static/tests/tours/main_flow.js @@ -1,11 +1,9 @@ odoo.define('test_main_flows.tour', function (require) { "use strict"; -const core = require('web.core'); +const {_t} = require('web.core'); const tour = require('web_tour.tour'); -const _t = core._t; - tour.register('main_flow_tour', { test: true, url: "/web", From 8ba6a881c5eb5f9110e57080ffa00be5b4506c73 Mon Sep 17 00:00:00 2001 From: Romeo Fragomeli Date: Fri, 7 Feb 2020 10:06:24 +0000 Subject: [PATCH 06/10] [REF] test_main_flows: use XMLID's as app menu selector Note that tours shouldn't be language dependent. By using XMLID's we are able to find a menu even if the tour is running in another language. The tip has been moved slightly but it's not a big deal, others selectors menu already used XMLID's. --- .../test_main_flows/static/tests/tours/main_flow.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/odoo/addons/test_main_flows/static/tests/tours/main_flow.js b/odoo/addons/test_main_flows/static/tests/tours/main_flow.js index dbe510467ac..6930eee21f9 100644 --- a/odoo/addons/test_main_flows/static/tests/tours/main_flow.js +++ b/odoo/addons/test_main_flows/static/tests/tours/main_flow.js @@ -425,12 +425,12 @@ tour.register('main_flow_tour', { content: _t('Go back to the home menu'), position: 'bottom', }, tour.STEPS.SHOW_APPS_MENU_ITEM, { - trigger: '.o_app:contains("Inventory")', + trigger: '.o_app[data-menu-xmlid="stock.menu_stock_root"]', content: _t('Go to Inventory'), position: 'right', edition: 'community' }, { - trigger: '.o_app > div:contains("Inventory")', + trigger: '.o_app[data-menu-xmlid="stock.menu_stock_root"]', content: _t('Go to Inventory'), position: 'bottom', edition: 'enterprise' @@ -489,12 +489,12 @@ tour.register('main_flow_tour', { content: _t('Go back to the home menu'), position: 'bottom', }, tour.STEPS.SHOW_APPS_MENU_ITEM, { - trigger: '.o_app:contains("Purchase")', + trigger: '.o_app[data-menu-xmlid="purchase.menu_purchase_root"]', content: _t('Go to Purchase'), position: 'right', edition: 'community' }, { - trigger: '.o_app > div:contains("Purchase")', + trigger: '.o_app[data-menu-xmlid="purchase.menu_purchase_root"]', content: _t('Go to Purchase'), position: 'bottom', edition: 'enterprise' @@ -545,12 +545,12 @@ tour.register('main_flow_tour', { content: _t('Go back to the home menu'), position: 'bottom', }, tour.STEPS.SHOW_APPS_MENU_ITEM, { - trigger: '.o_app:contains("Manufacturing")', + trigger: '.o_app[data-menu-xmlid="mrp.menu_mrp_root"]', content: _t('Go to Manufacturing'), position: 'right', edition: 'community' }, { - trigger: '.o_app > div:contains("Manufacturing")', + trigger: '.o_app[data-menu-xmlid="mrp.menu_mrp_root"]', content: _t('Go to Manufacturing'), position: 'bottom', edition: 'enterprise' From 298f2f4aae75ec4917be2ff90e0560fbab0a640c Mon Sep 17 00:00:00 2001 From: Romeo Fragomeli Date: Fri, 7 Feb 2020 10:43:38 +0000 Subject: [PATCH 07/10] [IMP] web_tour: add 'mobile' flag for steps in tours We are not able to precise in which case a step should be executed. We already have 'edition' and now we introduce 'mobile'. undefined: Desktop + Mobile* true: Mobile* only false: Desktop only *: Responsive (<= SM), see isMobile --- addons/web_tour/static/src/js/tour_manager.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/addons/web_tour/static/src/js/tour_manager.js b/addons/web_tour/static/src/js/tour_manager.js index 969f2dd66a0..ba4f096d8ee 100644 --- a/addons/web_tour/static/src/js/tour_manager.js +++ b/addons/web_tour/static/src/js/tour_manager.js @@ -2,6 +2,7 @@ odoo.define('web_tour.TourManager', function(require) { "use strict"; var core = require('web.core'); +var config = require('web.config'); var local_storage = require('web.local_storage'); var mixins = require('web.mixins'); var utils = require('web_tour.utils'); @@ -116,7 +117,8 @@ return core.Class.extend(mixins.EventDispatcherMixin, ServicesMixin, { return tour.wait_for.then((function () { tour.current_step = parseInt(local_storage.getItem(get_step_key(name))) || 0; tour.steps = _.filter(tour.steps, (function (step) { - return !step.edition || step.edition === this.edition; + return (!step.edition || step.edition === this.edition) && + (step.mobile === undefined || step.mobile === config.device.isMobile); }).bind(this)); if (tour_is_consumed || tour.current_step >= tour.steps.length) { From edf4f5c781fa75ad1506912c4494006caddcaa4e Mon Sep 17 00:00:00 2001 From: Romeo Fragomeli Date: Fri, 7 Feb 2020 11:47:52 +0000 Subject: [PATCH 08/10] [IMP] test_main_flows: execute main flow tour on mobile In this commit, we are still running the main flow tour on desktop but we also run it a second time on mobile. It's only available in enterprise, otherwise the test will be skipped. '375x667' correspond to the Bootstrap XS size. This value is already used for the mobile tests suite. Task ID: 2149402 --- .../addons/test_main_flows/tests/test_flow.py | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/odoo/addons/test_main_flows/tests/test_flow.py b/odoo/addons/test_main_flows/tests/test_flow.py index 2f359d4cccc..d450c335253 100644 --- a/odoo/addons/test_main_flows/tests/test_flow.py +++ b/odoo/addons/test_main_flows/tests/test_flow.py @@ -1,12 +1,12 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. +import odoo import odoo.tests +import unittest +class BaseTestUi(odoo.tests.HttpCase): -@odoo.tests.tagged('post_install', '-at_install') -class TestUi(odoo.tests.HttpCase): - - def test_01_main_flow_tour(self): + def main_flow_tour(self): # Define minimal accounting data to run without CoA a_expense = self.env['account.account'].create({ 'code': 'X2120', @@ -68,3 +68,21 @@ class TestUi(odoo.tests.HttpCase): }) self.start_tour("/web", 'main_flow_tour', login="admin", timeout=180) + +@odoo.tests.tagged('post_install', '-at_install') +class TestUi(BaseTestUi): + + def test_01_main_flow_tour(self): + self.main_flow_tour() + +@odoo.tests.tagged('post_install', '-at_install') +class TestUiMobile(BaseTestUi): + + browser_size = '375x667' + + def test_01_main_flow_tour_mobile(self): + + if odoo.release.version_info[-1] == 'e': + self.main_flow_tour() + else: + raise unittest.SkipTest("Mobile testing not needed in community") From ab2e3fdb7d801f8846b84733a47127e818a49d5b Mon Sep 17 00:00:00 2001 From: Romeo Fragomeli Date: Thu, 5 Mar 2020 13:44:24 +0000 Subject: [PATCH 09/10] [REF] *: add tour steps utils This commit moves some step utils in a dedicated file and add new ones. These steps will be very useful for the Main Flow Tour to avoid duplicated code. To do this, we also had to transform it into functions to allow utils to call each other. Existing one are converted for standardization purpose. Note that 'WEBSITE_NEW_PAGE' wasn't considered as an util. This is a very simple step only used twice. --- .../account_dashboard_setup_bar_tests.js | 2 +- .../static/tests/tours/tax_group_tests.js | 2 +- addons/crm/static/src/js/tours/crm.js | 6 +- .../tests/tours/event_configurator_ui.js | 2 +- .../static/src/js/tours/hr_expense.js | 2 +- .../static/src/js/tours/hr_recruitment.js | 2 +- .../static/src/js/tours/point_of_sale.js | 2 +- addons/project/static/src/js/tours/project.js | 2 +- .../tours/purchase_product_matrix_tour.js | 2 +- addons/sale/static/src/js/tours/sale.js | 2 +- .../tours/product_configurator_advanced_ui.js | 2 +- .../tours/product_configurator_edition_ui.js | 2 +- ...oduct_configurator_optional_products_ui.js | 2 +- .../product_configurator_pricelist_ui.js | 2 +- ...configurator_single_custom_attribute_ui.js | 2 +- .../tests/tours/product_configurator_ui.js | 2 +- .../tests/tours/sale_product_matrix_tour.js | 2 +- addons/web_tour/static/src/js/tour_manager.js | 27 +-- .../web_tour/static/src/js/tour_step_utils.js | 160 ++++++++++++++++++ addons/web_tour/views/tour_templates.xml | 1 + .../static/tests/tours/dashboard_tour.js | 2 +- .../static/src/js/tours/website_blog.js | 6 +- .../static/src/js/tours/website_event.js | 6 +- .../static/tests/tours/main_flow.js | 12 +- 24 files changed, 199 insertions(+), 53 deletions(-) create mode 100644 addons/web_tour/static/src/js/tour_step_utils.js diff --git a/addons/account/static/tests/tours/account_dashboard_setup_bar_tests.js b/addons/account/static/tests/tours/account_dashboard_setup_bar_tests.js index faec6e7b949..a212e1761ef 100644 --- a/addons/account/static/tests/tours/account_dashboard_setup_bar_tests.js +++ b/addons/account/static/tests/tours/account_dashboard_setup_bar_tests.js @@ -9,7 +9,7 @@ odoo.define('account.dashboard.setup.tour', function (require) { tour.register('account_render_report', { test: true, url: '/web', - }, [tour.STEPS.SHOW_APPS_MENU_ITEM, + }, [tour.stepUtils.showAppsMenuItem(), { id: 'account_menu_click', trigger: '.o_app[data-menu-xmlid="account.menu_finance"]', diff --git a/addons/account/static/tests/tours/tax_group_tests.js b/addons/account/static/tests/tours/tax_group_tests.js index 7d3ffd9ce85..08cb218f741 100644 --- a/addons/account/static/tests/tours/tax_group_tests.js +++ b/addons/account/static/tests/tours/tax_group_tests.js @@ -8,7 +8,7 @@ odoo.define('account.tax.group.tour.tests', function (require) { tour.register('account_tax_group', { test: true, url: "/web", - }, [tour.STEPS.SHOW_APPS_MENU_ITEM, + }, [tour.stepUtils.showAppsMenuItem(), { content: "Go to Invoicing", trigger: '.o_app[data-menu-xmlid="account.menu_finance"]', diff --git a/addons/crm/static/src/js/tours/crm.js b/addons/crm/static/src/js/tours/crm.js index 57f7b1d0df6..f0a5813de14 100644 --- a/addons/crm/static/src/js/tours/crm.js +++ b/addons/crm/static/src/js/tours/crm.js @@ -8,7 +8,7 @@ var _t = core._t; tour.register('crm_tour', { url: "/web", -}, [tour.STEPS.SHOW_APPS_MENU_ITEM, { +}, [tour.stepUtils.showAppsMenuItem(), { trigger: '.o_app[data-menu-xmlid="crm.crm_menu_root"]', content: _t("Ready to boost your sales? Your Pipeline can be found here, under the CRM app."), position: 'right', @@ -71,7 +71,7 @@ tour.register('crm_tour', { extra_trigger: '.o_lead_opportunity_form', content: _t("Use the breadcrumbs to go back to your sales pipeline."), position: "bottom" -}, tour.STEPS.TOGGLE_HOME_MENU, tour.STEPS.SHOW_APPS_MENU_ITEM, { +}, tour.stepUtils.toggleHomeMenu(), tour.stepUtils.showAppsMenuItem(), { trigger: '.o_app[data-menu-xmlid="base.menu_administration"]', content: _t("Configuration options are available in the Settings app."), position: "bottom", @@ -85,7 +85,7 @@ tour.register('crm_tour', { trigger: "div#invite_users .o_user_emails", content: _t("Invite coworkers via email.
Enter one email per line."), position: "right" -}, tour.STEPS.TOGGLE_HOME_MENU, tour.STEPS.SHOW_APPS_MENU_ITEM, { +}, tour.stepUtils.toggleHomeMenu(), tour.stepUtils.showAppsMenuItem(), { trigger: '.o_app[data-menu-xmlid="crm.crm_menu_root"]', content: _t("Good job! You completed the tour of the CRM app."), position: 'right', diff --git a/addons/event_sale/static/tests/tours/event_configurator_ui.js b/addons/event_sale/static/tests/tours/event_configurator_ui.js index b34187d796f..1170a142a09 100644 --- a/addons/event_sale/static/tests/tours/event_configurator_ui.js +++ b/addons/event_sale/static/tests/tours/event_configurator_ui.js @@ -6,7 +6,7 @@ var tour = require('web_tour.tour'); tour.register('event_configurator_tour', { url: "/web", test: true, -}, [tour.STEPS.SHOW_APPS_MENU_ITEM, { +}, [tour.stepUtils.showAppsMenuItem(), { trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]', edition: 'community' }, { diff --git a/addons/hr_expense/static/src/js/tours/hr_expense.js b/addons/hr_expense/static/src/js/tours/hr_expense.js index eddc613b1ce..0422d6870db 100644 --- a/addons/hr_expense/static/src/js/tours/hr_expense.js +++ b/addons/hr_expense/static/src/js/tours/hr_expense.js @@ -8,7 +8,7 @@ var _t = core._t; tour.register('hr_expense_tour' , { url: "/web" -}, [tour.STEPS.SHOW_APPS_MENU_ITEM, { +}, [tour.stepUtils.showAppsMenuItem(), { trigger: '.o_app[data-menu-xmlid="hr_expense.menu_hr_expense_root"]', content: _t("Want to manage your expenses? It starts here."), position: 'right', diff --git a/addons/hr_recruitment/static/src/js/tours/hr_recruitment.js b/addons/hr_recruitment/static/src/js/tours/hr_recruitment.js index 0f814426e8b..1939b5baf6c 100644 --- a/addons/hr_recruitment/static/src/js/tours/hr_recruitment.js +++ b/addons/hr_recruitment/static/src/js/tours/hr_recruitment.js @@ -6,7 +6,7 @@ var tour = require('web_tour.tour'); var _t = core._t; -tour.register('hr_recruitment_tour', [tour.STEPS.SHOW_APPS_MENU_ITEM, { +tour.register('hr_recruitment_tour', [tour.stepUtils.showAppsMenuItem(), { trigger: '.o_app[data-menu-xmlid="hr_recruitment.menu_hr_recruitment_root"]', content: _t('Want to start recruiting like a pro? Start here.'), position: 'right', diff --git a/addons/point_of_sale/static/src/js/tours/point_of_sale.js b/addons/point_of_sale/static/src/js/tours/point_of_sale.js index 5fc69341602..161b7516082 100644 --- a/addons/point_of_sale/static/src/js/tours/point_of_sale.js +++ b/addons/point_of_sale/static/src/js/tours/point_of_sale.js @@ -9,7 +9,7 @@ var _t = core._t; tour.register('point_of_sale_tour', { url: "/web", rainbowMan: false, -}, [tour.STEPS.SHOW_APPS_MENU_ITEM, { +}, [tour.stepUtils.showAppsMenuItem(), { trigger: '.o_app[data-menu-xmlid="point_of_sale.menu_point_root"]', content: _t("Ready to launch your point of sale?"), width: 215, diff --git a/addons/project/static/src/js/tours/project.js b/addons/project/static/src/js/tours/project.js index 951390455b9..407fa9cdb4b 100644 --- a/addons/project/static/src/js/tours/project.js +++ b/addons/project/static/src/js/tours/project.js @@ -8,7 +8,7 @@ var _t = core._t; tour.register('project_tour', { url: "/web", -}, [tour.STEPS.SHOW_APPS_MENU_ITEM, { +}, [tour.stepUtils.showAppsMenuItem(), { trigger: '.o_app[data-menu-xmlid="project.menu_main_pm"]', content: _t('Want a better way to manage your projects? It starts here.'), position: 'right', diff --git a/addons/purchase_product_matrix/static/tests/tours/purchase_product_matrix_tour.js b/addons/purchase_product_matrix/static/tests/tours/purchase_product_matrix_tour.js index 9148f888b53..7ffd79137b7 100644 --- a/addons/purchase_product_matrix/static/tests/tours/purchase_product_matrix_tour.js +++ b/addons/purchase_product_matrix/static/tests/tours/purchase_product_matrix_tour.js @@ -6,7 +6,7 @@ var tour = require('web_tour.tour'); tour.register('purchase_matrix_tour', { url: "/web", test: true, -}, [tour.STEPS.SHOW_APPS_MENU_ITEM, { +}, [tour.stepUtils.showAppsMenuItem(), { trigger: '.o_app[data-menu-xmlid="purchase.menu_purchase_root"]', }, { trigger: ".o_list_button_add", diff --git a/addons/sale/static/src/js/tours/sale.js b/addons/sale/static/src/js/tours/sale.js index c1177ccc4d0..4853c905b8b 100644 --- a/addons/sale/static/src/js/tours/sale.js +++ b/addons/sale/static/src/js/tours/sale.js @@ -8,7 +8,7 @@ var _t = core._t; tour.register('sale_tour', { url: "/web", -}, [tour.STEPS.SHOW_APPS_MENU_ITEM, { +}, [tour.stepUtils.showAppsMenuItem(), { trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]', content: _t('Open Sales app to send your first quotation in a few clicks.'), position: 'right', diff --git a/addons/sale_product_configurator/static/tests/tours/product_configurator_advanced_ui.js b/addons/sale_product_configurator/static/tests/tours/product_configurator_advanced_ui.js index 0f351b8be5e..16251f5612f 100644 --- a/addons/sale_product_configurator/static/tests/tours/product_configurator_advanced_ui.js +++ b/addons/sale_product_configurator/static/tests/tours/product_configurator_advanced_ui.js @@ -8,7 +8,7 @@ var optionVariantImage; tour.register('sale_product_configurator_advanced_tour', { url: "/web", test: true, -}, [tour.STEPS.SHOW_APPS_MENU_ITEM, { +}, [tour.stepUtils.showAppsMenuItem(), { trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]', // Note: The module sale_management is mandatory edition: 'community' }, { diff --git a/addons/sale_product_configurator/static/tests/tours/product_configurator_edition_ui.js b/addons/sale_product_configurator/static/tests/tours/product_configurator_edition_ui.js index 907cc86382a..14cf07663ee 100644 --- a/addons/sale_product_configurator/static/tests/tours/product_configurator_edition_ui.js +++ b/addons/sale_product_configurator/static/tests/tours/product_configurator_edition_ui.js @@ -6,7 +6,7 @@ var tour = require('web_tour.tour'); tour.register('sale_product_configurator_edition_tour', { url: "/web", test: true, -}, [tour.STEPS.SHOW_APPS_MENU_ITEM, { +}, [tour.stepUtils.showAppsMenuItem(), { trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]', edition: 'community' }, { diff --git a/addons/sale_product_configurator/static/tests/tours/product_configurator_optional_products_ui.js b/addons/sale_product_configurator/static/tests/tours/product_configurator_optional_products_ui.js index c68fc9debec..c566ce5b1da 100644 --- a/addons/sale_product_configurator/static/tests/tours/product_configurator_optional_products_ui.js +++ b/addons/sale_product_configurator/static/tests/tours/product_configurator_optional_products_ui.js @@ -6,7 +6,7 @@ var tour = require('web_tour.tour'); tour.register('sale_product_configurator_optional_products_tour', { url: "/web", test: true, -}, [tour.STEPS.SHOW_APPS_MENU_ITEM, { +}, [tour.stepUtils.showAppsMenuItem(), { trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]', edition: 'community' }, { diff --git a/addons/sale_product_configurator/static/tests/tours/product_configurator_pricelist_ui.js b/addons/sale_product_configurator/static/tests/tours/product_configurator_pricelist_ui.js index ff761c0b540..60fc965b39b 100644 --- a/addons/sale_product_configurator/static/tests/tours/product_configurator_pricelist_ui.js +++ b/addons/sale_product_configurator/static/tests/tours/product_configurator_pricelist_ui.js @@ -8,7 +8,7 @@ tour.register('sale_product_configurator_pricelist_tour', { test: true, }, [ -tour.STEPS.SHOW_APPS_MENU_ITEM, +tour.stepUtils.showAppsMenuItem(), { content: "navigate to the sale app", trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]', diff --git a/addons/sale_product_configurator/static/tests/tours/product_configurator_single_custom_attribute_ui.js b/addons/sale_product_configurator/static/tests/tours/product_configurator_single_custom_attribute_ui.js index 4c1b68da988..8b1db607579 100644 --- a/addons/sale_product_configurator/static/tests/tours/product_configurator_single_custom_attribute_ui.js +++ b/addons/sale_product_configurator/static/tests/tours/product_configurator_single_custom_attribute_ui.js @@ -6,7 +6,7 @@ var tour = require('web_tour.tour'); tour.register('sale_product_configurator_single_custom_attribute_tour', { url: "/web", test: true, -}, [tour.STEPS.SHOW_APPS_MENU_ITEM, { +}, [tour.stepUtils.showAppsMenuItem(), { trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]', edition: 'community' }, { diff --git a/addons/sale_product_configurator/static/tests/tours/product_configurator_ui.js b/addons/sale_product_configurator/static/tests/tours/product_configurator_ui.js index d1857944ae7..fad1fbd3309 100644 --- a/addons/sale_product_configurator/static/tests/tours/product_configurator_ui.js +++ b/addons/sale_product_configurator/static/tests/tours/product_configurator_ui.js @@ -9,7 +9,7 @@ var tour = require('web_tour.tour'); tour.register('sale_product_configurator_tour', { url: "/web", test: true, -}, [tour.STEPS.SHOW_APPS_MENU_ITEM, { +}, [tour.stepUtils.showAppsMenuItem(), { trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]', edition: 'community' }, { diff --git a/addons/sale_product_matrix/static/tests/tours/sale_product_matrix_tour.js b/addons/sale_product_matrix/static/tests/tours/sale_product_matrix_tour.js index deb3c8bb2ba..f5b3ff51c68 100644 --- a/addons/sale_product_matrix/static/tests/tours/sale_product_matrix_tour.js +++ b/addons/sale_product_matrix/static/tests/tours/sale_product_matrix_tour.js @@ -6,7 +6,7 @@ var tour = require('web_tour.tour'); tour.register('sale_matrix_tour', { url: "/web", test: true, -}, [tour.STEPS.SHOW_APPS_MENU_ITEM, { +}, [tour.stepUtils.showAppsMenuItem(), { trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]', }, { trigger: ".o_list_button_add", diff --git a/addons/web_tour/static/src/js/tour_manager.js b/addons/web_tour/static/src/js/tour_manager.js index ba4f096d8ee..647d0fe2563 100644 --- a/addons/web_tour/static/src/js/tour_manager.js +++ b/addons/web_tour/static/src/js/tour_manager.js @@ -6,6 +6,7 @@ var config = require('web.config'); var local_storage = require('web.local_storage'); var mixins = require('web.mixins'); var utils = require('web_tour.utils'); +var TourStepUtils = require('web_tour.TourStepUtils'); var RainbowMan = require('web.RainbowMan'); var RunningTourActionHelper = require('web_tour.RunningTourActionHelper'); var ServicesMixin = require('web.ServicesMixin'); @@ -427,30 +428,6 @@ return core.Class.extend(mixins.EventDispatcherMixin, ServicesMixin, { } } }, - - /** - * Tour predefined steps - */ - STEPS: { - SHOW_APPS_MENU_ITEM: { - edition: 'community', - trigger: '.o_menu_apps a', - auto: true, - position: "bottom", - }, - - TOGGLE_HOME_MENU: { - edition: "enterprise", - trigger: ".o_main_navbar .o_menu_toggle", - content: _t('Click on the Home icon to navigate across apps.'), - position: "bottom", - }, - - WEBSITE_NEW_PAGE: { - trigger: "#new-content-menu > a", - auto: true, - position: "bottom", - }, - }, + stepUtils: new TourStepUtils(this) }); }); diff --git a/addons/web_tour/static/src/js/tour_step_utils.js b/addons/web_tour/static/src/js/tour_step_utils.js new file mode 100644 index 00000000000..cd92f6fe019 --- /dev/null +++ b/addons/web_tour/static/src/js/tour_step_utils.js @@ -0,0 +1,160 @@ +odoo.define('web_tour.TourStepUtils', function (require) { +'use strict'; + +const core = require('web.core'); +const _t = core._t; + +return core.Class.extend({ + _getHelpMessage: (functionName, ...args) => `Generated by function tour utils ${functionName}(${args.join(', ')})`, + + addDebugHelp: helpMessage => step => { + if (typeof step.debugHelp === 'string') { + step.debugHelp = step.debugHelp + '\n' + helpMessage; + } else { + step.debugHelp = helpMessage; + } + return step; + }, + + editionEnterpriseModifier(step) { + step.edition = 'enterprise'; + return step; + }, + + mobileModifier(step) { + step.mobile = true; + return step; + }, + + showAppsMenuItem() { + return { + edition: 'community', + trigger: '.o_menu_apps a', + auto: true, + position: 'bottom', + }; + }, + + toggleHomeMenu() { + return { + edition: 'enterprise', + trigger: '.o_main_navbar .o_menu_toggle', + content: _t('Click on the Home icon to navigate across apps.'), + position: 'bottom', + }; + }, + + autoExpandMoreButtons(extra_trigger) { + return { + trigger: '.oe_button_box', + extra_trigger: extra_trigger, + auto: true, + run: actions => { + const $more = $('.oe_button_box .o_button_more'); + if ($more.length) { + actions.click($more); + } + }, + }; + }, + + goBackBreadcrumbsMobile(description, ...extraTrigger) { + return extraTrigger.map(element => ({ + mobile: true, + trigger: '.breadcrumb-item:not(.d-none):first', + extra_trigger: element, + content: description, + position: 'bottom', + debugHelp: this._getHelpMessage('goBackBreadcrumbsMobile', description, ...extraTrigger), + })); + }, + + goToAppSteps(dataMenuXmlid, description) { + return [ + this.showAppsMenuItem(), + { + trigger: `.o_app[data-menu-xmlid="${dataMenuXmlid}"]`, + content: description, + position: 'right', + edition: 'community', + }, + { + trigger: `.o_app[data-menu-xmlid="${dataMenuXmlid}"]`, + content: description, + position: 'bottom', + edition: 'enterprise', + }, + ].map(this.addDebugHelp(this._getHelpMessage('goToApp', dataMenuXmlid, description))); + }, + + openBuggerMenu(extraTrigger) { + return { + mobile: true, + trigger: '.o_mobile_menu_toggle', + extra_trigger: extraTrigger, + content: _t('Open bugger menu.'), + position: 'bottom', + debugHelp: this._getHelpMessage('openBuggerMenu', extraTrigger), + }; + }, + + statusbarButtonsSteps(innerTextButton, description, extraTrigger) { + return [ + { + mobile: true, + trigger: '.o_statusbar_buttons .btn.dropdown-toggle:contains(Action)', + extra_trigger: extraTrigger, + content: _t('Open Action Dropdown Menu.'), + position: 'bottom', + }, { + trigger: `.o_statusbar_buttons button:enabled:contains('${innerTextButton}')`, + content: description, + position: 'bottom', + }, + ].map(this.addDebugHelp(this._getHelpMessage('statusbarButtonsSteps', innerTextButton, description, extraTrigger))); + }, + + simulateEnterKeyboardInSearchModal() { + return { + mobile: true, + trigger: '.o_searchview_input', + extra_trigger: '.modal:not(.o_inactive_modal) .dropdown-menu.o_searchview_autocomplete', + position: 'bottom', + run: action => { + const keyboardEventUp = new KeyboardEvent('keyup', { + bubbles: true, + cancelable: true, + key: 'Enter', + code: 'Enter', + which: 13, + keyCode: 13 + }); + action.tip_widget.$anchor[0].dispatchEvent(keyboardEventUp); + }, + debugHelp: this._getHelpMessage('simulateEnterKeyboardInSearchModal'), + }; + }, + + mobileKanbanSearchMany2X(modalTitle, valueSearched) { + return [ + { + mobile: true, + trigger: '.o_enable_searchview', + extra_trigger: `.modal:not(.o_inactive_modal) .modal-title:contains('${modalTitle}')`, + position: 'bottom', + }, { + mobile: true, + trigger: '.o_searchview_input', + position: 'bottom', + run: `text ${valueSearched}`, + }, + this.simulateEnterKeyboardInSearchModal(), + { + mobile: true, + trigger: `.o_kanban_record .o_kanban_record_title :contains('${valueSearched}')`, + position: 'bottom', + }, + ].map(this.addDebugHelp(this._getHelpMessage('mobileKanbanSearchMany2X', modalTitle, valueSearched))); + }, +}); +}); diff --git a/addons/web_tour/views/tour_templates.xml b/addons/web_tour/views/tour_templates.xml index 816405a39d5..09376487f73 100644 --- a/addons/web_tour/views/tour_templates.xml +++ b/addons/web_tour/views/tour_templates.xml @@ -10,6 +10,7 @@ + diff --git a/addons/website/static/tests/tours/dashboard_tour.js b/addons/website/static/tests/tours/dashboard_tour.js index b02747c6caa..de1a5f10473 100644 --- a/addons/website/static/tests/tours/dashboard_tour.js +++ b/addons/website/static/tests/tours/dashboard_tour.js @@ -6,7 +6,7 @@ var tour = require("web_tour.tour"); tour.register("backend_dashboard", { test: true, url: "/web", -}, [tour.STEPS.SHOW_APPS_MENU_ITEM, +}, [tour.stepUtils.showAppsMenuItem(), { trigger: 'a[data-menu-xmlid="website.menu_website_configuration"]', }, { diff --git a/addons/website_blog/static/src/js/tours/website_blog.js b/addons/website_blog/static/src/js/tours/website_blog.js index c9a798b2abf..db7510ceb9f 100644 --- a/addons/website_blog/static/src/js/tours/website_blog.js +++ b/addons/website_blog/static/src/js/tours/website_blog.js @@ -8,7 +8,11 @@ odoo.define("website_blog.tour", function (require) { tour.register("blog", { url: "/", - }, [tour.STEPS.WEBSITE_NEW_PAGE, { + }, [{ + trigger: '#new-content-menu > a', + auto: true, + position: 'bottom', + }, { trigger: "a[data-action=new_blog_post]", content: _t("Select this menu item to create a new blog post."), position: "bottom", diff --git a/addons/website_event/static/src/js/tours/website_event.js b/addons/website_event/static/src/js/tours/website_event.js index 8e693c9ef8e..3f704012527 100644 --- a/addons/website_event/static/src/js/tours/website_event.js +++ b/addons/website_event/static/src/js/tours/website_event.js @@ -8,7 +8,11 @@ odoo.define("website_event.tour", function (require) { tour.register("event", { url: "/", - }, [tour.STEPS.WEBSITE_NEW_PAGE, { + }, [{ + trigger: '#new-content-menu > a', + auto: true, + position: 'bottom', + }, { trigger: "a[data-action=new_event]", content: _t("Click here to create a new event."), position: "bottom", diff --git a/odoo/addons/test_main_flows/static/tests/tours/main_flow.js b/odoo/addons/test_main_flows/static/tests/tours/main_flow.js index 6930eee21f9..0152cb7c05b 100644 --- a/odoo/addons/test_main_flows/static/tests/tours/main_flow.js +++ b/odoo/addons/test_main_flows/static/tests/tours/main_flow.js @@ -7,7 +7,7 @@ const tour = require('web_tour.tour'); tour.register('main_flow_tour', { test: true, url: "/web", -}, [tour.STEPS.SHOW_APPS_MENU_ITEM, { +}, [tour.stepUtils.showAppsMenuItem(), { trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]', content: _t('Organize your sales activities with the Sales app.'), position: 'right', @@ -276,7 +276,7 @@ tour.register('main_flow_tour', { trigger: '.o_menu_toggle', content: _t('Go back to the home menu'), position: 'bottom', -}, tour.STEPS.SHOW_APPS_MENU_ITEM, { +}, tour.stepUtils.showAppsMenuItem(), { trigger: '.o_app[data-menu-xmlid="crm.crm_menu_root"]', content: _t('Organize your sales activities with the CRM app.'), position: 'right', @@ -424,7 +424,7 @@ tour.register('main_flow_tour', { trigger: '.o_menu_toggle', content: _t('Go back to the home menu'), position: 'bottom', -}, tour.STEPS.SHOW_APPS_MENU_ITEM, { +}, tour.stepUtils.showAppsMenuItem(), { trigger: '.o_app[data-menu-xmlid="stock.menu_stock_root"]', content: _t('Go to Inventory'), position: 'right', @@ -488,7 +488,7 @@ tour.register('main_flow_tour', { trigger: '.o_menu_toggle', content: _t('Go back to the home menu'), position: 'bottom', -}, tour.STEPS.SHOW_APPS_MENU_ITEM, { +}, tour.stepUtils.showAppsMenuItem(), { trigger: '.o_app[data-menu-xmlid="purchase.menu_purchase_root"]', content: _t('Go to Purchase'), position: 'right', @@ -544,7 +544,7 @@ tour.register('main_flow_tour', { trigger: '.o_menu_toggle', content: _t('Go back to the home menu'), position: 'bottom', -}, tour.STEPS.SHOW_APPS_MENU_ITEM, { +}, tour.stepUtils.showAppsMenuItem(), { trigger: '.o_app[data-menu-xmlid="mrp.menu_mrp_root"]', content: _t('Go to Manufacturing'), position: 'right', @@ -587,7 +587,7 @@ tour.register('main_flow_tour', { trigger: '.o_menu_toggle', content: _t('Go back to the home menu'), position: 'bottom', -}, tour.STEPS.SHOW_APPS_MENU_ITEM, { +}, tour.stepUtils.showAppsMenuItem(), { trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]', content: _t('Organize your sales activities with the Sales app.'), position: 'bottom', From 8ff16b212882b7e05df3cbe1bdbcf0b7c15ef0af Mon Sep 17 00:00:00 2001 From: Romeo Fragomeli Date: Fri, 7 Feb 2020 11:50:48 +0000 Subject: [PATCH 10/10] [IMP] test_main_flows: adapt The Main Flow Tour to be usable on mobile This commit adapts some existing steps or add new ones to execute the same tour on mobile. This tour has many steps and give a good idea to know if new tasks broke mobile or not. The goal is that even if the design isn't perfect, users aren't blocked and are able to perform their usual tasks. Task ID: 2149402 --- .../static/tests/tours/main_flow.js | 564 +++++++++++++----- 1 file changed, 413 insertions(+), 151 deletions(-) diff --git a/odoo/addons/test_main_flows/static/tests/tours/main_flow.js b/odoo/addons/test_main_flows/static/tests/tours/main_flow.js index 0152cb7c05b..4f079715b2e 100644 --- a/odoo/addons/test_main_flows/static/tests/tours/main_flow.js +++ b/odoo/addons/test_main_flows/static/tests/tours/main_flow.js @@ -7,17 +7,10 @@ const tour = require('web_tour.tour'); tour.register('main_flow_tour', { test: true, url: "/web", -}, [tour.stepUtils.showAppsMenuItem(), { - trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]', - content: _t('Organize your sales activities with the Sales app.'), - position: 'right', - edition: 'community' -}, { - trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]', - content: _t('Organize your sales activities with the Sales app.'), - position: 'bottom', - edition: 'enterprise' -}, { +}, [ +...tour.stepUtils.goToAppSteps('sale.sale_menu_root', _t('Organize your sales activities with the Sales app.')), +tour.stepUtils.openBuggerMenu("li.breadcrumb-item.active:contains('Quotations')"), +{ // Add Stockable product trigger: ".o_menu_sections a:contains('Products')", extra_trigger: '.o_main_navbar', @@ -29,7 +22,7 @@ tour.register('main_flow_tour', { position: "bottom" }, { trigger: '.o-kanban-button-new', - extra_trigger: '.o_kanban_view', + extra_trigger: "li.breadcrumb-item.active:contains('Products')", content: _t("Let's create your first product."), position: 'bottom', }, { @@ -64,6 +57,7 @@ tour.register('main_flow_tour', { content: _t('Go to main tab'), position: 'top', }, { + mobile: false, trigger: ".o_field_widget[name=taxes_id] input", content: _t("Focus on customer taxes field."), run: function (actions) { @@ -79,18 +73,9 @@ tour.register('main_flow_tour', { trigger: '.o_form_button_save', content: _t("Save this product and the modifications you've made to it."), position: 'bottom', -}, { - trigger: ".oe_button_box", - extra_trigger: '.o_form_readonly', - auto: true, - run: function (actions) { - // auto expand "More" buttons - const $more = $(".oe_button_box .o_button_more"); - if ($more.length) { - actions.click($more); - } - }, -}, { +}, +tour.stepUtils.autoExpandMoreButtons('.o_form_readonly'), +{ trigger: ".oe_button_box .oe_stat_button:has(div[name=bom_count])", extra_trigger: '.o_form_readonly', content: _t('See Bill of material'), @@ -101,20 +86,41 @@ tour.register('main_flow_tour', { position: "bottom", }, { // Add first component + // FIXME in mobile replace list by kanban + form trigger: ".o_field_x2many_list_row_add > a", extra_trigger: ".o_form_editable", content: _t("Click here to add some lines."), position: "bottom", }, { + mobile: false, trigger: ".o_selected_row .o_required_modifier[name=product_id] input", content: _t("Select a product, or create a new one on the fly."), position: "right", run: "text the_flow.component1", }, { + mobile: false, trigger: ".ui-menu-item > a:contains('the_flow.component1')", auto: true, +}, { + mobile: true, + trigger: ".o_selected_row .o_required_modifier[name=product_id] input", + content: _t("Click here to open kanban search mobile."), + position: "bottom", +}, { + mobile: true, + trigger: ".modal-dialog .btn:contains('Create')", + extra_trigger: ".modal-dialog", + content: _t("Click here to add new line."), + position: "left", +}, { + mobile: true, + trigger: '.modal-body .o_form_editable .o_field_widget.o_input[name="name"]', + content: _t("Select a product, or create a new one on the fly."), + position: "right", + run: "text the_flow.component1", }, { // Edit first component + mobile: false, trigger: ".o_selected_row .o_external_button", content: _t("Click here to edit your component"), position: "right", @@ -131,48 +137,127 @@ tour.register('main_flow_tour', { content: _t('Go to purchase tab'), position: 'top', }, { + mobile: false, trigger: ".o_field_widget[name=seller_ids] .o_field_x2many_list_row_add > a", content: _t("Let's enter the cost price"), position: 'right', }, { + mobile: false, trigger: ".o_field_widget[name=name] input", extra_trigger: ".modal-dialog", content: _t('Select a seller'), position: 'top', run: "text the_flow.vendor", }, { + mobile: false, trigger: ".ui-menu-item > a:contains('the_flow.vendor')", in_modal: false, +}, { + mobile: true, + trigger: ".o_field_widget[name=seller_ids] .o-kanban-button-new", + content: _t("Let's select a vendor"), + position: 'bottom', +}, { + mobile: true, + trigger: ".o_form_editable .o_field_many2one[name=name] input", + extra_trigger: ".modal:not(.o_inactive_modal) .o_form_editable div:contains('Vendor')", + content: _t("Select a vendor, or create a new one on the fly."), + position: "bottom", +}, { + mobile: true, + trigger: ".modal-footer .btn:contains('Create')", + extra_trigger: ".modal:not(.o_inactive_modal) .modal-title:contains('Vendor')", + content: _t("Select a vendor, or create a new one on the fly."), + position: "right", +}, { + mobile: true, + trigger: ".modal-dialog .o_field_widget[name=name] input", + extra_trigger: ".modal:not(.o_inactive_modal) .modal-dialog .o_field_radio.o_field_widget[name=company_type]", + content: _t('Select a seller'), + position: 'top', + run: "text the_flow.vendor", +}, { + mobile: true, + trigger: '.btn:has(:contains(Save))', + extra_trigger: ".modal:not(.o_inactive_modal) .modal-title:contains('Vendor')", + content: _t("Save this product and the modifications you've made to it."), + position: 'right', }, { trigger: ".o_field_widget[name=price]", content: _t('Set the cost price'), position: 'right', run: "text 1", }, { + mobile: true, + trigger: ".modal-footer .btn-primary:contains('Save & Close')", + extra_trigger: ".modal:not(.o_inactive_modal) .modal-title:contains('Vendor')", + content: _t('Save & Close'), + position: 'right', +}, { + mobile: true, + trigger: ".modal-footer .btn-primary:contains('Save')", + extra_trigger: ".modal:not(.o_inactive_modal) .modal-title:contains('Component')", + content: _t('Save'), + position: 'right', +}, { + mobile: true, + trigger: '.o_field_widget[name=code]', + extra_trigger: ".o_field_widget[name=product_id] > .o_external_button", // Wait name_create + // click somewhere else to exit cell focus +}, { + mobile: false, trigger: 'label:contains("Vendor Taxes")', extra_trigger: ".o_field_widget[name=name] > .o_external_button", // Wait name_create // click somewhere else to exit cell focus }, { + mobile: false, trigger: ".modal-footer .btn-primary", content: _t('Save'), position: 'bottom', }, { // Add second component + mobile: false, trigger: ".o_field_x2many_list_row_add > a", extra_trigger: "body:not(:has(table.o_list_table div.o_field_widget[name='product_id'] input))", content: _t("Click here to add some lines."), position: "bottom", }, { + mobile: true, + trigger: ".o_field_x2many_list_row_add > a", + extra_trigger: ".o_form_editable", + content: _t("Click here to add some lines."), + position: "bottom", +}, { + mobile: true, + trigger: ".o_selected_row .o_required_modifier[name=product_id] input", + content: _t("Click here to open kanban search mobile."), + position: "bottom", +}, { + mobile: true, + trigger: ".modal-dialog .btn:contains('Create')", + extra_trigger: ".modal-dialog", + content: _t("Click here to add new line."), + position: "left", +}, { + mobile: true, + trigger: ".modal-body .o_form_editable .o_field_widget.o_input[name=\"name\"]", + content: _t("Select a product, or create a new one on the fly."), + position: "right", + run: "text the_flow.component2", +}, { + mobile: false, trigger: ".o_selected_row .o_required_modifier[name=product_id] input", extra_trigger: '.o_field_widget[name=bom_line_ids] .o_data_row:nth(1).o_selected_row', content: _t("Select a product, or create a new one on the fly."), position: "right", run: "text the_flow.component2", }, { + mobile: false, trigger: ".ui-menu-item > a:contains('the_flow.component2')", auto: true, }, { // Edit second component + mobile: false, trigger: ".o_selected_row .o_external_button", content: _t("Click here to edit your component"), position: "right", @@ -181,16 +266,32 @@ tour.register('main_flow_tour', { content: _t('Go to purchase tab'), position: 'top', }, { + mobile: true, + trigger: ".o_field_widget[name=seller_ids] .o-kanban-button-new", + content: _t("Let's select a vendor"), + position: 'bottom', +}, { + mobile: true, + trigger: '.o_form_editable .o_field_many2one[name="name"] input', + extra_trigger: ".modal:not(.o_inactive_modal) .o_form_editable div:contains('Vendor')", + content: _t("Select a vendor, or create a new one on the fly."), + position: "bottom", +}, +...tour.stepUtils.mobileKanbanSearchMany2X('Vendor', 'the_flow.vendor'), +{ + mobile: false, trigger: ".o_field_widget[name=seller_ids] .o_field_x2many_list_row_add > a", content: _t("Let's enter the cost price"), position: 'right', }, { + mobile: false, trigger: ".o_field_widget[name=name] input", extra_trigger: ".modal-dialog", content: _t('Select a seller'), position: 'top', run: "text the_flow.vendor", }, { + mobile: false, trigger: ".ui-menu-item > a:contains('the_flow.vendor')", auto: true, in_modal: false, @@ -200,9 +301,28 @@ tour.register('main_flow_tour', { position: 'right', run: "text 1", }, { + mobile: true, + trigger: ".modal-footer .btn-primary:contains('Save & Close')", + extra_trigger: ".modal:not(.o_inactive_modal) .modal-title:contains('Vendor')", + content: _t('Save & Close'), + position: 'right', +}, { + mobile: true, + trigger: ".modal-footer .btn-primary:contains('Save')", + extra_trigger: ".modal:not(.o_inactive_modal) .modal-title:contains('Component')", + content: _t('Save'), + position: 'right', +}, { + mobile: true, + trigger: '.o_field_widget[name=code]', + extra_trigger: ".o_field_widget[name=product_id] > .o_external_button", // Wait name_create + // click somewhere else to exit cell focus +}, { + mobile: false, trigger: 'label:contains("Vendor Taxes")', // click somewhere else to exit cell focus }, { + mobile: false, trigger: ".modal-footer .btn-primary", extra_trigger: ".o_field_widget[name=seller_ids] .o_data_row td:nth-child(2):contains('the_flow.vendor')", content: _t('Save'), @@ -213,10 +333,18 @@ tour.register('main_flow_tour', { content: _t('Save the bom.'), position: 'bottom', }, { + mobile: false, trigger: ".breadcrumb-item:first", content: _t("Use the breadcrumbs to go back to products."), position: "bottom" -}, { +}, +...tour.stepUtils.goBackBreadcrumbsMobile( + _t("Use the breadcrumbs to go back to products."), + undefined, + ".breadcrumb-item.active:contains('Bill of Materials')", + ".breadcrumb-item.active:contains('the_flow.product')" + ), +{ // Add service product trigger: '.o-kanban-button-new', extra_trigger: '.o_kanban_view', @@ -234,6 +362,7 @@ tour.register('main_flow_tour', { position: 'left', run: 'text "service"', }, { + mobile: false, trigger: ".o_field_widget[name=taxes_id] input", content: _t("Focus on customer taxes field."), run: function (actions) { @@ -258,13 +387,39 @@ tour.register('main_flow_tour', { content: _t('Change track service'), position: 'left', }, { + mobile: false, trigger: '.o_field_widget[name=project_id] input', content: _t('Choose project'), position: 'left', run: 'text the_flow.project', }, { + mobile: true, + trigger: '.o_field_widget[name=project_id] input', + content: _t('Choose project'), + position: 'left', +}, { + mobile: true, + trigger: ".modal-dialog .btn:contains('Create')", + extra_trigger: ".modal-dialog", + content: _t("Click here to add new line."), + position: "left", +}, { + mobile: true, + trigger: 'input[name=name]', + extra_trigger: ".modal:not(.o_inactive_modal) .modal-title:contains('Project')", + content: _t('Let\'s enter the name.'), + position: 'left', + run: 'text the_flow.project', +}, { + mobile: false, trigger: ".ui-menu-item > a:contains('the_flow.project')", auto: true, +}, { + mobile: true, + trigger: ".modal-footer .btn-primary:contains('Save')", + extra_trigger: ".modal:not(.o_inactive_modal) .modal-title:contains('Project')", + content: _t('Save'), + position: 'right', }, { trigger: '.o_form_button_save', extra_trigger: '.o_field_widget[name=project_id] > .o_external_button', // Wait name_create @@ -276,17 +431,9 @@ tour.register('main_flow_tour', { trigger: '.o_menu_toggle', content: _t('Go back to the home menu'), position: 'bottom', -}, tour.stepUtils.showAppsMenuItem(), { - trigger: '.o_app[data-menu-xmlid="crm.crm_menu_root"]', - content: _t('Organize your sales activities with the CRM app.'), - position: 'right', - edition: 'community' -}, { - trigger: '.o_app[data-menu-xmlid="crm.crm_menu_root"]', - content: _t('Organize your sales activities with the CRM app.'), - position: 'bottom', - edition: 'enterprise' -}, { +}, +...tour.stepUtils.goToAppSteps('crm.crm_menu_root', _t('Organize your sales activities with the CRM app.')), +{ trigger: ".o-kanban-button-new", extra_trigger: '.o_opportunity_kanban', content: _t("Click here to create your first opportunity and add it to your pipeline."), @@ -297,42 +444,90 @@ tour.register('main_flow_tour', { position: "right", run: "text the_flow.opportunity", }, { + mobile: false, trigger: ".o_kanban_quick_create .o_field_widget[name=partner_id] input", content: _t("Write the name of your customer to create one on the fly, or select an existing one."), position: "left", run: "text the_flow.customer", }, { + mobile: true, + trigger: ".o_kanban_quick_create .o_field_widget[name=partner_id] input", + content: _t("Write the name of your customer to create one on the fly, or select an existing one."), + position: "left", +}, { + mobile: true, + trigger: ".modal-dialog .btn:contains('Create')", + extra_trigger: ".modal-dialog", + content: _t("Click here to add new line."), + position: "left", +}, { + mobile: true, + trigger: '.o_field_widget[name=name] input', + extra_trigger: ".modal:not(.o_inactive_modal) .modal-title:contains('Customer')", + content: _t('Let\'s enter the name.'), + position: 'left', + run: 'text the_flow.customer', +}, { + mobile: false, trigger: ".ui-menu-item > a:contains('the_flow.customer')", auto: true, +}, { + mobile: true, + trigger: ".modal-footer .btn-primary:contains('Save')", + extra_trigger: ".modal:not(.o_inactive_modal) .modal-title:contains('Customer')", + content: _t('Save'), + position: 'right', }, { trigger: ".o_kanban_quick_create .o_kanban_add", extra_trigger: ".o_kanban_quick_create .o_field_widget[name=partner_id] > .o_external_button", // Wait name_create content: _t("Click here to add your opportunity."), position: "right", }, { + mobile: false, trigger: ".o_kanban_group:first .o_kanban_record:has(span:contains('the_flow.opportunity'))", content: _t("Drag & drop opportunities between columns as you progress in your sales cycle."), position: "right", run: "drag_and_drop .o_opportunity_kanban .o_kanban_group:eq(2) ", }, { + mobile: false, trigger: ".o_kanban_group:eq(2) > .o_kanban_record:has(span:contains('the_flow.opportunity'))", content: _t("Click on an opportunity to zoom to it."), position: "bottom", }, { -// Create a quotation - trigger: ".o_statusbar_buttons > button:enabled:contains('New Quotation')", - content: _t('

Create a quotation

'), - position: "right" + mobile: true, + trigger: ".o_kanban_group:first .o_kanban_record:has(span:contains('the_flow.opportunity'))", + content: _t("Open the_flow.opportunity"), + position: "bottom", }, { + mobile: true, + trigger: ".o_statusbar_status .btn.dropdown-toggle:contains(New)", + content: _t("Change status from New to proposition."), + position: "bottom", +}, { + mobile: true, + trigger: ".o_statusbar_status .btn:contains(Proposition)", + content: _t("Change status from New to proposition."), + position: "bottom", +}, +// Create a quotation +...tour.stepUtils.statusbarButtonsSteps('New Quotation', _t('

Create a quotation

')), +{ + mobile: false, trigger: ".o_field_widget[name=order_line] .o_field_x2many_list_row_add > a", content: _t("Click here to add some lines to your quotations."), position: "bottom", +}, { + mobile: true, + trigger: ".o_field_widget[name=order_line] .btn:contains(Add)", + content: _t("Click here to add some lines to your quotations."), + position: "bottom", }, { /** * We need both triggers because the "sale_product_configurator" module replaces the * "product_id" field with a "product_template_id" field. * This selector will still only ever select one element. */ + mobile: false, trigger: ".o_field_widget[name=product_id] input, .o_field_widget[name=product_template_id] input", content: _t("Select a product, or create a new one on the fly. The product will define the default sales price (that you can change), taxes and description automatically."), position: "right", @@ -351,20 +546,38 @@ tour.register('main_flow_tour', { }); }, }, { + mobile: false, trigger: ".ui-menu-item > a:contains('the_flow.product')", }, { + mobile: false, trigger: '.o_form_editable textarea[name="name"].product_loading_success', run: function () {} // wait for product loading }, { + mobile: true, + trigger: ".o_field_widget[name=product_id] input", + extra_trigger: ".modal:not(.o_inactive_modal) .modal-title:contains('Order Lines')", + content: _t("Select a product, or create a new one on the fly. The product will define the default sales price (that you can change), taxes and description automatically."), + position: "right", +}, +...tour.stepUtils.mobileKanbanSearchMany2X('Product', 'the_flow.product'), +{ + mobile: false, trigger: ".o_field_widget[name=order_line] .o_field_x2many_list_row_add > a", content: _t("Click here to add some lines to your quotations."), position: "bottom", +}, { + mobile: true, + trigger: ".modal-footer .btn-primary:contains('Save & New')", + extra_trigger: ".modal:not(.o_inactive_modal) .modal-title:contains('Order Lines')", + content: _t('Save & New'), + position: 'right', }, { /** * We need both triggers because the "sale_product_configurator" module replaces the * "product_id" field with a "product_template_id" field. * This selector will still only ever select one element. */ + mobile: false, trigger: ".o_field_widget[name=product_id] input, .o_field_widget[name=product_template_id] input", extra_trigger: '.o_field_widget[name=order_line] .o_data_row:nth(1).o_selected_row', content: _t("Select a product"), @@ -384,19 +597,33 @@ tour.register('main_flow_tour', { }); }, }, { + mobile: false, trigger: ".ui-menu-item > a:contains('the_flow.service')", }, { + mobile: false, trigger: '.o_form_editable textarea[name="name"].product_service_loading_success', run: function () {} // wait for product loading }, { + mobile: false, trigger: 'label:contains("Untaxed Amount")', // click somewhere else to exit cell focus }, { - trigger: ".o_statusbar_buttons > button:enabled:contains('Send by Email')", - extra_trigger: ".o_statusbar_status .btn-primary:contains('Quotation')", - content: _t("Try to send it to email"), - position: "bottom", -}, { + mobile: true, + trigger: ".o_field_widget[name=product_id] input", + extra_trigger: '.o_field_x2many_kanban[name=order_line] .oe_kanban_card:contains(the_flow.product)', + content: _t("Select a product, or create a new one on the fly. The product will define the default sales price (that you can change), taxes and description automatically."), + position: "right", +}, +...tour.stepUtils.mobileKanbanSearchMany2X('Product', 'the_flow.service'), +{ + mobile: true, + trigger: ".modal-footer .btn-primary:contains('Save & Close')", + extra_trigger: ".modal:not(.o_inactive_modal) .modal-title:contains('Order Lines')", + content: _t('Save & Close'), + position: 'right', +}, +...tour.stepUtils.statusbarButtonsSteps('Send by Email', _t("Try to send it to email"), ".o_statusbar_status .btn-primary:contains('Quotation')"), +{ trigger: ".o_field_widget[name=email]", content: _t("Enter an email address"), position: "right", @@ -409,11 +636,9 @@ tour.register('main_flow_tour', { trigger: ".modal-footer .btn-primary span:contains('Send')", content: _t("Try to send it to email"), position: "bottom", -}, { - trigger: ".o_statusbar_buttons > button:enabled:contains('Confirm')", - content: _t("

Confirm this quotation

"), - position: "bottom" -}, { +}, +...tour.stepUtils.statusbarButtonsSteps('Confirm', _t("

Confirm this quotation

")), +{ trigger: ".o_form_button_save", extra_trigger: ".o_statusbar_status .btn-primary:contains('Sales Order')", content: _t("

Save this quotation

"), @@ -424,18 +649,12 @@ tour.register('main_flow_tour', { trigger: '.o_menu_toggle', content: _t('Go back to the home menu'), position: 'bottom', -}, tour.stepUtils.showAppsMenuItem(), { - trigger: '.o_app[data-menu-xmlid="stock.menu_stock_root"]', - content: _t('Go to Inventory'), - position: 'right', - edition: 'community' -}, { - trigger: '.o_app[data-menu-xmlid="stock.menu_stock_root"]', - content: _t('Go to Inventory'), - position: 'bottom', - edition: 'enterprise' -}, { +}, +...tour.stepUtils.goToAppSteps('stock.menu_stock_root', _t('Go to Inventory')), +tour.stepUtils.openBuggerMenu("li.breadcrumb-item.active:contains('Inventory Overview')"), +{ trigger: ".o_menu_sections a:contains('Master Data')", + extra_trigger: '.o_main_navbar', content: _t("Go to Master Data"), position: "bottom" }, { @@ -443,18 +662,33 @@ tour.register('main_flow_tour', { content: _t("Reordering Rules"), position: "bottom" }, { + mobile: false, trigger: ".o_list_button_add", content: _t("Let's create a new reordering rule"), position: "right", }, { + mobile: true, + trigger: ".o-kanban-button-new", + content: _t("Let's create a new reordering rule"), + position: "right", +}, { + mobile: false, trigger: ".o_field_widget[name=product_id] input", content: _t("Write the name of your product."), position: "top", run: "text the_flow.component2", }, { + mobile: false, trigger: ".ui-menu-item > a:contains('the_flow.component2')", auto: true, }, { + mobile: true, + trigger: ".o_field_widget[name=product_id] input", + content: _t("Write the name of your product."), + position: "top", +}, +...tour.stepUtils.mobileKanbanSearchMany2X('Product', 'the_flow.component2'), +{ trigger: ".o_field_widget[name=product_min_qty]", content: _t("Set the minimum product quantity"), position: "right", @@ -468,7 +702,9 @@ tour.register('main_flow_tour', { trigger: ".o_form_button_save", content: _t("

Save this reordering rule

"), position: "bottom" -}, { +}, +tour.stepUtils.openBuggerMenu("li.breadcrumb-item.active:contains('OP/')"), +{ // Run the schedulers trigger: ".o_menu_sections a:contains('Operations')", content: _t("Go to Run Schedulers"), @@ -488,33 +724,23 @@ tour.register('main_flow_tour', { trigger: '.o_menu_toggle', content: _t('Go back to the home menu'), position: 'bottom', -}, tour.stepUtils.showAppsMenuItem(), { - trigger: '.o_app[data-menu-xmlid="purchase.menu_purchase_root"]', - content: _t('Go to Purchase'), - position: 'right', - edition: 'community' -}, { - trigger: '.o_app[data-menu-xmlid="purchase.menu_purchase_root"]', - content: _t('Go to Purchase'), - position: 'bottom', - edition: 'enterprise' -}, { +}, +...tour.stepUtils.goToAppSteps('purchase.menu_purchase_root', _t('Go to Purchase')), +{ + mobile: false, trigger: '.o_data_row:has(.o_data_cell:contains("the_flow.vendor"))', content: _t('Select the generated request for quotation'), position: 'bottom', }, { - trigger: ".o_statusbar_buttons > button:enabled:contains('Confirm Order')", - content: _t("Confirm quotation"), - position: "bottom", -}, { - trigger: ".o_statusbar_buttons > button:enabled:contains('Receive Products')", - content: _t("Receive Product"), - position: "bottom", -}, { - trigger: ".o_statusbar_buttons > button:enabled:contains('Validate')", - content: _t("Validate"), - position: "bottom", -}, { + mobile: true, + trigger: '.o_kanban_record:has(.o_kanban_record_title:contains("the_flow.vendor"))', + content: _t('Select the generated request for quotation'), + position: 'bottom', +}, +...tour.stepUtils.statusbarButtonsSteps('Confirm Order', _t("Confirm quotation")), +...tour.stepUtils.statusbarButtonsSteps('Receive Products', _t("Receive Product"), ".o_statusbar_status .btn.dropdown-toggle:contains('Purchase Order')"), +...tour.stepUtils.statusbarButtonsSteps('Validate', _t("Validate"), ".o_statusbar_status .btn.dropdown-toggle:contains('Ready')"), +{ trigger: ".modal-footer .btn-primary", extra_trigger: ".modal-dialog", content: _t("Apply"), @@ -523,19 +749,11 @@ tour.register('main_flow_tour', { trigger: ".o_back_button a, .breadcrumb-item:not('.active'):last", content: _t('go back to the purchase order'), position: 'bottom', -}, { - trigger: ".o_statusbar_buttons > button:enabled:contains('Create Bill')", - content: _t('go to Vendor Bills'), - position: 'bottom', -}, { - trigger: ".o_statusbar_buttons > button:enabled:contains('Post')", - content: _t("Try to send it to email"), - position: "bottom", -}, { - trigger: ".o_statusbar_buttons > button:enabled:contains('Register Payment')", - content: _t("Register Payment"), - position: "bottom", -}, { +}, +...tour.stepUtils.statusbarButtonsSteps('Create Bill', _t('go to Vendor Bills'), ".o_statusbar_status .btn.dropdown-toggle:contains('Purchase Order')"), +...tour.stepUtils.statusbarButtonsSteps('Post', _t("Try to send it to email"), ".o_statusbar_status .btn.dropdown-toggle:contains('Draft')"), +...tour.stepUtils.statusbarButtonsSteps('Register Payment', _t("Register Payment"), ".o_statusbar_status .btn.dropdown-toggle:contains('Posted')"), +{ trigger: ".modal-footer .btn-primary", content: _t("Validate"), position: "bottom", @@ -544,60 +762,65 @@ tour.register('main_flow_tour', { trigger: '.o_menu_toggle', content: _t('Go back to the home menu'), position: 'bottom', -}, tour.stepUtils.showAppsMenuItem(), { - trigger: '.o_app[data-menu-xmlid="mrp.menu_mrp_root"]', - content: _t('Go to Manufacturing'), - position: 'right', - edition: 'community' -}, { - trigger: '.o_app[data-menu-xmlid="mrp.menu_mrp_root"]', - content: _t('Go to Manufacturing'), - position: 'bottom', - edition: 'enterprise' -}, { +}, +...tour.stepUtils.goToAppSteps('mrp.menu_mrp_root', _t('Go to Manufacturing')), +tour.stepUtils.openBuggerMenu("li.breadcrumb-item.active:contains('Manufacturing Orders'), li.breadcrumb-item.active:contains('Work Centers Overview')"), +{ + mobile: false, trigger: ".o_menu_sections a[data-menu-xmlid='mrp.menu_mrp_manufacturing']", content: _t('Click on Operations menuitem'), position: 'bottom', }, { + mobile: false, trigger: ".o_menu_sections a[data-menu-xmlid='mrp.menu_mrp_production_action']", content: _t('Open manufacturing orders'), position: 'bottom', }, { + mobile: false, trigger: '.o_data_row:has(.o_data_cell:contains("the_flow.product")):first', content: _t('Select the generated manufacturing order'), position: 'bottom', }, { - trigger: ".o_statusbar_buttons > button[name='action_assign']:enabled", - content: _t("Check availability"), + mobile: true, + trigger: ".o_menu_sections a:contains('Operations')", + extra_trigger: '.o_main_navbar', + content: _t("Let's go to manufacturing order."), position: "bottom", }, { - trigger: ".o_statusbar_buttons > button:enabled:contains('Produce')", - content: _t("Produce"), - position: "bottom", + mobile: true, + trigger: ".o_menu_sections a:has(span:contains('Manufacturing Orders'))", + content: _t("Let's go to manufacturing order."), + position: "bottom" }, { + mobile: true, + trigger: '.o_kanban_record:has(.o_kanban_record_title:contains("the_flow.product")):first', + extra_trigger: "li.breadcrumb-item.active:contains('Manufacturing Orders')", + content: _t('Select the generated manufacturing order'), + position: 'bottom', +}, +...tour.stepUtils.statusbarButtonsSteps('Check availability', _t("Check availability")), +...tour.stepUtils.statusbarButtonsSteps('Produce', _t("Produce"), "body.o_web_client:not(.oe_wait)"), +{ + mobile: false, trigger: ".modal-footer .btn-primary:nth-child(3)", content: _t('Record Production'), position: 'bottom', }, { - trigger: ".o_statusbar_buttons > button:enabled:contains('Mark as Done')", - content: _t("Mark as Done"), - position: "bottom", -}, { + mobile: true, + trigger: '.modal-footer .btn-primary[name="do_produce"]:not(.o_invisible_modifier)', + content: _t('Record Production'), + position: 'bottom', +}, +...tour.stepUtils.statusbarButtonsSteps('Mark as Done', _t("Mark as Done"), ".o_statusbar_status .btn.dropdown-toggle:contains('To Close')"), +{ edition: "enterprise", trigger: '.o_menu_toggle', content: _t('Go back to the home menu'), position: 'bottom', -}, tour.stepUtils.showAppsMenuItem(), { - trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]', - content: _t('Organize your sales activities with the Sales app.'), - position: 'bottom', - edition: 'community' -}, { - trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]', - content: _t('Organize your sales activities with the Sales app.'), - position: 'bottom', - edition: 'enterprise' -}, { +}, +...tour.stepUtils.goToAppSteps('sale.sale_menu_root', _t('Organize your sales activities with the Sales app.')), +tour.stepUtils.openBuggerMenu("li.breadcrumb-item.active:contains('Quotations')"), +{ trigger: ".o_menu_sections a[data-menu-xmlid='sale.sale_order_menu']", content: _t("Go to Sales menu"), position: "bottom" @@ -606,19 +829,37 @@ tour.register('main_flow_tour', { content: _t("Go to the sales orders"), position: "bottom" }, { + mobile: false, trigger: ".o_data_row:first", extra_trigger: '.o_control_panel .breadcrumb:contains("Sales Orders")', content: _t("Go to the last sale order"), position: "right" }, { + mobile: true, + trigger: ".o_kanban_record:first", + extra_trigger: '.o_control_panel .breadcrumb:contains("Sales Orders")', + content: _t("Go to the last sale order"), + position: "bottom" +}, +tour.stepUtils.mobileModifier(tour.stepUtils.autoExpandMoreButtons('.o_control_panel .breadcrumb:contains("S0")')), +{ + mobile: false, trigger: '.oe_button_box .oe_stat_button:has(div[name=tasks_count])', content: _t('See Tasks'), position: 'right', +}, { + mobile: true, + trigger: '.oe_button_box .oe_stat_button:has(div[name=tasks_count])', + extra_trigger: '.o_control_panel .breadcrumb:contains("S0")', + content: _t('See Tasks'), + position: 'bottom', }, { trigger: '.o_field_widget[name=project_id]', content: _t('See Project'), position: 'right', -}, { +}, +tour.stepUtils.mobileModifier(tour.stepUtils.autoExpandMoreButtons('.o_control_panel .breadcrumb:contains("the_flow.project")')), +{ trigger: '.oe_button_box .oe_stat_button:has(span:contains("Timesheets"))', extra_trigger: '.o_form_readonly', content: _t('See Timesheets'), @@ -628,6 +869,7 @@ tour.register('main_flow_tour', { content: _t('Add a Timesheet'), position: 'bottom', }, { + // FIXME replace list by kanban + form trigger: '.o_selected_row input[name=name]', content: _t('Set description'), position: 'bottom', @@ -638,8 +880,15 @@ tour.register('main_flow_tour', { position: 'bottom', run: 'click', }, { + mobile: false, trigger: ".ui-menu-item > a", auto: true, +}, { + mobile: true, + trigger: ".o_kanban_record .o_kanban_record_title :contains('the_flow.service')", + extra_trigger: ".modal:not(.o_inactive_modal) .modal-title:contains('Task')", + content: _t("Select the the_flow.vendor"), + position: "bottom", }, { trigger: '.o_selected_row input[name=unit_amount]', content: _t('Set time'), @@ -649,28 +898,29 @@ tour.register('main_flow_tour', { trigger: '.o_list_button_save', content: _t('Save'), position: 'bottom', -}, { +}, +...tour.stepUtils.goBackBreadcrumbsMobile( + _t('Back to the sale order'), + undefined, + ".breadcrumb-item.active:contains('the_flow.project')", + ".breadcrumb-item.active:contains('the_flow.service')" + ), +{ + mobile: false, trigger: '.breadcrumb-item:nth-child(2) a', extra_trigger: '.o_list_button_add', // Waiting save content: _t('Back to the sale order'), position: 'bottom', -}, { - trigger: ".o_statusbar_buttons > button:enabled:contains('Create Invoice')", - content: _t("Validate"), - position: "bottom", -}, { +}, +...tour.stepUtils.statusbarButtonsSteps('Create Invoice', _t("Validate"), ".o_field_widget[name=order_line]"), +{ trigger: ".modal-footer .btn-primary", content: _t("Create and View Invoices"), position: "bottom", -}, { - trigger: ".o_statusbar_buttons > button:enabled:contains('Post')", - content: _t("Validate"), - position: "bottom", -}, { - trigger: ".o_statusbar_buttons > button:enabled:contains('Register Payment')", - content: _t("Register Payment"), - position: "bottom", -}, { +}, +...tour.stepUtils.statusbarButtonsSteps('Post', _t("Validate"), ".breadcrumb-item.active:contains('Draft Invoice')"), +...tour.stepUtils.statusbarButtonsSteps('Register Payment', _t("Register Payment"), ".breadcrumb-item.active:contains('INV/')"), +{ trigger: ".modal-footer .btn-primary", content: _t("Validate"), position: "bottom", @@ -722,6 +972,7 @@ tour.register('main_flow_tour', { position: 'right', run: 'text 9010.85', // + 12.65 }, { + // FIXME convert list in kanban + form edition: "enterprise", trigger: ".o_field_widget[name=line_ids] .o_field_x2many_list_row_add > a", content: _t("Click here to add some lines."), @@ -733,16 +984,30 @@ tour.register('main_flow_tour', { position: "bottom", run: "text 11.00", }, { + mobile: false, edition: "enterprise", trigger: ".o_selected_row .o_field_widget[name=partner_id] input", content: _t("Write the name of your customer."), position: "bottom", run: "text the_flow.customer", }, { + mobile: false, edition: "enterprise", trigger: ".ui-menu-item > a:contains('the_flow.customer')", in_modal: false, auto: true, +}, { + mobile: true, + edition: "enterprise", + trigger: ".o_selected_row .o_field_widget[name=partner_id] input", + content: _t("Write the name of your customer."), + position: "bottom", +}, { + mobile: true, + trigger: ".o_kanban_record .o_kanban_record_title :contains('the_flow.customer')", + extra_trigger: ".modal:not(.o_inactive_modal) .modal-title:contains('Partner')", + content: _t('Save'), + position: 'right', }, { edition: "enterprise", trigger: ".o_selected_row .o_field_widget[name=name]", @@ -754,12 +1019,9 @@ tour.register('main_flow_tour', { trigger: '.o_form_button_save', content: _t('Save.'), position: 'bottom', -}, { - edition: "enterprise", - trigger: ".o_statusbar_buttons > button:enabled:contains('Reconcile')", - content: _t('

Reconcile

'), - position: "bottom", -}, { +}, +...tour.stepUtils.statusbarButtonsSteps('Reconcile', _t('

Reconcile

'), ".breadcrumb-item.active:contains('the_flow.statement')").map(tour.stepUtils.editionEnterpriseModifier), +{ edition: "enterprise", trigger: "button.o_reconcile, button.o_validate", content: _t('

Click on Reconcile

'),