[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 <luvi@odoo.com>
This commit is contained in:
Michael (mcm)
2022-09-27 12:43:50 +02:00
co-authored by luvi
parent 305fbc0e10
commit 710271240f
124 changed files with 1549 additions and 3069 deletions
@@ -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]",
@@ -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",
@@ -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",
@@ -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");
@@ -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']"));
@@ -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',
}
]);
});
@@ -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",
@@ -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",
@@ -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'));
@@ -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: '<form><field name="product_id" widget="hr_holidays_radio_image"/></form>',
});
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);
});
});
@@ -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",
},
{
@@ -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) => {
<field name="program_type" widget="filterable_selection" options="{'whitelisted_values': ['coupon', 'promotion']}"/>
</form>`,
});
await click(target, ".o_form_button_edit");
assert.containsN(target, "select option", 3);
assert.containsOnce(
@@ -71,7 +70,6 @@ QUnit.module("Fields", (hooks) => {
<field name="program_type" widget="filterable_selection" options="{'blacklisted_values': ['gift_card']}"/>
</form>`,
});
await click(target, ".o_form_button_edit");
assert.containsN(target, "select option", 3);
assert.containsOnce(
@@ -96,7 +94,6 @@ QUnit.module("Fields", (hooks) => {
<field name="program_type" widget="filterable_selection" options="{'blacklisted_values': ['gift_card']}"/>
</form>`,
});
await click(target, ".o_form_button_edit");
assert.containsN(target, "select option", 4);
assert.containsOnce(
@@ -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
@@ -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"));
});
});
@@ -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) {
@@ -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,
@@ -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: () => {},
}]);
});
@@ -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");
});
@@ -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 <b>kanban view</b> to have an overview of your next tasks.")),
position: "right",
run: 'click',
@@ -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")' })
]);
@@ -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");
});
@@ -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', {
'<form><field name="sale_order_id" widget="sale_order_many2one"/></form>'
});
await clickEdit(this.target);
await clickDropdown(this.target, "sale_order_id");
assert.containsN(this.target, 'li.o-autocomplete--dropdown-item', 9);
@@ -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");
@@ -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) => {
</field>
</form>
`,
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) => {
</field>
</form>`,
});
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) => {
</form>
`,
});
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) => {
</form>
`,
});
await clickEdit(target);
await click(target.querySelector(".o_data_row .o_data_cell"));
assert.containsOnce(target, ".o_selected_row.o_is_section");
@@ -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")' }),
]);
@@ -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);
}
}
@@ -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";
@@ -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<boolean>}
*/
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();
}
@@ -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);
}
@@ -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;
@@ -3,6 +3,10 @@
.o_tags_input {
padding: 1px 0;
&:hover, &:focus-within {
border-color: var(--o-input-border-color);
}
}
.o_field_many2many_selection {
@@ -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) {
@@ -33,7 +33,6 @@
t-att-required="props.required"
t-on-change="onCurrentValueChange"
t-on-input="onCurrentValueInput"
t-on-blur="onBlur"
/>
<span>%</span>
</div>
@@ -48,7 +47,6 @@
t-att-value="formatCurrentValue()"
t-on-change="onCurrentValueChange"
t-on-input="onCurrentValueInput"
t-on-blur="onBlurDebounced"
/>
<span t-if="props.isCurrentValueEditable and props.isMaxValueEditable" t-esc="'/'" />
<input
@@ -60,7 +58,6 @@
t-att-value="formatMaxValue()"
t-on-change="onMaxValueChange"
t-on-input="onMaxValueInput"
t-on-blur="onBlurDebounced"
/>
</t>
</div>
@@ -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();
@@ -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;
@@ -0,0 +1,6 @@
/** @odoo-module **/
import { ControlPanel } from "@web/search/control_panel/control_panel";
export class FormControlPanel extends ControlPanel {}
FormControlPanel.template = "web.FormControlPanel";
@@ -0,0 +1,6 @@
.o_form_view .o_control_panel {
.o_cp_action_menus {
border-right: 1px solid $border-color;
}
}
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="web.FormControlPanel" owl="1">
<div class="o_control_panel" t-ref="root">
<div t-if="display['top']" class="o_cp_top">
<div class="o_cp_top_left w-auto d-flex flex-grow-1">
<t t-if="display['top-left']">
<t t-slot="control-panel-breadcrumb">
<t t-if="env.isSmall">
<t t-call="web.Breadcrumbs.Small" t-if="!env.config.noBreadcrumbs"/>
</t>
<t t-else="">
<t t-call="web.Breadcrumbs" t-if="!env.config.noBreadcrumbs"/>
</t>
</t>
<t t-slot="control-panel-status-indicator" />
</t>
</div>
<div class="o_cp_bottom_right w-auto flex-shrink-0 align-items-center">
<t t-slot="control-panel-action-menu" t-if="display['bottom-left']"/>
<div t-if="pagerProps and pagerProps.total > 0" class="o_cp_pager" role="search">
<Pager t-props="pagerProps"/>
</div>
<t t-slot="control-panel-create-button" />
</div>
</div>
</div>
</t>
</templates>
@@ -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);
@@ -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 },
@@ -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 {
@@ -7,52 +7,37 @@
<Layout className="model.useSampleModel ? 'o_view_sample_data' : ''" display="display">
<t t-set-slot="layout-buttons">
<t t-if="footerArchInfo and env.inDialog">
<t t-component="props.Renderer" record="model.root" Compiler="props.Compiler" archInfo="footerArchInfo"/>
<t t-component="props.Renderer" record="model.root" Compiler="props.Compiler" archInfo="footerArchInfo" enableViewButtons.bind="enableButtons" disableViewButtons.bind="disableButtons"/>
</t>
<t t-else="">
<t t-call="{{ props.buttonTemplate }}"/>
</t>
</t>
<t t-set-slot="control-panel-bottom-left">
<t t-if="props.info.actionMenus and !model.root.isVirtual">
<t t-set-slot="control-panel-action-menu">
<t t-if="props.info.actionMenus">
<ActionMenus
activeIds="[model.root.resId]"
activeIds="model.root.isVirtual ? [] : [model.root.resId]"
context="props.context"
items="getActionMenuItems()"
isDomainSelected="model.root.isDomainSelected"
resModel="model.root.resModel"
domain="props.domain"
onActionExecuted="() => model.load()"
/>
onBeforeAction.bind="beforeAction"
/>
</t>
</t>
<t t-set-slot="control-panel-bottom-left">
<t t-if="!env.isSmall and props.info.actionMenus and !model.root.isVirtual">
<ActionMenus
activeIds="[model.root.resId]"
context="props.context"
items="getActionMenuItems()"
isDomainSelected="model.root.isDomainSelected"
resModel="model.root.resModel"
domain="props.domain"
onActionExecuted="() => model.load()"
/>
<t t-set-slot="control-panel-status-indicator">
<t t-if="canEdit">
<FormStatusIndicator model="model" discard.bind="discard" save.bind="save" isDisabled="state.isDisabled" />
</t>
</t>
<t t-set-slot="control-panel-bottom-right">
<t t-if="env.isSmall and props.info.actionMenus and !model.root.isVirtual">
<ActionMenus
activeIds="[model.root.resId]"
context="props.context"
items="getActionMenuItems()"
isDomainSelected="model.root.isDomainSelected"
resModel="model.root.resModel"
domain="props.domain"
onActionExecuted="() => model.load()"
/>
<t t-set-slot="control-panel-create-button">
<t t-if="canCreate">
<button type="button" class="btn btn-outline-primary o_form_button_create" data-hotkey="c" t-on-click.stop="create" t-att-disabled="state.isDisabled">Create</button>
</t>
</t>
<t t-component="props.Renderer" record="model.root" Compiler="props.Compiler" archInfo="archInfo" translateAlert="translateAlert"/>
<t t-component="props.Renderer" record="model.root" Compiler="props.Compiler" archInfo="archInfo" translateAlert="translateAlert" enableViewButtons.bind="enableButtons" disableViewButtons.bind="disableButtons"/>
</Layout>
</div>
</div>
@@ -68,11 +53,8 @@
Discard
</button>
</div>
<div t-else="canEdit or canCreate" class="o_form_buttons_view">
<button t-if="canEdit" type="button" class="btn btn-primary o_form_button_edit" data-hotkey="a" data-bounce-button="" t-on-click.stop="edit">
Edit
</button>
<button t-if="canCreate" type="button" class="btn btn-secondary o_form_button_create" data-hotkey="c" t-on-click.stop="create">
<div t-elif="canCreate" class="o_form_buttons_view">
<button type="button" class="btn btn-secondary o_form_button_create" data-hotkey="c" t-on-click.stop="create">
Create
</button>
</div>
@@ -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);
});
}
@@ -0,0 +1,10 @@
.o_form_error_dialog {
border: none;
padding: 2rem;
top: 50%;
transform: translateY(-50%);
footer {
border: none;
}
}
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="web.FormErrorDialog" owl="1">
<Dialog header="false" size="'md'" contentClass="'o_form_error_dialog'">
<div role="alert" class="o_dialog_warning">
<h1 class="text-danger">Oh snap!</h1>
<p t-esc="message" style="white-space: pre-wrap;"/>
</div>
<t t-set-slot="footer">
<button class="btn btn-primary" t-on-click="props.close">Stay here</button>
<button class="btn btn-secondary" t-on-click="discard">Discard changes</button>
</t>
</Dialog>
</t>
</templates>
@@ -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(" ");
}
@@ -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]
);
}
}
@@ -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,
};
@@ -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;
}
}
}
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="web.FormStatusIndicator" owl="1">
<div class="o_form_status_indicator d-flex align-self-center align-items-center ms-4 rounded-pill position-relative" t-att-class="{ o_form_status_indicator_new_record: props.model.root.isVirtual }">
<t t-if="!props.model.root.isVirtual">
<t t-if="indicatorMode === 'invalid'">
<span class="position-absolute text-danger">Unable to save</span>
</t>
<t t-elif="indicatorMode === 'dirty'">
<span class="position-absolute text-muted">Unsaved changes</span>
</t>
</t>
<div class="o_form_status_indicator_buttons d-flex" t-att-class="{ 'o_form_status_indicator_buttons_hidden': !(props.model.root.isVirtual or displayButtons) }">
<button type="button" class="o_form_button_save btn btn-secondary" t-att-disabled="props.isDisabled" data-tooltip="Click to save" data-hotkey="s" t-on-click.stop="save">Save</button>
<button type="button" class="o_form_button_cancel btn btn-secondary" t-att-disabled="props.isDisabled" data-tooltip="Click to discard" data-hotkey="j" t-on-click.stop="discard">Discard</button>
</div>
</div>
</t>
</templates>
@@ -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,
@@ -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]);
}
}
@@ -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`);
}
@@ -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: () => {},
};
@@ -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();
}
@@ -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,
+9 -5
View File
@@ -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) {
@@ -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: `
<form>
<header>
<button string="Share" type="action" class="oe_highlight oe_read_only" />
<button string="Email" type="action" class="oe_highlight oe_read_only" />
</header>
<sheet>
<group>
<field name="display_name" />
</group>
</sheet>
</form>
`,
});
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");
@@ -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,
@@ -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) => {
</form>`,
});
await clickEdit(target);
await triggerEvents(target, ".ace-view-editor textarea", ["focus", "click"]);
assert.hasClass(target.querySelector(".ace-view-editor"), "ace_focus");
});
@@ -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: `
<form edit="0">
<field name="document" filename="foo"/>
<field name="foo"/>
</form>`,
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) => {
</form>`,
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);
@@ -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",
@@ -2,6 +2,7 @@
import {
click,
clickSave,
getFixture,
nextTick,
triggerEvent,
@@ -50,15 +51,6 @@ QUnit.module("Fields", (hooks) => {
</form>`,
});
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: `<form><field name="bar" readonly="1"/></form>`,
});
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",
@@ -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");
@@ -3,6 +3,7 @@
import { registry } from "@web/core/registry";
import {
click,
clickSave,
editInput,
getFixture,
makeDeferred,
@@ -147,14 +148,6 @@ QUnit.module("Fields", (hooks) => {
</form>`,
});
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) => {
</sheet>
</form>`,
});
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) => {
</form>`,
});
await click(target, ".o_form_button_edit");
await editInput(target, "[name='foo'] input", "<script>throw Error();</script>");
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,
"<script>throw Error();</script>",
"the value should have been properly escaped"
);
@@ -576,16 +557,9 @@ QUnit.module("Fields", (hooks) => {
</form>`,
});
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: `
<form>
<field name="foo" password="True" readonly="1" />
</form>`,
});
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) => {
</form>`,
});
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) => {
</form>`,
});
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",
@@ -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":""}]',
@@ -59,9 +59,6 @@ QUnit.module("Fields", (hooks) => {
</form>`,
});
// 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) => {
</form>`,
});
// switch to edit mode
await click(target, ".o_form_button_edit");
await click(target, ".o_field_color_picker button");
assert.strictEqual(
@@ -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) => {
</form>`,
});
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: '<form><field name="date"/></form>',
});
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: '<form><field name="date"/></form>',
});
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: '<form><field name="date"/></form>',
});
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: '<form><field name="date"/></form>',
});
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) => {
</div>
</form>`,
});
await click(target, ".o_form_button_edit");
await click(target, ".o_datepicker input");
assert.containsOnce(
@@ -379,8 +360,6 @@ QUnit.module("Fields", (hooks) => {
<field name="date" options="{ 'datepicker': { 'warn_future': true } }" />
</form>`,
});
// 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) => {
<field name="date" options="{ 'datepicker': { 'warn_future': true } }" />
</form>`,
});
// 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: '<form><field name="date"/></form>',
});
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: '<form><field name="date"/></form>',
});
await click(target, ".o_form_button_edit");
const year = new Date().getFullYear();
const input = target.querySelector(".o_field_widget[name='date'] input");
@@ -2,6 +2,7 @@
import {
click,
clickSave,
editInput,
getFixture,
patchDate,
@@ -69,22 +70,6 @@ QUnit.module("Fields", (hooks) => {
</form>`,
});
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) => {
</form>`,
});
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"
);
@@ -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");
}
);
@@ -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) => {
</sheet>
</form>`,
});
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) => {
</sheet>
</form>`,
});
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) => {
<field name="foo" widget="domain" options="{'model': 'partner'}" />
</form>`,
});
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);
});
@@ -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) => {
</form>`,
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) => {
</sheet>
</form>`,
});
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
@@ -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."
);
@@ -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: `<form><field name="float_field" options="{'type': 'number', 'step': 0.3}"/></form>`,
});
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: `<form><field name="float_field" options="{ 'digits': [0, 3] }" /></form>`,
});
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: `<form><field name="float_field" options="{ 'digits': [0, 3] }" /></form>`,
});
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: `
<form>
<sheet>
<field name="float_field" widget="float" digits="[5,3]"/>
<field name="int_field"/>
</sheet>
</form>`,
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",
@@ -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: "
@@ -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",
@@ -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"
);
});
});
@@ -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 */ `<form><field name="txt" readonly="1" /></form>`,
});
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 */ `<form><field name="txt" /></form>`,
});
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");
});
});
@@ -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) => {
</form>`,
});
// 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: `
<form>
<field name="document" widget="image" options="{'zoom': true, 'zoom_delay': 600}" />
<field name="document" widget="image" options="{'zoom': true, 'zoom_delay': 600}" readonly="1" />
</form>`,
});
// 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: `
<form>
<field name="document" widget="image" options="{'zoom': true, 'zoom_delay': 600}" />
</form>`,
});
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: `
<form>
<field name="document" widget="image" options="{'zoom': true, 'preview_image': 'document_preview', 'zoom_delay': 600}" />
<field name="document" widget="image" options="{'zoom': true, 'preview_image': 'document_preview', 'zoom_delay': 600}" readonly="1" />
</form>`,
});
@@ -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) => {
@@ -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"
);
@@ -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: '<form><field name="int_field"/></form>',
});
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: '<form><field name="int_field"/></form>',
});
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: `<form><field name="int_field" options="{'type': 'number'}"/></form>`,
});
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: `<form><field name="int_field" options="{'type': 'number', 'step': 3}"/></form>`,
});
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: '<form><field name="int_field"/></form>',
});
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: `<form><field name="int_field" options="{'format': 'false'}"/></form>`,
});
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: '<form><field name="int_field"/></form>',
});
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",
@@ -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) {
@@ -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();
@@ -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) => {
</form>`,
});
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) => {
</form>`,
});
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) {
@@ -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": '<form><field name="display_name"/></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) => {
</form>`,
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) => {
</form>`,
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) => {
</form>`,
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) => {
</form>`,
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...");
@@ -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) => {
</form>`,
});
await clickEdit(target);
assert.containsN(
target,
".o_field_many2many_tags_avatar.o_field_widget .badge",
@@ -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) => {
</form>`,
});
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) => {
</form>`,
});
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,
@@ -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) {
@@ -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) => {
<form>
<sheet>
<group>
<field name="trululu" string="custom label" />
<field name="trululu" string="custom label" open_target="new" />
</group>
</sheet>
</form>`,
@@ -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: `
<form>
<sheet>
<field name="trululu" />
<field name="trululu" open_target="new" />
</sheet>
</form>`,
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: `
<form>
<sheet>
<field name="trululu" />
<field name="trululu" open_target="new" />
</sheet>
</form>`,
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,
"<span>aaa</span><br><span>Street</span><br><span>City ZIP</span>",
"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: `
<form>
<form edit="0">
<field name="display_name" />
<field name="turtles" />
</form>`,
@@ -587,7 +569,7 @@ QUnit.module("Fields", (hooks) => {
serverData,
resId: 1,
arch: `
<form>
<form edit="0">
<field name="display_name" />
<field name="turtles" />
</form>`,
@@ -644,8 +626,6 @@ QUnit.module("Fields", (hooks) => {
</form>`,
});
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) => {
<form>
<sheet>
<group>
<field name="user_id" />
<field name="user_id" open_target="new" />
</group>
</sheet>
</form>`,
@@ -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) => {
</form>`,
});
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: `
<form>
<form edit="0">
<field name="trululu" />
</form>`,
});
@@ -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: `
<form edit="0">
<field name="trululu" />
</form>`,
});
// 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: `
<form>
<field name="trululu" options="{'always_reload': 1}" readonly="1" />
</form>`,
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) => {
<form>
<field name="p">
<tree editable="bottom">
<field name="product_id" />
<field name="product_id" open_target="new" />
</tree>
</field>
</form>`,
@@ -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) => {
<field name="name" />
<field name="turtle_ids" widget="one2many">
<tree editable="bottom">
<field name="type_id" />
<field name="type_id" open_target="new" />
</tree>
</field>
</form>`,
@@ -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: '<form><field name="product_id" /></form>',
});
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: `
<form>
<sheet>
<field name="product_id" can_create="false" can_write="false" />
<field name="product_id" can_create="false" can_write="false" open_target="new" />
</sheet>
</form>`,
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) => {
</form>`,
});
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) => {
<field name="turtles">
<tree editable="bottom">
<field name="display_name" />
<field name="turtle_trululu" />
<field name="turtle_trululu" open_target="new" />
</tree>
</field>
</form>`,
@@ -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) => {
</form>`,
});
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: '<form><field name="trululu" /></form>',
});
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": '<form><field name="trululu" /></form>',
"partner,false,form": '<form><field name="trululu" open_target="new" /></form>',
};
await makeView({
@@ -4025,7 +3996,7 @@ QUnit.module("Fields", (hooks) => {
serverData,
resModel: "partner",
resId: 2,
arch: '<form><field name="trululu" /></form>',
arch: '<form><field name="trululu" open_target="new" /></form>',
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) => {
</form>`,
});
await clickEdit(target);
await selectDropdownItem(target, "trululu", "Search More...");
const modal = target.querySelector(".modal");
@@ -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) => {
</form>`,
});
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) => {
</form>`,
});
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) => {
</form>`,
});
// 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) => {
</form>`,
});
// 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) => {
</form>`,
});
// 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) => {
</form>`,
});
// 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) => {
</form>`,
});
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) => {
</form>`,
});
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: `
<form>
<form edit="0">
<sheet>
<field name="float_field" options="{'currency_field': 'company_currency_id'}"/>
<field name="company_currency_id"/>
@@ -1008,7 +936,7 @@ QUnit.module("Fields", (hooks) => {
type: "form",
resModel: "partner",
arch: `
<form>
<form edit="0">
<sheet>
<field name="monetary" widget="float"/>
<field name="currency_id" invisible="1"/>
@@ -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) => {
</form>`,
});
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");
});
});
@@ -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");
File diff suppressed because it is too large Load Diff
@@ -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;
@@ -64,7 +64,7 @@ QUnit.module("Fields", (hooks) => {
});
assert.hasClass(target.querySelector(".o_field_widget"), "o_field_pdf_viewer");
assert.containsNone(target, ".o_select_file_button", "there should be no 'Upload' button");
assert.containsOnce(target, ".o_select_file_button", "there should be an 'Upload' button");
assert.containsOnce(
target,
".o_field_widget iframe.o_pdfview_iframe",
@@ -75,7 +75,7 @@ QUnit.module("Fields", (hooks) => {
});
QUnit.test("PdfViewerField: upload rendering", async function (assert) {
assert.expect(6);
assert.expect(5);
await makeView({
type: "form",
@@ -97,9 +97,8 @@ QUnit.module("Fields", (hooks) => {
assert.containsOnce(target, ".o_pdfview_iframe", "there is a PDF Viewer");
assert.strictEqual(getIframeProtocol(), "blob");
await click(target, ".o_form_button_save");
await clickSave(target);
assert.strictEqual(getIframeProtocol(), "http");
assert.strictEqual(getIframeViewerParams(), "model=partner&field=document&id=2");
assert.strictEqual(getIframeProtocol(), "blob");
});
});
@@ -1,6 +1,6 @@
/** @odoo-module **/
import { click, getFixture } from "@web/../tests/helpers/utils";
import { getFixture } from "@web/../tests/helpers/utils";
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
let serverData;
@@ -81,8 +81,6 @@ QUnit.module("Fields", (hooks) => {
"right mask should be rotated from 360*(10/100) = 36 degrees"
);
// switch to edit mode and check the result
await click(target.querySelector(".o_form_button_edit"));
assert.containsOnce(
target,
".o_field_percent_pie.o_field_widget .o_pie",
@@ -111,8 +109,6 @@ QUnit.module("Fields", (hooks) => {
"right mask should be rotated from 360*(10/100) = 36 degrees"
);
// save
await click(target.querySelector(".o_form_button_save"));
assert.containsOnce(
target,
".o_field_percent_pie.o_field_widget .o_pie",
@@ -179,8 +175,6 @@ QUnit.module("Fields", (hooks) => {
"right mask should be hidden since the value > 50%"
);
// switch to edit mode and check the result
await click(target.querySelector(".o_form_button_edit"));
assert.containsOnce(
target,
".o_field_percent_pie.o_field_widget .o_pie",
@@ -204,8 +198,6 @@ QUnit.module("Fields", (hooks) => {
"right mask should be hidden since the value > 50%"
);
// save
await click(target.querySelector(".o_form_button_save"));
assert.containsOnce(
target,
".o_field_percent_pie.o_field_widget .o_pie",
@@ -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;
@@ -30,7 +30,7 @@ QUnit.module("Fields", (hooks) => {
QUnit.module("PercentageField");
QUnit.test("PercentageField in form view", async function (assert) {
assert.expect(6);
assert.expect(5);
await makeView({
serverData,
@@ -51,12 +51,6 @@ QUnit.module("Fields", (hooks) => {
},
resId: 1,
});
assert.strictEqual(
target.querySelector(".o_field_widget").textContent,
"44.4%",
"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,
"44.4",
@@ -70,10 +64,10 @@ QUnit.module("Fields", (hooks) => {
const field = target.querySelector("[name='float_field'] input");
await editInput(target, "[name='float_field'] input", "24");
assert.strictEqual(field.value, "24", "The value should not be formated yet.");
await click(target.querySelector(".o_form_button_save"));
await clickSave(target);
assert.strictEqual(
target.querySelector(".o_field_widget").textContent,
"24%",
target.querySelector(".o_field_widget input").value,
"24",
"The new value should be formatted properly."
);
});
@@ -1,7 +1,7 @@
/** @odoo-module **/
import { getNextTabableElement } from "@web/core/utils/ui";
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;
@@ -31,11 +31,12 @@ QUnit.module("Fields", (hooks) => {
QUnit.module("PhoneField");
QUnit.test("PhoneField in form view on normal screens", async function (assert) {
QUnit.test("PhoneField in form view on normal screens (readonly)", async function (assert) {
await makeView({
serverData,
type: "form",
resModel: "partner",
mode: "readonly",
arch: `
<form>
<sheet>
@@ -55,9 +56,24 @@ QUnit.module("Fields", (hooks) => {
);
assert.strictEqual(phone.textContent, "yop", "value should be displayed properly");
assert.hasAttrValue(phone, "href", "tel:yop", "should have proper tel prefix");
});
QUnit.test("PhoneField in form view on normal screens (edit)", async function (assert) {
await makeView({
serverData,
type: "form",
resModel: "partner",
arch: `
<form>
<sheet>
<group>
<field name="foo" widget="phone"/>
</group>
</sheet>
</form>`,
resId: 1,
});
// switch to edit mode and check the result
await click(target.querySelector(".o_form_button_edit"));
assert.containsOnce(
target,
'input[type="tel"]',
@@ -73,9 +89,9 @@ QUnit.module("Fields", (hooks) => {
await editInput(target, "input[type='tel']", "new");
// save
await click(target.querySelector(".o_form_button_save"));
await clickSave(target);
assert.strictEqual(
target.querySelector(".o_field_phone a").textContent,
target.querySelector("input[type='tel']").value,
"new",
"new value should be displayed properly"
);
@@ -2,6 +2,7 @@
import {
click,
clickSave,
getFixture,
nextTick,
triggerEvent,
@@ -211,8 +212,6 @@ QUnit.module("Fields", (hooks) => {
"should temporary have no empty star since we are hovering the third value"
);
// switch to edit mode and check the result
await click(target, ".o_form_button_edit");
assert.containsN(
target,
".o_field_widget .o_priority a.o_priority_star",
@@ -230,8 +229,6 @@ QUnit.module("Fields", (hooks) => {
"should still have one empty star since the value is the second value"
);
// save
await click(target, ".o_form_button_save");
assert.containsN(
target,
".o_field_widget .o_priority a.o_priority_star",
@@ -249,9 +246,6 @@ QUnit.module("Fields", (hooks) => {
"should still have one empty star since the value is the second value"
);
// switch to edit mode to check that the new value was properly written
await click(target, ".o_form_button_edit");
assert.containsN(
target,
".o_field_widget .o_priority a.o_priority_star",
@@ -292,7 +286,7 @@ QUnit.module("Fields", (hooks) => {
);
// save
await click(target, ".o_form_button_save");
await clickSave(target);
assert.containsN(
target,
".o_field_widget .o_priority a.o_priority_star",
@@ -6,11 +6,14 @@ import {
} from "@web/../tests/helpers/mock_services";
import {
click,
clickSave,
editInput,
getFixture,
nextTick
nextTick,
patchWithCleanup,
} from "@web/../tests/helpers/utils";
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
import { browser } from "@web/core/browser/browser";
import { registry } from "@web/core/registry";
let serverData;
@@ -52,6 +55,10 @@ QUnit.module("Fields", (hooks) => {
};
setupViewRegistries();
patchWithCleanup(browser, {
setTimeout: (fn) => fn(),
});
});
QUnit.module("ProgressBarField");
@@ -95,11 +102,9 @@ QUnit.module("Fields", (hooks) => {
"10 / 2",
"The initial value of the progress bar should be correct"
);
// The view should be in edit mode
await click(target.querySelector(".o_form_button_edit"));
await editInput(target, ".o_field_widget[name=display_name] input", "new name");
await click(target.querySelector(".o_form_button_save"));
await clickSave(target);
assert.strictEqual(
target.querySelector(".o_progressbar_value").textContent,
@@ -134,9 +139,6 @@ QUnit.module("Fields", (hooks) => {
},
});
// The view should be in edit mode by default
await click(target.querySelector(".o_form_button_edit"));
assert.ok(target.querySelector(".o_form_view .o_form_editable"), "Form in edit mode");
assert.strictEqual(
@@ -155,7 +157,7 @@ QUnit.module("Fields", (hooks) => {
);
await editInput(target, ".o_progressbar_value.o_input", "69");
target.querySelector(".o_progressbar_value.o_input").blur();
await click(target, ".o_form_view");
await nextTick();
assert.strictEqual(
@@ -164,7 +166,7 @@ QUnit.module("Fields", (hooks) => {
"New value should be different after focusing out of the field"
);
await click(target.querySelector(".o_form_button_save"));
await clickSave(target);
assert.strictEqual(
target.querySelector(".o_progressbar_value").textContent,
@@ -200,8 +202,6 @@ QUnit.module("Fields", (hooks) => {
}
},
});
// The view should be in edit mode by default
await click(target.querySelector(".o_form_button_edit"));
assert.ok(target.querySelector(".o_form_view .o_form_editable"), "Form in edit mode");
assert.ok(
@@ -216,7 +216,7 @@ QUnit.module("Fields", (hooks) => {
await editInput(target, ".o_progressbar_value.o_input", "69");
await click(target.querySelector(".o_form_button_save"));
await clickSave(target);
assert.strictEqual(
target.querySelector(".o_progressbar_value").textContent,
@@ -258,19 +258,15 @@ QUnit.module("Fields", (hooks) => {
"99 / 0",
"Initial value should be correct"
);
// The view should be in edit mode by default
await click(target.querySelector(".o_form_button_edit"));
assert.ok(target.querySelector(".o_form_view .o_form_editable"), "Form in edit mode");
await click(target.querySelector(".o_progress"));
const input = target.querySelector(".o_progressbar_value.o_input");
assert.strictEqual(input.value, "0.44", "Initial value in input is correct");
await editInput(target, ".o_progressbar_value.o_input", "69");
await click(target.querySelector(".o_form_button_save"));
await clickSave(target);
assert.strictEqual(
target.querySelector(".o_progressbar_value").textContent,
@@ -313,8 +309,6 @@ QUnit.module("Fields", (hooks) => {
"99 / 0",
"Initial value should be correct"
);
// The view should be in edit mode by default
await click(target.querySelector(".o_form_button_edit"));
assert.ok(target.querySelector(".o_form_view .o_form_editable"), "Form in edit mode");
@@ -330,7 +324,7 @@ QUnit.module("Fields", (hooks) => {
await nextTick();
assert.strictEqual(document.activeElement, maxVal, "Second input is focused");
maxVal.blur();
await click(target, ".o_form_view");
await nextTick();
assert.containsNone(
target,
@@ -342,7 +336,7 @@ QUnit.module("Fields", (hooks) => {
await editInput(target, ".o_progressbar input:nth-of-type(1)", "2000");
await editInput(target, ".o_progressbar input:nth-of-type(2)", "69");
await click(target.querySelector(".o_form_button_save"));
await clickSave(target);
assert.strictEqual(
target.querySelector(".o_progressbar_value").textContent,
@@ -360,7 +354,7 @@ QUnit.module("Fields", (hooks) => {
type: "form",
resModel: "partner",
arch: `
<form>
<form edit="0">
<field name="float_field" invisible="1"/>
<field name="int_field" widget="progressbar" options="{'editable': true, 'max_value': 'float_field', 'edit_max_value': true}"/>
</form>`,
@@ -370,8 +364,6 @@ QUnit.module("Fields", (hooks) => {
},
});
assert.ok(target.querySelector(".o_form_view .o_form_readonly"), "Form in readonly mode");
assert.strictEqual(
target.querySelector(".o_progressbar_value").textContent,
"99 / 0",
@@ -555,68 +547,6 @@ QUnit.module("Fields", (hooks) => {
}
);
QUnit.test(
"ProgressBar: value should update in readonly mode with right parameter when typing in input with field value",
async function (assert) {
assert.expect(7);
serverData.models.partner.records[0].int_field = 99;
await makeView({
serverData,
type: "form",
resModel: "partner",
arch: `
<form>
<field name="int_field" widget="progressbar" title="ProgressBarTitle" options="{'editable': true, 'editable_readonly': true}" />
</form>`,
resId: 1,
mockRPC(route, args) {
if (args.method === "write") {
assert.strictEqual(
args.args[1].int_field,
69,
"New value of progress bar saved"
);
}
},
});
assert.strictEqual(
target.querySelector(".o_progressbar_value").textContent,
"99%",
"Initial value should be correct"
);
assert.strictEqual(
target.querySelector(".o_progressbar_title").textContent,
"ProgressBarTitle"
);
await click(target.querySelector(".o_progress"));
assert.strictEqual(
target.querySelector(".o_progressbar_value.o_input").value,
"99",
"Initial value in input is correct"
);
assert.strictEqual(
target.querySelector(".o_progressbar_title").textContent,
"ProgressBarTitle"
);
await editInput(target, ".o_field_widget input", "69.6");
assert.strictEqual(
target.querySelector(".o_progressbar_value").textContent,
"69%",
"New value should be different than initial after changing it"
);
assert.strictEqual(
target.querySelector(".o_progressbar_title").textContent,
"ProgressBarTitle"
);
}
);
QUnit.test(
"ProgressBarField: write float instead of int works, in locale",
async function (assert) {
@@ -657,7 +587,6 @@ QUnit.module("Fields", (hooks) => {
"Initial value should be correct"
);
await click(target.querySelector(".o_form_button_edit"));
assert.ok(target.querySelector(".o_form_view .o_form_editable"), "Form in edit mode");
await click(target.querySelector(".o_progress"));
@@ -667,7 +596,7 @@ QUnit.module("Fields", (hooks) => {
await editInput(target, ".o_field_widget input", "1#037:9");
await click(target.querySelector(".o_form_button_save"));
await clickSave(target);
assert.strictEqual(
target.querySelector(".o_progressbar_value").textContent,
@@ -699,8 +628,6 @@ QUnit.module("Fields", (hooks) => {
</form>`,
resId: 1,
});
// The view should be in edit mode by default
await click(target.querySelector(".o_form_button_edit"));
assert.ok(target.querySelector(".o_form_view .o_form_editable"), "Form in edit mode");
@@ -718,8 +645,8 @@ QUnit.module("Fields", (hooks) => {
);
await editInput(target, ".o_progressbar_value.o_input", "trente sept virgule neuf");
await click(target.querySelector(".o_form_button_save"));
assert.containsOnce(target, ".o_form_button_save", "The form has not been saved");
await clickSave(target);
assert.containsOnce(target, ".o_form_dirty", "The form has not been saved");
assert.verifySteps(["Show error message"], "The error message was shown correctly");
}
);
@@ -1,6 +1,14 @@
/** @odoo-module **/
import { click, editInput, getFixture, nextTick, triggerEvent } from "@web/../tests/helpers/utils";
import {
click,
clickDiscard,
clickSave,
editInput,
getFixture,
nextTick,
triggerEvent,
} from "@web/../tests/helpers/utils";
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
let serverData;
@@ -147,8 +155,6 @@ QUnit.module("Fields", (hooks) => {
mockRPC,
});
await click(target, ".o_form_button_edit");
const field = target.querySelector(".o_field_properties");
assert.ok(field, "The field must be in the view");
@@ -190,8 +196,6 @@ QUnit.module("Fields", (hooks) => {
mockRPC,
});
await click(target, ".o_form_button_edit");
const field = target.querySelector(".o_field_properties");
assert.ok(field, "The field must be in the view");
@@ -255,12 +259,12 @@ QUnit.module("Fields", (hooks) => {
assert.notOk(inputValue.value);
// Discard the form view and check that the properties take its old values
await click(target, ".o_form_button_cancel");
await clickDiscard(target);
const propertyValue = document.querySelector(
".o_property_field:first-child .o_property_field_value"
".o_property_field:first-child .o_property_field_value input"
);
assert.strictEqual(
propertyValue.innerText,
propertyValue.value,
"char value",
"Discarding the form view should reset the old values"
);
@@ -293,8 +297,6 @@ QUnit.module("Fields", (hooks) => {
mockRPC,
});
await click(target, ".o_form_button_edit");
const field = target.querySelector(".o_field_properties");
assert.ok(field, "The field must be in the view");
@@ -352,8 +354,6 @@ QUnit.module("Fields", (hooks) => {
mockRPC,
});
await click(target, ".o_form_button_edit");
const field = target.querySelector(".o_field_properties");
assert.ok(field, "The field must be in the view");
@@ -498,8 +498,6 @@ QUnit.module("Fields", (hooks) => {
return [...labels].map((label) => label.innerText);
};
await click(target, ".o_form_button_edit");
const field = target.querySelector(".o_field_properties");
assert.ok(field, "The field must be in the view");
@@ -585,7 +583,6 @@ QUnit.module("Fields", (hooks) => {
return [...tags].map((tag) => tag.innerText);
};
await click(target, ".o_form_button_edit");
await click(target, ".o_property_field:nth-child(2) .o_field_property_open_popover");
let popover = target.querySelector(".o_property_field_popover");
// Select the tags type
@@ -718,7 +715,6 @@ QUnit.module("Fields", (hooks) => {
mockRPC,
});
await click(target, ".o_form_button_edit");
await click(target, ".o_property_field:nth-child(2) .o_field_property_open_popover");
const popover = target.querySelector(".o_property_field_popover");
// Select the many2one type
@@ -807,7 +803,6 @@ QUnit.module("Fields", (hooks) => {
return [...selectedUsers].map((badge) => badge.innerText);
};
await click(target, ".o_form_button_edit");
await click(target, ".o_property_field:nth-child(2) .o_field_property_open_popover");
const popover = target.querySelector(".o_property_field_popover");
// Select the many2many type
@@ -916,16 +911,15 @@ QUnit.module("Fields", (hooks) => {
// check initial properties
assert.equal(
target.querySelector("[property-name=property_1] .o_property_field_value").innerText,
target.querySelector("[property-name=property_1] .o_property_field_value input").value,
"01/01/2019"
);
assert.equal(
target.querySelector("[property-name=property_2] .o_property_field_value").innerText,
target.querySelector("[property-name=property_2] .o_property_field_value input").value,
"01/01/2019 11:00:00"
);
// edit date property
await click(target, ".o_form_button_edit");
await click(target, ".o_property_field[property-name=property_1] input");
await click(document.body, ".datepicker [data-day='12/31/2018']");
assert.equal(target.querySelector("[property-name=property_1] input").value, "12/31/2018");
@@ -945,7 +939,7 @@ QUnit.module("Fields", (hooks) => {
// save
assert.verifySteps([]);
await click(target, ".o_form_button_save");
await clickSave(target);
assert.verifySteps(["write", "read"]);
});
});
@@ -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;
@@ -92,7 +92,7 @@ QUnit.module("Fields", (hooks) => {
assert.containsOnce(target, "input:checked", "one of the input should be checked");
await click(target, ".o_form_button_save");
await clickSave(target);
assert.hasAttrValue(
target.querySelector("input.o_radio_input:checked"),
@@ -116,7 +116,7 @@ QUnit.module("Fields", (hooks) => {
"none of the input should be checked"
);
await click(target, ".o_form_button_save");
await clickSave(target);
assert.strictEqual(
target.querySelector(".o_notification_title").textContent,
"Invalid fields: "
@@ -195,7 +195,7 @@ QUnit.module("Fields", (hooks) => {
// click on 2nd option
await click(target.querySelectorAll("input.o_radio_input")[1]);
await click(target, ".o_form_button_save");
await clickSave(target);
assert.hasAttrValue(
target.querySelector("input.o_radio_input:checked"),
@@ -244,7 +244,7 @@ QUnit.module("Fields", (hooks) => {
});
QUnit.test("fieldradio widget with numerical keys encoded as strings", async function (assert) {
assert.expect(7);
assert.expect(5);
serverData.models.partner.fields.selection = {
type: "selection",
@@ -273,25 +273,13 @@ QUnit.module("Fields", (hooks) => {
);
assert.containsNone(target, ".o_radio_input:checked", "no value should be checked");
await click(target, ".o_form_button_edit");
assert.containsNone(target, ".o_radio_input:checked", "no value should be checked");
await click(target.querySelectorAll("input.o_radio_input")[1]);
await click(target, ".o_form_button_save");
await clickSave(target);
assert.strictEqual(
target.querySelector(".o_field_widget").textContent.replace(/\s+/g, ""),
"RedBlack"
);
assert.containsOnce(
target,
".o_radio_input[data-index='1']:checked",
"'Black' should be checked"
);
await click(target, ".o_form_button_edit");
assert.containsOnce(
target,
@@ -338,7 +326,6 @@ QUnit.module("Fields", (hooks) => {
},
});
await click(target, ".o_form_button_edit");
assert.containsN(
target,
".o_field_widget[name='trululu'] .o_radio_item",
@@ -363,7 +350,7 @@ QUnit.module("Fields", (hooks) => {
resId: 2,
serverData,
arch: `
<form>
<form edit="0">
<field name="trululu" widget="radio" />
</form>`,
});

Some files were not shown because too many files have changed in this diff Show More