From 710271240f8af09a09421d7c5e4afe20147d58f3 Mon Sep 17 00:00:00 2001 From: "Michael (mcm)" Date: Tue, 13 Sep 2022 13:52:22 +0000 Subject: [PATCH] [IMP] web: remove readonly mode of form view This commit does multiple things: - The readonly mode of form view is removed but not for the fields. it means that the fields in the view are always in edit mode except if we force them to be readonly. - The control panel is revamped to take less vertical space and shows now the record editing (dirtiness)/validity status after editing the record. - The record is saved only when leaving the view or by clicking the save button when hovering the record status in the control panel. - The record can still be discarded by clicking the discard button when hovering the status text in control panel. task id: 2822553 X-original-commit: 77824ad44b6945a9811120380747f87ef6362ae2 Part-of: odoo/odoo#101118 Co-authored-by: luvi --- addons/account/static/src/js/tours/account.js | 2 +- .../static/tests/tours/tax_group_tests.js | 5 - .../tests/analytic_distribution_tests.js | 3 - .../static/src/tests/iban_widget_test.js | 8 +- .../crm/static/tests/crm_rainbowman_tests.js | 8 +- .../tours/crm_email_and_phone_propagation.js | 22 +- .../static/tests/tours/crm_forecast_tour.js | 4 - .../crm/static/tests/tours/crm_rainbowman.js | 8 +- .../static/tests/m2x_avatar_employee_tests.js | 2 +- .../radio_image_field_tests.js | 11 +- .../static/tests/tours/skills_tour.js | 5 +- .../views/filterable_selection_field_tests.js | 5 +- .../tests/qunit_suite_tests/chatter_tests.js | 4 +- .../components/field_text_emojis_tests.js | 10 +- .../m2x_avatar_user_tests.js | 10 +- .../widgets/form_renderer_tests.js | 39 +- .../src/js/tours/mass_mailing_editor_tour.js | 8 +- .../static/tests/mass_mailing_html_tests.js | 11 +- addons/project/static/src/js/tours/project.js | 2 +- .../tours/purchase_product_matrix_tour.js | 2 +- .../section_one2many_field_tests.js | 9 +- .../static/tests/sale_order_many2one_tests.js | 3 +- .../description_page_field_tests.js | 3 +- .../question_page_one2many_field_tests.js | 16 +- ...event_sale_with_product_configurator_ui.js | 2 +- .../static/src/core/datepicker/datepicker.js | 4 +- .../src/search/action_menus/action_menus.js | 5 +- .../src/views/basic_relational_model.js | 13 +- .../web/static/src/views/fields/fields.scss | 42 + .../many2many_tags/many2many_tags_field.scss | 4 + .../many2many_tags_avatar_field.scss | 4 + .../fields/progress_bar/progress_bar_field.js | 18 +- .../progress_bar/progress_bar_field.xml | 3 - .../src/views/fields/relational_utils.js | 4 +- .../src/views/fields/x2many/x2many_field.js | 19 - .../form/control_panel/form_control_panel.js | 6 + .../control_panel/form_control_panel.scss | 6 + .../form/control_panel/form_control_panel.xml | 31 + .../static/src/views/form/form_compiler.js | 21 +- .../static/src/views/form/form_controller.js | 83 +- .../src/views/form/form_controller.scss | 11 +- .../static/src/views/form/form_controller.xml | 48 +- .../form_error_dialog/form_error_dialog.js | 66 ++ .../form_error_dialog/form_error_dialog.scss | 10 + .../form_error_dialog/form_error_dialog.xml | 17 + .../web/static/src/views/form/form_label.js | 5 +- .../static/src/views/form/form_renderer.js | 8 +- .../form_status_indicator.js | 35 + .../form_status_indicator.scss | 15 + .../form_status_indicator.xml | 21 + addons/web/static/src/views/form/form_view.js | 3 +- .../status_bar_buttons/status_bar_buttons.js | 5 +- .../static/src/views/list/list_renderer.js | 7 - .../src/views/view_button/view_button.js | 6 + .../src/views/view_button/view_button_hook.js | 22 +- .../settings_form_view/settings_form_view.js | 2 + addons/web/static/tests/helpers/utils.js | 14 +- .../tests/mobile/mobile_form_view_tests.js | 44 - .../mobile/mobile_statusbar_field_tests.js | 4 +- .../views/fields/ace_editor_field_tests.js | 3 +- .../tests/views/fields/binary_field_tests.js | 113 +- .../fields/boolean_favorite_field_tests.js | 8 +- .../tests/views/fields/boolean_field_tests.js | 27 +- .../fields/boolean_toggle_field_tests.js | 11 +- .../tests/views/fields/char_field_tests.js | 98 +- .../tests/views/fields/color_field_tests.js | 3 +- .../views/fields/color_picker_field_tests.js | 6 - .../tests/views/fields/date_field_tests.js | 62 +- .../views/fields/daterange_field_tests.js | 71 +- .../views/fields/datetime_field_tests.js | 33 +- .../tests/views/fields/domain_field_tests.js | 50 +- .../tests/views/fields/email_field_tests.js | 33 +- .../views/fields/float_factor_field_tests.js | 15 +- .../tests/views/fields/float_field_tests.js | 101 +- .../views/fields/float_time_field_tests.js | 24 +- .../views/fields/float_toggle_field_tests.js | 5 +- .../tests/views/fields/handle_field_tests.js | 20 +- .../tests/views/fields/html_field_tests.js | 28 +- .../tests/views/fields/image_field_tests.js | 38 +- .../views/fields/image_url_field_tests.js | 2 +- .../tests/views/fields/integer_field_tests.js | 52 +- .../fields/label_selection_field_tests.js | 46 - .../fields/many2many_binary_field_tests.js | 25 +- .../many2many_checkboxes_field_tests.js | 45 +- .../views/fields/many2many_field_tests.js | 72 +- .../many2many_tags_avatar_field_tests.js | 21 +- .../fields/many2many_tags_field_tests.js | 35 +- .../fields/many2one_avatar_field_tests.js | 10 +- .../views/fields/many2one_field_tests.js | 172 ++- .../views/fields/monetary_field_tests.js | 138 +-- .../views/fields/numeric_fields_tests.js | 3 - .../views/fields/one2many_field_tests.js | 356 +----- .../views/fields/pdf_viewer_field_tests.js | 11 +- .../views/fields/percent_pie_field_tests.js | 10 +- .../views/fields/percentage_field_tests.js | 16 +- .../tests/views/fields/phone_field_tests.js | 28 +- .../views/fields/priority_field_tests.js | 10 +- .../views/fields/progress_bar_field_tests.js | 111 +- .../views/fields/properties_field_tests.js | 36 +- .../tests/views/fields/radio_field_tests.js | 27 +- .../views/fields/reference_field_tests.js | 51 +- .../fields/remaining_days_field_tests.js | 65 +- .../views/fields/selection_field_tests.js | 57 +- .../views/fields/signature_field_tests.js | 5 +- .../views/fields/stat_info_field_tests.js | 110 +- .../fields/state_selection_field_tests.js | 2 - .../views/fields/statusbar_field_tests.js | 5 - .../tests/views/fields/text_field_tests.js | 49 +- .../tests/views/fields/url_field_tests.js | 102 +- .../tests/views/form/form_compiler_tests.js | 15 +- .../tests/views/form/form_view_tests.js | 1039 +++++------------ .../web/static/tests/views/list_view_tests.js | 46 +- .../view_dialogs/form_view_dialog_tests.js | 3 +- .../select_create_dialog_tests.js | 1 - .../views/widgets/attach_document_tests.js | 3 +- .../tests/views/widgets/week_days_tests.js | 12 +- .../webclient/actions/concurrency_tests.js | 4 +- .../webclient/actions/window_action_tests.js | 115 +- .../web_tour/static/src/js/tour_step_utils.js | 27 +- .../slide_category_one2many_field_tests.js | 7 +- .../static/tests/tours/main_flow.js | 73 +- .../static/tests/tours/constraint.js | 2 +- .../test_new_api/static/tests/tours/x2many.js | 40 +- odoo/tests/common.py | 2 +- 124 files changed, 1549 insertions(+), 3069 deletions(-) create mode 100644 addons/web/static/src/views/form/control_panel/form_control_panel.js create mode 100644 addons/web/static/src/views/form/control_panel/form_control_panel.scss create mode 100644 addons/web/static/src/views/form/control_panel/form_control_panel.xml create mode 100644 addons/web/static/src/views/form/form_error_dialog/form_error_dialog.js create mode 100644 addons/web/static/src/views/form/form_error_dialog/form_error_dialog.scss create mode 100644 addons/web/static/src/views/form/form_error_dialog/form_error_dialog.xml create mode 100644 addons/web/static/src/views/form/form_status_indicator/form_status_indicator.js create mode 100644 addons/web/static/src/views/form/form_status_indicator/form_status_indicator.scss create mode 100644 addons/web/static/src/views/form/form_status_indicator/form_status_indicator.xml diff --git a/addons/account/static/src/js/tours/account.js b/addons/account/static/src/js/tours/account.js index 010a156432a..b2b2f1ca97d 100644 --- a/addons/account/static/src/js/tours/account.js +++ b/addons/account/static/src/js/tours/account.js @@ -86,7 +86,7 @@ tour.register('account_tour', { ...tour.stepUtils.saveForm(), { trigger: "button[name=action_post]", - extra_trigger: "button.o_form_button_edit", + extra_trigger: "button.o_form_button_create", content: _t("Once your invoice is ready, press CONFIRM."), }, { trigger: "button[name=action_invoice_sent]", diff --git a/addons/account/static/tests/tours/tax_group_tests.js b/addons/account/static/tests/tours/tax_group_tests.js index 7d7d0b33329..4133825b607 100644 --- a/addons/account/static/tests/tours/tax_group_tests.js +++ b/addons/account/static/tests/tours/tax_group_tests.js @@ -63,11 +63,6 @@ tour.register('account_tax_group', { content: "Save the account move", trigger: '.o_form_button_save', }, - // Edit account.move - { - content: "Edit the account move", - trigger: '.o_form_button_edit', - }, // Edit tax group amount { content: "Edit tax group amount", diff --git a/addons/analytic/static/tests/analytic_distribution_tests.js b/addons/analytic/static/tests/analytic_distribution_tests.js index 1ef871906e8..8924c1f42b2 100644 --- a/addons/analytic/static/tests/analytic_distribution_tests.js +++ b/addons/analytic/static/tests/analytic_distribution_tests.js @@ -2,7 +2,6 @@ import { click, - clickEdit, getFixture, } from "@web/../tests/helpers/utils"; import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; @@ -139,8 +138,6 @@ QUnit.module("Analytic", (hooks) => { "should have rendered 'FI 69.7%'" ); - await clickEdit(target); - assert.containsN( target, ".o_delete", diff --git a/addons/base_iban/static/src/tests/iban_widget_test.js b/addons/base_iban/static/src/tests/iban_widget_test.js index b3848d2d27f..769fae6159c 100644 --- a/addons/base_iban/static/src/tests/iban_widget_test.js +++ b/addons/base_iban/static/src/tests/iban_widget_test.js @@ -1,6 +1,6 @@ /** @odoo-module **/ import { start, startServer } from '@mail/../tests/helpers/test_utils'; -import { click, clickEdit, clickSave, editInput, getFixture } from "@web/../tests/helpers/utils"; +import { click, clickSave, editInput, getFixture } from "@web/../tests/helpers/utils"; import { DELAY } from "@base_iban/components/iban_widget/iban_widget"; @@ -55,8 +55,6 @@ QUnit.module('Fields', {}, function () { QUnit.test('Iban Widget full flow', async assert => { const { target, advanceTime, afterNextRender } = await openPreparedView(assert, [validIban], ""); - assert.containsNone(target, ".o_iban", "Shouldn't display any validation icon while not editing"); - await clickEdit(target); assert.containsNone(target, ".o_iban", "Shouldn't display any validation icon while not editing a specific line"); @@ -73,10 +71,6 @@ QUnit.module('Fields', {}, function () { await clickSave(target); assert.containsNone(target, ".o_iban", "Shouldn't display any validation while not editing"); - await clickEdit(target); - assert.containsNone(target, ".o_iban", - "Shouldn't display any validation icon while not editing a specific line"); - await click(target, "td.o_iban_cell"); await afterNextRender(() => advanceTime(DELAY)); assert.containsOnce(target, "i.fa.fa-times.o_iban_fail", "The validation icon should be present while clicking on an already filled IBAN"); diff --git a/addons/crm/static/tests/crm_rainbowman_tests.js b/addons/crm/static/tests/crm_rainbowman_tests.js index 15d6836ed3d..82723d2223d 100644 --- a/addons/crm/static/tests/crm_rainbowman_tests.js +++ b/addons/crm/static/tests/crm_rainbowman_tests.js @@ -4,7 +4,6 @@ import "@crm/../tests/mock_server"; import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; import { click, - clickEdit, clickSave, dragAndDrop, getFixture, @@ -125,6 +124,7 @@ QUnit.module('Crm Rainbowman Triggers', { ...this.testFormView, resId: 6, mockRPC: getMockRpc(assert), + mode: "readonly", }); await click(target.querySelector(".o_statusbar_status button[data-value='3']")); @@ -139,7 +139,6 @@ QUnit.module('Crm Rainbowman Triggers', { resId: 6, mockRPC: getMockRpc(assert), }); - await clickEdit(target); await click(target.querySelector(".o_statusbar_status button[data-value='3']")); assert.verifySteps([]); // no message displayed yet @@ -155,6 +154,7 @@ QUnit.module('Crm Rainbowman Triggers', { ...this.testFormView, resId: 2, mockRPC: getMockRpc(assert), + mode: "readonly", }); await click(target.querySelector(".o_statusbar_status button[data-value='3']")); @@ -168,6 +168,7 @@ QUnit.module('Crm Rainbowman Triggers', { ...this.testFormView, resId: 1, mockRPC: getMockRpc(assert), + mode: "readonly", }); await click(target.querySelector(".o_statusbar_status button[data-value='3']")); @@ -181,6 +182,7 @@ QUnit.module('Crm Rainbowman Triggers', { ...this.testFormView, resId: 8, mockRPC: getMockRpc(assert), + mode: "readonly", }); await click(target.querySelector(".o_statusbar_status button[data-value='3']")); @@ -194,6 +196,7 @@ QUnit.module('Crm Rainbowman Triggers', { ...this.testFormView, resId: 10, mockRPC: getMockRpc(assert), + mode: "readonly", }); await click(target.querySelector(".o_statusbar_status button[data-value='3']")); @@ -207,6 +210,7 @@ QUnit.module('Crm Rainbowman Triggers', { ...this.testFormView, resId: 1, mockRPC: getMockRpc(assert), + mode: "readonly", }); await click(target.querySelector(".o_statusbar_status button[data-value='2']")); diff --git a/addons/crm/static/tests/tours/crm_email_and_phone_propagation.js b/addons/crm/static/tests/tours/crm_email_and_phone_propagation.js index 3ccb30554e7..0e132f105c4 100644 --- a/addons/crm/static/tests/tours/crm_email_and_phone_propagation.js +++ b/addons/crm/static/tests/tours/crm_email_and_phone_propagation.js @@ -15,18 +15,11 @@ odoo.define('crm.crm_email_and_phone_propagation', function (require) { trigger: '.o_kanban_record .o_kanban_record_title span:contains(Test Lead Propagation)', content: 'Open the first lead', run: 'click', - }, { - trigger: '.o_form_button_edit', - extra_trigger: '.o_lead_opportunity_form.o_form_readonly', - content: 'Edit the lead', - run: 'click', }, { trigger: '.o_form_button_save', extra_trigger: '.o_form_editable .o_field_widget[name=email_from] input', content: 'Save the lead', run: 'click', - }, { - trigger: '.o_form_readonly', }, ]); @@ -42,10 +35,6 @@ odoo.define('crm.crm_email_and_phone_propagation', function (require) { trigger: '.o_kanban_record .o_kanban_record_title span:contains(Test Lead Propagation)', content: 'Open the first lead', run: 'click', - }, { - trigger: '.o_form_button_edit', - content: 'Edit the lead', - run: 'click', }, { trigger: '.o_form_editable .o_field_widget[name=email_from] input', extra_trigger: '.o_form_editable .o_field_widget[name=phone] input', @@ -55,14 +44,13 @@ odoo.define('crm.crm_email_and_phone_propagation', function (require) { action.remove_text("", ".o_form_editable .o_field_widget[name=phone] input"); }, }, { - trigger: '.o_form_button_save', + trigger: '.o_back_button', // wait the the warning message to be visible extra_trigger: '.o_form_sheet_bg .fa-exclamation-triangle:not(.o_invisible_modifier)', - content: 'Save the lead', + content: 'Save the lead and exit to kanban', run: 'click', - }, { - trigger: '.o_form_readonly', - }, + },{ + trigger: '.o_kanban_renderer', + } ]); - }); diff --git a/addons/crm/static/tests/tours/crm_forecast_tour.js b/addons/crm/static/tests/tours/crm_forecast_tour.js index 742b2a54204..e5d99938e1f 100644 --- a/addons/crm/static/tests/tours/crm_forecast_tour.js +++ b/addons/crm/static/tests/tours/crm_forecast_tour.js @@ -64,10 +64,6 @@ tour.register('crm_forecast', { trigger: ".o_kanban_record .o_kanban_record_title:contains('Test Opportunity 1')", content: "edit lead", run: "click" - }, { - trigger: ".o_form_button_edit", - content: "edit datetime", - run: "click" }, { trigger: ".o_field_widget[name=date_deadline] input", content: "complete expected closing", diff --git a/addons/crm/static/tests/tours/crm_rainbowman.js b/addons/crm/static/tests/tours/crm_rainbowman.js index f56e3404220..3189d0d810c 100644 --- a/addons/crm/static/tests/tours/crm_rainbowman.js +++ b/addons/crm/static/tests/tours/crm_rainbowman.js @@ -60,14 +60,18 @@ }, { trigger: ".o_statusbar_status button[data-value='4']", content: "move lead to won stage", - }, { + }, + ...tour.stepUtils.saveForm(), + { trigger: ".o_statusbar_status button[data-value='1']", extra_trigger: ".o_reward_rainbow", content: "move lead to previous stage & rainbowman appears", }, { trigger: "button[name=action_set_won_rainbowman]", content: "click button mark won", - }, { + }, + ...tour.stepUtils.saveForm(), + { trigger: ".o_menu_brand", extra_trigger: ".o_reward_rainbow", content: "last rainbowman appears", diff --git a/addons/hr/static/tests/m2x_avatar_employee_tests.js b/addons/hr/static/tests/m2x_avatar_employee_tests.js index 03c7c049346..17d576427b6 100644 --- a/addons/hr/static/tests/m2x_avatar_employee_tests.js +++ b/addons/hr/static/tests/m2x_avatar_employee_tests.js @@ -183,7 +183,7 @@ QUnit.module('hr', {}, function () { res_id: m2xHrAvatarUserId1, views: [[false, 'form']], }); - assert.strictEqual(document.querySelector('.o_field_widget[name=employee_id]').innerText.trim(), 'Mario'); + assert.strictEqual(document.querySelector('.o_field_widget[name=employee_id] input').value.trim(), 'Mario'); await dom.click(document.querySelector('.o_m2o_avatar > img')); diff --git a/addons/hr_holidays/static/tests/qunit_suite_tests/radio_image_field_tests.js b/addons/hr_holidays/static/tests/qunit_suite_tests/radio_image_field_tests.js index 5669d0edc3a..a9be64ae707 100644 --- a/addons/hr_holidays/static/tests/qunit_suite_tests/radio_image_field_tests.js +++ b/addons/hr_holidays/static/tests/qunit_suite_tests/radio_image_field_tests.js @@ -1,6 +1,6 @@ /** @odoo-module **/ -import { click, clickEdit, clickSave, getFixture } from "@web/../tests/helpers/utils"; +import { click, clickSave, getFixture } from "@web/../tests/helpers/utils"; import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; let serverData; @@ -42,11 +42,6 @@ QUnit.module("Fields", (hooks) => { serverData, arch: '
', }); - assert.containsOnce(target, ".o_field_widget.o_field_hr_holidays_radio_image"); - assert.containsNone(target, ".o_radio_input"); - assert.containsNone(target, "img"); - - await clickEdit(target); assert.containsOnce(target, ".o_field_widget.o_field_hr_holidays_radio_image"); assert.containsN(target, ".o_radio_input", 3); @@ -58,7 +53,7 @@ QUnit.module("Fields", (hooks) => { await clickSave(target); assert.containsOnce(target, ".o_field_widget.o_field_hr_holidays_radio_image"); - assert.containsNone(target, ".o_radio_input"); - assert.containsOnce(target, "img", 1); + assert.containsN(target, ".o_radio_input", 3); + assert.containsN(target, "img", 3); }); }); diff --git a/addons/hr_skills/static/tests/tours/skills_tour.js b/addons/hr_skills/static/tests/tours/skills_tour.js index 85e26f1cdfd..b630ad37d4c 100644 --- a/addons/hr_skills/static/tests/tours/skills_tour.js +++ b/addons/hr_skills/static/tests/tours/skills_tour.js @@ -24,10 +24,6 @@ tour.register('hr_skills_tour', { content: "Save", trigger: ".o_form_button_save", }, - { - content: "Switch to edit", - trigger: ".o_form_button_edit", - }, { content: "Add a new Resume experience", trigger: ".o_field_resume_one2many tr.o_resume_group_header button.btn-secondary", @@ -50,6 +46,7 @@ tour.register('hr_skills_tour', { { content: "Save it", trigger: ".o_form_button_save", + in_modal: true, run: "click", }, { diff --git a/addons/loyalty/static/tests/views/filterable_selection_field_tests.js b/addons/loyalty/static/tests/views/filterable_selection_field_tests.js index 11a90ed71f5..300b035819d 100644 --- a/addons/loyalty/static/tests/views/filterable_selection_field_tests.js +++ b/addons/loyalty/static/tests/views/filterable_selection_field_tests.js @@ -1,6 +1,6 @@ /** @odoo-module **/ -import { click, editSelect, getFixture } from "@web/../tests/helpers/utils"; +import { editSelect, getFixture } from "@web/../tests/helpers/utils"; import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; let serverData; @@ -47,7 +47,6 @@ QUnit.module("Fields", (hooks) => { `, }); - await click(target, ".o_form_button_edit"); assert.containsN(target, "select option", 3); assert.containsOnce( @@ -71,7 +70,6 @@ QUnit.module("Fields", (hooks) => { `, }); - await click(target, ".o_form_button_edit"); assert.containsN(target, "select option", 3); assert.containsOnce( @@ -96,7 +94,6 @@ QUnit.module("Fields", (hooks) => { `, }); - await click(target, ".o_form_button_edit"); assert.containsN(target, "select option", 4); assert.containsOnce( diff --git a/addons/mail/static/tests/qunit_suite_tests/chatter_tests.js b/addons/mail/static/tests/qunit_suite_tests/chatter_tests.js index d7fdebec780..9cd0bff5026 100644 --- a/addons/mail/static/tests/qunit_suite_tests/chatter_tests.js +++ b/addons/mail/static/tests/qunit_suite_tests/chatter_tests.js @@ -4,7 +4,7 @@ import { start, startServer } from '@mail/../tests/helpers/test_utils'; import { ROUTES_TO_IGNORE } from '@mail/../tests/helpers/webclient_setup'; import testUtils from 'web.test_utils'; -import { clickEdit, patchWithCleanup, selectDropdownItem } from '@web/../tests/helpers/utils'; +import { patchWithCleanup, selectDropdownItem } from '@web/../tests/helpers/utils'; import { ListController } from "@web/views/list/list_controller"; QUnit.module('mail', {}, function () { @@ -489,8 +489,6 @@ QUnit.test('many2many_tags_email widget can load more than 40 records', async fu assert.strictEqual(document.querySelectorAll('.o_field_widget[name="partner_ids"] .badge').length, 100); - await clickEdit(document.body); - assert.containsOnce(document.body, '.o_form_editable'); // add a record to the relation diff --git a/addons/mail/static/tests/qunit_suite_tests/components/field_text_emojis_tests.js b/addons/mail/static/tests/qunit_suite_tests/components/field_text_emojis_tests.js index b48a21bf78f..ed9f43f7a63 100644 --- a/addons/mail/static/tests/qunit_suite_tests/components/field_text_emojis_tests.js +++ b/addons/mail/static/tests/qunit_suite_tests/components/field_text_emojis_tests.js @@ -1,6 +1,6 @@ /**@odoo-module **/ -import { clickEdit, clickSave, getFixture } from "@web/../tests/helpers/utils"; +import { getFixture } from "@web/../tests/helpers/utils"; import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; QUnit.module("Field text emojis", (hooks) => { @@ -34,13 +34,7 @@ QUnit.module("Field text emojis", (hooks) => { assert.containsOnce(target, ".o_field_text_emojis"); assert.containsOnce(target, ".o_field_text_emojis button"); - assert.isNotVisible(target.querySelector(".o_field_text_emojis button")); - - await clickEdit(target); + assert.isVisible(target.querySelector(".o_field_text_emojis button")); assert.isVisible(target, ".o_field_text_emojis button .fa-smile"); - - await clickSave(target); - assert.containsOnce(target, ".o_field_text_emojis button"); - assert.isNotVisible(target.querySelector(".o_field_text_emojis button")); }); }); diff --git a/addons/mail/static/tests/qunit_suite_tests/m2x_avatar_user_tests.js b/addons/mail/static/tests/qunit_suite_tests/m2x_avatar_user_tests.js index 5826d05e1cd..a72ae912db8 100644 --- a/addons/mail/static/tests/qunit_suite_tests/m2x_avatar_user_tests.js +++ b/addons/mail/static/tests/qunit_suite_tests/m2x_avatar_user_tests.js @@ -143,7 +143,7 @@ QUnit.module('mail', {}, function () { 'view_mode': 'form', 'views': [[false, 'form']], }); - assert.strictEqual(target.querySelector(".o_field_many2one_avatar_user > div > a").textContent, "Mario") + assert.strictEqual(target.querySelector(".o_field_many2one_avatar_user input").value, "Mario") triggerHotkey("control+k") await nextTick(); @@ -161,7 +161,7 @@ QUnit.module('mail', {}, function () { ]) await click(target, "#o_command_3") await nextTick(); - assert.strictEqual(target.querySelector(".o_field_many2one_avatar_user > div > a").textContent, "Luigi") + assert.strictEqual(target.querySelector(".o_field_many2one_avatar_user input").value, "Luigi") }); QUnit.test('many2one_avatar_user widget edited by the smart action "Assign to me"', async function (assert) { @@ -184,7 +184,7 @@ QUnit.module('mail', {}, function () { 'view_mode': 'form', 'views': [[false, 'form']], }); - assert.strictEqual(target.querySelector(".o_field_many2one_avatar_user > div > a").textContent, "Mario") + assert.strictEqual(target.querySelector(".o_field_many2one_avatar_user input").value, "Mario") triggerHotkey("control+k") await nextTick(); const idx = [...target.querySelectorAll(".o_command")].map(el => el.textContent).indexOf("Assign/Unassign to meALT + SHIFT + I") @@ -193,14 +193,14 @@ QUnit.module('mail', {}, function () { // Assign me (Luigi) triggerHotkey("alt+shift+i") await nextTick(); - assert.strictEqual(target.querySelector(".o_field_many2one_avatar_user > div > a").textContent, "Luigi") + assert.strictEqual(target.querySelector(".o_field_many2one_avatar_user input").value, "Luigi") // Unassign me triggerHotkey("control+k"); await nextTick(); await click([...target.querySelectorAll(".o_command")][idx]) await nextTick(); - assert.containsNone(target, ".o_field_many2one_avatar_user > div > a"); + assert.strictEqual(target.querySelector(".o_field_many2one_avatar_user input").value, "") }); QUnit.test('many2many_avatar_user widget edited by the smart action "Assign to..."', async function (assert) { diff --git a/addons/mail/static/tests/qunit_suite_tests/widgets/form_renderer_tests.js b/addons/mail/static/tests/qunit_suite_tests/widgets/form_renderer_tests.js index 4263e194f71..4d63990a93c 100644 --- a/addons/mail/static/tests/qunit_suite_tests/widgets/form_renderer_tests.js +++ b/addons/mail/static/tests/qunit_suite_tests/widgets/form_renderer_tests.js @@ -560,7 +560,7 @@ QUnit.test('chatter should become enabled when creation done', async function (a }); QUnit.test('read more/less links are not duplicated when switching from read to edit mode', async function (assert) { - assert.expect(5); + assert.expect(3); const pyEnv = await startServer(); const resPartnerId1 = pyEnv['res.partner'].create({}); @@ -622,33 +622,10 @@ QUnit.test('read more/less links are not duplicated when switching from read to '.o_Message_readMoreLess', "there should be only one read more" ); - await afterNextRender(() => afterEvent({ - eventName: 'o-component-message-read-more-less-inserted', - func: () => document.querySelector('.o_form_button_edit').click(), - message: "should wait until read more/less is inserted after clicking on edit", - predicate: ({ message }) => message.id === mailMessageId1, - })); - assert.containsOnce( - document.body, - '.o_Message_readMoreLess', - "there should still be only one read more after switching to edit mode" - ); - - await afterNextRender(() => afterEvent({ - eventName: 'o-component-message-read-more-less-inserted', - func: () => document.querySelector('.o_form_button_cancel').click(), - message: "should wait until read more/less is inserted after canceling edit", - predicate: ({ message }) => message.id === mailMessageId1, - })); - assert.containsOnce( - document.body, - '.o_Message_readMoreLess', - "there should still be only one read more after switching back to read mode" - ); }); QUnit.test('read more links becomes read less after being clicked', async function (assert) { - assert.expect(6); + assert.expect(5); const pyEnv = await startServer(); const resPartnerId1 = pyEnv['res.partner'].create({}); @@ -716,18 +693,6 @@ QUnit.test('read more links becomes read less after being clicked', async functi "Read More/Less link should contain 'Read More' as text" ); - await afterNextRender(() => afterEvent({ - eventName: 'o-component-message-read-more-less-inserted', - func: () => document.querySelector('.o_form_button_edit').click(), - message: "should wait until read more/less is inserted after clicking on edit", - predicate: ({ message }) => message.id === mailMessageId1, - })); - assert.strictEqual( - document.querySelector('.o_Message_readMoreLess').textContent, - 'Read More', - "Read More/Less link should contain 'Read More' as text" - ); - document.querySelector('.o_Message_readMoreLess').click(); assert.strictEqual( document.querySelector('.o_Message_readMoreLess').textContent, diff --git a/addons/mass_mailing/static/src/js/tours/mass_mailing_editor_tour.js b/addons/mass_mailing/static/src/js/tours/mass_mailing_editor_tour.js index ef194c00ad0..95ce87fbec8 100644 --- a/addons/mass_mailing/static/src/js/tours/mass_mailing_editor_tour.js +++ b/addons/mass_mailing/static/src/js/tours/mass_mailing_editor_tour.js @@ -47,9 +47,11 @@ odoo.define('mass_mailing.mass_mailing_editor_tour', function (require) { trigger: 'input[name="subject"]', run: 'text Test', }, { - trigger: 'button.o_form_button_save', - }, { - trigger: 'iframe.o_readonly', + trigger: '.o_form_view', // blur previous input + }, + ...tour.stepUtils.saveForm(), + { + trigger: 'iframe .o_editable', run: () => {}, }]); }); diff --git a/addons/mass_mailing/static/tests/mass_mailing_html_tests.js b/addons/mass_mailing/static/tests/mass_mailing_html_tests.js index 6a7c37b1d02..920beb418eb 100644 --- a/addons/mass_mailing/static/tests/mass_mailing_html_tests.js +++ b/addons/mass_mailing/static/tests/mass_mailing_html_tests.js @@ -69,7 +69,7 @@ QUnit.module('field html', (hooks) => { }); QUnit.test('save arch and html', async function (assert) { - assert.expect(4); + assert.expect(2); await makeView({ type: "form", @@ -95,15 +95,6 @@ QUnit.module('field html', (hooks) => { let fieldReadonly = fixture.querySelector('.o_field_widget[name="body_html"]'); let fieldEdit = fixture.querySelector('.o_field_widget[name="body_arch"]'); - assert.strictEqual($(fieldReadonly).css('display'), 'block', "should display the readonly mode"); - assert.strictEqual($(fieldEdit).css('display'), 'none', "should hide the edit mode"); - - await testUtils.clickEdit(fixture); - await testUtils.nextTick(); - - fieldReadonly = fixture.querySelector('.o_field_widget[name="body_html"]'); - fieldEdit = fixture.querySelector('.o_field_widget[name="body_arch"]'); - assert.strictEqual($(fieldReadonly).css('display'), 'none', "should hide the readonly mode"); assert.strictEqual($(fieldEdit).css('display'), 'block', "should display the edit mode"); }); diff --git a/addons/project/static/src/js/tours/project.js b/addons/project/static/src/js/tours/project.js index ab7289dd9a4..9f836dcc083 100644 --- a/addons/project/static/src/js/tours/project.js +++ b/addons/project/static/src/js/tours/project.js @@ -108,7 +108,7 @@ tour.register('project_tour', { run: "click", }, { trigger: ".breadcrumb-item:not(.active):last", - extra_trigger: '.o_form_project_tasks.o_form_readonly', + extra_trigger: '.o_form_project_tasks.o_form_saved', content: Markup(_t("Let's go back to the kanban view to have an overview of your next tasks.")), position: "right", run: 'click', 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 9366face458..43dbd58d957 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 @@ -101,7 +101,7 @@ tour.register('purchase_matrix_tour', { }, { trigger: 'span:contains("Confirm")', run: 'click' // apply the matrix -}, ...tour.stepUtils.saveForm('.o_field_cell.o_data_cell.o_list_number:contains("8.20")') +}, ...tour.stepUtils.saveForm({ extra_trigger: '.o_field_cell.o_data_cell.o_list_number:contains("8.20")' }) ]); diff --git a/addons/resource/static/tests/components/section_one2many_field_tests.js b/addons/resource/static/tests/components/section_one2many_field_tests.js index 94167d4f121..56cb49e773b 100644 --- a/addons/resource/static/tests/components/section_one2many_field_tests.js +++ b/addons/resource/static/tests/components/section_one2many_field_tests.js @@ -1,6 +1,6 @@ /** @odoo-module */ -import { clickEdit, getFixture } from "@web/../tests/helpers/utils"; +import { getFixture } from "@web/../tests/helpers/utils"; import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; QUnit.module("SectionOneToManyField", (hooks) => { @@ -73,13 +73,8 @@ QUnit.module("SectionOneToManyField", (hooks) => { assert.doesNotHaveClass(rows[1], "o_is_line_section fw-bold"); assert.strictEqual(rows[0].textContent, "firstSectionTitle"); assert.strictEqual(rows[1].textContent, "recordTitle5"); - assert.strictEqual(rows[0].querySelector("td[name=title]").getAttribute("colspan"), "2"); + assert.strictEqual(rows[0].querySelector("td[name=title]").getAttribute("colspan"), "3"); assert.strictEqual(rows[1].querySelector("td[name=title]").getAttribute("colspan"), null); - - assert.containsNone(target, ".o_list_record_remove"); - - await clickEdit(target); - assert.containsOnce(target, ".o_list_record_remove"); assert.containsNone(target, ".o_is_line_section .o_list_record_remove"); }); diff --git a/addons/sale_expense/static/tests/sale_order_many2one_tests.js b/addons/sale_expense/static/tests/sale_order_many2one_tests.js index 2e33a52b6ef..740fb2ae062 100644 --- a/addons/sale_expense/static/tests/sale_order_many2one_tests.js +++ b/addons/sale_expense/static/tests/sale_order_many2one_tests.js @@ -1,7 +1,7 @@ /** @odoo-module **/ import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; -import { clickDropdown, clickEdit, getFixture } from "@web/../tests/helpers/utils"; +import { clickDropdown, getFixture } from "@web/../tests/helpers/utils"; QUnit.module('sale_expense', { @@ -67,7 +67,6 @@ QUnit.module('sale_expense', { '
' }); - await clickEdit(this.target); await clickDropdown(this.target, "sale_order_id"); assert.containsN(this.target, 'li.o-autocomplete--dropdown-item', 9); diff --git a/addons/survey/static/tests/components/description_page_field_tests.js b/addons/survey/static/tests/components/description_page_field_tests.js index 9e8687be88d..dbc5cb6e4cb 100644 --- a/addons/survey/static/tests/components/description_page_field_tests.js +++ b/addons/survey/static/tests/components/description_page_field_tests.js @@ -1,6 +1,6 @@ /** @odoo-module */ -import { click, clickEdit, getFixture } from "@web/../tests/helpers/utils"; +import { click, getFixture } from "@web/../tests/helpers/utils"; import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; QUnit.module("DescriptionPageField", (hooks) => { @@ -78,7 +78,6 @@ QUnit.module("DescriptionPageField", (hooks) => { assert.containsN(target, "td.o_survey_description_page_cell", 2); assert.containsNone(target, "button.o_icon_button"); - await clickEdit(target); await click(target.querySelector(".o_data_cell")); assert.containsOnce(target.querySelector(".o_data_row"), "button.o_icon_button"); assert.containsNone(target, ".modal .o_form_view"); diff --git a/addons/survey/static/tests/components/question_page_one2many_field_tests.js b/addons/survey/static/tests/components/question_page_one2many_field_tests.js index d7c70bc3447..a1abe443f02 100644 --- a/addons/survey/static/tests/components/question_page_one2many_field_tests.js +++ b/addons/survey/static/tests/components/question_page_one2many_field_tests.js @@ -1,13 +1,6 @@ /** @odoo-module */ -import { - click, - clickEdit, - editInput, - getFixture, - nextTick, - triggerHotkey, -} from "@web/../tests/helpers/utils"; +import { click, editInput, getFixture, nextTick, triggerHotkey } from "@web/../tests/helpers/utils"; import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; QUnit.module("QuestionPageOneToManyField", (hooks) => { @@ -108,7 +101,9 @@ QUnit.module("QuestionPageOneToManyField", (hooks) => {
`, + mode: "readonly", }); + await click(target.querySelector(".o_data_cell")); assert.containsNone(target, ".o_selected_row"); assert.containsOnce(target, ".modal .o_form_view"); @@ -131,7 +126,6 @@ QUnit.module("QuestionPageOneToManyField", (hooks) => {
`, }); - await clickEdit(target); await click(target.querySelector(".o_data_cell")); assert.hasClass(target.querySelector(".o_is_section"), "o_selected_row"); assert.containsNone(target, ".modal .o_form_view"); @@ -155,7 +149,6 @@ QUnit.module("QuestionPageOneToManyField", (hooks) => { `, }); - await clickEdit(target); await click(target.querySelector(".o_data_row:nth-child(2) .o_data_cell")); assert.containsNone(target, ".o_selected_row"); assert.containsOnce(target, ".modal .o_form_view"); @@ -184,7 +177,6 @@ QUnit.module("QuestionPageOneToManyField", (hooks) => { `, }); - await clickEdit(target); assert.containsNone(target, ".o_selected_row"); await click(target.querySelectorAll(".o_field_x2many_list_row_add a")[1]); @@ -215,7 +207,6 @@ QUnit.module("QuestionPageOneToManyField", (hooks) => { `, }); - await clickEdit(target); await click(target.querySelector(".o_field_x2many_list_row_add a")); assert.containsNone(target, ".o_selected_row"); assert.containsOnce(target, ".modal .o_form_view"); @@ -241,7 +232,6 @@ QUnit.module("QuestionPageOneToManyField", (hooks) => { `, }); - await clickEdit(target); await click(target.querySelector(".o_data_row .o_data_cell")); assert.containsOnce(target, ".o_selected_row.o_is_section"); diff --git a/addons/test_sale_product_configurators/static/tests/tours/event_sale_with_product_configurator_ui.js b/addons/test_sale_product_configurators/static/tests/tours/event_sale_with_product_configurator_ui.js index 97ab0359514..90d257762ad 100644 --- a/addons/test_sale_product_configurators/static/tests/tours/event_sale_with_product_configurator_ui.js +++ b/addons/test_sale_product_configurators/static/tests/tours/event_sale_with_product_configurator_ui.js @@ -113,5 +113,5 @@ tour.register('event_sale_with_product_configurator_tour', { in_modal: false, }, { trigger: '.o_event_sale_js_event_configurator_ok', -}, ...tour.stepUtils.saveForm('.o_field_cell.o_data_cell.o_list_number:contains("60.00")'), +}, ...tour.stepUtils.saveForm({ extra_trigger: '.o_field_cell.o_data_cell.o_list_number:contains("60.00")' }), ]); diff --git a/addons/web/static/src/core/datepicker/datepicker.js b/addons/web/static/src/core/datepicker/datepicker.js index 81ba4fdbfa0..599558b3e74 100644 --- a/addons/web/static/src/core/datepicker/datepicker.js +++ b/addons/web/static/src/core/datepicker/datepicker.js @@ -171,7 +171,7 @@ export class DatePicker extends Component { */ updateInput({ useStatic } = {}) { const [formattedDate] = this.formatValue(this.date, this.getOptions(useStatic)); - if (formattedDate) { + if (formattedDate !== null) { this.inputRef.el.value = formattedDate; } } @@ -230,7 +230,7 @@ export class DatePicker extends Component { // Reset to default (= given) date. this.updateInput(); } - if (!areDateEquals(this.date, parsedDate)) { + if (parsedDate !== null && !areDateEquals(this.date, parsedDate)) { this.props.onDateTimeChanged(parsedDate); } } diff --git a/addons/web/static/src/search/action_menus/action_menus.js b/addons/web/static/src/search/action_menus/action_menus.js index fdecb049424..224abd8f176 100644 --- a/addons/web/static/src/search/action_menus/action_menus.js +++ b/addons/web/static/src/search/action_menus/action_menus.js @@ -111,7 +111,8 @@ export class ActionMenus extends Component { * @private * @param {Object} item */ - onItemSelected(item) { + async onItemSelected(item) { + await this.props.onBeforeAction(item); if (item.callback) { item.callback([item]); } else if (item.action) { @@ -142,8 +143,10 @@ ActionMenus.props = { }, }, onActionExecuted: { type: Function, optional: true }, + onBeforeAction: { type: Function, optional: true }, }; ActionMenus.defaultProps = { onActionExecuted: () => {}, + onBeforeAction: () => {}, }; ActionMenus.template = "web.ActionMenus"; diff --git a/addons/web/static/src/views/basic_relational_model.js b/addons/web/static/src/views/basic_relational_model.js index c7b0b01b4a4..719209b13f2 100644 --- a/addons/web/static/src/views/basic_relational_model.js +++ b/addons/web/static/src/views/basic_relational_model.js @@ -326,6 +326,10 @@ export class Record extends DataPoint { return !this.resId; } + get isValid() { + return !this._invalidFields.size; + } + get resId() { if (this.__bm_handle__) { const resId = this.model.__bm__.localData[this.__bm_handle__].res_id; @@ -349,6 +353,10 @@ export class Record extends DataPoint { return Promise.all([...proms, this._updatePromise]); } + // ------------------------------------------------------------------------- + // Getters + // ------------------------------------------------------------------------- + async checkValidity(urgent) { if (!urgent) { await this.askChanges(); @@ -685,7 +693,7 @@ export class Record extends DataPoint { * reloading after changes are applied, typically used to defer the load. * @returns {Promise} */ - async save(options = { stayInEdition: false, noReload: false, savePoint: false }) { + async save(options = { stayInEdition: true, noReload: false, savePoint: false }) { const shouldSwitchToReadonly = !options.stayInEdition && this.isInEdition; let resolveSavePromise; this._savePromise = new Promise((r) => { @@ -810,9 +818,6 @@ export class Record extends DataPoint { this.model.__bm__.discardChanges(this.__bm_handle__); this._invalidFields = new Set(); this.__syncData(); - if (this.resId) { - this.switchMode("readonly"); - } this.model.notify(); } diff --git a/addons/web/static/src/views/fields/fields.scss b/addons/web/static/src/views/fields/fields.scss index d877330e9e6..579411fe1e0 100644 --- a/addons/web/static/src/views/fields/fields.scss +++ b/addons/web/static/src/views/fields/fields.scss @@ -1,3 +1,45 @@ .o_field_cursor_disabled { cursor: not-allowed; } + +.o_field_widget:not(.o_legacy_field_widget) { + .o_input { + border: $input-border-width solid transparent; + padding: $o-input-padding-y $o-input-padding-x; + + &:hover, &:focus { + border-color: var(--o-input-border-color); + } + } + + .o_field_many2one_selection, .o_field_many2many_selection, .o_datepicker { + .o_dropdown_button, .o_datepicker_button { + display: none; + } + + &:hover, &:focus-within { + .o_dropdown_button, .o_datepicker_button { + display: unset; + } + } + } + + .o_field_many2one_selection { + display: flex; + width: 100%; + + .o_external_button { + visibility: hidden; + } + + &:hover, &:focus-within { + .o_external_button { + visibility: visible; + } + } + } +} + +.o_kanban_quick_create .o_field_widget .o_input { + border-color: var(--o-input-border-color); +} diff --git a/addons/web/static/src/views/fields/many2many_tags/many2many_tags_field.scss b/addons/web/static/src/views/fields/many2many_tags/many2many_tags_field.scss index 8ba997bd7bf..4be3b641cf8 100644 --- a/addons/web/static/src/views/fields/many2many_tags/many2many_tags_field.scss +++ b/addons/web/static/src/views/fields/many2many_tags/many2many_tags_field.scss @@ -12,6 +12,10 @@ .o_tags_input { padding: 1px 0; + &:hover, &:focus-within { + border-color: var(--o-input-border-color); + } + .o_tag { padding-left: 0.6em; padding-right: 0.6em; diff --git a/addons/web/static/src/views/fields/many2many_tags_avatar/many2many_tags_avatar_field.scss b/addons/web/static/src/views/fields/many2many_tags_avatar/many2many_tags_avatar_field.scss index 490a1cd8307..21d169e6cfb 100644 --- a/addons/web/static/src/views/fields/many2many_tags_avatar/many2many_tags_avatar_field.scss +++ b/addons/web/static/src/views/fields/many2many_tags_avatar/many2many_tags_avatar_field.scss @@ -3,6 +3,10 @@ .o_tags_input { padding: 1px 0; + + &:hover, &:focus-within { + border-color: var(--o-input-border-color); + } } .o_field_many2many_selection { diff --git a/addons/web/static/src/views/fields/progress_bar/progress_bar_field.js b/addons/web/static/src/views/fields/progress_bar/progress_bar_field.js index bba216f92b9..0eb33fc45c8 100644 --- a/addons/web/static/src/views/fields/progress_bar/progress_bar_field.js +++ b/addons/web/static/src/views/fields/progress_bar/progress_bar_field.js @@ -3,12 +3,11 @@ import { _lt } from "@web/core/l10n/translation"; import { registry } from "@web/core/registry"; import { useAutofocus } from "@web/core/utils/hooks"; -import { useDebounced } from "@web/core/utils/timing"; import { useNumpadDecimal } from "../numpad_decimal_hook"; import { parseFloat } from "../parsers"; import { standardFieldProps } from "../standard_field_props"; -const { Component, onWillUpdateProps, useRef, useState } = owl; +const { Component, onWillUpdateProps, useRef, useState, useExternalListener } = owl; const formatters = registry.category("formatters"); const parsers = registry.category("parsers"); @@ -17,13 +16,13 @@ export class ProgressBarField extends Component { useNumpadDecimal(); useAutofocus({ refName: "maxValue", selectAll: true }); useAutofocus({ refName: "currentValue", selectAll: true }); + useExternalListener(document.body, "click", this.onClickAway, { capture: true }); this.root = useRef("numpadDecimal"); this.state = useState({ currentValue: this.getCurrentValue(this.props), maxValue: this.getMaxValue(this.props), isEditing: false, }); - this.onBlurDebounced = useDebounced(this.onBlur); onWillUpdateProps((nextProps) => { Object.assign(this.state, { currentValue: this.getCurrentValue(nextProps), @@ -113,14 +112,13 @@ export class ProgressBarField extends Component { this.state.isEditing = true; } } - // When both max and current value are editable, as one input is blurred when - // switching to the other, the state would revert to isEditing = false. We need - // to stay in edition mode if the focus is still in the field. - onBlur() { - if (this.root.el && this.root.el.contains(document.activeElement)) { - return; + /** + * @param {MouseEvent} ev + */ + onClickAway(ev) { + if (this.root.el && !this.root.el.contains(ev.target)) { + this.state.isEditing = false; } - this.state.isEditing = false; } onCurrentValueInput(ev) { diff --git a/addons/web/static/src/views/fields/progress_bar/progress_bar_field.xml b/addons/web/static/src/views/fields/progress_bar/progress_bar_field.xml index 0c782b1a2f6..eb5107ec10b 100644 --- a/addons/web/static/src/views/fields/progress_bar/progress_bar_field.xml +++ b/addons/web/static/src/views/fields/progress_bar/progress_bar_field.xml @@ -33,7 +33,6 @@ t-att-required="props.required" t-on-change="onCurrentValueChange" t-on-input="onCurrentValueInput" - t-on-blur="onBlur" /> % @@ -48,7 +47,6 @@ t-att-value="formatCurrentValue()" t-on-change="onCurrentValueChange" t-on-input="onCurrentValueInput" - t-on-blur="onBlurDebounced" /> diff --git a/addons/web/static/src/views/fields/relational_utils.js b/addons/web/static/src/views/fields/relational_utils.js index f0f6b82b918..9ec84a605b9 100644 --- a/addons/web/static/src/views/fields/relational_utils.js +++ b/addons/web/static/src/views/fields/relational_utils.js @@ -437,9 +437,7 @@ export class X2ManyFieldDialog extends Component { this.modalRef.el.querySelector(`#${autofocusFieldId}`)) || this.modalRef.el.querySelector(".o_field_widget input"); } else { - elementToFocus = - this.modalRef.el.querySelector("button.btn-primary") || - this.modalRef.el.querySelector(".o_control_panel .o_form_button_edit"); + elementToFocus = this.modalRef.el.querySelector("button.btn-primary"); } if (elementToFocus) { elementToFocus.focus(); diff --git a/addons/web/static/src/views/fields/x2many/x2many_field.js b/addons/web/static/src/views/fields/x2many/x2many_field.js index abb6a95284a..cecb17b0a59 100644 --- a/addons/web/static/src/views/fields/x2many/x2many_field.js +++ b/addons/web/static/src/views/fields/x2many/x2many_field.js @@ -151,7 +151,6 @@ export class X2ManyField extends Component { return props; } - const mode = this.props.record.mode; // handle column_invisible modifiers const columns = archInfo.columns .map((col) => { @@ -172,24 +171,6 @@ export class X2ManyField extends Component { return col.buttons.length > 0; } return true; - }) - .filter((col) => { - // filter out oe_read_only/oe_edit_only columns - // note: remove this oe_read/edit_only logic when form view - // will always be in edit mode - if (col.type === "field") { - if (mode === "readonly") { - return !/\boe_edit_only\b/.test(col.className); - } else { - return !/\boe_read_only\b/.test(col.className); - } - } else if (col.type === "button_group") { - if (mode === "readonly") { - return col.buttons.some((btn) => !/\boe_edit_only\b/.test(btn.className)); - } else { - return col.buttons.some((btn) => !/\boe_read_only\b/.test(btn.className)); - } - } }); const editable = archInfo.editable || this.props.editable; diff --git a/addons/web/static/src/views/form/control_panel/form_control_panel.js b/addons/web/static/src/views/form/control_panel/form_control_panel.js new file mode 100644 index 00000000000..b4ec4b5ead3 --- /dev/null +++ b/addons/web/static/src/views/form/control_panel/form_control_panel.js @@ -0,0 +1,6 @@ +/** @odoo-module **/ + +import { ControlPanel } from "@web/search/control_panel/control_panel"; + +export class FormControlPanel extends ControlPanel {} +FormControlPanel.template = "web.FormControlPanel"; diff --git a/addons/web/static/src/views/form/control_panel/form_control_panel.scss b/addons/web/static/src/views/form/control_panel/form_control_panel.scss new file mode 100644 index 00000000000..8800ecb22ef --- /dev/null +++ b/addons/web/static/src/views/form/control_panel/form_control_panel.scss @@ -0,0 +1,6 @@ + +.o_form_view .o_control_panel { + .o_cp_action_menus { + border-right: 1px solid $border-color; + } +} diff --git a/addons/web/static/src/views/form/control_panel/form_control_panel.xml b/addons/web/static/src/views/form/control_panel/form_control_panel.xml new file mode 100644 index 00000000000..165afc5c3c5 --- /dev/null +++ b/addons/web/static/src/views/form/control_panel/form_control_panel.xml @@ -0,0 +1,31 @@ + + + + +
+
+
+ + + + + + + + + + + +
+
+ + + +
+
+
+
+ +
diff --git a/addons/web/static/src/views/form/form_compiler.js b/addons/web/static/src/views/form/form_compiler.js index 2709bcebfdc..92b80fa3d94 100644 --- a/addons/web/static/src/views/form/form_compiler.js +++ b/addons/web/static/src/views/form/form_compiler.js @@ -175,6 +175,13 @@ export class FormCompiler extends ViewCompiler { return hasContent ? buttonBox : null; } + compileButton(el, params) { + const compiled = super.compileButton(el, params); + compiled.setAttribute("disable", "props.disableViewButtons"); + compiled.setAttribute("enable", "props.enableViewButtons"); + return compiled; + } + /** * @override */ @@ -218,9 +225,11 @@ export class FormCompiler extends ViewCompiler { const displayClasses = sheetNode ? `d-flex {{ uiService.size < ${SIZES.XXL} ? "flex-column" : "flex-nowrap h-100" }}` : "d-block"; + const stateClasses = + "{{ props.record.isVirtual or props.record.isDirty ? 'o_form_dirty' : 'o_form_saved' }}"; const form = createElement("div", { "t-att-class": "props.class", - "t-attf-class": `{{props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} ${displayClasses}`, + "t-attf-class": `{{props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} ${displayClasses} ${stateClasses}`, }); if (!sheetNode) { for (const child of el.childNodes) { @@ -329,13 +338,6 @@ export class FormCompiler extends ViewCompiler { : `props.record.fields.${fieldName}.string`, fieldInfo: `props.archInfo.fieldNodes[${fieldId}]`, }; - // note: remove this oe_read/edit_only logic when form view - // will always be in edit mode - if (child.classList.contains("oe_read_only")) { - props.className = `'oe_read_only'`; - } else if (child.classList.contains("oe_edit_only")) { - props.className = `'oe_edit_only'`; - } mainSlot.setAttribute("props", objectToString(props)); mainSlot.setAttribute("Component", "constructor.components.FormLabel"); mainSlot.setAttribute("subType", "'item_component'"); @@ -422,9 +424,6 @@ export class FormCompiler extends ViewCompiler { const slot = createElement("t", { "t-set-slot": `button_${slotId++}`, isVisible: button.getAttribute("t-if") || true, - displayInReadOnly: - button.hasAttribute("className") && - button.getAttribute("className").includes("oe_read_only"), }); append(slot, button); append(statusBarButtons, slot); diff --git a/addons/web/static/src/views/form/form_controller.js b/addons/web/static/src/views/form/form_controller.js index 3e6a81c2360..c2e69a98dad 100644 --- a/addons/web/static/src/views/form/form_controller.js +++ b/addons/web/static/src/views/form/form_controller.js @@ -15,6 +15,8 @@ import { standardViewProps } from "@web/views/standard_view_props"; import { isX2Many } from "@web/views/utils"; import { useViewButtons } from "@web/views/view_button/view_button_hook"; import { useSetupView } from "@web/views/view_hook"; +import { FormStatusIndicator } from "./form_status_indicator/form_status_indicator"; +import { useFormErrorDialog } from "./form_error_dialog/form_error_dialog"; const { Component, onWillStart, useEffect, useRef, onRendered, useState, toRaw } = owl; @@ -94,7 +96,14 @@ export class FormController extends Component { this.user = useService("user"); this.viewService = useService("view"); this.ui = useService("ui"); + this.state = useState({ + isDisabled: false, + }); useBus(this.ui.bus, "resize", this.render); + useFormErrorDialog(async () => { + await this.discard(); + this.env.config.historyBack(); + }); this.archInfo = this.props.archInfo; const activeFields = this.archInfo.activeFields; @@ -103,6 +112,16 @@ export class FormController extends Component { const beforeLoadProm = new Promise((r) => { this.beforeLoadResolver = r; }); + + const { create, edit } = this.archInfo.activeActions; + this.canCreate = create && !this.props.preventCreate; + this.canEdit = edit && !this.props.preventEdit; + + let mode = this.props.mode || "edit"; + if (!this.canEdit) { + mode = "readonly"; + } + this.model = useModel( this.props.Model, { @@ -113,17 +132,13 @@ export class FormController extends Component { activeFields, viewMode: "form", rootType: "record", - mode: this.props.mode, + mode, beforeLoadProm, }, { ignoreUseSampleModel: true, } ); - const { create, edit } = this.archInfo.activeActions; - - this.canCreate = create && !this.props.preventCreate; - this.canEdit = edit && !this.props.preventEdit; this.cpButtonsRef = useRef("cpButtons"); @@ -235,9 +250,9 @@ export class FormController extends Component { !isInEdition && !rootRef.el.querySelector(".o_content").contains(document.activeElement) ) { - const elementToFocus = - rootRef.el.querySelector(".o_content button.btn-primary") || - rootRef.el.querySelector(".o_control_panel .o_form_button_edit"); + const elementToFocus = rootRef.el.querySelector( + ".o_content button.btn-primary" + ); if (elementToFocus) { elementToFocus.focus(); } @@ -301,11 +316,18 @@ export class FormController extends Component { key: "delete", description: this.env._t("Delete"), callback: () => this.deleteRecord(), + skipSave: true, }); } return Object.assign({}, this.props.info.actionMenus, { other: otherActionItems }); } + async beforeAction(item) { + if ((this.model.root.isDirty || this.model.root.isVirtual) && !item.skipSave) { + await this.model.root.save({ stayInEdition: true }); + } + } + async duplicateRecord() { await this.model.root.duplicate(); } @@ -325,16 +347,11 @@ export class FormController extends Component { } disableButtons() { - const btns = this.cpButtonsRef.el.querySelectorAll(".o_cp_buttons button"); - for (const btn of btns) { - btn.setAttribute("disabled", "1"); - } - return btns; + this.state.isDisabled = true; } - enableButtons(btns) { - for (const btn of btns) { - btn.removeAttribute("disabled"); - } + + enableButtons() { + this.state.isDisabled = false; } async edit() { @@ -342,12 +359,20 @@ export class FormController extends Component { } async create() { - this.disableButtons(); - await this.model.load({ resId: null }); + await this.model.root.askChanges(); // ensures that isDirty is correct + let canProceed = true; + if (this.model.root.isDirty) { + canProceed = await this.model.root.save({ stayInEdition: true }); + } + if (canProceed) { + this.disableButtons(); + await this.model.load({ resId: null }); + this.enableButtons(); + } } async save(params = {}) { - const disabledButtons = this.disableButtons(); + this.disableButtons(); const record = this.model.root; let saved = false; @@ -360,13 +385,17 @@ export class FormController extends Component { ...record.dirtyTranslatableFields, ]); } - - if (this.props.saveRecord) { - saved = await this.props.saveRecord(record, params); - } else { - saved = await record.save(); + try { + if (this.props.saveRecord) { + saved = await this.props.saveRecord(record, params); + } else { + saved = await record.save(); + } + } catch { + // if the save failed, we want to re-enable buttons + this.enableButtons(); } - this.enableButtons(disabledButtons); + this.enableButtons(); if (saved && this.props.onSave) { this.props.onSave(record); } @@ -424,7 +453,7 @@ export class FormController extends Component { } FormController.template = `web.FormView`; -FormController.components = { ActionMenus, Layout }; +FormController.components = { ActionMenus, FormStatusIndicator, Layout }; FormController.props = { ...standardViewProps, discardRecord: { type: Function, optional: true }, diff --git a/addons/web/static/src/views/form/form_controller.scss b/addons/web/static/src/views/form/form_controller.scss index 6a44a81253e..21a10b58558 100644 --- a/addons/web/static/src/views/form/form_controller.scss +++ b/addons/web/static/src/views/form/form_controller.scss @@ -201,7 +201,7 @@ } .oe_title { - max-width: map-get($container-max-widths, md) - (2 * $o-horizontal-padding); + max-width: 75%; } .o_field_x2many .o_list_table .o_handle_cell .o_row_handle { @@ -986,8 +986,15 @@ max-width: 300px; } } - @include media-breakpoint-down(md) { + .o_control_panel { + padding-right: 16px; + + > div.o_cp_top { + display: grid; + } + } + .o_cp_buttons { width: 100%; div, .o-kanban-button-new { diff --git a/addons/web/static/src/views/form/form_controller.xml b/addons/web/static/src/views/form/form_controller.xml index 89541836651..348f0c484ae 100644 --- a/addons/web/static/src/views/form/form_controller.xml +++ b/addons/web/static/src/views/form/form_controller.xml @@ -7,52 +7,37 @@ - + - - + + + onBeforeAction.bind="beforeAction" + /> - - - + + + - - - + + + - + @@ -68,11 +53,8 @@ Discard -
- -
diff --git a/addons/web/static/src/views/form/form_error_dialog/form_error_dialog.js b/addons/web/static/src/views/form/form_error_dialog/form_error_dialog.js new file mode 100644 index 00000000000..c93447a2b30 --- /dev/null +++ b/addons/web/static/src/views/form/form_error_dialog/form_error_dialog.js @@ -0,0 +1,66 @@ +/** @odoo-module **/ + +import { Dialog } from "@web/core/dialog/dialog"; +import { RPCError } from "@web/core/network/rpc_service"; +import { registry } from "@web/core/registry"; + +const { Component, onWillDestroy } = owl; + +export class FormErrorDialog extends Component { + setup() { + const { data, message } = this.props; + if (data && data.arguments && data.arguments.length > 0) { + this.message = data.arguments[0]; + } else { + this.message = message; + } + } + + async discard() { + await this.props.onDiscard(); + this.props.close(); + } +} +FormErrorDialog.template = "web.FormErrorDialog"; +FormErrorDialog.components = { Dialog }; + +function makeFormErrorHandler(onDiscard) { + return (env, error, originalError) => { + if ( + originalError && + originalError.legacy && + originalError.message && + originalError.message instanceof RPCError + ) { + const event = originalError.event; + originalError = originalError.message; + error.unhandledRejectionEvent.preventDefault(); + if (event.isDefaultPrevented()) { + // in theory, here, event was already handled + return true; + } + event.preventDefault(); + + env.services.dialog.add(FormErrorDialog, { + message: originalError.message, + data: originalError.data, + onDiscard, + }); + + return true; + } + return false; + }; +} + +let formId = 0; + +export function useFormErrorDialog(onDiscard) { + const errorHandlerKey = `form_error_handler_${++formId}`; + registry + .category("error_handlers") + .add(errorHandlerKey, makeFormErrorHandler(onDiscard), { sequence: 0 }); + onWillDestroy(() => { + registry.category("error_handlers").remove(errorHandlerKey); + }); +} diff --git a/addons/web/static/src/views/form/form_error_dialog/form_error_dialog.scss b/addons/web/static/src/views/form/form_error_dialog/form_error_dialog.scss new file mode 100644 index 00000000000..dac7ad8b96d --- /dev/null +++ b/addons/web/static/src/views/form/form_error_dialog/form_error_dialog.scss @@ -0,0 +1,10 @@ +.o_form_error_dialog { + border: none; + padding: 2rem; + top: 50%; + transform: translateY(-50%); + + footer { + border: none; + } +} diff --git a/addons/web/static/src/views/form/form_error_dialog/form_error_dialog.xml b/addons/web/static/src/views/form/form_error_dialog/form_error_dialog.xml new file mode 100644 index 00000000000..d3cbbc549a5 --- /dev/null +++ b/addons/web/static/src/views/form/form_error_dialog/form_error_dialog.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + diff --git a/addons/web/static/src/views/form/form_label.js b/addons/web/static/src/views/form/form_label.js index 3c50689dabf..c4f386b2727 100644 --- a/addons/web/static/src/views/form/form_label.js +++ b/addons/web/static/src/views/form/form_label.js @@ -8,7 +8,7 @@ const { Component, xml } = owl; export class FormLabel extends Component { get className() { - const { invalid, empty } = fieldVisualFeedback( + const { invalid, empty, readonly } = fieldVisualFeedback( this.props.fieldInfo.FieldComponent, this.props.record, this.props.fieldName, @@ -21,6 +21,9 @@ export class FormLabel extends Component { if (empty) { classes.push("o_form_label_empty"); } + if (readonly) { + classes.push("o_form_label_readonly"); + } return classes.join(" "); } diff --git a/addons/web/static/src/views/form/form_renderer.js b/addons/web/static/src/views/form/form_renderer.js index 8725b330733..eccf98f060f 100644 --- a/addons/web/static/src/views/form/form_renderer.js +++ b/addons/web/static/src/views/form/form_renderer.js @@ -43,21 +43,21 @@ export class FormRenderer extends Component { if (!disableAutofocus) { const rootRef = useRef("compiled_view_root"); useEffect( - (isInEdition, rootEl) => { + (isVirtual, rootEl) => { if (!rootEl) { return; } let elementToFocus; - if (isInEdition) { + if (isVirtual) { elementToFocus = (autofocusFieldId && rootEl.querySelector(`#${autofocusFieldId}`)) || - rootEl.querySelector(`.o_content .o_field_widget input`); + rootEl.querySelector(`.o_content .o_field_widget input[type="text"]`); } if (elementToFocus) { elementToFocus.focus(); } }, - () => [this.props.record.isInEdition, rootRef.el] + () => [this.props.record.isVirtual, rootRef.el] ); } } diff --git a/addons/web/static/src/views/form/form_status_indicator/form_status_indicator.js b/addons/web/static/src/views/form/form_status_indicator/form_status_indicator.js new file mode 100644 index 00000000000..a3263ccc40e --- /dev/null +++ b/addons/web/static/src/views/form/form_status_indicator/form_status_indicator.js @@ -0,0 +1,35 @@ +/** @odoo-module **/ + +const { Component } = owl; + +export class FormStatusIndicator extends Component { + get displayButtons() { + return this.indicatorMode !== "saved"; + } + + get indicatorMode() { + if (this.props.model.root.isVirtual) { + return this.props.model.root.isValid ? "dirty" : "invalid"; + } else if (!this.props.model.root.isValid) { + return "invalid"; + } else if (this.props.model.root.isDirty) { + return "dirty"; + } else { + return "saved"; + } + } + + async discard() { + await this.props.discard(); + } + async save() { + await this.props.save(); + } +} +FormStatusIndicator.template = "web.FormStatusIndicator"; +FormStatusIndicator.props = { + model: Object, + save: Function, + discard: Function, + isDisabled: Boolean, +}; diff --git a/addons/web/static/src/views/form/form_status_indicator/form_status_indicator.scss b/addons/web/static/src/views/form/form_status_indicator/form_status_indicator.scss new file mode 100644 index 00000000000..c1ccb2e97ae --- /dev/null +++ b/addons/web/static/src/views/form/form_status_indicator/form_status_indicator.scss @@ -0,0 +1,15 @@ +.o_form_status_indicator:not(.o_form_status_indicator_new_record) { + .o_form_status_indicator_buttons { + visibility: hidden; + } + + &:hover { + span { + visibility: hidden; + } + + .o_form_status_indicator_buttons:not(.o_form_status_indicator_buttons_hidden) { + visibility: visible; + } + } +} diff --git a/addons/web/static/src/views/form/form_status_indicator/form_status_indicator.xml b/addons/web/static/src/views/form/form_status_indicator/form_status_indicator.xml new file mode 100644 index 00000000000..ecfa52083b7 --- /dev/null +++ b/addons/web/static/src/views/form/form_status_indicator/form_status_indicator.xml @@ -0,0 +1,21 @@ + + + + +
+ + + Unable to save + + + Unsaved changes + + +
+ + +
+
+
+ +
diff --git a/addons/web/static/src/views/form/form_view.js b/addons/web/static/src/views/form/form_view.js index 8d24b6b19df..271add520b6 100644 --- a/addons/web/static/src/views/form/form_view.js +++ b/addons/web/static/src/views/form/form_view.js @@ -6,13 +6,14 @@ import { RelationalModel } from "../basic_relational_model"; import { FormArchParser } from "./form_arch_parser"; import { FormController } from "./form_controller"; import { FormCompiler } from "./form_compiler"; +import { FormControlPanel } from "./control_panel/form_control_panel"; export const formView = { type: "form", display_name: "Form", multiRecord: false, - display: { controlPanel: { ["top-right"]: false } }, searchMenuTypes: [], + ControlPanel: FormControlPanel, Controller: FormController, Renderer: FormRenderer, ArchParser: FormArchParser, diff --git a/addons/web/static/src/views/form/status_bar_buttons/status_bar_buttons.js b/addons/web/static/src/views/form/status_bar_buttons/status_bar_buttons.js index d525216b306..7ac55d22731 100644 --- a/addons/web/static/src/views/form/status_bar_buttons/status_bar_buttons.js +++ b/addons/web/static/src/views/form/status_bar_buttons/status_bar_buttons.js @@ -11,10 +11,7 @@ export class StatusBarButtons extends Component { return []; } return Object.entries(this.props.slots) - .filter( - (entry) => - entry[1].isVisible && (entry[1].displayInReadOnly ? this.props.readonly : true) - ) + .filter((entry) => entry[1].isVisible) .map((entry) => entry[0]); } } diff --git a/addons/web/static/src/views/list/list_renderer.js b/addons/web/static/src/views/list/list_renderer.js index 8233fc80f6a..113c995dfff 100644 --- a/addons/web/static/src/views/list/list_renderer.js +++ b/addons/web/static/src/views/list/list_renderer.js @@ -572,13 +572,6 @@ export class ListRenderer extends Component { if (column.type === "button_group") { classNames.push("o_list_button"); } - // note: remove this oe_read/edit_only logic when form view - // will always be in edit mode - if (/\boe_edit_only\b/.test(column.className)) { - classNames.push("oe_edit_only"); - } else if (/\boe_read_only\b/.test(column.className)) { - classNames.push("oe_read_only"); - } if (column.widget) { classNames.push(`o_${column.widget}_cell`); } diff --git a/addons/web/static/src/views/view_button/view_button.js b/addons/web/static/src/views/view_button/view_button.js index afbd9b4cee6..e9c7222a764 100644 --- a/addons/web/static/src/views/view_button/view_button.js +++ b/addons/web/static/src/views/view_button/view_button.js @@ -92,6 +92,8 @@ export class ViewButton extends Component { this.env[DROPDOWN].close(); } }, + disableAction: this.props.disable, + enableAction: this.props.enable, }); } @@ -133,7 +135,9 @@ ViewButton.props = [ "hotkey?", "icon?", "defaultRank?", + "disable?", "disabled?", + "enable?", "size?", "tabindex?", "title?", @@ -145,4 +149,6 @@ ViewButton.defaultProps = { tag: "button", className: "", clickParams: {}, + disable: () => {}, + enable: () => {}, }; diff --git a/addons/web/static/src/views/view_button/view_button_hook.js b/addons/web/static/src/views/view_button/view_button_hook.js index c237325e7c7..955b34e5ec3 100644 --- a/addons/web/static/src/views/view_button/view_button_hook.js +++ b/addons/web/static/src/views/view_button/view_button_hook.js @@ -15,12 +15,15 @@ function disableButtons(el) { return btns; } -function enableButtons(el, manuallyDisabledButtons) { +function enableButtons(el, manuallyDisabledButtons, enableAction) { if (el) { for (const btn of manuallyDisabledButtons) { btn.removeAttribute("disabled"); } } + if (enableAction) { + enableAction(); + } } function undefinedAsTrue(val) { @@ -37,8 +40,17 @@ export function useViewButtons(model, ref, options = {}) { return true; }); useSubEnv({ - async onClickViewButton({ clickParams, getResParams, beforeExecute }) { + async onClickViewButton({ + clickParams, + getResParams, + beforeExecute, + disableAction, + enableAction, + }) { const manuallyDisabledButtons = disableButtons(getEl()); + if (disableAction) { + disableAction(); + } async function execute() { let _continue = true; @@ -48,7 +60,7 @@ export function useViewButtons(model, ref, options = {}) { _continue = _continue && undefinedAsTrue(await beforeExecuteAction(clickParams)); if (!_continue) { - enableButtons(getEl(), manuallyDisabledButtons); + enableButtons(getEl(), manuallyDisabledButtons, enableAction); return; } const params = getResParams(); @@ -85,7 +97,7 @@ export function useViewButtons(model, ref, options = {}) { error = _e; await doActionParams.onClose(); } - enableButtons(getEl(), manuallyDisabledButtons); + enableButtons(getEl(), manuallyDisabledButtons, enableAction); if (error) { return Promise.reject(error); } @@ -100,7 +112,7 @@ export function useViewButtons(model, ref, options = {}) { }; dialog.add(ConfirmationDialog, dialogProps, { onClose: resolve }); }); - enableButtons(getEl(), manuallyDisabledButtons); + enableButtons(getEl(), manuallyDisabledButtons, enableAction); } else { return execute(); } diff --git a/addons/web/static/src/webclient/settings_form_view/settings_form_view.js b/addons/web/static/src/webclient/settings_form_view/settings_form_view.js index 46bad63fee1..795138af49a 100644 --- a/addons/web/static/src/webclient/settings_form_view/settings_form_view.js +++ b/addons/web/static/src/webclient/settings_form_view/settings_form_view.js @@ -1,6 +1,7 @@ /** @odoo-module **/ import { registry } from "@web/core/registry"; +import { ControlPanel } from "@web/search/control_panel/control_panel"; import { formView } from "@web/views/form/form_view"; import { SettingsFormController } from "./settings_form_controller"; import { SettingsFormRenderer } from "./settings_form_renderer"; @@ -10,6 +11,7 @@ export const settingsFormView = { ...formView, display: {}, buttonTemplate: "web.SettingsFormView.Buttons", + ControlPanel: ControlPanel, Controller: SettingsFormController, Compiler: SettingsFormCompiler, Renderer: SettingsFormRenderer, diff --git a/addons/web/static/tests/helpers/utils.js b/addons/web/static/tests/helpers/utils.js index 5d32f1e4450..e48dc2f256e 100644 --- a/addons/web/static/tests/helpers/utils.js +++ b/addons/web/static/tests/helpers/utils.js @@ -385,16 +385,17 @@ export function clickCreate(htmlElement) { } export function clickEdit(htmlElement) { - if (htmlElement.querySelectorAll(".o_form_button_edit").length) { - return click(htmlElement, ".o_form_button_edit"); - } else if (htmlElement.querySelectorAll(".o_list_button_edit").length) { + if (htmlElement.querySelectorAll(".o_list_button_edit").length) { return click(htmlElement, ".o_list_button_edit"); } else { throw new Error("No edit button found to be clicked."); } } -export function clickSave(htmlElement) { +export async function clickSave(htmlElement) { + if (htmlElement.querySelectorAll(".o_form_status_indicator").length) { + await mouseEnter(htmlElement, ".o_form_status_indicator"); + } if (htmlElement.querySelectorAll(".o_form_button_save").length) { return click(htmlElement, ".o_form_button_save"); } else if (htmlElement.querySelectorAll(".o_list_button_save").length) { @@ -404,7 +405,10 @@ export function clickSave(htmlElement) { } } -export function clickDiscard(htmlElement) { +export async function clickDiscard(htmlElement) { + if (htmlElement.querySelectorAll(".o_form_status_indicator").length) { + await mouseEnter(htmlElement, ".o_form_status_indicator"); + } if (htmlElement.querySelectorAll(".o_form_button_cancel").length) { return click(htmlElement, ".o_form_button_cancel"); } else if (htmlElement.querySelectorAll(".o_list_button_discard").length) { diff --git a/addons/web/static/tests/mobile/mobile_form_view_tests.js b/addons/web/static/tests/mobile/mobile_form_view_tests.js index c21fcac3bd5..bb29f0f4452 100644 --- a/addons/web/static/tests/mobile/mobile_form_view_tests.js +++ b/addons/web/static/tests/mobile/mobile_form_view_tests.js @@ -3,7 +3,6 @@ import { registry } from "@web/core/registry"; import { click, - clickEdit, clickSave, editInput, getFixture, @@ -124,7 +123,6 @@ QUnit.module("Mobile Views", ({ beforeEach }) => { ); // change display_name to update buttons modifiers and make it visible - await clickEdit(fixture); await editInput(fixture, ".o_field_widget[name=display_name] input", "test"); await clickSave(fixture); assert.containsOnce( @@ -135,43 +133,6 @@ QUnit.module("Mobile Views", ({ beforeEach }) => { } ); - QUnit.test( - `statusbar "Action" button not displayed in edit mode with .oe_read_only button`, - async (assert) => { - await makeView({ - type: "form", - resModel: "partner", - serverData, - arch: ` -
-
-
- - - - - -
- `, - }); - - assert.containsNone( - fixture, - ".o_statusbar_buttons .dropdown", - "dropdown should not be there" - ); - - await clickSave(fixture); - assert.containsOnce( - fixture, - ".o_statusbar_buttons .dropdown", - "dropdown should not be there" - ); - } - ); - QUnit.test( `statusbar "Action" button shouldn't be displayed for only one visible button`, async (assert) => { @@ -195,8 +156,6 @@ QUnit.module("Mobile Views", ({ beforeEach }) => { `, }); - await clickEdit(fixture); - // There should be a simple statusbar button and no action dropdown assert.containsNone( fixture, @@ -248,7 +207,6 @@ QUnit.module("Mobile Views", ({ beforeEach }) => { `, }); - await clickEdit(fixture); // Now there should an action dropdown, because there are two visible buttons assert.containsOnce( fixture, @@ -449,8 +407,6 @@ QUnit.module("Mobile Views", ({ beforeEach }) => { }, }); - await clickEdit(fixture); - window.scrollTo({ top: 265, left: 0 }); assert.strictEqual(window.scrollY, 265, "Should have scrolled 265 px vertically"); assert.strictEqual(window.screenLeft, 0, "Should be 0 px from left as it is"); diff --git a/addons/web/static/tests/mobile/mobile_statusbar_field_tests.js b/addons/web/static/tests/mobile/mobile_statusbar_field_tests.js index 03254bb08e9..91f50eaa4c6 100644 --- a/addons/web/static/tests/mobile/mobile_statusbar_field_tests.js +++ b/addons/web/static/tests/mobile/mobile_statusbar_field_tests.js @@ -102,10 +102,10 @@ QUnit.module("Mobile Fields", ({ beforeEach }) => { `, }); - assert.hasClass( + assert.doesNotHaveClass( fixture.querySelector(".o_field_statusbar"), "o_field_empty", - "statusbar widget should have class o_field_empty" + "statusbar widget should have class o_field_empty in edit" ); assert.containsOnce( fixture, diff --git a/addons/web/static/tests/views/fields/ace_editor_field_tests.js b/addons/web/static/tests/views/fields/ace_editor_field_tests.js index 7e45300da30..d028d922f38 100644 --- a/addons/web/static/tests/views/fields/ace_editor_field_tests.js +++ b/addons/web/static/tests/views/fields/ace_editor_field_tests.js @@ -1,6 +1,6 @@ /** @odoo-module **/ -import { clickEdit, getFixture, triggerEvents } from "@web/../tests/helpers/utils"; +import { getFixture, triggerEvents } from "@web/../tests/helpers/utils"; import { pagerNext } from "@web/../tests/search/helpers"; import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; @@ -70,7 +70,6 @@ QUnit.module("Fields", (hooks) => { `, }); - await clickEdit(target); await triggerEvents(target, ".ace-view-editor textarea", ["focus", "click"]); assert.hasClass(target.querySelector(".ace-view-editor"), "ace_focus"); }); diff --git a/addons/web/static/tests/views/fields/binary_field_tests.js b/addons/web/static/tests/views/fields/binary_field_tests.js index e680e657b46..edd77e93518 100644 --- a/addons/web/static/tests/views/fields/binary_field_tests.js +++ b/addons/web/static/tests/views/fields/binary_field_tests.js @@ -4,6 +4,7 @@ import { registerCleanup } from "@web/../tests/helpers/cleanup"; import { makeMockXHR } from "@web/../tests/helpers/mock_services"; import { click, + clickSave, editInput, getFixture, makeDeferred, @@ -66,8 +67,81 @@ QUnit.module("Fields", (hooks) => { QUnit.module("BinaryField"); + QUnit.test("BinaryField is correctly rendered (readonly)", async function (assert) { + assert.expect(6); + + async function send(data) { + assert.ok(data instanceof FormData); + assert.strictEqual( + data.get("field"), + "document", + "we should download the field document" + ); + assert.strictEqual( + data.get("data"), + "coucou==\n", + "we should download the correct data" + ); + + this.status = 200; + this.response = new Blob([data.get("data")], { type: "text/plain" }); + } + const MockXHR = makeMockXHR("", send); + + patchWithCleanup( + browser, + { + XMLHttpRequest: MockXHR, + }, + { pure: true } + ); + + await makeView({ + serverData, + type: "form", + resModel: "partner", + arch: ` +
+ + + `, + resId: 1, + }); + assert.containsOnce( + target, + '.o_field_widget[name="document"] a > .fa-download', + "the binary field should be rendered as a downloadable link in readonly" + ); + assert.strictEqual( + target.querySelector('.o_field_widget[name="document"]').textContent, + "coucou.txt", + "the binary field should display the name of the file in the link" + ); + assert.strictEqual( + target.querySelector(".o_field_char").textContent, + "coucou.txt", + "the filename field should have the file name as value" + ); + + // Testing the download button in the field + // We must avoid the browser to download the file effectively + const prom = makeDeferred(); + const downloadOnClick = (ev) => { + const target = ev.target; + if (target.tagName === "A" && "download" in target.attributes) { + ev.preventDefault(); + document.removeEventListener("click", downloadOnClick); + prom.resolve(); + } + }; + document.addEventListener("click", downloadOnClick); + registerCleanup(() => document.removeEventListener("click", downloadOnClick)); + await click(target.querySelector('.o_field_widget[name="document"] a')); + await prom; + }); + QUnit.test("BinaryField is correctly rendered", async function (assert) { - assert.expect(15); + assert.expect(9); async function send(data) { assert.ok(data instanceof FormData); @@ -106,39 +180,6 @@ QUnit.module("Fields", (hooks) => { `, resId: 1, }); - assert.containsOnce( - target, - '.o_field_widget[name="document"] a > .fa-download', - "the binary field should be rendered as a downloadable link in readonly" - ); - assert.strictEqual( - target.querySelector('.o_field_widget[name="document"]').textContent, - "coucou.txt", - "the binary field should display the name of the file in the link" - ); - assert.strictEqual( - target.querySelector(".o_field_char").textContent, - "coucou.txt", - "the filename field should have the file name as value" - ); - - // Testing the download button in the field - // We must avoid the browser to download the file effectively - const prom = makeDeferred(); - const downloadOnClick = (ev) => { - const target = ev.target; - if (target.tagName === "A" && "download" in target.attributes) { - ev.preventDefault(); - document.removeEventListener("click", downloadOnClick); - prom.resolve(); - } - }; - document.addEventListener("click", downloadOnClick); - registerCleanup(() => document.removeEventListener("click", downloadOnClick)); - await click(target.querySelector('.o_field_widget[name="document"] a')); - await prom; - - await click(target, ".o_form_button_edit"); assert.containsNone( target, @@ -178,7 +219,7 @@ QUnit.module("Fields", (hooks) => { "the filename field should be empty since we removed the file" ); - await click(target, ".o_form_button_save"); + await clickSave(target); assert.containsNone( target, '.o_field_widget[name="document"] a > .fa-download', @@ -230,8 +271,6 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - await click(target, ".o_form_button_edit"); - const file = new File(["test"], "fake_file.txt", { type: "text/plain" }); await editInput(target, ".o_field_binary .o_input_file", file); diff --git a/addons/web/static/tests/views/fields/boolean_favorite_field_tests.js b/addons/web/static/tests/views/fields/boolean_favorite_field_tests.js index 9a780983108..6881f4254f8 100644 --- a/addons/web/static/tests/views/fields/boolean_favorite_field_tests.js +++ b/addons/web/static/tests/views/fields/boolean_favorite_field_tests.js @@ -1,6 +1,6 @@ /** @odoo-module **/ -import { click, getFixture } from "@web/../tests/helpers/utils"; +import { click, clickSave, getFixture } from "@web/../tests/helpers/utils"; import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; let serverData; @@ -114,8 +114,6 @@ QUnit.module("Fields", (hooks) => { 'the label should say "Add to Favorites"' ); - // switch to edit mode - await click(target, ".o_form_button_edit"); assert.containsOnce( target, ".o_field_widget .o_favorite > a i.fa.fa-star-o", @@ -141,7 +139,7 @@ QUnit.module("Fields", (hooks) => { ); // save - await click(target, ".o_form_button_save"); + await clickSave(target); assert.containsOnce( target, ".o_field_widget .o_favorite > a i.fa.fa-star", @@ -188,7 +186,7 @@ QUnit.module("Fields", (hooks) => { ); // save - await click(target, ".o_list_button_save"); + await clickSave(target); assert.containsOnce( target, ".o_data_row:first .o_field_widget .o_favorite > a i.fa.fa-star-o", diff --git a/addons/web/static/tests/views/fields/boolean_field_tests.js b/addons/web/static/tests/views/fields/boolean_field_tests.js index 86c698d885d..5278b69d38c 100644 --- a/addons/web/static/tests/views/fields/boolean_field_tests.js +++ b/addons/web/static/tests/views/fields/boolean_field_tests.js @@ -2,6 +2,7 @@ import { click, + clickSave, getFixture, nextTick, triggerEvent, @@ -50,15 +51,6 @@ QUnit.module("Fields", (hooks) => { `, }); - assert.containsOnce(target, ".o_field_boolean input:checked", "checkbox should be checked"); - assert.containsOnce( - target, - ".o_field_boolean input:disabled", - "checkbox should be disabled" - ); - - // switch to edit mode and check the result - await click(target, ".o_form_button_edit"); assert.containsOnce( target, ".o_field_boolean input:checked", @@ -79,15 +71,13 @@ QUnit.module("Fields", (hooks) => { ); // save - await click(target, ".o_form_button_save"); + await clickSave(target); assert.containsNone( target, ".o_field_boolean input:checked", "checkbox should still no longer be checked" ); - // switch to edit mode and test the opposite change - await click(target, ".o_form_button_edit"); assert.containsNone( target, ".o_field_boolean input:checked", @@ -155,7 +145,7 @@ QUnit.module("Fields", (hooks) => { ); // save - await click(target, ".o_form_button_save"); + await clickSave(target); assert.containsOnce( target, ".o_field_boolean input:checked", @@ -211,7 +201,7 @@ QUnit.module("Fields", (hooks) => { await click(cell, ".o-checkbox"); // save - await click(target.querySelector(".o_list_button_save")); + await clickSave(target); cell = target.querySelector("tr.o_data_row td:not(.o_list_record_selector)"); assert.ok( cell.querySelector(".o-checkbox input:not(:checked)").disabled, @@ -236,7 +226,7 @@ QUnit.module("Fields", (hooks) => { await click(cell, ".o-checkbox"); // Save - await click(target.querySelector(".o_list_button_save")); + await clickSave(target); assert.containsN( target, "tbody td:not(.o_list_record_selector) .o-checkbox", @@ -282,14 +272,7 @@ QUnit.module("Fields", (hooks) => { serverData, arch: `
`, }); - assert.containsOnce(target, ".o_field_boolean input:checked", "checkbox should be checked"); - assert.containsOnce( - target, - ".o_field_boolean input:disabled", - "checkbox should be disabled" - ); - await click(target, ".o_form_button_edit"); assert.containsOnce( target, ".o_field_boolean input:checked", diff --git a/addons/web/static/tests/views/fields/boolean_toggle_field_tests.js b/addons/web/static/tests/views/fields/boolean_toggle_field_tests.js index 1f28dab830b..558154e33b4 100644 --- a/addons/web/static/tests/views/fields/boolean_toggle_field_tests.js +++ b/addons/web/static/tests/views/fields/boolean_toggle_field_tests.js @@ -1,6 +1,6 @@ /** @odoo-module **/ -import { click, clickEdit, getFixture } from "@web/../tests/helpers/utils"; +import { click, getFixture } from "@web/../tests/helpers/utils"; import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; let serverData; @@ -79,7 +79,7 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - assert.containsOnce(target, ".o_form_readonly"); + assert.containsOnce(target, ".o_form_editable"); assert.containsOnce(target, ".form-check.o_boolean_toggle"); assert.notOk(target.querySelector(".o_boolean_toggle input").disabled); assert.notOk(target.querySelector(".o_boolean_toggle input").checked); @@ -98,8 +98,6 @@ QUnit.module("Fields", (hooks) => { assert.containsOnce(target, ".form-check.o_boolean_toggle"); assert.ok(target.querySelector(".o_boolean_toggle input").disabled); - await click(target.querySelector(".o_form_button_edit")); - assert.ok(target.querySelector(".o_boolean_toggle input").disabled); }); QUnit.test("BooleanToggleField is enabled in edit mode", async function (assert) { @@ -112,7 +110,6 @@ QUnit.module("Fields", (hooks) => { }); assert.containsOnce(target, ".form-check.o_boolean_toggle"); - await click(target.querySelector(".o_form_button_edit")); assert.notOk(target.querySelector(".o_boolean_toggle input").disabled); assert.notOk(target.querySelector(".o_boolean_toggle input").checked); @@ -165,8 +162,6 @@ QUnit.module("Fields", (hooks) => { ".form-check.o_boolean_toggle", "Boolean toggle widget applied to boolean field" ); - - await clickEdit(target); assert.containsNone(target, ".o_boolean_toggle input:checked"); await click(target, ".o_boolean_toggle"); @@ -193,8 +188,6 @@ QUnit.module("Fields", (hooks) => { ".form-check.o_boolean_toggle", "Boolean toggle widget applied to boolean field" ); - - await clickEdit(target); assert.containsNone(target, ".o_boolean_toggle input:checked"); await click(target, ".o_boolean_toggle"); diff --git a/addons/web/static/tests/views/fields/char_field_tests.js b/addons/web/static/tests/views/fields/char_field_tests.js index 05bd6634daf..95748c10fdf 100644 --- a/addons/web/static/tests/views/fields/char_field_tests.js +++ b/addons/web/static/tests/views/fields/char_field_tests.js @@ -3,6 +3,7 @@ import { registry } from "@web/core/registry"; import { click, + clickSave, editInput, getFixture, makeDeferred, @@ -147,14 +148,6 @@ QUnit.module("Fields", (hooks) => { `, }); - assert.strictEqual( - target.querySelector(".o_field_widget").textContent, - "yop", - "the value should be displayed properly" - ); - - // switch to edit mode and check the result - await click(target, ".o_form_button_edit"); assert.containsOnce( target, ".o_field_widget input[type='text']", @@ -170,9 +163,9 @@ QUnit.module("Fields", (hooks) => { await editInput(target, ".o_field_widget input[type='text']", "limbo"); // save - await click(target, ".o_form_button_save"); + await clickSave(target); assert.strictEqual( - target.querySelector(".o_field_widget").textContent, + target.querySelector(".o_field_widget input[type='text']").value, "limbo", "the new value should be displayed" ); @@ -203,12 +196,8 @@ QUnit.module("Fields", (hooks) => { }, }); - await click(target, ".o_form_button_edit"); - await editInput(target, ".o_field_widget input[type='text']", ""); - - // save - await click(target, ".o_form_button_save"); + await clickSave(target); } ); @@ -229,9 +218,6 @@ QUnit.module("Fields", (hooks) => { `, }); - - await click(target, ".o_form_button_edit"); - assert.hasAttrValue( target.querySelector("input"), "maxlength", @@ -271,7 +257,7 @@ QUnit.module("Fields", (hooks) => { await editInput(cell, "input", "brolo"); // save - await click(target, ".o_list_button_save"); + await clickSave(target); cell = target.querySelector("tbody td:not(.o_list_record_selector)"); assert.doesNotHaveClass( cell.parentElement, @@ -336,8 +322,6 @@ QUnit.module("Fields", (hooks) => { }, }); - await click(target, ".o_form_button_edit"); - assert.hasClass(target.querySelector("[name=foo] input"), "o_field_translate"); assert.containsOnce( @@ -465,7 +449,6 @@ QUnit.module("Fields", (hooks) => { } }, }); - await click(target, ".o_form_button_edit"); // this will not affect the translate_fields effect until the record is // saved but is set for consistency of the test @@ -546,12 +529,10 @@ QUnit.module("Fields", (hooks) => { `, }); - await click(target, ".o_form_button_edit"); await editInput(target, "[name='foo'] input", ""); - - await click(target, ".o_form_button_save"); + await clickSave(target); assert.strictEqual( - target.querySelector(".o_field_widget").textContent, + target.querySelector(".o_field_widget input").value, "", "the value should have been properly escaped" ); @@ -576,16 +557,9 @@ QUnit.module("Fields", (hooks) => { `, }); - await click(target, ".o_form_button_edit"); - await editInput(target, ".o_field_widget[name='foo'] input", " abc "); - await editInput(target, ".o_field_widget[name='foo2'] input", " def "); - - await click(target, ".o_form_button_save"); - - // edit mode - await click(target, ".o_form_button_edit"); + await clickSave(target); assert.strictEqual( target.querySelector(".o_field_widget[name='foo'] input").value, "abc", @@ -629,7 +603,6 @@ QUnit.module("Fields", (hooks) => { }, }); - await click(target, ".o_form_button_edit"); await click(target, ".o_field_x2many_list_row_add a"); assert.strictEqual( target.querySelector(".o_field_widget[name='foo'] input").value, @@ -763,8 +736,6 @@ QUnit.module("Fields", (hooks) => { }, }); - await click(target, ".o_form_button_edit"); - assert.strictEqual( target.querySelector(".o_field_widget[name='foo'] input").value, "yop", @@ -802,7 +773,6 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - await click(target.querySelector(".o_form_button_edit")); assert.hasAttrValue( target.querySelector('.o_field_widget[name="display_name"] input'), "autocomplete", @@ -820,7 +790,6 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - await click(target.querySelector(".o_form_button_edit")); assert.hasAttrValue( target.querySelector('.o_field_widget[name="display_name"] input'), "autocomplete", @@ -838,12 +807,6 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - assert.strictEqual( - target.querySelector('.o_field_widget[name="foo"]').innerText, - "***", - "password should be displayed with stars" - ); - await click(target.querySelector(".o_form_button_edit")); assert.strictEqual( target.querySelector('.o_field_widget[name="foo"] input').value, "yop", @@ -856,6 +819,30 @@ QUnit.module("Fields", (hooks) => { ); }); + QUnit.test("input field: readonly password", async function (assert) { + await makeView({ + type: "form", + resModel: "partner", + resId: 1, + serverData, + arch: ` +
+ + `, + }); + + assert.notEqual( + target.querySelector(".o_field_char").textContent, + "yop", + "password field value should not be visible in read mode" + ); + assert.strictEqual( + target.querySelector(".o_field_char").textContent, + "***", + "password field value should be hidden with '*' in read mode" + ); + }); + QUnit.test("input field: change password value", async function (assert) { await makeView({ type: "form", @@ -868,19 +855,6 @@ QUnit.module("Fields", (hooks) => { `, }); - assert.notEqual( - target.querySelector(".o_field_char").textContent, - "yop", - "password field value should not be visible in read mode" - ); - assert.strictEqual( - target.querySelector(".o_field_char").textContent, - "***", - "password field value should be hidden with '*' in read mode" - ); - - await click(target, ".o_form_button_edit"); - assert.hasAttrValue( target.querySelector(".o_field_char input"), "type", @@ -908,14 +882,6 @@ QUnit.module("Fields", (hooks) => { `, }); - assert.strictEqual( - target.querySelector(".o_field_char").textContent, - "", - "password field value should be empty in read mode" - ); - - await click(target, ".o_form_button_edit"); - assert.hasAttrValue( target.querySelector(".o_field_char input"), "type", diff --git a/addons/web/static/tests/views/fields/color_field_tests.js b/addons/web/static/tests/views/fields/color_field_tests.js index abf8a7beafa..abab67061d0 100644 --- a/addons/web/static/tests/views/fields/color_field_tests.js +++ b/addons/web/static/tests/views/fields/color_field_tests.js @@ -1,5 +1,5 @@ /** @odoo-module **/ -import { click, editInput, getFixture, clickEdit } from "@web/../tests/helpers/utils"; +import { click, editInput, getFixture } from "@web/../tests/helpers/utils"; import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; let serverData; @@ -129,7 +129,6 @@ QUnit.module("Fields", (hooks) => { "field has the default color set as background if no value has been selected" ); assert.strictEqual(target.querySelector(".o_field_color input").value, "#000000"); - await clickEdit(target); await editInput(target, ".o_field_char[name='foo'] input", "someValue"); assert.verifySteps([ 'onchange [[1],{"id":1,"foo":"someValue","hex_color":false},"foo",{"foo":"1","hex_color":""}]', diff --git a/addons/web/static/tests/views/fields/color_picker_field_tests.js b/addons/web/static/tests/views/fields/color_picker_field_tests.js index b8d5d78d2a3..f5050684848 100644 --- a/addons/web/static/tests/views/fields/color_picker_field_tests.js +++ b/addons/web/static/tests/views/fields/color_picker_field_tests.js @@ -59,9 +59,6 @@ QUnit.module("Fields", (hooks) => { `, }); - // switch to edit mode - await click(target, ".o_form_button_edit"); - assert.hasClass( target.querySelectorAll(".o_field_color_picker button"), "o_colorlist_item_color_0", @@ -102,9 +99,6 @@ QUnit.module("Fields", (hooks) => { `, }); - // switch to edit mode - await click(target, ".o_form_button_edit"); - await click(target, ".o_field_color_picker button"); assert.strictEqual( diff --git a/addons/web/static/tests/views/fields/date_field_tests.js b/addons/web/static/tests/views/fields/date_field_tests.js index 5a17d19374f..a26b6cc2179 100644 --- a/addons/web/static/tests/views/fields/date_field_tests.js +++ b/addons/web/static/tests/views/fields/date_field_tests.js @@ -9,6 +9,8 @@ import { patchTimeZone, triggerEvent, triggerEvents, + clickSave, + clickCreate, } from "@web/../tests/helpers/utils"; import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; import { makeFakeLocalizationService } from "@web/../tests/helpers/mock_services"; @@ -120,7 +122,6 @@ QUnit.module("Fields", (hooks) => { `, }); - await click(target, ".o_form_button_edit"); assert.containsNone( document.body, ".bootstrap-datetimepicker-widget", @@ -155,11 +156,11 @@ QUnit.module("Fields", (hooks) => { assert.containsOnce( target, - ".o_field_widget > span", - "should have one span in the form view" + ".o_field_widget input", + "should have one input in the form view" ); assert.strictEqual( - target.querySelector(".o_field_widget > span").textContent, + target.querySelector(".o_field_widget input").value, "", "and it should be empty" ); @@ -175,7 +176,6 @@ QUnit.module("Fields", (hooks) => { serverData, arch: '
', }); - await click(target, ".o_form_button_edit"); const input = target.querySelector(".o_field_widget[name='date'] input"); assert.strictEqual(input.value, "02/03/2017"); @@ -196,7 +196,6 @@ QUnit.module("Fields", (hooks) => { serverData, arch: '
', }); - await click(target, ".o_form_button_edit"); const input = target.querySelector(".o_field_widget[name='date'] input"); assert.strictEqual(input.value, ""); @@ -215,7 +214,6 @@ QUnit.module("Fields", (hooks) => { serverData, arch: '
', }); - await click(target, ".o_form_button_edit"); await click(target, ".o_datepicker input"); // open datepicker and select a date @@ -236,7 +234,7 @@ QUnit.module("Fields", (hooks) => { }); QUnit.test("DateField in form view (with positive time zone offset)", async function (assert) { - assert.expect(8); + assert.expect(7); patchTimeZone(120); // Should be ignored by date fields @@ -257,14 +255,6 @@ QUnit.module("Fields", (hooks) => { }, }); - assert.strictEqual( - target.querySelector(".o_field_date").textContent, - "02/03/2017", - "the date should be correctly displayed in readonly" - ); - - // switch to edit mode - await click(target, ".o_form_button_edit"); assert.strictEqual( target.querySelector(".o_datepicker_input").value, "02/03/2017", @@ -304,9 +294,9 @@ QUnit.module("Fields", (hooks) => { ); // save - await click(target, ".o_form_button_save"); + await clickSave(target); assert.strictEqual( - target.querySelector(".o_field_date").textContent, + target.querySelector(".o_field_date input").value, "02/22/2017", "the selected date should be displayed after saving" ); @@ -323,14 +313,6 @@ QUnit.module("Fields", (hooks) => { arch: '
', }); - assert.strictEqual( - target.querySelector(".o_field_date").textContent, - "02/03/2017", - "the date should be correctly displayed in readonly" - ); - - // switch to edit mode - await click(target, ".o_form_button_edit"); assert.strictEqual( target.querySelector(".o_datepicker_input").value, "02/03/2017", @@ -351,7 +333,6 @@ QUnit.module("Fields", (hooks) => { `, }); - await click(target, ".o_form_button_edit"); await click(target, ".o_datepicker input"); assert.containsOnce( @@ -379,8 +360,6 @@ QUnit.module("Fields", (hooks) => { `, }); - // switch to edit mode - await click(target, ".o_form_button_edit"); // open datepicker and select another value await click(target, ".o_datepicker input"); @@ -430,8 +409,6 @@ QUnit.module("Fields", (hooks) => { `, }); - // switch to edit mode - await click(target, ".o_form_button_edit"); assert.strictEqual( target.querySelector(".o_field_widget[name='date'] input").value, @@ -439,11 +416,8 @@ QUnit.module("Fields", (hooks) => { "The existing record should have a value for the date field" ); - // save with no changes - await click(target, ".o_form_button_save"); - //Create a new record - await click(target, ".o_form_button_create"); + await clickCreate(target); assert.notOk( target.querySelector(".o_field_widget[name='date'] input").value, "The new record should not have a value that the framework would have set" @@ -513,7 +487,7 @@ QUnit.module("Fields", (hooks) => { ); // save - await click(target.querySelector(".o_list_button_save")); + await clickSave(target); assert.strictEqual( target.querySelector("tr.o_data_row td:not(.o_list_record_selector)").textContent, "02/22/2017", @@ -534,8 +508,6 @@ QUnit.module("Fields", (hooks) => { } }, }); - // switch to edit mode - await click(target, ".o_form_button_edit"); assert.strictEqual( target.querySelector(".o_datepicker_input").value, @@ -553,7 +525,7 @@ QUnit.module("Fields", (hooks) => { ); // save - await click(target, ".o_form_button_save"); + await clickSave(target); assert.strictEqual( target.querySelector(".o_field_date").textContent, "", @@ -574,7 +546,6 @@ QUnit.module("Fields", (hooks) => { assert.step(method); }, }); - await click(target, ".o_form_button_edit"); assert.strictEqual( target.querySelector(".o_datepicker_input").value, @@ -585,8 +556,8 @@ QUnit.module("Fields", (hooks) => { const input = target.querySelector(".o_field_widget[name='datetime'] input"); input.value = "02/08/2017"; await triggerEvents(input, null, ["input", "change", "focusout"]); - await click(target, ".o_form_button_save"); + assert.containsOnce(target, ".o_form_saved"); assert.verifySteps(["get_views", "read"]); // should not have save as nothing changed } ); @@ -604,7 +575,6 @@ QUnit.module("Fields", (hooks) => { serverData, arch: '
', }); - await click(target, ".o_form_button_edit"); $(target).on("show.datetimepicker", () => { assert.containsOnce( @@ -660,8 +630,7 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - const dateViewForm = target.querySelector(".o_field_date").textContent; - await click(target.querySelector(".o_form_button_edit")); + const dateViewForm = target.querySelector(".o_field_date input").value; await click(target, ".o_datepicker input"); assert.strictEqual( @@ -671,9 +640,9 @@ QUnit.module("Fields", (hooks) => { ); await click(document.body.querySelector(".day[data-day*='/22/']")); const dateEditForm = target.querySelector(".o_datepicker_input").value; - await click(target.querySelector(".o_form_button_save")); + await clickSave(target); assert.strictEqual( - target.querySelector(".o_field_date").textContent, + target.querySelector(".o_field_date input").value, dateEditForm, "date field should be the same as the one selected in the view form" ); @@ -692,7 +661,6 @@ QUnit.module("Fields", (hooks) => { serverData, arch: '
', }); - await click(target, ".o_form_button_edit"); const year = new Date().getFullYear(); const input = target.querySelector(".o_field_widget[name='date'] input"); diff --git a/addons/web/static/tests/views/fields/daterange_field_tests.js b/addons/web/static/tests/views/fields/daterange_field_tests.js index 58c6bbc5028..3cdf6dc4f3d 100644 --- a/addons/web/static/tests/views/fields/daterange_field_tests.js +++ b/addons/web/static/tests/views/fields/daterange_field_tests.js @@ -2,6 +2,7 @@ import { click, + clickSave, editInput, getFixture, patchDate, @@ -69,22 +70,6 @@ QUnit.module("Fields", (hooks) => { `, }); - let fields = target.querySelectorAll(".o_field_daterange"); - // Check date display correctly in readonly - assert.strictEqual( - fields[0].textContent, - "02/08/2017 15:30:00", - "the start date should be correctly displayed in readonly" - ); - assert.strictEqual( - fields[fields.length - 1].textContent, - "03/13/2017 05:30:00", - "the end date should be correctly displayed in readonly" - ); - - // Edit - await click(target, ".o_form_button_edit"); - // Check date range picker initialization assert.containsN( document.body, @@ -103,7 +88,7 @@ QUnit.module("Fields", (hooks) => { ); // open the first one - fields = target.querySelectorAll(".o_field_daterange"); + let fields = target.querySelectorAll(".o_field_daterange"); await click(fields[0].querySelector("input")); let datepicker = document.querySelector( @@ -206,12 +191,12 @@ QUnit.module("Fields", (hooks) => { ); // Save - await click(target, ".o_form_button_save"); - fields = target.querySelectorAll(".o_field_daterange"); + await clickSave(target); + fields = target.querySelectorAll(".o_field_daterange input"); // Check date after save - assert.strictEqual(fields[0].textContent, "02/08/2017 15:30:00"); - assert.strictEqual(fields[fields.length - 1].textContent, "02/09/2017 05:30:00"); + assert.strictEqual(fields[0].value, "02/08/2017 15:30:00"); + assert.strictEqual(fields[fields.length - 1].value, "02/09/2017 05:30:00"); } ); @@ -234,23 +219,6 @@ QUnit.module("Fields", (hooks) => { }); let fields = target.querySelectorAll(".o_field_daterange"); - - // Check date display correctly in readonly - assert.strictEqual( - fields[0].textContent, - "02/03/2017", - "the start date should be correctly displayed in readonly" - ); - assert.strictEqual( - fields[fields.length - 1].textContent, - "02/08/2017", - "the end date should be correctly displayed in readonly" - ); - - // Edit - await click(target, ".o_form_button_edit"); - - fields = target.querySelectorAll(".o_field_daterange"); const datepickers = document.querySelectorAll(`.daterangepicker`); // Check date range picker initialization @@ -361,17 +329,17 @@ QUnit.module("Fields", (hooks) => { ); // Save - await click(target, ".o_form_button_save"); + await clickSave(target); fields = target.querySelectorAll(".o_field_daterange"); // Check date after save assert.strictEqual( - fields[0].textContent, + fields[0].querySelector("input").value, "02/13/2017", "the start date should be '02/13/2017' after save" ); assert.strictEqual( - fields[fields.length - 1].textContent, + fields[1].querySelector("input").value, "03/18/2017", "the end date should be '03/18/2017' after save" ); @@ -399,9 +367,7 @@ QUnit.module("Fields", (hooks) => { `, }); - await click(target, ".o_form_button_edit"); await click(target.querySelector(".o_field_daterange[name='datetime'] input")); - assert.isVisible( document.querySelector(".daterangepicker[data-name='datetime']"), "date range picker should be opened" @@ -445,18 +411,16 @@ QUnit.module("Fields", (hooks) => { // check date display correctly in readonly assert.strictEqual( - target.querySelector(".o_field_daterange").textContent, + target.querySelector(".o_field_daterange input").value, "02/08/2017 15:30:00", "the start date should be correctly displayed in readonly" ); assert.strictEqual( - target.querySelectorAll(".o_field_daterange")[1].textContent, + target.querySelectorAll(".o_field_daterange input")[1].value, "03/13/2017 05:30:00", "the end date should be correctly displayed in readonly" ); - // edit form - await click(target.querySelector(".o_form_button_edit")); // update input for Datetime await editInput( target, @@ -464,12 +428,12 @@ QUnit.module("Fields", (hooks) => { "02/08/2017 11:30:00" ); // save form - await click(target.querySelector(".o_form_button_save")); + await clickSave(target); assert.strictEqual( - target.querySelector(".o_field_daterange").textContent, + target.querySelector(".o_field_daterange input").value, "02/08/2017 11:30:00", - "the start date should be correctly displayed in readonly after manual update" + "the start date should be correctly displayed after manual update" ); } ); @@ -492,7 +456,6 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - await click(target, ".o_form_button_edit"); await editInput(target, ".o_field_daterange[name='date'] input", "blabla"); // click outside daterange field await click(target); @@ -511,7 +474,7 @@ QUnit.module("Fields", (hooks) => { // again enter wrong value and try to save should raise invalid fields value await editInput(target, ".o_field_daterange[name='date'] input", "blabla"); - await click(target.querySelector(".o_form_button_save")); + await clickSave(target); assert.strictEqual( target.querySelector(".o_notification_title").textContent, "Invalid fields: " @@ -591,12 +554,12 @@ QUnit.module("Fields", (hooks) => { }); assert.strictEqual( - target.querySelector(".o_field_daterange[name='datetime']").textContent, + target.querySelector(".o_field_daterange[name='datetime'] input").value, "02/08/2017", "the start date should only show date when option formatType is Date" ); assert.strictEqual( - target.querySelector(".o_field_daterange[name='datetime_end']").textContent, + target.querySelector(".o_field_daterange[name='datetime_end'] input").value, "03/13/2017", "the end date should only show date when option formatType is Date" ); diff --git a/addons/web/static/tests/views/fields/datetime_field_tests.js b/addons/web/static/tests/views/fields/datetime_field_tests.js index 9f33d0496cf..70eb8fd068d 100644 --- a/addons/web/static/tests/views/fields/datetime_field_tests.js +++ b/addons/web/static/tests/views/fields/datetime_field_tests.js @@ -4,6 +4,7 @@ import { registry } from "@web/core/registry"; import { makeFakeLocalizationService } from "@web/../tests/helpers/mock_services"; import { click, + clickSave, editInput, getFixture, patchTimeZone, @@ -67,13 +68,11 @@ QUnit.module("Fields", (hooks) => { const expectedDateString = "02/08/2017 12:00:00"; // 10:00:00 without timezone assert.strictEqual( - target.querySelector(".o_field_datetime").textContent, + target.querySelector(".o_field_datetime input").value, expectedDateString, "the datetime should be correctly displayed in readonly" ); - // switch to edit mode - await click(target, ".o_form_button_edit"); assert.strictEqual( target.querySelector(".o_datepicker_input").value, expectedDateString, @@ -126,9 +125,9 @@ QUnit.module("Fields", (hooks) => { ); // save - await click(target, ".o_form_button_save"); + await clickSave(target); assert.strictEqual( - target.querySelector(".o_field_datetime").textContent, + target.querySelector(".o_field_datetime input").value, newExpectedDateString, "the selected date should be displayed after saving" ); @@ -155,8 +154,6 @@ QUnit.module("Fields", (hooks) => { }, }); - await click(target, ".o_form_button_edit"); - await click(target, ".o_datepicker_input"); assert.containsOnce(document.body, ".bootstrap-datetimepicker-widget"); @@ -325,7 +322,7 @@ QUnit.module("Fields", (hooks) => { ); // save - await click(target.querySelector(".o_list_button_save")); + await clickSave(target); assert.strictEqual( target.querySelector("tr.o_data_row td:not(.o_list_record_selector)").textContent, newExpectedDateString, @@ -390,8 +387,6 @@ QUnit.module("Fields", (hooks) => { }, }); - // switch to edit mode - await click(target, ".o_form_button_edit"); assert.strictEqual( target.querySelector(".o_datepicker_input").value, "02/08/2017 12:00:00", @@ -408,7 +403,7 @@ QUnit.module("Fields", (hooks) => { ); // save - await click(target, ".o_form_button_save"); + await clickSave(target); assert.strictEqual( target.querySelector(".o_field_datetime").textContent, "", @@ -452,7 +447,7 @@ QUnit.module("Fields", (hooks) => { // switch to form view await click(target, ".o_field_widget[name='p'] .o_data_cell"); assert.strictEqual( - document.body.querySelector(".modal .o_field_date[name='datetime']").textContent, + document.body.querySelector(".modal .o_field_date[name='datetime'] input").value, "02/07/2017", "the datetime (date widget) should be correctly displayed in form view" ); @@ -495,7 +490,7 @@ QUnit.module("Fields", (hooks) => { // switch to form view await click(target, ".o_field_widget[name='p'] .o_data_cell"); assert.strictEqual( - document.body.querySelector(".modal .o_field_date[name='datetime']").textContent, + document.body.querySelector(".modal .o_field_date[name='datetime'] input").value, "02/08/2017", "the datetime (date widget) should be correctly displayed in form view" ); @@ -556,8 +551,6 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - await click(target, ".o_form_button_edit"); - const datetime = target.querySelector(".o_field_datetime input"); // Enter a beginning of date and press enter to validate @@ -573,10 +566,10 @@ QUnit.module("Fields", (hooks) => { assert.strictEqual(datetime.value, datetimeValue); // Save and check that it's still ok - await click(target, ".o_form_button_save"); + await clickSave(target); - const { textContent } = target.querySelector(".o_field_datetime span"); - assert.strictEqual(textContent, datetimeValue); + const { value } = target.querySelector(".o_field_datetime input"); + assert.strictEqual(value, datetimeValue); }); QUnit.test( @@ -595,15 +588,13 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - await click(target, ".o_form_button_edit"); - await editInput(target, ".o_field_widget input", "01/08/22"); await triggerEvent(target, ".o_field_widget input", "keydown", { key: "Enter" }); assert.strictEqual(target.querySelector(".o_field_widget input").value, "01/08/2022"); // Click outside the field to check that the field is not changed - await click(target); + await clickSave(target); assert.strictEqual(target.querySelector(".o_field_widget input").value, "01/08/2022"); } ); diff --git a/addons/web/static/tests/views/fields/domain_field_tests.js b/addons/web/static/tests/views/fields/domain_field_tests.js index a4460e31347..85d413641bc 100644 --- a/addons/web/static/tests/views/fields/domain_field_tests.js +++ b/addons/web/static/tests/views/fields/domain_field_tests.js @@ -3,6 +3,7 @@ import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; import { click, + clickSave, editInput, getFixture, makeDeferred, @@ -112,7 +113,7 @@ QUnit.module("Fields", (hooks) => { }); assert.strictEqual( - target.querySelector(".o_read_mode").textContent, + target.querySelector(".o_edit_mode").textContent, "This domain is not supported.", "The widget should not crash the view, but gracefully admit its failure." ); @@ -136,7 +137,6 @@ QUnit.module("Fields", (hooks) => { `, }); - await click(target, ".o_form_button_edit"); // As the domain is empty, there should be a button to add the first // domain part @@ -189,7 +189,7 @@ QUnit.module("Fields", (hooks) => { // Saving the form view should show a readonly domain containing the // "color" field - await click(target, ".o_form_button_save"); + await clickSave(target); assert.ok( target.querySelector(".o_field_domain").textContent.includes("Color index"), "field selector readonly value should now contain 'Color index'" @@ -216,7 +216,6 @@ QUnit.module("Fields", (hooks) => { `, }); - await click(target, ".o_form_button_edit"); // As the domain is equal to [["id", "=", 1]] there should be a field // selector to change this @@ -293,7 +292,6 @@ QUnit.module("Fields", (hooks) => { `, }); - await click(target, ".o_form_button_edit"); assert.strictEqual( target.querySelector(".o_domain_show_selection_button").textContent.trim(), @@ -312,7 +310,7 @@ QUnit.module("Fields", (hooks) => { ); QUnit.test("domain field: handle false domain as []", async function (assert) { - assert.expect(4); + assert.expect(3); serverData.models.partner.records[0].foo = false; serverData.models.partner.fields.bar.type = "char"; @@ -343,8 +341,6 @@ QUnit.module("Fields", (hooks) => { ".o_field_widget[name='foo']:not(.o_field_empty)", "there should be a domain field, not considered empty" ); - - await click(target, ".o_form_button_edit"); assert.containsNone( target, ".o_field_widget[name='foo'] .text-warning", @@ -460,13 +456,10 @@ QUnit.module("Fields", (hooks) => { await doAction(webClient, 1); assert.verifySteps(["[]"]); - await click(target, ".o_form_button_edit"); - assert.strictEqual( target.querySelector(".o_domain_show_selection_button").textContent.trim(), "2 record(s)" ); - assert.verifySteps(["[]"]); await editInput(target, ".o_domain_debug_input", "[['id', '<', 40]]"); // the count should not be re-computed when editing with the textarea @@ -476,15 +469,12 @@ QUnit.module("Fields", (hooks) => { ); assert.verifySteps([]); - await click(target, ".o_form_button_save"); + await clickSave(target); assert.strictEqual( target.querySelector(".o_domain_show_selection_button").textContent.trim(), "1 record(s)" ); - assert.verifySteps([ - '[["id","<",40]]', // to validate the domain, before saving - '[["id","<",40]]', // to render in readonly once it has been saved - ]); + assert.verifySteps(['[["id","<",40]]']); }); QUnit.test( @@ -531,13 +521,10 @@ QUnit.module("Fields", (hooks) => { await doAction(webClient, 1); assert.verifySteps(["[]"]); - await click(target, ".o_form_button_edit"); - assert.strictEqual( target.querySelector(".o_domain_show_selection_button").textContent.trim(), "2 record(s)" ); - assert.verifySteps(["[]"]); await editInput(target, ".o_domain_debug_input", "[['abc']]"); // the count should not be re-computed when editing with the textarea @@ -547,7 +534,7 @@ QUnit.module("Fields", (hooks) => { ); assert.verifySteps([]); - await click(target, ".o_form_button_save"); + await clickSave(target); assert.hasClass( target.querySelector(".o_field_domain"), "o_field_invalid", @@ -603,8 +590,6 @@ QUnit.module("Fields", (hooks) => { await doAction(webClient, 1); assert.verifySteps(["[]"]); - await click(target, ".o_form_button_edit"); - assert.strictEqual( target.querySelector(".o_domain_show_selection_button").textContent.trim(), "2 record(s)" @@ -616,7 +601,6 @@ QUnit.module("Fields", (hooks) => { target.querySelector(".o_domain_show_selection_button").textContent.trim(), "2 record(s)" ); - assert.verifySteps(["[]"]); // click on the refresh button await click(target, ".o_refresh_count"); @@ -709,7 +693,6 @@ QUnit.module("Fields", (hooks) => { }); await doAction(webClient, 1); - await click(target, ".o_form_button_edit"); assert.strictEqual(target.querySelector(".o_domain_debug_input").value, rawDomain); rawDomain = ` @@ -720,7 +703,7 @@ QUnit.module("Fields", (hooks) => { await editInput(target, ".o_domain_debug_input", rawDomain); assert.strictEqual(target.querySelector(".o_domain_debug_input").value, rawDomain); - await click(target, ".o_form_button_save"); + await clickSave(target); }); QUnit.test("domain field: edit through selector (dynamic content)", async function (assert) { @@ -762,8 +745,6 @@ QUnit.module("Fields", (hooks) => { await doAction(webClient, 1); assert.verifySteps(["/web/action/load", "get_views", "read", "search_count", "fields_get"]); - await click(target, ".o_form_button_edit"); - assert.verifySteps(["search_count"]); assert.strictEqual(target.querySelector(".o_domain_debug_input").value, rawDomain); assert.containsOnce(target, ".o_datepicker", "there should be a datepicker"); @@ -775,15 +756,6 @@ QUnit.module("Fields", (hooks) => { assert.strictEqual(target.querySelector(".o_domain_debug_input").value, rawDomain); assert.verifySteps([]); - // Save - await click(target, ".o_form_button_save"); - assert.verifySteps(["search_count"]); - - // Edit again - await click(target, ".o_form_button_edit"); - assert.verifySteps(["search_count"]); - assert.strictEqual(target.querySelector(".o_domain_debug_input").value, rawDomain); - // Manually input a date rawDomain = `[("date", ">=", "2020-09-09")]`; await editInput(target, ".o_datepicker_input", "09/09/2020"); @@ -791,12 +763,8 @@ QUnit.module("Fields", (hooks) => { assert.strictEqual(target.querySelector(".o_domain_debug_input").value, rawDomain); // Save - await click(target, ".o_form_button_save"); + await clickSave(target); assert.verifySteps(["write", "read", "search_count"]); - - // Edit again - await click(target, ".o_form_button_edit"); - assert.verifySteps(["search_count"]); assert.strictEqual(target.querySelector(".o_domain_debug_input").value, rawDomain); }); diff --git a/addons/web/static/tests/views/fields/email_field_tests.js b/addons/web/static/tests/views/fields/email_field_tests.js index 13d588a5633..eaaa211bc6c 100644 --- a/addons/web/static/tests/views/fields/email_field_tests.js +++ b/addons/web/static/tests/views/fields/email_field_tests.js @@ -1,6 +1,6 @@ /** @odoo-module **/ -import { click, editInput, getFixture } from "@web/../tests/helpers/utils"; +import { click, clickSave, editInput, getFixture } from "@web/../tests/helpers/utils"; import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; let serverData; @@ -58,13 +58,8 @@ QUnit.module("Fields", (hooks) => { `, resId: 1, }); - let mailtoLink = target.querySelector(".o_field_email a.o_form_uri.o_text_overflow"); - assert.containsOnce(target, mailtoLink, "should have a anchor with correct classes"); - assert.strictEqual(mailtoLink.textContent, "yop", "the value should be displayed properly"); - assert.hasAttrValue(mailtoLink, "href", "mailto:yop", "should have proper mailto prefix"); // switch to edit mode and check the result - await click(target.querySelector(".o_form_button_edit")); const mailtoEdit = target.querySelector('.o_field_email input[type="email"]'); assert.containsOnce(target, mailtoEdit, "should have an input for the email field"); assert.strictEqual( @@ -77,15 +72,9 @@ QUnit.module("Fields", (hooks) => { await editInput(target, ".o_field_email input[type='email']", "new"); // save - await click(target.querySelector(".o_form_button_save")); - mailtoLink = target.querySelector(".o_field_email a"); - assert.strictEqual(mailtoLink.textContent, "new", "new value should be displayed properly"); - assert.hasAttrValue( - mailtoLink, - "href", - "mailto:new", - "should still have proper mailto prefix" - ); + await clickSave(target); + const mailtoLink = target.querySelector(".o_field_email input[type='email']"); + assert.strictEqual(mailtoLink.value, "new", "new value should be displayed properly"); }); QUnit.test("EmailField in editable list view", async function (assert) { @@ -132,7 +121,7 @@ QUnit.module("Fields", (hooks) => { await editInput(cell, "input", "new"); // save - await click(target.querySelector(".o_list_button_save")); + await clickSave(target); cell = target.querySelector("tbody td:not(.o_list_record_selector)"); assert.doesNotHaveClass( cell.parentElement, @@ -168,11 +157,9 @@ QUnit.module("Fields", (hooks) => { `, }); - assert.strictEqual(target.querySelector(".o_field_email input").placeholder, "Placeholder"); - - await click(target.querySelector(".o_form_button_save")); - const mailtoLink = target.querySelector(".o_field_email a"); - assert.strictEqual(mailtoLink.textContent, "", "the value should be displayed properly"); + const input = target.querySelector(".o_field_email input"); + assert.strictEqual(input.placeholder, "Placeholder"); + assert.strictEqual(input.value, "", "the value should be displayed properly"); }); QUnit.test("EmailField trim user value", async function (assert) { @@ -184,8 +171,7 @@ QUnit.module("Fields", (hooks) => { }); await editInput(target, ".o_field_widget[name='foo'] input", " abc@abc.com "); const mailFieldInput = target.querySelector('.o_field_widget[name="foo"] input'); - await click(target.querySelector(".o_form_button_save")); - await click(target.querySelector(".o_form_button_edit")); + await clickSave(target); assert.strictEqual( mailFieldInput.value, "abc@abc.com", @@ -229,7 +215,6 @@ QUnit.module("Fields", (hooks) => { ); // edit the phone field, but with the mail in readonly mode - await click(target.querySelector(".o_form_button_edit")); await editInput(target, ".o_field_widget[name='int_field'] input", 3); // check rendering after changes diff --git a/addons/web/static/tests/views/fields/float_factor_field_tests.js b/addons/web/static/tests/views/fields/float_factor_field_tests.js index ff5ebeff88a..3e0ec00ac5e 100644 --- a/addons/web/static/tests/views/fields/float_factor_field_tests.js +++ b/addons/web/static/tests/views/fields/float_factor_field_tests.js @@ -1,6 +1,6 @@ /** @odoo-module **/ -import { click, editInput, getFixture } from "@web/../tests/helpers/utils"; +import { clickSave, editInput, getFixture } from "@web/../tests/helpers/utils"; import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; let serverData; @@ -25,7 +25,7 @@ QUnit.module("Fields", (hooks) => { QUnit.module("FloatFactorField"); QUnit.test("FloatFactorField in form view", async function (assert) { - assert.expect(4); + assert.expect(3); await makeView({ type: "form", @@ -45,13 +45,6 @@ QUnit.module("Fields", (hooks) => { } }, }); - assert.strictEqual( - target.querySelector(".o_field_widget").textContent, - "4.55", // 9.1 / 0.5 - "The formatted value should be displayed properly." - ); - - await click(target, ".o_form_button_edit"); assert.strictEqual( target.querySelector(".o_field_widget[name='qux'] input").value, "4.55", @@ -59,10 +52,10 @@ QUnit.module("Fields", (hooks) => { ); await editInput(target, ".o_field_widget[name='qux'] input", "2.3"); - await click(target, ".o_form_button_save"); + await clickSave(target); assert.strictEqual( - target.querySelector(".o_field_widget").textContent, + target.querySelector(".o_field_widget input").value, "2.30", "The new value should be saved and displayed properly." ); diff --git a/addons/web/static/tests/views/fields/float_field_tests.js b/addons/web/static/tests/views/fields/float_field_tests.js index 6a84d68e00e..478825efa80 100644 --- a/addons/web/static/tests/views/fields/float_field_tests.js +++ b/addons/web/static/tests/views/fields/float_field_tests.js @@ -1,7 +1,7 @@ /** @odoo-module **/ import { makeFakeLocalizationService } from "@web/../tests/helpers/mock_services"; -import { click, editInput, getFixture, triggerEvent } from "@web/../tests/helpers/utils"; +import { click, clickSave, editInput, getFixture, triggerEvent } from "@web/../tests/helpers/utils"; import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; import { registry } from "@web/core/registry"; @@ -50,7 +50,7 @@ QUnit.module("Fields", (hooks) => { ); assert.strictEqual( - target.querySelector(".o_field_widget").textContent, + target.querySelector(".o_field_widget input").value, "0.00", "Non-set float field should be considered as 0." ); @@ -68,7 +68,7 @@ QUnit.module("Fields", (hooks) => { }); assert.strictEqual( - target.querySelector(".o_field_float").textContent, + target.querySelector(".o_field_float input").value, "0.4", "should contain a number rounded to 1 decimal" ); @@ -84,7 +84,7 @@ QUnit.module("Fields", (hooks) => { }); assert.strictEqual( - target.querySelector(".o_field_float").textContent, + target.querySelector(".o_field_float input").value, "0.4", "should contain a number rounded to 1 decimal" ); @@ -100,7 +100,7 @@ QUnit.module("Fields", (hooks) => { }); assert.strictEqual( - target.querySelector(".o_field_float").textContent, + target.querySelector(".o_field_float input").value, "0.4", "should contain a number rounded to 1 decimal" ); @@ -115,8 +115,6 @@ QUnit.module("Fields", (hooks) => { arch: `
`, }); - await click(target, ".o_form_button_edit"); - assert.ok( target.querySelector(".o_field_widget input").hasAttribute("step"), "Integer field with option type must have a step attribute." @@ -145,19 +143,11 @@ QUnit.module("Fields", (hooks) => { "Float field should be considered set for value 0." ); assert.strictEqual( - target.querySelector(".o_field_widget").textContent, + target.querySelector(".o_field_widget input").value, "0.000", "The value should be displayed properly." ); - await click(target, ".o_form_button_edit"); - - assert.strictEqual( - target.querySelector(".o_field_widget[name=float_field] input").value, - "0.000", - "The value should be rendered with correct precision." - ); - await editInput(target, 'div[name="float_field"] input', "108.2451938598598"); assert.strictEqual( target.querySelector(".o_field_widget[name=float_field] input").value, @@ -166,10 +156,10 @@ QUnit.module("Fields", (hooks) => { ); await editInput(target, ".o_field_widget[name=float_field] input", "18.8958938598598"); - await click(target, ".o_form_button_save"); + await clickSave(target); assert.strictEqual( - target.querySelector(".o_field_widget").textContent, + target.querySelector(".o_field_widget input").value, "18.896", "The new value should be rounded properly." ); @@ -184,40 +174,36 @@ QUnit.module("Fields", (hooks) => { arch: `
`, }); - await click(target, ".o_form_button_edit"); await editInput(target, ".o_field_widget[name=float_field] input", "=20+3*2"); - await click(target, ".o_form_button_save"); + await clickSave(target); assert.strictEqual( - target.querySelector(".o_field_widget").textContent, + target.querySelector(".o_field_widget input").value, "26.000", "The new value should be calculated properly." ); - await click(target, ".o_form_button_edit"); await editInput(target, ".o_field_widget[name=float_field] input", "=2**3"); - await click(target, ".o_form_button_save"); + await clickSave(target); assert.strictEqual( - target.querySelector(".o_field_widget").textContent, + target.querySelector(".o_field_widget input").value, "8.000", "The new value should be calculated properly." ); - await click(target, ".o_form_button_edit"); await editInput(target, ".o_field_widget[name=float_field] input", "=2^3"); - await click(target, ".o_form_button_save"); + await clickSave(target); assert.strictEqual( - target.querySelector(".o_field_widget").textContent, + target.querySelector(".o_field_widget input").value, "8.000", "The new value should be calculated properly." ); - await click(target, ".o_form_button_edit"); await editInput(target, ".o_field_widget[name=float_field] input", "=100/3"); - await click(target, ".o_form_button_save"); + await clickSave(target); assert.strictEqual( - target.querySelector(".o_field_widget").textContent, + target.querySelector(".o_field_widget input").value, "33.333", "The new value should be calculated properly." ); @@ -232,9 +218,8 @@ QUnit.module("Fields", (hooks) => { arch: `
`, }); - await click(target, ".o_form_button_edit"); await editInput(target, ".o_field_widget[name=float_field] input", "=abc"); - await click(target, ".o_form_button_save"); + await clickSave(target); assert.hasClass( target.querySelector(".o_field_widget[name=float_field]"), @@ -244,7 +229,7 @@ QUnit.module("Fields", (hooks) => { assert.containsOnce(target, ".o_form_editable", "form view should still be editable"); await editInput(target, ".o_field_widget[name=float_field] input", "=3:2?+4"); - await click(target, ".o_form_button_save"); + await clickSave(target); assert.containsOnce(target, ".o_form_editable", "form view should still be editable"); assert.hasClass( @@ -291,32 +276,6 @@ QUnit.module("Fields", (hooks) => { ); }); - QUnit.test("do not trigger a field_changed if they have not changed", async function (assert) { - serverData.models.partner.records[0].float_field = false; - serverData.models.partner.records[0].int_field = false; - await makeView({ - serverData, - type: "form", - resModel: "partner", - arch: ` -
- - - - -
`, - mockRPC(route, { method }) { - assert.step(method); - }, - resId: 1, - }); - - await click(target.querySelector(".o_form_button_edit")); - await click(target.querySelector(".o_form_button_save")); - - assert.verifySteps(["get_views", "read"]); // should not have save as nothing changed - }); - QUnit.test("float field with type number option", async function (assert) { await makeView({ serverData, @@ -336,7 +295,6 @@ QUnit.module("Fields", (hooks) => { makeFakeLocalizationService({ thousandsSep: ",", grouping: [3, 0] }) ); - await click(target.querySelector(".o_form_button_edit")); assert.ok( target.querySelector(".o_field_widget input").hasAttribute("type"), "Float field with option type must have a type attribute." @@ -348,19 +306,12 @@ QUnit.module("Fields", (hooks) => { 'Float field with option type must have a type attribute equals to "number".' ); await editInput(target, ".o_field_widget[name=float_field] input", "123456.7890"); - await click(target.querySelector(".o_form_button_save")); - await click(target.querySelector(".o_form_button_edit")); + await clickSave(target); assert.strictEqual( target.querySelector(".o_field_widget input").value, "123456.789", "Float value must be not formatted if input type is number. (but the trailing 0 is gone)" ); - await click(target.querySelector(".o_form_button_save")); - assert.strictEqual( - target.querySelector(".o_field_widget").textContent, - "123,456.79", - "Float value must be formatted in readonly view even if the input type is number." - ); }); QUnit.test( @@ -386,7 +337,6 @@ QUnit.module("Fields", (hooks) => { }) ); - await click(target.querySelector(".o_form_button_edit")); assert.ok( target.querySelector(".o_field_widget input").hasAttribute("type"), "Float field with option type must have a type attribute." @@ -398,19 +348,12 @@ QUnit.module("Fields", (hooks) => { 'Float field with option type must have a type attribute equals to "number".' ); await editInput(target, ".o_field_widget[name=float_field] input", "123456.789"); - await click(target.querySelector(".o_form_button_save")); - await click(target.querySelector(".o_form_button_edit")); + await clickSave(target); assert.strictEqual( target.querySelector(".o_field_widget input").value, "123456.789", "Float value must be not formatted if input type is number." ); - await click(target.querySelector(".o_form_button_save")); - assert.strictEqual( - target.querySelector(".o_field_widget").textContent, - "123.456,79", - "Float value must be formatted in readonly view even if the input type is number." - ); } ); @@ -430,7 +373,6 @@ QUnit.module("Fields", (hooks) => { makeFakeLocalizationService({ thousandsSep: ",", grouping: [3, 0] }) ); - await click(target.querySelector(".o_form_button_edit")); assert.hasAttrValue( target.querySelector(".o_field_widget input"), "type", @@ -439,8 +381,7 @@ QUnit.module("Fields", (hooks) => { ); await editInput(target, ".o_field_widget[name=float_field] input", "123456.7890"); - await click(target.querySelector(".o_form_button_save")); - await click(target.querySelector(".o_form_button_edit")); + await clickSave(target); assert.strictEqual( target.querySelector(".o_field_widget input").value, "123,456.79", diff --git a/addons/web/static/tests/views/fields/float_time_field_tests.js b/addons/web/static/tests/views/fields/float_time_field_tests.js index e8aada0d09d..e9b9b59f313 100644 --- a/addons/web/static/tests/views/fields/float_time_field_tests.js +++ b/addons/web/static/tests/views/fields/float_time_field_tests.js @@ -1,6 +1,6 @@ /** @odoo-module **/ -import { click, editInput, getFixture, triggerEvent } from "@web/../tests/helpers/utils"; +import { clickSave, editInput, getFixture, triggerEvent } from "@web/../tests/helpers/utils"; import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; let serverData; @@ -26,7 +26,7 @@ QUnit.module("Fields", (hooks) => { QUnit.module("FloatTimeField"); QUnit.test("FloatTimeField in form view", async function (assert) { - assert.expect(5); + assert.expect(4); await makeView({ serverData, @@ -52,13 +52,6 @@ QUnit.module("Fields", (hooks) => { }); // 9 + 0.1 * 60 = 9.06 - assert.strictEqual( - target.querySelector(".o_field_widget").textContent, - "09:06", - "The formatted time value should be displayed properly." - ); - - await click(target, ".o_form_button_edit"); assert.strictEqual( target.querySelector(".o_field_float_time[name=qux] input").value, "09:06", @@ -76,9 +69,9 @@ QUnit.module("Fields", (hooks) => { "The new value should be displayed properly in the input." ); - await click(target, ".o_form_button_save"); + await clickSave(target); assert.strictEqual( - target.querySelector(".o_field_widget").textContent, + target.querySelector(".o_field_widget input").value, "-11:48", "The new value should be saved and displayed properly." ); @@ -108,12 +101,11 @@ QUnit.module("Fields", (hooks) => { }); assert.strictEqual( - target.querySelector(".o_field_widget").textContent, + target.querySelector(".o_field_widget input").value, "09:06", "The formatted time value should be displayed properly." ); - await click(target, ".o_form_button_edit"); await editInput(target.querySelector(".o_field_float_time[name=qux] input"), null, "9.5"); assert.strictEqual( target.querySelector(".o_field_float_time[name=qux] input").value, @@ -121,9 +113,9 @@ QUnit.module("Fields", (hooks) => { "The new value should be displayed properly in the input." ); - await click(target, ".o_form_button_save"); + await clickSave(target); assert.strictEqual( - target.querySelector(".o_field_widget").textContent, + target.querySelector(".o_field_widget input").value, "09:30", "The new value should be saved and displayed properly." ); @@ -145,7 +137,7 @@ QUnit.module("Fields", (hooks) => { null, "blabla" ); - await click(target, ".o_form_button_save"); + await clickSave(target); assert.strictEqual( target.querySelector(".o_notification_title").textContent, "Invalid fields: " diff --git a/addons/web/static/tests/views/fields/float_toggle_field_tests.js b/addons/web/static/tests/views/fields/float_toggle_field_tests.js index 427c0c29b1a..76925189200 100644 --- a/addons/web/static/tests/views/fields/float_toggle_field_tests.js +++ b/addons/web/static/tests/views/fields/float_toggle_field_tests.js @@ -1,6 +1,6 @@ /** @odoo-module **/ -import { click, clickEdit, clickSave, getFixture } from "@web/../tests/helpers/utils"; +import { click, clickSave, getFixture } from "@web/../tests/helpers/utils"; import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; let serverData; @@ -47,9 +47,6 @@ QUnit.module("Fields", (hooks) => { "0.056", // 0.4444 * 0.125 "The formatted time value should be displayed properly." ); - - await clickEdit(target); - assert.strictEqual( target.querySelector("button.o_field_float_toggle").textContent, "0.056", diff --git a/addons/web/static/tests/views/fields/handle_field_tests.js b/addons/web/static/tests/views/fields/handle_field_tests.js index 4d17d8b8797..b638af8041f 100644 --- a/addons/web/static/tests/views/fields/handle_field_tests.js +++ b/addons/web/static/tests/views/fields/handle_field_tests.js @@ -73,16 +73,13 @@ QUnit.module("Fields", (hooks) => { "handle should not have any content" ); - assert.strictEqual( - getComputedStyle(target.querySelector("td span.o_row_handle")).display, - "none", - "handle should be invisible in readonly mode" + assert.isVisible( + target.querySelector("td span.o_row_handle"), + "handle should be invisible" ); assert.containsN(target, "span.o_row_handle", 2, "should have 2 handles"); - await click(target, ".o_form_button_edit"); - assert.hasClass( target.querySelector("td"), "o_handle_cell", @@ -150,16 +147,7 @@ QUnit.module("Fields", (hooks) => { assert.strictEqual( getComputedStyle(target.querySelector("td span.o_row_handle")).display, "none", - "handle should be invisible in readonly mode" - ); - - await click(target, ".o_form_button_edit"); - - assert.containsN(target, ".o_row_handle", 3, "the handle fields should still be there"); - assert.strictEqual( - getComputedStyle(target.querySelector("td span.o_row_handle")).display, - "none", - "the handle icons should still not be displayed (on readonly fields)" + "handle should be invisible" ); }); }); diff --git a/addons/web/static/tests/views/fields/html_field_tests.js b/addons/web/static/tests/views/fields/html_field_tests.js index eccac230021..2ef1afd4acc 100644 --- a/addons/web/static/tests/views/fields/html_field_tests.js +++ b/addons/web/static/tests/views/fields/html_field_tests.js @@ -1,6 +1,6 @@ /** @odoo-module **/ -import { click, editInput, getFixture } from "@web/../tests/helpers/utils"; +import { editInput, getFixture } from "@web/../tests/helpers/utils"; import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; import { registry } from "@web/core/registry"; import { HtmlField } from "@web/views/fields/html/html_field"; @@ -34,7 +34,21 @@ QUnit.module("Fields", ({ beforeEach }) => { QUnit.module("HtmlField"); - QUnit.test("html fields are correctly rendered", async (assert) => { + QUnit.test("html fields are correctly rendered (readonly)", async (assert) => { + await makeView({ + type: "form", + resModel: "partner", + resId: 1, + serverData, + arch: /* xml */ `
`, + }); + + assert.containsOnce(target, "div.kek"); + assert.strictEqual(target.querySelector(".o_field_html .kek").style.color, "red"); + assert.strictEqual(target.querySelector(".o_field_html").textContent, "some text"); + }); + + QUnit.test("html fields are correctly rendered (edit)", async (assert) => { await makeView({ type: "form", resModel: "partner", @@ -43,11 +57,6 @@ QUnit.module("Fields", ({ beforeEach }) => { arch: /* xml */ `
`, }); - assert.containsOnce(target, ".o_field_html", "should have a text area"); - assert.strictEqual(target.querySelector(".o_field_html .kek").style.color, "red"); - assert.strictEqual(target.querySelector(".o_field_html").textContent, "some text"); - - await click(target, ".o_form_button_edit"); const textarea = target.querySelector(".o_field_html textarea"); assert.ok(textarea, "should have a text area"); assert.strictEqual(textarea.value, RED_TEXT); @@ -58,10 +67,5 @@ QUnit.module("Fields", ({ beforeEach }) => { await editInput(textarea, null, BLUE_TEXT); assert.strictEqual(textarea.value, BLUE_TEXT); - - await click(target, ".o_form_button_save"); - - assert.strictEqual(target.querySelector(".o_field_html .kek").style.color, "blue"); - assert.strictEqual(target.querySelector(".o_field_html").textContent, "hello world"); }); }); diff --git a/addons/web/static/tests/views/fields/image_field_tests.js b/addons/web/static/tests/views/fields/image_field_tests.js index 510bf92807e..ce36b089db6 100644 --- a/addons/web/static/tests/views/fields/image_field_tests.js +++ b/addons/web/static/tests/views/fields/image_field_tests.js @@ -5,7 +5,6 @@ import { getFixture, nextTick, triggerEvent, - clickEdit, clickSave, editInput, } from "@web/../tests/helpers/utils"; @@ -131,8 +130,6 @@ QUnit.module("Fields", (hooks) => { "the image should correctly set its attributes" ); - await click(target, ".o_form_button_edit"); - assert.containsOnce( target, ".o_field_image .o_select_file_button", @@ -209,8 +206,6 @@ QUnit.module("Fields", (hooks) => { "the image should correctly set its attributes" ); - await click(target, ".o_form_button_edit"); - assert.containsOnce( target, ".o_field_image .o_select_file_button", @@ -241,7 +236,6 @@ QUnit.module("Fields", (hooks) => { "data:image/png;base64,coucou==", "the image should have the initial src" ); - await click(target, ".o_form_button_edit"); // Whitebox: replace the event target before the event is handled by the field so that we can modify // the files that it will take into account. This relies on the fact that it reads the files from // event.target and not from a direct reference to the input element. @@ -281,7 +275,6 @@ QUnit.module("Fields", (hooks) => { `, }); // The view must be in edit mode - await click(target.querySelector(".o_form_button_edit")); assert.strictEqual( target.querySelector("input.o_input_file").getAttribute("accept"), ".png,.jpeg", @@ -320,7 +313,7 @@ QUnit.module("Fields", (hooks) => { ); }); - QUnit.test("ImageField: zoom and zoom_delay options", async function (assert) { + QUnit.test("ImageField: zoom and zoom_delay options (readonly)", async (assert) => { serverData.models.partner.records[0].document = MY_IMAGE; await makeView({ @@ -330,7 +323,7 @@ QUnit.module("Fields", (hooks) => { serverData, arch: `
- + `, }); // data-tooltip attribute is used by the tooltip service @@ -344,8 +337,22 @@ QUnit.module("Fields", (hooks) => { "600", "tooltip has the right delay" ); + }); + + QUnit.test("ImageField: zoom and zoom_delay options (edit)", async function (assert) { + serverData.models.partner.records[0].document = MY_IMAGE; + + await makeView({ + type: "form", + resModel: "partner", + resId: 1, + serverData, + arch: ` +
+ + `, + }); - await click(target.querySelector(".o_form_button_edit")); assert.ok( !target.querySelector(".o_field_image img").dataset["tooltipInfo"], "the tooltip is not present in edition" @@ -353,7 +360,7 @@ QUnit.module("Fields", (hooks) => { }); QUnit.test( - "ImageField displays the right images with zoom and preview_image options", + "ImageField displays the right images with zoom and preview_image options (readonly)", async function (assert) { serverData.models.partner.records[0].document = "3 kb"; serverData.models.partner.records[0].__last_update = "2022-08-05 08:37:00"; @@ -365,7 +372,7 @@ QUnit.module("Fields", (hooks) => { serverData, arch: `
- + `, }); @@ -479,7 +486,7 @@ QUnit.module("Fields", (hooks) => { }, }); - await click(target, ".o_form_button_save"); + await clickSave(target); assert.containsOnce( target.querySelector(".o_form_view"), @@ -535,7 +542,6 @@ QUnit.module("Fields", (hooks) => { assert.verifySteps(["get_views", "read"]); assert.strictEqual(getUnique(target.querySelector(".o_field_image img")), "1659688620000"); - await clickEdit(target); assert.verifySteps([]); // same unique as before assert.strictEqual(getUnique(target.querySelector(".o_field_image img")), "1659688620000"); @@ -547,8 +553,8 @@ QUnit.module("Fields", (hooks) => { await clickSave(target); assert.verifySteps(["write", "read"]); - // different unique: the record has been written - assert.strictEqual(getUnique(target.querySelector(".o_field_image img")), "1659692220000"); + + assert.strictEqual(getUnique(target.querySelector(".o_field_image img")), "1659688620000"); }); QUnit.test("unique in url change on record change", async (assert) => { diff --git a/addons/web/static/tests/views/fields/image_url_field_tests.js b/addons/web/static/tests/views/fields/image_url_field_tests.js index 6545ec6f7fe..274562f0b5b 100644 --- a/addons/web/static/tests/views/fields/image_url_field_tests.js +++ b/addons/web/static/tests/views/fields/image_url_field_tests.js @@ -228,7 +228,7 @@ QUnit.module("Fields", (hooks) => { }); assert.hasClass( - target.querySelector('div[name="foo"].o_field_empty'), + target.querySelector('div[name="foo"]'), "o_field_image_url", "the widget should have the correct class" ); diff --git a/addons/web/static/tests/views/fields/integer_field_tests.js b/addons/web/static/tests/views/fields/integer_field_tests.js index c6001131d7a..7b8ef00acb4 100644 --- a/addons/web/static/tests/views/fields/integer_field_tests.js +++ b/addons/web/static/tests/views/fields/integer_field_tests.js @@ -4,6 +4,7 @@ import { localization } from "@web/core/l10n/localization"; import { defaultLocalization } from "@web/../tests/helpers/mock_services"; import { click, + clickSave, editInput, getFixture, patchWithCleanup, @@ -56,7 +57,7 @@ QUnit.module("Fields", (hooks) => { ); assert.strictEqual( - target.querySelector(".o_field_widget").textContent, + target.querySelector(".o_field_widget input").value, "0", "Non-set integer field should be recognized as 0." ); @@ -71,8 +72,6 @@ QUnit.module("Fields", (hooks) => { arch: '
', }); - await click(target, ".o_form_button_edit"); - assert.strictEqual( target.querySelector(".o_field_widget[name=int_field] input").value, "10", @@ -87,10 +86,9 @@ QUnit.module("Fields", (hooks) => { "The value should be correctly displayed in the input." ); - await click(target, ".o_form_button_save"); - + await clickSave(target); assert.strictEqual( - target.querySelector(".o_field_widget").textContent, + target.querySelector(".o_field_widget input").value, "30", "The new value should be saved and displayed properly." ); @@ -105,12 +103,11 @@ QUnit.module("Fields", (hooks) => { arch: '
', }); - await click(target, ".o_form_button_edit"); await editInput(target, ".o_field_widget[name=int_field] input", "=100/3"); - await click(target, ".o_form_button_save"); + await clickSave(target); assert.strictEqual( - target.querySelector(".o_field_widget").textContent, + target.querySelector(".o_field_widget input").value, "33", "The new value should be calculated properly." ); @@ -127,8 +124,6 @@ QUnit.module("Fields", (hooks) => { arch: `
`, }); - await click(target, ".o_form_button_edit"); - assert.ok( target.querySelector(".o_field_widget input").hasAttribute("type"), "Integer field with option type must have a type attribute." @@ -142,22 +137,13 @@ QUnit.module("Fields", (hooks) => { ); await editInput(target, ".o_field_widget[name=int_field] input", "1234567890"); - await click(target, ".o_form_button_save"); - await click(target, ".o_form_button_edit"); + await clickSave(target); assert.strictEqual( target.querySelector(".o_field_widget input").value, "1234567890", "Integer value must be not formatted if input type is number." ); - - await click(target, ".o_form_button_save"); - - assert.strictEqual( - target.querySelector(".o_field_widget").textContent, - "1,234,567,890", - "Integer value must be formatted in readonly view even if the input type is number." - ); }); QUnit.test("with 'step' option", async function (assert) { @@ -169,13 +155,10 @@ QUnit.module("Fields", (hooks) => { arch: `
`, }); - await click(target, ".o_form_button_edit"); - assert.ok( target.querySelector(".o_field_widget input").hasAttribute("step"), "Integer field with option type must have a step attribute." ); - assert.hasAttrValue( target.querySelector(".o_field_widget input"), "step", @@ -195,8 +178,6 @@ QUnit.module("Fields", (hooks) => { arch: '
', }); - await click(target, ".o_form_button_edit"); - assert.hasAttrValue( target.querySelector(".o_field_widget input"), "type", @@ -205,8 +186,7 @@ QUnit.module("Fields", (hooks) => { ); await editInput(target, ".o_field_widget[name=int_field] input", "1234567890"); - await click(target, ".o_form_button_save"); - await click(target, ".o_form_button_edit"); + await clickSave(target); assert.strictEqual( target.querySelector(".o_field_widget input").value, @@ -224,14 +204,6 @@ QUnit.module("Fields", (hooks) => { arch: `
`, }); - assert.strictEqual( - target.querySelector(".o_field_widget[name=int_field]").textContent, - "8069", - "Integer value must not be formatted" - ); - - await click(target, ".o_form_button_edit"); - assert.strictEqual( target.querySelector(".o_field_widget input").value, "8069", @@ -250,14 +222,6 @@ QUnit.module("Fields", (hooks) => { arch: '
', }); - assert.strictEqual( - target.querySelector(".o_field_widget[name=int_field]").textContent, - "8,069", - "Integer value must be formatted by default" - ); - - await click(target, ".o_form_button_edit"); - assert.strictEqual( target.querySelector(".o_field_widget input").value, "8,069", diff --git a/addons/web/static/tests/views/fields/label_selection_field_tests.js b/addons/web/static/tests/views/fields/label_selection_field_tests.js index 73ef19c83d0..8b686dd979a 100644 --- a/addons/web/static/tests/views/fields/label_selection_field_tests.js +++ b/addons/web/static/tests/views/fields/label_selection_field_tests.js @@ -91,52 +91,6 @@ QUnit.module("Fields", (hooks) => { "Blocked", "the label should say 'Blocked' since this is the label value for that state" ); - - // // switch to edit mode and check the result - await click(target.querySelector(".o_form_button_edit")); - assert.containsOnce( - target, - ".o_field_widget .badge.text-bg-warning", - "should have a warning status label since selection is the second, blocked state" - ); - assert.containsNone( - target, - ".o_field_widget .badge.text-bg-secondary", - "should not have a default status since selection is the second, blocked state" - ); - assert.containsNone( - target, - ".o_field_widget .badge.text-bg-success", - "should not have a success status since selection is the second, blocked state" - ); - assert.strictEqual( - target.querySelector(".o_field_widget .badge.text-bg-warning").textContent, - "Blocked", - "the label should say 'Blocked' since this is the label value for that state" - ); - - // save - await click(target.querySelector(".o_form_button_save")); - assert.containsOnce( - target, - ".o_field_widget .badge.text-bg-warning", - "should have a warning status label since selection is the second, blocked state" - ); - assert.containsNone( - target, - ".o_field_widget .badge.text-bg-secondary", - "should not have a default status since selection is the second, blocked state" - ); - assert.containsNone( - target, - ".o_field_widget .badge.text-bg-success", - "should not have a success status since selection is the second, blocked state" - ); - assert.strictEqual( - target.querySelector(".o_field_widget .badge.text-bg-warning").textContent, - "Blocked", - "the label should say 'Blocked' since this is the label value for that state" - ); }); QUnit.test("LabelSelectionField in editable list view", async function (assert) { diff --git a/addons/web/static/tests/views/fields/many2many_binary_field_tests.js b/addons/web/static/tests/views/fields/many2many_binary_field_tests.js index 1737efc4c98..796327e478b 100644 --- a/addons/web/static/tests/views/fields/many2many_binary_field_tests.js +++ b/addons/web/static/tests/views/fields/many2many_binary_field_tests.js @@ -1,6 +1,6 @@ /** @odoo-module **/ -import { click, getFixture, nextTick } from "@web/../tests/helpers/utils"; +import { click, clickSave, getFixture, nextTick } from "@web/../tests/helpers/utils"; import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; import { registry } from "@web/core/registry"; @@ -111,19 +111,17 @@ QUnit.module("Fields", (hooks) => { "div.o_field_widget .oe_fileupload .o_attachments", "there should be one attachment" ); - assert.containsNone( + assert.containsOnce( target, "div.o_field_widget .oe_fileupload .o_attach", - "there should not be an Add button (readonly)" + "there should be an Add button (edit)" ); - assert.containsNone( + assert.containsOnce( target, "div.o_field_widget .oe_fileupload .o_attachment .o_attachment_delete", - "there should not be a Delete button (readonly)" + "there should be a Delete button (edit)" ); - // to edit mode - await click(target, ".o_form_button_edit"); assert.containsOnce( target, "div.o_field_widget .oe_fileupload .o_attach", @@ -176,7 +174,7 @@ QUnit.module("Fields", (hooks) => { ) ); - await click(target, ".o_form_button_save"); + await clickSave(target); assert.containsOnce( target, "div.o_field_widget .oe_fileupload .o_attachments", @@ -251,20 +249,17 @@ QUnit.module("Fields", (hooks) => { "div.o_field_widget .oe_fileupload .o_attachments", "there should be one attachment" ); - assert.containsNone( + assert.containsOnce( target, "div.o_field_widget .oe_fileupload .o_attach", - "there should not be an Add button (readonly)" + "there should be an Add button (edit)" ); - assert.containsNone( + assert.containsOnce( target, "div.o_field_widget .oe_fileupload .o_attachment .o_attachment_delete", - "there should not be a Delete button (readonly)" + "there should be a Delete button (edit)" ); - // to edit mode - await click(target, ".o_form_button_edit"); - // Set and trigger the import of 2 files in the input const fileInput = target.querySelector('input[type="file"]'); const dataTransfer = new DataTransfer(); diff --git a/addons/web/static/tests/views/fields/many2many_checkboxes_field_tests.js b/addons/web/static/tests/views/fields/many2many_checkboxes_field_tests.js index 5059e041bb5..9acd753e8ea 100644 --- a/addons/web/static/tests/views/fields/many2many_checkboxes_field_tests.js +++ b/addons/web/static/tests/views/fields/many2many_checkboxes_field_tests.js @@ -67,22 +67,17 @@ QUnit.module("Fields", (hooks) => { assert.ok(checkboxes[0].checked); assert.notOk(checkboxes[1].checked); - assert.containsN(target, "div.o_field_widget div.form-check input:disabled", 2); - - await click(target, ".o_form_button_edit"); - assert.containsNone(target, "div.o_field_widget div.form-check input:disabled"); // add a m2m value by clicking on input checkboxes = target.querySelectorAll("div.o_field_widget div.form-check input"); await click(checkboxes[1]); - await click(target, ".o_form_button_save"); + await clickSave(target); assert.containsN(target, "div.o_field_widget div.form-check input:checked", 2); // remove a m2m value by clinking on label - await click(target, ".o_form_button_edit"); await click(target.querySelector("div.o_field_widget div.form-check > label")); - await click(target, ".o_form_button_save"); + await clickSave(target); checkboxes = target.querySelectorAll("div.o_field_widget div.form-check input"); assert.notOk(checkboxes[0].checked); assert.ok(checkboxes[1].checked); @@ -111,25 +106,6 @@ QUnit.module("Fields", (hooks) => { 2, "should have fetched and displayed the 2 values of the many2many" ); - - assert.ok( - target.querySelector("div.o_field_widget div.form-check input").checked, - "first checkbox should be checked" - ); - assert.notOk( - target.querySelectorAll("div.o_field_widget div.form-check input")[1].checked, - "second checkbox should not be checked" - ); - - assert.containsN( - target, - "div.o_field_widget div.form-check input:disabled", - 2, - "the checkboxes should be disabled" - ); - - await click(target, ".o_form_button_edit"); - assert.containsN( target, "div.o_field_widget div.form-check input:disabled", @@ -166,11 +142,9 @@ QUnit.module("Fields", (hooks) => { `, }); - await click(target, ".o_form_button_edit"); - await click(target.querySelectorAll("div.o_field_widget div.form-check input")[0]); await click(target.querySelectorAll("div.o_field_widget div.form-check input")[1]); - await click(target, ".o_form_button_save"); + await clickSave(target); assert.notOk( target.querySelectorAll("div.o_field_widget div.form-check input")[0].checked, "first checkbox should not be checked" @@ -197,8 +171,6 @@ QUnit.module("Fields", (hooks) => { `, }); - await click(target, ".o_form_button_edit"); - assert.strictEqual( target.querySelector(".o_field_widget[name='int_field'] input").value, "10" @@ -255,8 +227,6 @@ QUnit.module("Fields", (hooks) => { }, }); - await click(target, ".o_form_button_edit"); - assert.containsN( target, ".o_field_widget[name='timmy'] input[type='checkbox']:checked", @@ -269,7 +239,7 @@ QUnit.module("Fields", (hooks) => { ); await click(checkboxes[checkboxes.length - 1]); - await click(target, ".o_form_button_save"); + await clickSave(target); checkboxes = target.querySelectorAll( ".o_field_widget[name='timmy'] input[type='checkbox']" ); @@ -316,8 +286,6 @@ QUnit.module("Fields", (hooks) => { }, }); - await click(target, ".o_form_button_edit"); - assert.containsN( target, ".o_field_widget[name='timmy'] input[type='checkbox']", @@ -331,7 +299,7 @@ QUnit.module("Fields", (hooks) => { // toggle the first value await click(target.querySelector(".o_field_widget[name='timmy'] input[type='checkbox']")); - await click(target, ".o_form_button_save"); + await clickSave(target); assert.notOk( target.querySelector(".o_field_widget[name='timmy'] input[type='checkbox']").checked ); @@ -367,7 +335,6 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - await click(target, ".o_form_button_edit"); await click(target.querySelector(".o_data_cell")); // edit the timmy field by (un)checking boxes on the widget @@ -379,7 +346,7 @@ QUnit.module("Fields", (hooks) => { assert.notOk(secondCheckbox.checked, "the checkbox should be unticked"); await click(target.querySelector(".modal .o_form_button_save")); - await click(target.querySelector(".o_form_button_save")); + await clickSave(target); }); QUnit.test("Many2ManyCheckBoxesField with default values", async function (assert) { diff --git a/addons/web/static/tests/views/fields/many2many_field_tests.js b/addons/web/static/tests/views/fields/many2many_field_tests.js index 6e1de164f77..3f4bc49f5c8 100644 --- a/addons/web/static/tests/views/fields/many2many_field_tests.js +++ b/addons/web/static/tests/views/fields/many2many_field_tests.js @@ -4,7 +4,6 @@ import { browser } from "@web/core/browser/browser"; import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; import { click, - clickEdit, clickSave, clickOpenedDropdownItem, editInput, @@ -223,7 +222,7 @@ QUnit.module("Fields", (hooks) => { QUnit.module("Many2ManyField"); QUnit.test("many2many kanban: edition", async function (assert) { - assert.expect(33); + assert.expect(31); serverData.views = { "partner_type,false,form": '
', @@ -296,17 +295,6 @@ QUnit.module("Fields", (hooks) => { }, }); - assert.ok( - !$(target).find(".o_kanban_renderer .delete_icon").length, - "delete icon should not be visible in readonly" - ); - assert.ok( - !$(target).find(".o_field_many2many .o-kanban-button-new").length, - '"Add" button should not be visible in readonly' - ); - - await clickEdit(target); - assert.strictEqual( $(target).find(".o_kanban_record:not(.o_kanban_ghost)").length, 2, @@ -467,7 +455,6 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - await clickEdit(target); assert.strictEqual( target .querySelector(".o_field_many2many[name=timmy] .o-kanban-button-new") @@ -549,13 +536,6 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - assert.ok( - !$(target).find(".o-kanban-button-new").length, - '"Add" button should not be available in readonly' - ); - - await clickEdit(target); - assert.ok( $(target).find(".o-kanban-button-new").length, '"Add" button should be available in edit' @@ -603,7 +583,6 @@ QUnit.module("Fields", (hooks) => { `, resId: 1, }); - await clickEdit(target); // color is red assert.containsOnce(target, ".o-kanban-button-new", '"Add" button should be available'); @@ -653,7 +632,7 @@ QUnit.module("Fields", (hooks) => { }); QUnit.test("many2many list (non editable): edition", async function (assert) { - assert.expect(34); + assert.expect(32); serverData.models.partner.records[0].timmy = [12, 14]; serverData.models.partner_type.records.push({ id: 15, display_name: "bronze", color: 6 }); @@ -693,18 +672,6 @@ QUnit.module("Fields", (hooks) => { "read", // main record "read", // relational field ]); - assert.containsNone( - target, - ".o_list_record_remove", - "delete icon should not be visible in readonly" - ); - assert.containsNone( - target, - ".o_field_x2many_list_row_add", - '"Add an item" should not be visible in readonly' - ); - - await clickEdit(target); assert.containsN( target, @@ -842,7 +809,7 @@ QUnit.module("Fields", (hooks) => { }); QUnit.test("many2many list (editable): edition", async function (assert) { - assert.expect(31); + assert.expect(29); serverData.models.partner.records[0].timmy = [12, 14]; serverData.models.partner_type.records.push({ id: 15, display_name: "bronze", color: 6 }); @@ -879,11 +846,6 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - assert.containsNone(target, ".o_list_record_remove"); - assert.containsNone(target, ".o_field_x2many_list_row_add"); - - await clickEdit(target); - assert.containsN( target, ".o_list_renderer td.o_list_number", @@ -1020,8 +982,6 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - await clickEdit(target); - assert.containsOnce( target, ".o_field_x2many_list_row_add", @@ -1048,8 +1008,6 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - await clickEdit(target); - assert.containsOnce( target, ".o_field_x2many_list_row_add", @@ -1079,8 +1037,6 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - assert.containsNone(target, ".o_field_x2many_list_row_add"); - await clickEdit(target); assert.containsOnce(target, ".o_field_x2many_list_row_add"); }); @@ -1138,7 +1094,6 @@ QUnit.module("Fields", (hooks) => { assert.containsOnce(target, ".o_field_many2many .o_field_x2many_list_row_add"); await clickSave(target); - await clickEdit(target); assert.containsOnce(target, ".o_field_many2many .o_data_row .o_list_record_remove"); await click(target.querySelector(".o_field_many2many .o_data_row .o_list_record_remove")); @@ -1169,8 +1124,6 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - await clickEdit(target); - // color is red -> create and delete actions are available assert.containsOnce( target, @@ -1239,8 +1192,6 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - await clickEdit(target); - // color is red -> link and unlink actions are available assert.containsOnce( target, @@ -1300,8 +1251,6 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - await clickEdit(target); - // color is red -> link and unlink actions are available assert.containsOnce( target, @@ -1364,7 +1313,6 @@ QUnit.module("Fields", (hooks) => { `, resId: 1, }); - await clickEdit(target); // color is red -> link and unlink actions are available assert.containsOnce(target, ".o-kanban-button-new", "should have the 'Add' button"); @@ -1416,7 +1364,6 @@ QUnit.module("Fields", (hooks) => { `, resId: 1, }); - await clickEdit(target); // color is red -> link and unlink actions are available assert.containsOnce(target, ".o-kanban-button-new", "should have the 'Add' button"); @@ -1502,8 +1449,6 @@ QUnit.module("Fields", (hooks) => { }, }); - await clickEdit(target); - await click(target.querySelector(".o_field_x2many_list_row_add a")); await click($(target).find(".modal .o_data_row:first .o_data_cell")[0]); @@ -1569,7 +1514,6 @@ QUnit.module("Fields", (hooks) => { `, resId: 1, }); - await clickEdit(target); await click(target.querySelector(".o_field_x2many_list_row_add a")); assert.strictEqual($(".modal .o_data_row").length, 1, "should contain only one row (gold)"); @@ -1608,7 +1552,6 @@ QUnit.module("Fields", (hooks) => { }); assert.verifySteps(["get_views", "read", "read"]); - await clickEdit(target); await click($(target).find("td.o_data_cell:first")[0]); assert.verifySteps(["get_views", "read"]); @@ -1671,8 +1614,6 @@ QUnit.module("Fields", (hooks) => { }, }); - await clickEdit(target); - assert.verifySteps(["get_views", "read"]); await editInput(target, ".o_field_widget[name=foo] input", "trigger onchange"); @@ -1823,7 +1764,6 @@ QUnit.module("Fields", (hooks) => { }); // First round: add 51 records in batch - await clickEdit(target); await click(target.querySelector(".o_field_x2many_list_row_add a")); var $modal = $(".modal-lg"); @@ -1844,7 +1784,6 @@ QUnit.module("Fields", (hooks) => { await clickSave(target); // Secound round: remove one record - await clickEdit(target); var trash_buttons = $(target).find( ".o_field_many2many.o_field_widget .o_field_x2many.o_field_x2many_list .o_list_record_remove" ); @@ -1942,7 +1881,6 @@ QUnit.module("Fields", (hooks) => { } }, }); - await clickEdit(target); await click(target, ".o_field_many2many_selection input"); checkGetViews = true; await clickOpenedDropdownItem(target, "timmy", "Search More..."); @@ -2000,6 +1938,7 @@ QUnit.module("Fields", (hooks) => { await click(target.querySelector(".o_data_cell")); await editInput(target, ".o_field_many2many_selection input", "indianapolis"); + await nextTick(); await clickOpenedDropdownItem(target, "timmy", "Create and edit..."); assert.containsOnce(target, ".modal .o_field_many2one"); assert.strictEqual( @@ -2055,8 +1994,8 @@ QUnit.module("Fields", (hooks) => { }, }); - await clickEdit(target); await editInput(target, ".o_field_many2many_selection input", "indianapolis"); + await nextTick(); await clickOpenedDropdownItem(target, "timmy", "Create and edit..."); assert.containsOnce(target, ".modal .o_field_many2one"); assert.strictEqual( @@ -2120,7 +2059,6 @@ QUnit.module("Fields", (hooks) => { }, }); - await clickEdit(target); await click(target, ".o_data_cell"); await editInput(target, ".o_field_many2many_selection input", "indianapolis"); await clickOpenedDropdownItem(target, "timmy", "Create and edit..."); diff --git a/addons/web/static/tests/views/fields/many2many_tags_avatar_field_tests.js b/addons/web/static/tests/views/fields/many2many_tags_avatar_field_tests.js index 00abfbaf44e..71ddfc2d1c2 100644 --- a/addons/web/static/tests/views/fields/many2many_tags_avatar_field_tests.js +++ b/addons/web/static/tests/views/fields/many2many_tags_avatar_field_tests.js @@ -1,12 +1,6 @@ /** @odoo-module **/ -import { - click, - clickEdit, - clickSave, - getFixture, - selectDropdownItem, -} from "@web/../tests/helpers/utils"; +import { click, clickSave, getFixture, selectDropdownItem } from "@web/../tests/helpers/utils"; import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; let serverData; @@ -206,12 +200,12 @@ QUnit.module("Fields", (hooks) => { assert.strictEqual( tag.dataset["tooltipTemplate"], "web.TagsList.Tooltip", - "uses the proper tooltip template", + "uses the proper tooltip template" ); const tooltipInfo = JSON.parse(tag.dataset["tooltipInfo"]); assert.strictEqual( - tooltipInfo.tags.map(tag => tag.text).join(" "), - 'record 6 record 7', + tooltipInfo.tags.map((tag) => tag.text).join(" "), + "record 6 record 7", "shows a tooltip on hover" ); @@ -363,12 +357,12 @@ QUnit.module("Fields", (hooks) => { assert.strictEqual( tag.dataset["tooltipTemplate"], "web.TagsList.Tooltip", - "uses the proper tooltip template", + "uses the proper tooltip template" ); const tooltipInfo = JSON.parse(tag.dataset["tooltipInfo"]); assert.strictEqual( - tooltipInfo.tags.map(tag => tag.text).join(" "), - 'aaa record 5', + tooltipInfo.tags.map((tag) => tag.text).join(" "), + "aaa record 5", "shows a tooltip on hover" ); @@ -391,7 +385,6 @@ QUnit.module("Fields", (hooks) => { `, }); - await clickEdit(target); assert.containsN( target, ".o_field_many2many_tags_avatar.o_field_widget .badge", diff --git a/addons/web/static/tests/views/fields/many2many_tags_field_tests.js b/addons/web/static/tests/views/fields/many2many_tags_field_tests.js index 7cccd6f41f5..32ace21498f 100644 --- a/addons/web/static/tests/views/fields/many2many_tags_field_tests.js +++ b/addons/web/static/tests/views/fields/many2many_tags_field_tests.js @@ -6,7 +6,6 @@ import { Many2ManyTagsField } from "@web/views/fields/many2many_tags/many2many_t import { click, clickDropdown, - clickEdit, clickOpenedDropdownItem, clickSave, editInput, @@ -162,7 +161,7 @@ QUnit.module("Fields", (hooks) => { }); QUnit.test("Many2ManyTagsField with color: rendering and edition", async function (assert) { - assert.expect(28); + assert.expect(26); serverData.models.partner.records[0].timmy = [12, 14]; serverData.models.partner_type.records.push({ id: 13, display_name: "red", color: 8 }); @@ -211,19 +210,6 @@ QUnit.module("Fields", (hooks) => { "o_tag_color_2", "should have correctly set the color" ); - - await clickEdit(target); - - assert.containsN( - target, - ".o_field_many2many_tags .badge", - 2, - "should still contain 2 tags in edit mode" - ); - assert.ok( - target.querySelector(".o_tag_color_2 .o_tag_badge_text").textContent === "gold", - 'first tag should still contain "gold" and be color 2 in edit mode' - ); assert.containsN( target, ".o_field_many2many_tags .o_delete", @@ -274,7 +260,7 @@ QUnit.module("Fields", (hooks) => { `should not contain tag 'silver' anymore but found: ${textContent}` ); // save the record (should do the write RPC with the correct commands) - await click(target.querySelector(".o_form_button_save")); + await clickSave(target); // checkbox 'Hide in Kanban' const badgeElement = target.querySelectorAll(".o_field_many2many_tags .badge")[1]; // selects 'red' tag @@ -426,8 +412,6 @@ QUnit.module("Fields", (hooks) => { "should have fetched and rendered gold partner tag" ); - await clickEdit(target); - await clickDropdown(target, "timmy"); const autocompleteDropdown = target.querySelector(".o-autocomplete--dropdown-menu"); @@ -540,7 +524,6 @@ QUnit.module("Fields", (hooks) => { ); // Update the color in edit => write on save with rest of the record - await clickEdit(target); await click(badgeNode); await click(target, '.o_colorlist button[data-color="6"]'); await nextTick(); @@ -727,7 +710,6 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - await clickEdit(target); assert.containsOnce(target, ".o_field_many2many_tags .badge", "should contain one tag"); // update foo, which will trigger an onchange and update timmy @@ -840,8 +822,6 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - await clickEdit(target); - assert.strictEqual( target.querySelectorAll(".o_field_many2many_tags .badge").length, 1, @@ -913,7 +893,7 @@ QUnit.module("Fields", (hooks) => { assert.containsOnce(target, ".o_field_many2many_tags .badge"); - await click(target.querySelector(".o_form_button_save")); + await clickSave(target); assert.strictEqual( target.querySelector(".o_field_many2many_tags").textContent.trim(), @@ -986,7 +966,6 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - await clickEdit(target); assert.deepEqual( getNodesTextContent(target.querySelectorAll(".o_data_cell")), ["second recordaaa"], @@ -1027,7 +1006,6 @@ QUnit.module("Fields", (hooks) => { }); assert.verifySteps(["world"]); - await clickEdit(target); await selectDropdownItem(target, "timmy", "silver"); assert.verifySteps(["world"]); }); @@ -1056,7 +1034,6 @@ QUnit.module("Fields", (hooks) => { `, }); - await clickEdit(target); await selectDropdownItem(target, "timmy", "Search More..."); assert.ok(target.querySelector(".o_dialog"), "should have open the modal"); @@ -1113,7 +1090,6 @@ QUnit.module("Fields", (hooks) => { `, }); - await clickEdit(target); await selectDropdownItem(target, "timmy", "Search More..."); // -1 for the one that is already on the form & +1 for the select all, @@ -1172,7 +1148,6 @@ QUnit.module("Fields", (hooks) => { } }, }); - await clickEdit(target); await editInput(target, `div[name="timmy"] input`, "Ralts"); await nameSearchProm; @@ -1244,8 +1219,6 @@ QUnit.module("Fields", (hooks) => { }, }); - await clickEdit(target); - await editInput(target, ".o_field_widget input", "hello"); await nameSearchProm; await nextTick(); @@ -1301,8 +1274,6 @@ QUnit.module("Fields", (hooks) => { }, }); - await clickEdit(target); - // turtle_bar is true -> create and delete actions are available assert.containsOnce( target, diff --git a/addons/web/static/tests/views/fields/many2one_avatar_field_tests.js b/addons/web/static/tests/views/fields/many2one_avatar_field_tests.js index 8d857f7c2fa..514e7490bf1 100644 --- a/addons/web/static/tests/views/fields/many2one_avatar_field_tests.js +++ b/addons/web/static/tests/views/fields/many2one_avatar_field_tests.js @@ -3,7 +3,6 @@ import { browser } from "@web/core/browser/browser"; import { click, - clickEdit, clickSave, editInput, getFixture, @@ -78,7 +77,7 @@ QUnit.module("Fields", (hooks) => { }); assert.strictEqual( - target.querySelector(".o_field_widget[name=user_id]").textContent.trim(), + target.querySelector(".o_field_widget[name=user_id] input").value, "Aline" ); assert.containsOnce( @@ -86,8 +85,6 @@ QUnit.module("Fields", (hooks) => { '.o_m2o_avatar > img[data-src="/web/image/user/17/avatar_128"]' ); - await clickEdit(target); - assert.containsOnce(target, ".o_input_dropdown"); assert.strictEqual(target.querySelector(".o_input_dropdown input").value, "Aline"); assert.containsOnce(target, ".o_external_button"); @@ -105,7 +102,7 @@ QUnit.module("Fields", (hooks) => { await clickSave(target); assert.strictEqual( - target.querySelector(".o_field_widget[name=user_id]").textContent.trim(), + target.querySelector(".o_field_widget[name=user_id] input").value, "Christine" ); assert.containsOnce( @@ -113,7 +110,6 @@ QUnit.module("Fields", (hooks) => { '.o_m2o_avatar > img[data-src="/web/image/user/19/avatar_128"]' ); - await clickEdit(target); await editInput(target, '.o_field_widget[name="user_id"] input', ""); assert.containsNone(target, ".o_m2o_avatar > img"); @@ -121,7 +117,7 @@ QUnit.module("Fields", (hooks) => { await clickSave(target); assert.containsNone(target, ".o_m2o_avatar > img"); - assert.containsNone(target, ".o_m2o_avatar > .o_m2o_avatar_empty"); + assert.containsOnce(target, ".o_m2o_avatar > .o_m2o_avatar_empty"); }); QUnit.test("onchange in form view flow", async function (assert) { diff --git a/addons/web/static/tests/views/fields/many2one_field_tests.js b/addons/web/static/tests/views/fields/many2one_field_tests.js index bc1163f45c0..13b1bd742d7 100644 --- a/addons/web/static/tests/views/fields/many2one_field_tests.js +++ b/addons/web/static/tests/views/fields/many2one_field_tests.js @@ -10,7 +10,6 @@ import { click, clickDiscard, clickDropdown, - clickEdit, clickOpenedDropdownItem, clickSave, dragAndDrop, @@ -214,7 +213,7 @@ QUnit.module("Fields", (hooks) => { QUnit.module("Many2oneField"); QUnit.test("many2ones in form views", async function (assert) { - assert.expect(5); + assert.expect(2); function createMockActionService(assert) { return { @@ -253,7 +252,7 @@ QUnit.module("Fields", (hooks) => {
- +
`, @@ -278,11 +277,6 @@ QUnit.module("Fields", (hooks) => { }, }); - assert.containsOnce(target, "a.o_form_uri:contains(aaa)", "should contain a link"); - await click(target, "a.o_form_uri"); - - await click(target, ".o_form_button_edit"); - await click(target, ".o_external_button"); assert.strictEqual( target.querySelector(".modal .modal-title").textContent.trim(), @@ -313,7 +307,7 @@ QUnit.module("Fields", (hooks) => { arch: `
- +
`, mockRPC(route, { args, method }) { @@ -324,8 +318,6 @@ QUnit.module("Fields", (hooks) => { }, }); - await clickEdit(target); - // click on the external button (should do an RPC) await click(target, ".o_external_button"); // save and close modal @@ -390,7 +382,7 @@ QUnit.module("Fields", (hooks) => { arch: `
- +
`, mockRPC(route, { method }) { @@ -400,8 +392,6 @@ QUnit.module("Fields", (hooks) => { }, }); - await click(target, ".o_form_button_edit"); - // click on the external button (should do an RPC) await click(target, ".o_external_button"); @@ -412,7 +402,7 @@ QUnit.module("Fields", (hooks) => { // save and close modal await clickSave(target.querySelector(".modal")); // save form - await click(target, ".o_form_button_save"); + await clickSave(target); // click next on pager await click(target, ".o_pager .o_pager_next"); @@ -447,13 +437,6 @@ QUnit.module("Fields", (hooks) => { }, }); - assert.strictEqual( - target.querySelector("a.o_form_uri").innerHTML, - "aaa
Street
City ZIP", - "input should have a multi-line content in readonly due to show_address" - ); - - await click(target, ".o_form_button_edit"); assert.strictEqual(target.querySelector("input.o_input").value, "aaa"); assert.strictEqual( target.querySelector(".o_field_many2one_extra").innerHTML, @@ -493,7 +476,6 @@ QUnit.module("Fields", (hooks) => { }, }); - await click(target, ".o_form_button_edit"); const input = target.querySelector(".o_field_widget input"); assert.strictEqual(input.value, "aaa"); @@ -543,7 +525,7 @@ QUnit.module("Fields", (hooks) => { serverData, resId: 1, arch: ` -
+ `, @@ -587,7 +569,7 @@ QUnit.module("Fields", (hooks) => { serverData, resId: 1, arch: ` -
+ `, @@ -644,8 +626,6 @@ QUnit.module("Fields", (hooks) => { `, }); - await click(target, ".o_form_button_edit"); - await selectDropdownItem(target, "trululu", "Search More..."); assert.strictEqual($("tr.o_data_row").length, 9, "should display 9 records"); @@ -754,7 +734,7 @@ QUnit.module("Fields", (hooks) => { QUnit.test( "onchanges on many2ones trigger when editing record in form view", async function (assert) { - assert.expect(11); + assert.expect(10); serverData.models.partner.onchanges.user_id = function () {}; serverData.models.user.fields.other_field = { string: "Other Field", type: "char" }; @@ -774,7 +754,7 @@ QUnit.module("Fields", (hooks) => {
- +
`, @@ -794,7 +774,6 @@ QUnit.module("Fields", (hooks) => { }); // open the many2one in form view and change something - await click(target, ".o_form_button_edit"); await click(target, ".o_external_button"); await editInput( target, @@ -814,11 +793,6 @@ QUnit.module("Fields", (hooks) => { "read", "onchange", ]); - - // save the main record, and check that no extra rpcs are done (record - // is not dirty, only a related record was modified) - await click(target, ".o_form_button_save"); - assert.verifySteps([]); } ); @@ -938,14 +912,6 @@ QUnit.module("Fields", (hooks) => { `, }); - assert.containsOnce( - target, - ".o_field_widget[name='trululu'] > span", - "should be displayed inside a span (sanity check)" - ); - assert.containsNone(target, "span.o_form_uri", "should not have an anchor"); - - await click(target, ".o_form_button_edit"); assert.containsNone( target, ".o_field_widget[name='trululu'] .o_external_button", @@ -1086,14 +1052,12 @@ QUnit.module("Fields", (hooks) => { }, }); - await click(target, ".o_form_button_edit"); - // trigger the onchange await editInput(target, ".o_field_widget[name='foo'] input", "3"); assert.verifySteps(["onchange"]); // save - await click(target, ".o_form_button_save"); + await clickSave(target); assert.verifySteps(["write"]); } ); @@ -1142,7 +1106,6 @@ QUnit.module("Fields", (hooks) => { // event to specify a fake session to prevent it from crashing patchWithCleanup(session.user_context, {}); - await click(target, ".o_form_button_edit"); await clickDropdown(target, "trululu"); let dropdown = target.querySelector(".o_field_many2one[name='trululu'] .dropdown-menu"); @@ -1229,22 +1192,22 @@ QUnit.module("Fields", (hooks) => { ); // save - await click(target, ".o_form_button_save"); + await clickSave(target); assert.strictEqual( - target.querySelector("a.o_form_uri").textContent, + target.querySelector(".o_field_many2one input").value, "Partner 20", "should display correct value after save" ); }); - QUnit.test("many2one in non edit mode", async function (assert) { + QUnit.test("many2one in non edit mode (with value)", async function (assert) { await makeView({ type: "form", resModel: "partner", resId: 1, serverData, arch: ` -
+ `, }); @@ -1256,12 +1219,23 @@ QUnit.module("Fields", (hooks) => { "#id=4&model=partner", "href should contain id and model" ); + }); + + QUnit.test("many2one in non edit mode (without value)", async function (assert) { + serverData.models.partner.records[0].trululu = false; + + await makeView({ + type: "form", + resModel: "partner", + resId: 1, + serverData, + arch: ` +
+ + `, + }); // Remove value from many2one and then save, there should be no link anymore - await click(target, ".o_form_button_edit"); - await editInput(target, ".o_field_many2one input", ""); - - await click(target, ".o_form_button_save"); assert.containsNone(target, "a.o_form_uri"); }); @@ -1320,7 +1294,6 @@ QUnit.module("Fields", (hooks) => { } }, }); - await click(target, ".o_form_button_edit"); assert.strictEqual( target.querySelector(".o_field_many2one input").value, @@ -1404,7 +1377,34 @@ QUnit.module("Fields", (hooks) => { assert.verifySteps(["search: ", "search: first", "search: first", "search: first"]); }); - QUnit.test("many2one field with option always_reload", async function (assert) { + QUnit.test("many2one field with option always_reload (readonly)", async function (assert) { + let count = 0; + await makeView({ + type: "form", + resModel: "partner", + resId: 2, + serverData, + arch: ` +
+ + `, + mockRPC(route, { method }) { + if (method === "name_get") { + count++; + return Promise.resolve([[1, "first record\nand some address"]]); + } + }, + }); + + assert.strictEqual(count, 1, "an extra name_get should have been done"); + assert.ok( + target.querySelector("a.o_form_uri").textContent.includes("and some address"), + "should display additional result" + ); + assert.containsNone(target, ".o_field_many2one_extra"); + }); + + QUnit.test("many2one field with option always_reload (edit)", async function (assert) { let count = 0; await makeView({ type: "form", @@ -1424,22 +1424,15 @@ QUnit.module("Fields", (hooks) => { }); assert.strictEqual(count, 1, "an extra name_get should have been done"); - assert.ok( - target.querySelector("a.o_form_uri").textContent.includes("and some address"), - "should display additional result" - ); - - await click(target, ".o_form_button_edit"); assert.strictEqual( target.querySelector(".o_field_widget[name='trululu'] input").value, "first record", "actual field value should be displayed to be edited" ); - - await click(target, ".o_form_button_save"); - assert.ok( - target.querySelector("a.o_form_uri").textContent.includes("and some address"), - "should still display additional result" + assert.containsOnce(target, ".o_field_many2one_extra"); + assert.strictEqual( + target.querySelector(".o_field_many2one_extra").textContent, + "and some address" ); }); @@ -2469,7 +2462,7 @@ QUnit.module("Fields", (hooks) => {
- +
`, @@ -2480,7 +2473,6 @@ QUnit.module("Fields", (hooks) => { }, }); - await clickEdit(target); await click(target.querySelector("td.o_data_cell")); await click(target, ".o_external_button"); assert.containsOnce(target, ".modal"); @@ -2568,7 +2560,7 @@ QUnit.module("Fields", (hooks) => { - + `, @@ -2593,7 +2585,6 @@ QUnit.module("Fields", (hooks) => { }, }); - await click(target, ".o_form_button_edit"); await click(target, ".o_data_cell"); await click(target, ".o_external_button"); @@ -2634,8 +2625,6 @@ QUnit.module("Fields", (hooks) => { } }, }); - await click(target, ".o_form_button_edit"); - click(target, ".o_field_widget[name=product_id] input"); }); @@ -2661,8 +2650,6 @@ QUnit.module("Fields", (hooks) => { } }, }); - await click(target, ".o_form_button_edit"); - click(target, ".o_field_widget[name='trululu'] input"); } ); @@ -2738,11 +2725,10 @@ QUnit.module("Fields", (hooks) => { arch: '
', }); assert.strictEqual( - target.querySelector(".o_field_widget[name='product_id']").textContent, + target.querySelector(".o_field_widget[name='product_id'] input").value, "", "the tag a should be empty" ); - await click(target, ".o_form_button_edit"); await click(target, ".o_field_widget[name='product_id'] input"); await click(target.querySelector(".o_field_widget[name='product_id'] .dropdown-item")); @@ -2752,9 +2738,9 @@ QUnit.module("Fields", (hooks) => { "should have selected xphone" ); - await click(target, ".o_form_button_cancel"); + await clickDiscard(target); assert.strictEqual( - target.querySelector(".o_field_widget[name='product_id']").textContent, + target.querySelector(".o_field_widget[name='product_id'] input").value, "", "the tag a should be empty" ); @@ -2812,7 +2798,6 @@ QUnit.module("Fields", (hooks) => { }, }); - await click(target, ".o_form_button_edit"); click(target, ".o_field_widget[name='product_id'] input"); click(target, ".o_field_widget[name='trululu'] input"); } @@ -3068,7 +3053,7 @@ QUnit.module("Fields", (hooks) => { arch: `
- +
`, mockRPC(route) { @@ -3150,7 +3135,6 @@ QUnit.module("Fields", (hooks) => { }, }); - await click(target, ".o_form_button_edit"); await click(target.querySelector(".o_field_widget[name=product_id] input")); assert.containsNone(target, ".o-autocomplete a:contains(Start typing...)"); @@ -3321,8 +3305,6 @@ QUnit.module("Fields", (hooks) => { }, }); - await click(target, ".o_form_button_edit"); - const input = target.querySelector(".o_field_many2one input"); await editInput(input, null, "first"); @@ -3395,8 +3377,6 @@ QUnit.module("Fields", (hooks) => { `, }); - await click(target, ".o_form_button_edit"); - const input = target.querySelector(".o_field_many2one input"); assert.ok(input.value, "many2one should have value"); @@ -3580,8 +3560,6 @@ QUnit.module("Fields", (hooks) => { }, }); - await click(target, ".o_form_button_edit"); - // trigger a name_search (domain should be []) await click(target, ".o_field_widget[name=trululu] input"); // close the dropdown @@ -3629,8 +3607,6 @@ QUnit.module("Fields", (hooks) => { }, }); - await click(target, ".o_form_button_edit"); - // add a first row with a specific domain for the m2o domain = [["id", "in", [10]]]; // domain for subrecord 1 await click(target, ".o_field_x2many_list_row_add a"); @@ -3835,7 +3811,7 @@ QUnit.module("Fields", (hooks) => { - + `, @@ -3848,7 +3824,6 @@ QUnit.module("Fields", (hooks) => { }); // Opening the modal - await clickEdit(target); await click(target.querySelectorAll(".o_data_row td")[1]); await click(target, ".o_external_button"); assert.containsOnce(target, ".modal"); @@ -3940,8 +3915,6 @@ QUnit.module("Fields", (hooks) => { `, }); - await click(target, ".o_form_button_edit"); - await click(target, ".o_field_many2one input"); assert.containsOnce(target, ".o_field_many2one .dropdown-menu"); @@ -4001,8 +3974,6 @@ QUnit.module("Fields", (hooks) => { arch: '
', }); - await clickEdit(target); - await selectDropdownItem(target, "trululu", "Search More..."); const modal = target.querySelector(".modal"); await toggleGroupByMenu(modal); @@ -4017,7 +3988,7 @@ QUnit.module("Fields", (hooks) => { QUnit.test("focus when closing many2one modal in many2one modal", async function (assert) { serverData.views = { - "partner,false,form": '
', + "partner,false,form": '
', }; await makeView({ @@ -4025,7 +3996,7 @@ QUnit.module("Fields", (hooks) => { serverData, resModel: "partner", resId: 2, - arch: '
', + arch: '
', mockRPC(route, { method }) { if (method === "get_formview_id") { return Promise.resolve(false); @@ -4034,7 +4005,6 @@ QUnit.module("Fields", (hooks) => { }); // Open many2one modal - await clickEdit(target); await click(target, ".o_external_button"); const originalModal = target.querySelector(".modal"); @@ -4102,8 +4072,6 @@ QUnit.module("Fields", (hooks) => { `, }); - await clickEdit(target); - await selectDropdownItem(target, "trululu", "Search More..."); const modal = target.querySelector(".modal"); diff --git a/addons/web/static/tests/views/fields/monetary_field_tests.js b/addons/web/static/tests/views/fields/monetary_field_tests.js index 5504a8d8217..2c56014771f 100644 --- a/addons/web/static/tests/views/fields/monetary_field_tests.js +++ b/addons/web/static/tests/views/fields/monetary_field_tests.js @@ -4,7 +4,6 @@ import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; import { addRow, click, - clickEdit, clickSave, editInput, getFixture, @@ -108,14 +107,6 @@ QUnit.module("Fields", (hooks) => { `, }); - assert.strictEqual( - target.querySelector(".o_field_widget").textContent, - "$\u00a09.10", - "The value should be displayed properly." - ); - - await clickEdit(target); - assert.containsOnce( target, ".o_field_monetary > div.text-nowrap", @@ -141,8 +132,8 @@ QUnit.module("Fields", (hooks) => { await clickSave(target); assert.strictEqual( - target.querySelector(".o_field_widget").textContent, - "$\u00a0108.25", + target.querySelector(".o_field_widget input").value, + "108.25", "The new value should be rounded properly." ); }); @@ -169,13 +160,6 @@ QUnit.module("Fields", (hooks) => { `, }); - assert.strictEqual( - target.querySelector(".o_field_widget").textContent, - "$\u00a09.10", - "The value should be displayed properly." - ); - - await clickEdit(target); assert.strictEqual( target.querySelector(".o_field_widget input").value, "9.10", @@ -196,8 +180,8 @@ QUnit.module("Fields", (hooks) => { await clickSave(target); assert.strictEqual( - target.querySelector(".o_field_widget").textContent, - "$\u00a0108.25", + target.querySelector(".o_field_widget input").value, + "108.25", "The new value should be rounded properly." ); }); @@ -225,12 +209,11 @@ QUnit.module("Fields", (hooks) => { }); // Test computation and rounding - await clickEdit(target); await editInput(target, ".o_field_monetary input", "=100/3"); await clickSave(target); assert.strictEqual( - target.querySelector(".o_field_widget").textContent, - "$\u00a033.33", + target.querySelector(".o_field_widget input").value, + "33.33", "The new value should be calculated and rounded properly." ); }); @@ -258,12 +241,11 @@ QUnit.module("Fields", (hooks) => { }); // Test computation and rounding - await clickEdit(target); await editInput(target, ".o_field_monetary input", "=100/3"); await clickSave(target); assert.strictEqual( - target.querySelector(".o_field_widget").textContent, - "$\u00a033.33", + target.querySelector(".o_field_widget input").value, + "33.33", "The new value should be calculated and rounded properly." ); }); @@ -290,14 +272,6 @@ QUnit.module("Fields", (hooks) => { `, }); - // Non-breaking space between the currency and the amount - assert.strictEqual( - target.querySelector(".o_field_widget").textContent, - "0.00\u00a0€", - "The value should be displayed properly." - ); - - await clickEdit(target); assert.strictEqual( target.querySelector(".o_field_widget input").value, "0.00", @@ -317,10 +291,9 @@ QUnit.module("Fields", (hooks) => { ); await clickSave(target); - // Non-breaking space between the currency and the amount assert.strictEqual( - target.querySelector(".o_field_widget").textContent, - "108.25\u00a0€", + target.querySelector(".o_field_widget input").value, + "108.25", "The new value should be rounded properly." ); }); @@ -347,14 +320,6 @@ QUnit.module("Fields", (hooks) => { `, }); - // Non-breaking space between the currency and the amount - assert.strictEqual( - target.querySelector(".o_field_widget").textContent, - "0.00\u00a0€", - "The value should be displayed properly." - ); - - await clickEdit(target); assert.strictEqual( target.querySelector(".o_field_widget input").value, "0.00", @@ -374,10 +339,9 @@ QUnit.module("Fields", (hooks) => { ); await clickSave(target); - // Non-breaking space between the currency and the amount assert.strictEqual( - target.querySelector(".o_field_widget").textContent, - "108.25\u00a0€", + target.querySelector(".o_field_widget input").value, + "108.25", "The new value should be rounded properly." ); }); @@ -417,14 +381,6 @@ QUnit.module("Fields", (hooks) => { `, }); - // Non-breaking space between the currency and the amount - assert.strictEqual( - target.querySelector(".o_field_widget").textContent, - "99.1234\u00a0Bs.F", - "The value should be displayed properly." - ); - - await clickEdit(target); assert.strictEqual( target.querySelector(".o_field_widget input").value, "99.1234", @@ -444,10 +400,9 @@ QUnit.module("Fields", (hooks) => { ); await clickSave(target); - // Non-breaking space between the currency and the amount assert.strictEqual( - target.querySelector(".o_field_widget").textContent, - "99.1111\u00a0Bs.F", + target.querySelector(".o_field_widget input").value, + "99.1111", "The new value should be rounded properly." ); }); @@ -487,14 +442,6 @@ QUnit.module("Fields", (hooks) => { `, }); - // Non-breaking space between the currency and the amount - assert.strictEqual( - target.querySelector(".o_field_widget").textContent, - "99.1234\u00a0Bs.F", - "The value should be displayed properly." - ); - - await clickEdit(target); assert.strictEqual( target.querySelector(".o_field_widget input").value, "99.1234", @@ -514,10 +461,9 @@ QUnit.module("Fields", (hooks) => { ); await clickSave(target); - // Non-breaking space between the currency and the amount assert.strictEqual( - target.querySelector(".o_field_widget").textContent, - "99.1111\u00a0Bs.F", + target.querySelector(".o_field_widget input").value, + "99.1111", "The new value should be rounded properly." ); }); @@ -734,14 +680,6 @@ QUnit.module("Fields", (hooks) => { `, }); - assert.strictEqual( - target.querySelector(".o_field_monetary").textContent, - "$\u00a04.20", - "readonly value should contain the currency" - ); - - await clickEdit(target); - // replace bottom with new helpers when they exist await click(target, ".o_field_many2one_selection input"); const euroM2OListItem = Array.from( @@ -757,10 +695,9 @@ QUnit.module("Fields", (hooks) => { ); await clickSave(target); - assert.strictEqual( - target.querySelector(".o_field_monetary").textContent, - "4.20\u00a0€", + target.querySelector(".o_field_monetary input").value, + "4.20", "The new value should still be correct." ); }); @@ -787,14 +724,6 @@ QUnit.module("Fields", (hooks) => { `, }); - assert.strictEqual( - target.querySelector(".o_field_monetary").textContent, - "$\u00a04.20", - "readonly value should contain the currency" - ); - - await clickEdit(target); - // replace bottom with new helpers when they exist await click(target, ".o_field_many2one_selection input"); const euroM2OListItem = Array.from( @@ -810,10 +739,9 @@ QUnit.module("Fields", (hooks) => { ); await clickSave(target); - assert.strictEqual( - target.querySelector(".o_field_monetary").textContent, - "4.20\u00a0€", + target.querySelector(".o_field_monetary input").value, + "4.20", "The new value should still be correct." ); }); @@ -831,7 +759,7 @@ QUnit.module("Fields", (hooks) => { type: "form", resModel: "partner", arch: ` -
+ @@ -1008,7 +936,7 @@ QUnit.module("Fields", (hooks) => { type: "form", resModel: "partner", arch: ` - + @@ -1059,14 +987,6 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - // Non-breaking space between the currency and the amount - assert.strictEqual( - target.querySelector(".o_field_widget").textContent, - "$\u00a0-8.9", - "The value should be displayed properly." - ); - - await click(target.querySelector(".o_form_button_edit")); assert.strictEqual( target.querySelector(".o_field_widget[name=float_field] input").value, "-8.9", @@ -1086,11 +1006,10 @@ QUnit.module("Fields", (hooks) => { "The value should should be formatted on blur." ); - await click(target.querySelector(".o_form_button_save")); - // Non-breaking space between the currency and the amount + await clickSave(target); assert.strictEqual( - target.querySelector(".o_field_widget").textContent, - "$\u00a0109.2", + target.querySelector(".o_field_widget input").value, + "109.2", "The new value should be rounded properly." ); }); @@ -1110,8 +1029,6 @@ QUnit.module("Fields", (hooks) => { `, }); - await clickEdit(target); - // Non-breaking space between the currency and the amount assert.strictEqual( target.querySelector(".o_field_widget[name=float_field] input").value, @@ -1154,10 +1071,5 @@ QUnit.module("Fields", (hooks) => { assert.containsOnce(target, ".o_form_editable"); assert.strictEqual(target.querySelector("[name=monetary_field] input").value, "0.00"); - - await clickSave(target); - - assert.containsOnce(target, ".o_form_readonly"); - assert.strictEqual(target.querySelector("[name=monetary_field] span").innerText, "0.00"); }); }); diff --git a/addons/web/static/tests/views/fields/numeric_fields_tests.js b/addons/web/static/tests/views/fields/numeric_fields_tests.js index b67fd369a82..a64fadc872c 100644 --- a/addons/web/static/tests/views/fields/numeric_fields_tests.js +++ b/addons/web/static/tests/views/fields/numeric_fields_tests.js @@ -105,9 +105,6 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - // Record edit mode - await click(target.querySelector(".o_form_button_edit")); - // Get all inputs const floatFactorField = target.querySelector(".o_field_float_factor input"); const floatInput = target.querySelector(".o_field_float input"); diff --git a/addons/web/static/tests/views/fields/one2many_field_tests.js b/addons/web/static/tests/views/fields/one2many_field_tests.js index dc3bdc57c85..3b89e05dd1e 100644 --- a/addons/web/static/tests/views/fields/one2many_field_tests.js +++ b/addons/web/static/tests/views/fields/one2many_field_tests.js @@ -6,7 +6,6 @@ import { click, clickCreate, clickDiscard, - clickEdit, clickSave, clickM2OHighlightedItem, clickOpenedDropdownItem, @@ -308,7 +307,6 @@ QUnit.module("Fields", (hooks) => { `, resId: 1, }); - await click(target, ".o_form_button_edit"); await addRow(target, ".o_field_x2many_list"); @@ -429,7 +427,6 @@ QUnit.module("Fields", (hooks) => { } }, }); - await clickEdit(target); await addRow(target); const targetInput = target.querySelector(".o_selected_row [name=foo] input"); @@ -492,15 +489,14 @@ QUnit.module("Fields", (hooks) => { }); assert.verifySteps(["get_views", "read", "read"]); - assert.containsNone(target, "td.o_list_record_selector"); - assert.containsNone(target, ".o_field_x2many_list_row_add"); - assert.containsNone(target, "td.o_list_record_remove", 1); - - await clickEdit(target); + // assert.containsNone(target, "td.o_list_record_selector"); + // assert.containsNone(target, ".o_field_x2many_list_row_add"); + // assert.containsNone(target, "td.o_list_record_remove"); + // await clickEdit(target); assert.containsOnce(target, ".o_field_x2many_list_row_add"); assert.hasAttrValue(target.querySelector(".o_field_x2many_list_row_add"), "colspan", "2"); - assert.containsOnce(target, "td.o_list_record_remove", 1); + assert.containsOnce(target, "td.o_list_record_remove"); }); QUnit.test("transferring class attributes in one2many sub fields", async function (assert) { @@ -520,9 +516,6 @@ QUnit.module("Fields", (hooks) => { }); assert.containsOnce(target, "td.hey"); - await clickEdit(target); - assert.containsOnce(target, "td.hey"); - await click(target.querySelector("td.o_data_cell")); assert.containsOnce(target, 'td.hey div[name="turtle_foo"] input'); // WOWL to check! hey on input? }); @@ -733,13 +726,6 @@ QUnit.module("Fields", (hooks) => { ["yop", "blip", "kawa"] ); - await clickEdit(target); - - assert.deepEqual( - [...target.querySelectorAll(".o_data_cell.o_list_char")].map((el) => el.innerText), - ["yop", "blip", "kawa"] - ); - // Drag and drop the second line in first position await dragAndDrop("tbody tr:nth-child(2) .o_handle_cell", "tbody tr", "top"); @@ -819,7 +805,6 @@ QUnit.module("Fields", (hooks) => { }, }); - await clickEdit(target); await click(target.querySelectorAll(".o_data_cell")[1]); await editInput(target, ".o_selected_row .o_field_widget[name=turtle_foo] input", "hop"); await clickSave(target); @@ -899,7 +884,6 @@ QUnit.module("Fields", (hooks) => { }, }); - await clickEdit(target); await click(target.querySelectorAll(".o_data_cell")[1]); await editInput( target, @@ -984,7 +968,6 @@ QUnit.module("Fields", (hooks) => { ["second record"] ); - await clickEdit(target); await click(target.querySelectorAll(".o_data_cell")[1]); await editInput(target, ".o_selected_row [name=turtle_foo] input", "hop"); await clickSave(target); @@ -1030,8 +1013,6 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - await clickEdit(target); - assert.deepEqual(getNodesTextContent(target.querySelectorAll(".o_data_cell.o_list_char")), [ "yop", "blip", @@ -1092,8 +1073,6 @@ QUnit.module("Fields", (hooks) => { }, }); - await clickEdit(target); - assert.deepEqual( getNodesTextContent(target.querySelectorAll(".o_data_cell.o_list_char")), ["yop", "blip", "kawa"] @@ -1156,8 +1135,6 @@ QUnit.module("Fields", (hooks) => { ["yop", "blip", "kawa"] ); - await clickEdit(target); - await click(target.querySelector(".o_data_cell.o_list_char")); await editInput(target, '.o_list_renderer div[name="turtle_foo"] input', "blurp"); @@ -1242,13 +1219,12 @@ QUnit.module("Fields", (hooks) => { ).join(""); }; - await clickEdit(target); assert.strictEqual(getTurtleFooValues(), "#20#21#22#23#24#25#26#27#28#29"); await click(target.querySelector(".o_data_cell.o_list_char")); await editInput(target, "div[name=turtle_foo] input", "blurp"); // click outside of the one2many to unselect the row - await click(target, ".o_cp_bottom_left"); + await click(target, ".o_form_view"); assert.strictEqual(getTurtleFooValues(), "blurp#21#22#23#24#25#26#27#28#29"); // the domain fail if the widget does not use the already loaded data. @@ -1256,7 +1232,6 @@ QUnit.module("Fields", (hooks) => { assert.containsNone(target, ".modal"); assert.strictEqual(getTurtleFooValues(), "#20#21#22#23#24#25#26#27#28#29"); - await clickEdit(target); // Drag and drop the third line in second position await dragAndDrop("tbody tr:nth-child(3) .o_handle_cell", "tbody tr:nth-child(2)"); assert.strictEqual(getTurtleFooValues(), "#20#30#31#32#33#34#35#36#37#38"); @@ -1265,7 +1240,7 @@ QUnit.module("Fields", (hooks) => { await dragAndDrop("tbody tr:nth-child(3) .o_handle_cell", "tbody tr:nth-child(2)"); assert.strictEqual(getTurtleFooValues(), "#20#39#40#41#42#43#44#45#46#47"); - await click(target, ".o_cp_bottom_left"); + await click(target, ".o_form_view"); assert.strictEqual(getTurtleFooValues(), "#20#39#40#41#42#43#44#45#46#47"); await clickDiscard(target); @@ -1313,7 +1288,6 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - await clickEdit(target); await click(target.querySelector(".o_field_widget[name=turtles] .o_pager_next")); await click( @@ -1449,7 +1423,6 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - await clickEdit(target); await click(target.querySelector(".o_field_widget[name=turtles] .o_pager_next")); await click( @@ -1563,7 +1536,6 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - await clickEdit(target); assert.containsOnce(target, ".o_data_row"); // change the value of foo to trigger the onchange @@ -1619,7 +1591,6 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - await clickEdit(target); assert.containsOnce( target, ".o_data_row", @@ -1695,7 +1666,6 @@ QUnit.module("Fields", (hooks) => { }, resId: 1, }); - await clickEdit(target); assert.containsOnce(target, ".o_data_row"); assert.deepEqual( @@ -2005,8 +1975,6 @@ QUnit.module("Fields", (hooks) => { const formHandle = Object.keys(model.localData).find((k) => /partner/.test(k)); - await clickEdit(target); - assert.deepEqual( Object.values(model.get(formHandle).data.turtles.data).map((r) => { return r.data; @@ -2086,13 +2054,6 @@ QUnit.module("Fields", (hooks) => { "yop", ]); - await clickEdit(target); - assert.deepEqual(getNodesTextContent(target.querySelectorAll(".o_data_cell.o_list_char")), [ - "My little Foo Value", - "blip", - "yop", - ]); - assert.verifySteps([]); // Drag and drop the second line in first position @@ -2363,7 +2324,6 @@ QUnit.module("Fields", (hooks) => { // add a record on page one checkRead = true; - await clickEdit(target); await click(target.querySelector(".o-kanban-button-new")); await editInput(target, ".modal input", "new record"); @@ -2394,7 +2354,6 @@ QUnit.module("Fields", (hooks) => { // delete a record on page one checkRead = true; - await clickEdit(target); assert.strictEqual( target.querySelector(".o_kanban_record:not(.o_kanban_ghost)").innerText, "relational record 10" @@ -2423,7 +2382,6 @@ QUnit.module("Fields", (hooks) => { await clickSave(target); // add and delete records in both pages - await clickEdit(target); checkRead = true; readIDs = undefined; // add and delete a record in page 1 @@ -2500,7 +2458,7 @@ QUnit.module("Fields", (hooks) => { resModel: "partner", serverData, arch: ` -
+ @@ -2557,8 +2515,6 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - await clickEdit(target); - assert.strictEqual(target.querySelector(".o_kanban_record ").innerText, "donatello"); await click(target.querySelector(".o_kanban_record")); @@ -2633,7 +2589,6 @@ QUnit.module("Fields", (hooks) => { `, resId: 1, }); - await clickEdit(target); await addRow(target); await editInput(target, 'div[name="turtle_int"] input', "5"); await click(target.querySelector(".modal-footer button.btn-primary")); @@ -2721,7 +2676,6 @@ QUnit.module("Fields", (hooks) => { "relational record 1" ); - await clickEdit(target); await click(target.querySelector(".o_field_one2many tbody td")); assert.hasClass( target.querySelector(".o_field_one2many tbody .o_data_row"), @@ -2754,7 +2708,6 @@ QUnit.module("Fields", (hooks) => { ); // edit again and save - await clickEdit(target); await click(target.querySelector(".o_field_one2many tbody td")); await editInput(target, ".o_field_one2many tbody td input", "new value"); await click(target.querySelector(".o_form_view")); @@ -2783,9 +2736,6 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); assert.containsNone(target, ".o_field_x2many_list_row_add"); - - await clickEdit(target); - assert.containsNone(target, ".o_field_x2many_list_row_add"); }); QUnit.test("one2many list: conditional create/delete actions", async function (assert) { @@ -2806,7 +2756,6 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - await clickEdit(target); // bar is true -> create and delete action are available assert.containsOnce(target, ".o_field_x2many_list_row_add"); assert.containsN(target, "td.o_list_record_remove button", 2); @@ -2859,7 +2808,6 @@ QUnit.module("Fields", (hooks) => { } }, }); - await clickEdit(target); assert.containsN(target, "td.o_list_record_remove button", 3); await click(target.querySelector("td.o_list_record_remove button")); @@ -2905,7 +2853,6 @@ QUnit.module("Fields", (hooks) => { } }, }); - await clickEdit(target); assert.containsN(target, "td.o_list_record_remove button", 3); await click(target.querySelector("td.o_list_record_remove button")); @@ -2920,7 +2867,7 @@ QUnit.module("Fields", (hooks) => { }); QUnit.test("one2many kanban: edition", async function (assert) { - assert.expect(22); + assert.expect(20); serverData.models.partner.records[0].p = [2]; await makeView({ @@ -2969,11 +2916,6 @@ QUnit.module("Fields", (hooks) => { }, }); - assert.containsNone(target, ".delete_icon"); - assert.containsNone(target, ".o_field_one2many .o-kanban-button-new"); - - await clickEdit(target); - assert.containsOnce(target, ".o_kanban_record:not(.o_kanban_ghost)"); assert.strictEqual( target.querySelector(".o_kanban_record span").textContent, @@ -3100,7 +3042,6 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - await clickEdit(target); assert.deepEqual( [ ...target.querySelectorAll( @@ -3138,9 +3079,6 @@ QUnit.module("Fields", (hooks) => { `, resId: 1, }); - assert.containsNone(target, ".o-kanban-button-new"); - - await clickEdit(target); assert.containsNone(target, ".o-kanban-button-new"); assert.containsOnce(target, ".o_field_x2many_kanban .delete_icon"); @@ -3175,7 +3113,6 @@ QUnit.module("Fields", (hooks) => { `, resId: 1, }); - await clickEdit(target); // bar is initially true -> create and delete actions are available assert.containsOnce(target, ".o-kanban-button-new", '"Add" button should be available'); @@ -3223,7 +3160,6 @@ QUnit.module("Fields", (hooks) => { }); // add a record, add value to turtle_foo then click in form view to confirm it - await clickEdit(target); await addRow(target); await editInput(target, 'div[name="turtle_foo"] input', "nora"); @@ -3237,7 +3173,7 @@ QUnit.module("Fields", (hooks) => { }); QUnit.test("one2many list (non editable): edition", async function (assert) { - assert.expect(12); + assert.expect(10); let nbWrite = 0; serverData.models.partner.records[0].p = [2, 4]; @@ -3271,11 +3207,6 @@ QUnit.module("Fields", (hooks) => { }, }); - assert.containsNone(target, ".o_list_record_remove"); - assert.containsNone(target, ".o_field_x2many_list_row_add"); - - await clickEdit(target); - assert.containsN(target, "td.o_list_number", 2); assert.strictEqual( target.querySelector(".o_list_renderer tbody td").textContent, @@ -3341,7 +3272,6 @@ QUnit.module("Fields", (hooks) => { }); // edit mode, then click on Add an item and enter a value - await clickEdit(target); await addRow(target); await editInput(target, ".o_selected_row > td input", "kartoffel"); assert.strictEqual(target.querySelector("td .o_field_char input").value, "kartoffel"); @@ -3383,7 +3313,6 @@ QUnit.module("Fields", (hooks) => { // edit mode, then click on Add an item, enter value in turtle_foo and Add an item again assert.containsOnce(target, "tr.o_data_row"); - await clickEdit(target); await addRow(target); await editInput(target, 'div[name="turtle_foo"] input', "nora"); await addRow(target); @@ -3429,7 +3358,6 @@ QUnit.module("Fields", (hooks) => { // edit mode, then click on Add an item assert.containsNone(target, "tr.o_data_row"); - await clickEdit(target); await addRow(target); assert.strictEqual(target.querySelector(".o_data_row textarea").value, ""); @@ -3460,7 +3388,6 @@ QUnit.module("Fields", (hooks) => { // edit mode, then click on Add an item, enter value in turtle_foo and Add an item again assert.containsOnce(target, "tr.o_data_row"); assert.strictEqual(target.querySelector(".o_data_cell").innerText, "blip"); - await clickEdit(target); await addRow(target); await editInput(target, ".o_field_widget[name=turtle_foo] input", "aaa"); assert.containsN(target, "tr.o_data_row", 2); @@ -3497,7 +3424,6 @@ QUnit.module("Fields", (hooks) => { // edit mode, then click on Add an item, then click elsewhere assert.containsNone(target, "tr.o_data_row"); - await clickEdit(target); await addRow(target); await click(target); assert.containsNone(target, "tr.o_data_row"); @@ -3528,7 +3454,6 @@ QUnit.module("Fields", (hooks) => { }); // add a record, to reach the page size limit - await clickEdit(target); await addRow(target); // the record currently being added should not count in the pager assert.containsNone(target, ".o_field_widget[name=turtles] .o_pager"); @@ -3567,7 +3492,6 @@ QUnit.module("Fields", (hooks) => { }); // add a record, then discard - await clickEdit(target); await addRow(target); await clickDiscard(target); @@ -3601,7 +3525,6 @@ QUnit.module("Fields", (hooks) => { }); // add a (empty) record - await clickEdit(target); await addRow(target); // go on next page. The new record is not valid and should be discarded @@ -3633,7 +3556,6 @@ QUnit.module("Fields", (hooks) => { }); // add a record with a dirty state, but not valid - await clickEdit(target); await addRow(target); await editInput(target, '.o_field_widget[name="turtle_int"] input', 4321); @@ -3708,7 +3630,6 @@ QUnit.module("Fields", (hooks) => { }); // add 4 records (to have more records than the limit) - await clickEdit(target); await addRow(target); await editInput(target, '.o_field_widget[name="turtle_foo"] input', "nora"); await addRow(target); @@ -3750,7 +3671,6 @@ QUnit.module("Fields", (hooks) => { // edit mode, then click on Add an item, then click elsewhere assert.containsNone(target, "tr.o_data_row"); - await clickEdit(target); await addRow(target); assert.containsOnce(target, "tr.o_data_row"); @@ -3793,7 +3713,6 @@ QUnit.module("Fields", (hooks) => { }); // edit mode, then click on Add an item, then press enter - await clickEdit(target); await addRow(target); triggerHotkey("Enter"); await nextTick(); @@ -3817,7 +3736,6 @@ QUnit.module("Fields", (hooks) => { resId: 2, }); - await clickEdit(target); await addRow(target); assert.containsOnce(target, ".o_data_row"); assert.hasClass(target.querySelectorAll(".o_data_row")[0], "o_selected_row"); @@ -3860,7 +3778,6 @@ QUnit.module("Fields", (hooks) => { resId: 2, }); - await clickEdit(target); await addRow(target); assert.strictEqual( document.activeElement, @@ -3910,7 +3827,6 @@ QUnit.module("Fields", (hooks) => { // edit mode, then click on Add an item assert.containsNone(target, "tr.o_data_row"); - await clickEdit(target); await addRow(target); // input some text in required turtle_foo field @@ -3954,7 +3870,6 @@ QUnit.module("Fields", (hooks) => { }, }); - await clickEdit(target); await addRow(target); assert.containsOnce(target, "tr.o_data_row"); @@ -3986,7 +3901,6 @@ QUnit.module("Fields", (hooks) => { }, }); - await clickEdit(target); await addRow(target); assert.containsOnce(target, "tr.o_data_row"); @@ -4024,8 +3938,6 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - await clickEdit(target); - await addRow(target); await addRow(target, ".modal"); @@ -4065,7 +3977,6 @@ QUnit.module("Fields", (hooks) => { }); assert.verifySteps(["get_views", "read", "read"]); - await clickEdit(target); await click(target.querySelector(".o_data_cell")); await editInput(target, ".o_field_widget[name=product_id] input", ""); assert.verifySteps([], "no onchange should be done as line is invalid"); @@ -4109,7 +4020,6 @@ QUnit.module("Fields", (hooks) => { }, }); - await clickEdit(target); await click(target.querySelector(".o_field_one2many tbody td")); await editInput( target.querySelector(".o_field_one2many tbody td input"), @@ -4205,7 +4115,6 @@ QUnit.module("Fields", (hooks) => { assert.step(args.method); }, }); - await clickEdit(target); const td = target.querySelector("td"); assert.strictEqual(td.textContent, "9"); await click(td); @@ -4236,7 +4145,6 @@ QUnit.module("Fields", (hooks) => { assert.step(args.method); }, }); - await clickEdit(target); const td = target.querySelector("td"); assert.strictEqual(td.textContent, "01/25/2017"); @@ -4293,7 +4201,6 @@ QUnit.module("Fields", (hooks) => { }, resId: 1, }); - await clickEdit(target); assert.containsOnce(target, ".o_data_row"); // empty o2m by triggering the onchange @@ -4385,7 +4292,6 @@ QUnit.module("Fields", (hooks) => { }, resId: 1, }); - await clickEdit(target); assert.containsOnce(target, ".o_data_row"); await click(target.querySelector(".o_field_one2many td")); await editInput(target, ".o_field_widget[name=display_name] input", "blurp"); @@ -4481,7 +4387,6 @@ QUnit.module("Fields", (hooks) => { `, resId: 1, }); - await clickEdit(target); assert.containsNone(target, ".o_data_row"); await addRow(target); @@ -4536,7 +4441,6 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - await clickEdit(target); assert.containsOnce(target, ".o_form_view .o_field_x2many_list_row_add "); assert.containsNone(target, ".o_form_view input"); @@ -4566,7 +4470,6 @@ QUnit.module("Fields", (hooks) => { `, resId: 1, }); - await clickEdit(target); assert.deepEqual( [...target.querySelectorAll(".o_data_cell")].map((el) => el.textContent), ["first record", "second record", "aaa"] @@ -4601,7 +4504,6 @@ QUnit.module("Fields", (hooks) => { `, resId: 1, }); - await clickEdit(target); // switch the first row in edition await click(target.querySelector(".o_data_cell")); @@ -4688,7 +4590,6 @@ QUnit.module("Fields", (hooks) => { assert.containsOnce(target, ".o_data_cell[data-tooltip='xphone']"); assert.containsNone(target, ".o_data_cell[data-tooltip='xpad']"); - await clickEdit(target); await addRow(target); checkOnchange = true; @@ -4741,7 +4642,6 @@ QUnit.module("Fields", (hooks) => { } }, }); - await clickEdit(target); await addRow(target); // write in the many2one field, value = 37 (xphone) @@ -4830,7 +4730,6 @@ QUnit.module("Fields", (hooks) => { }, }); - await clickEdit(target); // open a modal await click(target.querySelector("tr.o_data_row td[data-tooltip='xphone']")); @@ -4870,7 +4769,6 @@ QUnit.module("Fields", (hooks) => { }, }); - await clickEdit(target); await click(target, ".o_field_widget[name=product_id] input"); } ); @@ -4908,7 +4806,6 @@ QUnit.module("Fields", (hooks) => { }, }); - await clickEdit(target); await click(target.querySelector("tr.o_data_row td[data-tooltip='xphone']")); // trigger a name search @@ -4949,7 +4846,6 @@ QUnit.module("Fields", (hooks) => { }, }); - await clickEdit(target); await addRow(target); }); @@ -4992,7 +4888,6 @@ QUnit.module("Fields", (hooks) => { }, }); - await clickEdit(target); await addRow(target); await editInput(target, '[name="turtle_foo"] input', "hammer"); await addRow(target); @@ -5015,7 +4910,6 @@ QUnit.module("Fields", (hooks) => { `, resId: 1, }); - await clickEdit(target); await addRow(target); assert.containsOnce( target, @@ -5067,7 +4961,6 @@ QUnit.module("Fields", (hooks) => { }, }); - await clickEdit(target); await addRow(target); } ); @@ -5103,7 +4996,6 @@ QUnit.module("Fields", (hooks) => { }, }); - await clickEdit(target); await addRow(target); assert.containsOnce(target, ".modal"); } @@ -5167,7 +5059,6 @@ QUnit.module("Fields", (hooks) => { }, }); - await clickEdit(target); await addRow(target); assert.strictEqual( @@ -5179,8 +5070,7 @@ QUnit.module("Fields", (hooks) => { await click($(".modal .o_data_row:first .o_list_record_selector input")[0]); await nextTick(); // additional render due to the change of selection (done in owl, not pure js) await click($(".modal .o_select_button")[0]); - await click($(".o_form_button_save")[0]); - await clickEdit(target); + await clickSave(target); await addRow(target); assert.strictEqual( @@ -5206,7 +5096,7 @@ QUnit.module("Fields", (hooks) => { "should display the record values in one2many list" ); - await click($(".o_form_button_save")[0]); + await clickSave(target); }); QUnit.test("one2many with many2many widget: edition", async function (assert) { @@ -5273,7 +5163,6 @@ QUnit.module("Fields", (hooks) => { }, }); //await new Promise(() => {}) - await clickEdit(target); await click($(target).find(".o_data_cell:first")[0]); assert.strictEqual( $(".modal .modal-title").first().text().trim(), @@ -5288,10 +5177,9 @@ QUnit.module("Fields", (hooks) => { await clickM2OHighlightedItem(target, "product_id"); await click($(".modal-footer button:first")[0]); - await click($(".o_form_button_save")[0]); + await clickSave(target); // add a one2many record - await clickEdit(target); await addRow(target); await click($(".modal .o_data_row:first .o_list_record_selector input")[0]); await nextTick(); // wait for re-rendering because of the change of selection @@ -5303,7 +5191,7 @@ QUnit.module("Fields", (hooks) => { await clickM2OHighlightedItem(target, "product_id"); await click($(".modal .modal-footer button:first")[0]); - await click($(".o_form_button_save")[0]); + await clickSave(target); }); QUnit.test("new record, the context is properly evaluated and sent", async function (assert) { @@ -5369,7 +5257,6 @@ QUnit.module("Fields", (hooks) => { }, }); - await clickEdit(target); await addRow(target); }); @@ -5409,7 +5296,6 @@ QUnit.module("Fields", (hooks) => { } }, }); - await clickEdit(target); assert.containsOnce(target, ".o_data_row"); await click(target.querySelector(".o_data_row .o_data_cell")); @@ -5539,7 +5425,6 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - await clickEdit(target); await addRow(target); await click(target.querySelector(".modal-footer button.btn-primary")); @@ -5733,8 +5618,6 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - await clickEdit(target); - await addRow(target, ".o_field_one2many"); assert.containsOnce(target, ".modal"); await click(target.querySelector(".modal-footer button")); @@ -5790,7 +5673,7 @@ QUnit.module("Fields", (hooks) => { }); QUnit.test("many2one and many2many in one2many", async function (assert) { - assert.expect(13); + assert.expect(12); serverData.models.turtle.records[1].product_id = 37; serverData.models.partner.records[0].turtles = [2, 3]; @@ -5862,13 +5745,9 @@ QUnit.module("Fields", (hooks) => { "m2m values should have been correctly fetched" ); - await click(target.querySelector(".o_data_row td")); - - assert.strictEqual(target.querySelector(".modal .o_field_widget").innerText, "xphone"); - - await click(target.querySelector(".modal-footer button")); - - await clickEdit(target); + // await click(target.querySelector(".o_data_row td")); + // assert.strictEqual(target.querySelector(".modal .o_field_widget").innerText, "xphone"); + // await click(target.querySelector(".modal-footer button")); assert.containsOnce( target, @@ -5946,7 +5825,6 @@ QUnit.module("Fields", (hooks) => { assert.step(args.method); }, }); - await clickEdit(target); assert.containsN(target, ".o_data_row", 2); await click(target.querySelector(".o_list_record_remove")); @@ -6185,7 +6063,7 @@ QUnit.module("Fields", (hooks) => { resModel: "partner", serverData, arch: ` -
+ @@ -6249,8 +6127,6 @@ QUnit.module("Fields", (hooks) => { }, }); - await clickEdit(target); - await click(target.querySelector(".o_data_row td")); // edit first record await click(target.querySelector('div[name="partner_ids"] input')); @@ -6282,7 +6158,7 @@ QUnit.module("Fields", (hooks) => { - + `, @@ -6301,8 +6177,6 @@ QUnit.module("Fields", (hooks) => { }, }); - await clickEdit(target); - // edit the first partner in the one2many partner form view await click(target.querySelector(".o_data_row td.o_data_cell")); // open form view for many2one @@ -6530,7 +6404,7 @@ QUnit.module("Fields", (hooks) => { resModel: "partner", serverData, arch: ` -
+ @@ -6602,8 +6476,7 @@ QUnit.module("Fields", (hooks) => { "should not have kanban records yet" ); - // // switch to edit mode and create a new kanban record - await clickEdit(target); + // create a new kanban record await click(target, ".o_field_widget .o-kanban-button-new"); // save & close the modal @@ -6761,9 +6634,6 @@ QUnit.module("Fields", (hooks) => { await click(target, btn1Warn); assert.verifySteps(["button_warn_partner_4"]); - // switch to edit mode - await clickEdit(target); - // click on existing buttons await click(target, btn1Disabled); assert.verifySteps(["button_disabled_partner_4"]); @@ -6813,8 +6683,7 @@ QUnit.module("Fields", (hooks) => { ]); // save the form - await clickSave(target); - assert.verifySteps([]); // the write has already been done + assert.containsOnce(target, ".o_form_saved"); // click all buttons await click(target, btn1Disabled); @@ -6852,7 +6721,6 @@ QUnit.module("Fields", (hooks) => { `, resId: 1, }); - await clickEdit(target); await click(target.querySelector(".o_data_row td")); const turtleFooInput = target.querySelector('[name="turtle_foo"] input'); @@ -6891,8 +6759,6 @@ QUnit.module("Fields", (hooks) => { } }, }); - await clickEdit(target); - assert.containsOnce(target, ".o_data_row", "should start with one data row"); await addRow(target); @@ -6939,7 +6805,6 @@ QUnit.module("Fields", (hooks) => { } }, }); - await clickEdit(target); assert.containsOnce(target, ".o_data_row", "should start with one data row"); @@ -6980,15 +6845,12 @@ QUnit.module("Fields", (hooks) => { `, resId: 1, }); - await clickEdit(target); await addRow(target); assert.containsOnce(target, ".modal"); assert.containsNone(target, ".modal .modal-footer .o_btn_remove"); // Discard a modal await click(target.querySelector(".modal-footer .btn-secondary")); - - await clickDiscard(target); }); QUnit.test('x2many fields use their "mode" attribute', async function (assert) { @@ -7054,7 +6916,6 @@ QUnit.module("Fields", (hooks) => { }, resId: 1, }); - await clickEdit(target); assert.strictEqual( target.querySelector('.o_field_widget[name="int_field"] input').value, "0" @@ -7121,7 +6982,6 @@ QUnit.module("Fields", (hooks) => { }, resId: 1, }); - await clickEdit(target); assert.strictEqual( $(target).find('.o_field_widget[name="int_field"] input').val(), @@ -7185,7 +7045,6 @@ QUnit.module("Fields", (hooks) => { `, resId: 1, }); - await clickEdit(target); assert.strictEqual( target.querySelector('.o_field_widget[name="int_field"] input').value, "10" @@ -7234,7 +7093,6 @@ QUnit.module("Fields", (hooks) => { `, resId: 1, }); - await clickEdit(target); assert.strictEqual( target.querySelector('.o_field_widget[name="int_field"] input').value, "10" @@ -7432,8 +7290,6 @@ QUnit.module("Fields", (hooks) => { } }, }); - await clickEdit(target); - patchWithCleanup(form.env.services.notification, { add: (message, params) => { assert.step(params.type); @@ -7512,8 +7368,6 @@ QUnit.module("Fields", (hooks) => { resId: 1, context: { active_field: 2 }, }); - await clickEdit(target); - await click(target.querySelector(".o_data_cell")); await editInput(target, ".o_field_widget[name=display_name] input", "abc"); await clickSave(target); @@ -7539,8 +7393,6 @@ QUnit.module("Fields", (hooks) => { assert.step(args.method); }, }); - await clickEdit(target); - await editInput(target, '[name="foo"] input', "abcd"); assert.verifySteps(["get_views", "read", "onchange"]); }); @@ -7598,7 +7450,7 @@ QUnit.module("Fields", (hooks) => { ); QUnit.test("multi level of nested x2manys, onchange and rawChanges", async function (assert) { - assert.expect(8); + assert.expect(7); serverData.models.partner.records[0].p = [1]; serverData.models.partner.onchanges = { name: function () {}, @@ -7634,14 +7486,7 @@ QUnit.module("Fields", (hooks) => { assert.containsOnce(target, ".o_data_row"); - // open the o2m record in readonly first - await click(target.querySelector(".o_data_row td")); - assert.containsOnce(target, ".modal .o_form_readonly"); - - await clickDiscard(target.querySelector(".modal")); - - // switch to edit mode and open it again - await clickEdit(target); + // open the dialog await click(target.querySelector(".o_data_row td")); assert.containsOnce(target, ".modal .o_form_editable"); assert.containsOnce(target, ".modal .o_data_row"); @@ -7692,8 +7537,6 @@ QUnit.module("Fields", (hooks) => { assert.step(args.method); }, }); - await clickEdit(target); - // triggers an onchange on partner, because the new record is valid await addRow(target); @@ -7784,8 +7627,6 @@ QUnit.module("Fields", (hooks) => { } }, }); - - await clickEdit(target); await addRow(target); assert.containsN(target, "tr.o_data_row", 41); assert.hasClass([...target.querySelectorAll("tr.o_data_row")].pop(), "o_selected_row"); @@ -7819,8 +7660,6 @@ QUnit.module("Fields", (hooks) => { `, resId: 1, }); - - await clickEdit(target); // add a new record page 1 (this increases the limit to 4) await addRow(target); await editInput(target, '.o_data_row [name="turtle_foo"] input', "rainbow dash"); @@ -7863,8 +7702,6 @@ QUnit.module("Fields", (hooks) => { } }, }); - - await clickEdit(target); await click(target, ".o_field_widget[name=turtles] .o_pager_next"); assert.containsN(target, "tr.o_data_row", 2); @@ -8081,8 +7918,6 @@ QUnit.module("Fields", (hooks) => { `, resId: 1, }); - await clickEdit(target); - let rows = target.querySelectorAll(".o_data_row"); await click(rows[0].querySelector(".o_data_cell")); assert.containsOnce(target, ".o_data_row.o_selected_row"); @@ -8191,7 +8026,6 @@ QUnit.module("Fields", (hooks) => { }, resId: 1, }); - await clickEdit(target); assert.strictEqual(target.querySelector(".o_data_row .o_data_cell").textContent, "blip"); // click and edit value to 'foo', which will trigger onchange @@ -8418,7 +8252,6 @@ QUnit.module("Fields", (hooks) => { } }, }); - await clickEdit(target); await click(target.querySelector(".o_list_record_remove button")); assert.deepEqual( [...target.querySelectorAll(".o_data_row")].map((el) => el.textContent), @@ -8460,7 +8293,6 @@ QUnit.module("Fields", (hooks) => { } }, }); - await clickEdit(target); assert.deepEqual( getNodesTextContent(target.querySelectorAll(".o_data_row .o_data_cell.o_list_char")), ["yop", "blip"] @@ -8493,8 +8325,6 @@ QUnit.module("Fields", (hooks) => { `, resId: 1, }); - await clickEdit(target); - // starting condition assert.deepEqual( [...target.querySelectorAll(".o_data_row")].map((el) => el.textContent), @@ -8538,8 +8368,6 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - await clickEdit(target); - // starting condition assert.deepEqual( [...target.querySelectorAll(".o_data_row")].map((el) => el.textContent), @@ -8587,8 +8415,6 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - await clickEdit(target); - // starting condition assert.deepEqual( [...target.querySelectorAll(".o_data_row")].map((el) => el.textContent), @@ -8641,8 +8467,6 @@ QUnit.module("Fields", (hooks) => { `, resId: 1, }); - await clickEdit(target); - // click add a new line // check turtle_int for new is the current max of the page await addRow(target); @@ -8676,9 +8500,6 @@ QUnit.module("Fields", (hooks) => { `, resId: 1, }); - - await clickEdit(target); - // click add a new line // check turtle_int for new is the current max of the page +1 await addRow(target); @@ -8806,7 +8627,6 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - await click(target, ".o_form_button_edit"); assert.deepEqual( [...target.querySelectorAll(".o_data_cell.foo")].map((el) => el.textContent), ["blip", "kawa"] @@ -8907,7 +8727,6 @@ QUnit.module("Fields", (hooks) => { `, resId: 1, }); - await click(target, ".o_form_button_edit"); await addRow(target); assert.containsN(target, ".o_data_row", 4); @@ -8956,9 +8775,6 @@ QUnit.module("Fields", (hooks) => { assert.step(args.method); }, }); - - await clickEdit(target); - // swap 2 lines in the one2many await dragAndDrop("tbody tr:nth-child(2) .o_handle_cell", "tbody tr", "top"); @@ -9224,7 +9040,6 @@ QUnit.module("Fields", (hooks) => { }, }); - await clickEdit(target); assert.deepEqual( [...target.querySelector(".o_field_x2many_list_row_add").children].map( (el) => el.textContent @@ -9305,9 +9120,6 @@ QUnit.module("Fields", (hooks) => { `, }); - - await clickEdit(target); - assert.containsOnce(target, ".o_data_row"); assert.strictEqual(target.querySelector(".o_data_cell").textContent, "second record"); @@ -9390,7 +9202,6 @@ QUnit.module("Fields", (hooks) => { }); // add a turtle on second partner - await clickEdit(target); await click(target.querySelectorAll(".o_data_row")[1].querySelector(".o_data_cell")); await addRow(target.querySelector(".modal")); await editInput(target, ".modal .o_field_widget[name=display_name] input", "michelangelo"); @@ -9568,8 +9379,6 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - await clickEdit(target); - // add a row, fill it, then trigger the tab shortcut await addRow(target); await editInput(target, "[name=turtle_foo] textarea", "ninja"); @@ -9617,8 +9426,6 @@ QUnit.module("Fields", (hooks) => { } }, }); - await clickEdit(target); - await click(target.querySelector(".o_data_row .o_data_cell")); await editInput(target, "[name='turtle_foo'] input", "Test"); assert.containsOnce(target, ".o_data_row"); @@ -9791,7 +9598,7 @@ QUnit.module("Fields", (hooks) => { await addRow(target); await clickSave(target); - assert.containsOnce(target, ".o_form_readonly"); + assert.containsOnce(target, ".o_form_editable"); assert.strictEqual( target.querySelector(".o_data_row").textContent.trim(), "some foo value" @@ -9833,8 +9640,6 @@ QUnit.module("Fields", (hooks) => { }, }); - await clickEdit(target); - // Add a record in the list await addRow(target); await editInput(target, ".modal div[name=display_name] input", "michelangelo"); @@ -9893,7 +9698,6 @@ QUnit.module("Fields", (hooks) => { "should be 2 columns in the one2many" ); - await clickEdit(target); await selectDropdownItem(target, "product_id", "xphone"); assert.containsOnce( @@ -9934,8 +9738,6 @@ QUnit.module("Fields", (hooks) => { `, resId: 1, }); - await clickEdit(target); - assert.strictEqual( target.querySelector(".o_field_widget[name=product_id] input").value, "" @@ -9975,7 +9777,6 @@ QUnit.module("Fields", (hooks) => { resId: 1, }); - await clickEdit(target); assert.strictEqual( target.querySelector(".o_field_widget[name=product_id] input").value, "" @@ -10083,17 +9884,14 @@ QUnit.module("Fields", (hooks) => { ["yop", "blip", "kawa"] ); - // should not work (form in mode "readonly") - await dragAndDrop(".o_kanban_record:nth-child(1)", ".o_kanban_record:nth-child(3)"); - - assert.deepEqual( - [...target.querySelectorAll(".o_kanban_record:not(.o_kanban_ghost)")].map( - (el) => el.innerText - ), - ["yop", "blip", "kawa"] - ); - - await clickEdit(target); + // // should not work (form in mode "readonly") + // await dragAndDrop(".o_kanban_record:nth-child(1)", ".o_kanban_record:nth-child(3)"); + // assert.deepEqual( + // [...target.querySelectorAll(".o_kanban_record:not(.o_kanban_ghost)")].map( + // (el) => el.innerText + // ), + // ["yop", "blip", "kawa"] + // ); await dragAndDrop(".o_kanban_record:nth-child(1)", ".o_kanban_record:nth-child(3)"); @@ -10133,7 +9931,6 @@ QUnit.module("Fields", (hooks) => { assert.containsOnce(target, ".o_data_row"); // edit first row - await clickEdit(target); await click(target.querySelector(".o_data_row .o_data_cell")); assert.hasClass(target.querySelector(".o_data_row"), "o_selected_row"); target.querySelector(".o_selected_row .o_field_widget[name=turtle_int] input").value = "44"; @@ -10446,40 +10243,6 @@ QUnit.module("Fields", (hooks) => { assert.strictEqual(target.querySelector('th[data-name="date"]').offsetWidth, width); }); - QUnit.test("editable one2many list with oe_read_only button", async function (assert) { - await makeView({ - type: "form", - resModel: "partner", - serverData, - arch: ` -
- - - -