[FIX] web: Many2One in dialog: open related record in dialog

The external button of the Many2one field has been changed with the
"always edit" feature of the form view, s.t. it displays by default
another icon (a right arrow) and when clicked, opens the related
record in another plain screen action instead of a FormViewDialog.

However, this doesn't work for Many2One fields that are already in
dialogs, because
 1) if it's an action dialog (target="new"), that dialog will be
    closed when opening the related record and the user loses its
    working context
 2) if it's another type of dialog (e.g. FormViewDialog), the
    related record opens in the background and the dialog remains
    open, which is obviously a bad UX experiment. This had been
    locally fixes at some places [2].

This commit fixes the issue by automatically opening the related
record in a FormViewDialog if the Many2One is itself already in a
dialog.

This commit also fixes an issue with the scenario where the related
record opens in a dialog: if there were changes done in the main
record, those changes where lost when the user clicked on "Save"
in the dialog of the related record. We now only reload the
display_name of the related record, and apply it to the model.

[2] https://github.com/odoo/enterprise/commit/ba0e95fe42696dcf44b8feddeb302f04876fcbdc

Task 3191319

closes odoo/odoo#112959

Related: odoo/enterprise#37213
Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
Aaron Bohy
2023-02-27 09:37:18 +01:00
parent c7fbae7a34
commit 211ad04773
11 changed files with 451 additions and 239 deletions
@@ -507,6 +507,31 @@ export class Record extends DataPoint {
for (const [fieldName, value] of Object.entries(changes)) {
const fieldType = this.fields[fieldName].type;
data[fieldName] = mapWowlValueToLegacy(value, fieldType);
// special case for many2ones: they can be updated with a new name (e.g. if edited from
// the dialog), but in the basic_model it worked differently, we had a datapoint for the
// many2one value and we reloaded it directly. In the new model, we directly update the
// value [id, display_name], so we reload beforehand, in the many2one field itself. In
// the next few lines, we thus manually apply the renaming on the legacy datapoint.
if (this.fields[fieldName].type === "many2one" && Array.isArray(changes[fieldName])) {
const newName = changes[fieldName][1];
if (newName || newName === "") {
const bm = this.model.__bm__;
const m2oDatapointId = bm.get(this.__bm_handle__).data[fieldName].id;
const m2oDatapoint = bm.localData[m2oDatapointId];
if (m2oDatapoint && m2oDatapoint.data.id === changes[fieldName][0]) {
m2oDatapoint.data.display_name = newName;
}
}
}
// same for reference fields
if (this.fields[fieldName].type === "reference" && changes[fieldName].displayName) {
const bm = this.model.__bm__;
const m2oDatapointId = bm.get(this.__bm_handle__).data[fieldName].id;
const m2oDatapoint = bm.localData[m2oDatapointId];
if (m2oDatapoint) {
m2oDatapoint.data.display_name = changes[fieldName].displayName;
}
}
}
if (this._urgentSave) {
const fieldNames = await this.model.__bm__.notifyChanges(this.__bm_handle__, data, {
@@ -58,11 +58,6 @@ export class Many2OneField extends Component {
string: { type: String, optional: true },
canScanBarcode: { type: Boolean, optional: true },
update: { type: Function, optional: true },
openTarget: {
type: String,
validate: (v) => ["current", "new"].includes(v),
optional: true,
},
};
static defaultProps = {
canOpen: true,
@@ -74,7 +69,6 @@ export class Many2OneField extends Component {
searchLimit: 7,
string: "",
canScanBarcode: false,
openTarget: "current",
};
static SEARCH_MORE_LIMIT = 320;
@@ -101,8 +95,11 @@ export class Many2OneField extends Component {
activeActions: this.state.activeActions,
isToMany: false,
onRecordSaved: async (record) => {
await this.props.record.load();
await this.updateRecord(m2oTupleFromData(record.data));
const resId = this.props.value[0];
const fields = ["display_name"];
const context = this.props.record.getFieldContext(this.props.name);
const records = await this.orm.read(this.relation, [resId], fields, { context });
await this.updateRecord(m2oTupleFromData(records[0]));
if (this.props.record.model.root.id !== this.props.record.id) {
this.props.record.switchMode("readonly");
}
@@ -237,10 +234,10 @@ export class Many2OneField extends Component {
}
}
onExternalBtnClick() {
if (this.props.openTarget === "current") {
this.openAction();
} else {
if (this.env.inDialog) {
this.openDialog(this.resId);
} else {
this.openAction();
}
}
async onBarcodeBtnClick() {
@@ -309,7 +306,6 @@ export const many2OneField = {
canCreateEdit: canCreate && !options.no_create_edit,
nameCreateField: options.create_name_field,
canScanBarcode: !!options.can_scan_barcode,
openTarget: attrs.open_target,
string,
};
},
@@ -46,7 +46,7 @@
<button
type="button"
class="btn btn-secondary fa o_external_button"
t-att-class="props.openTarget === 'current' ? 'fa-arrow-right' : 'fa-external-link'"
t-att-class="env.inDialog ? 'fa-external-link' : 'fa-arrow-right'"
tabindex="-1"
draggable="false"
aria-label="Internal link"
@@ -33,7 +33,7 @@ import {
toggleMenuItem,
validateSearch,
} from "@web/../tests/search/helpers";
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
import { makeView, makeViewInDialog, setupViewRegistries } from "@web/../tests/views/helpers";
import { createWebClient, doAction } from "@web/../tests/webclient/helpers";
import { browser } from "@web/core/browser/browser";
import { errorService } from "@web/core/errors/error_service";
@@ -247,7 +247,7 @@ QUnit.module("Fields", (hooks) => {
</form>`,
};
await makeView({
await makeViewInDialog({
type: "form",
resModel: "partner",
resId: 1,
@@ -256,7 +256,7 @@ QUnit.module("Fields", (hooks) => {
<form>
<sheet>
<group>
<field name="trululu" string="custom label" open_target="new" />
<field name="trululu" string="custom label"/>
</group>
</sheet>
</form>`,
@@ -283,7 +283,9 @@ QUnit.module("Fields", (hooks) => {
await click(target, ".o_external_button");
assert.strictEqual(
target.querySelector(".modal .modal-title").textContent.trim(),
target
.querySelector(".o_dialog:not(.o_inactive_modal) .modal-title")
.textContent.trim(),
"Open: custom label",
"dialog title should display the custom string label"
);
@@ -293,7 +295,7 @@ QUnit.module("Fields", (hooks) => {
});
QUnit.test("editing a many2one, but not changing anything", async function (assert) {
assert.expect(2);
assert.expect(1);
serverData.views = {
"partner,false,form": `
@@ -302,16 +304,15 @@ QUnit.module("Fields", (hooks) => {
</form>`,
};
await makeView({
await makeViewInDialog({
type: "form",
resModel: "partner",
resId: 1,
resIds: [1, 2],
serverData,
arch: `
<form>
<sheet>
<field name="trululu" open_target="new" />
<field name="trululu"/>
</sheet>
</form>`,
mockRPC(route, { args, method }) {
@@ -319,25 +320,18 @@ QUnit.module("Fields", (hooks) => {
assert.deepEqual(args[0], [4], "should call get_formview_id with correct id");
return Promise.resolve(false);
}
if (method === "write") {
throw new Error("should not call write");
}
},
});
// click on the external button (should do an RPC)
await click(target, ".o_external_button");
// save and close modal
await clickSave(target.querySelector(".modal"));
await clickSave(target.querySelectorAll(".modal")[1]);
// save form
await clickSave(target);
// click next on pager
await click(target, ".o_pager .o_pager_next");
// this checks that the view did not ask for confirmation that the
// record is dirty
assert.strictEqual(
target.querySelector(".o_pager").textContent.trim(),
"2 / 2",
"pager should be at second page"
);
});
QUnit.test("context in many2one and default get", async function (assert) {
@@ -378,46 +372,42 @@ QUnit.module("Fields", (hooks) => {
</form>`,
};
await makeView({
await makeViewInDialog({
type: "form",
resModel: "partner",
resId: 1,
resIds: [1, 2],
serverData,
arch: `
<form>
<sheet>
<field name="trululu" open_target="new" />
<field name="trululu"/>
</sheet>
</form>`,
mockRPC(route, { method }) {
if (method === "get_formview_id") {
return Promise.resolve(false);
}
if (method === "write") {
assert.step("write");
}
},
});
// click on the external button (should do an RPC)
await click(target, ".o_external_button");
const input = target.querySelector(".modal .o_field_widget[name='foo'] input");
const input = target.querySelector(
".o_dialog:not(.o_inactive_modal) .o_field_widget[name='foo'] input"
);
input.value = "brandon";
await triggerEvent(input, null, "change");
// save and close modal
await clickSave(target.querySelector(".modal"));
await clickSave(target.querySelectorAll(".modal")[1]);
assert.verifySteps(["write"]);
// save form
await clickSave(target);
// click next on pager
await click(target, ".o_pager .o_pager_next");
// this checks that the view did not ask for confirmation that the
// record is dirty
assert.strictEqual(
target.querySelector(".o_pager").textContent.trim(),
"2 / 2",
"pager should be at second page"
);
assert.verifySteps([]);
}
);
@@ -799,7 +789,7 @@ QUnit.module("Fields", (hooks) => {
</form>`,
};
await makeView({
await makeViewInDialog({
type: "form",
resModel: "partner",
resId: 1,
@@ -808,7 +798,7 @@ QUnit.module("Fields", (hooks) => {
<form>
<sheet>
<group>
<field name="user_id" open_target="new" />
<field name="user_id"/>
</group>
</sheet>
</form>`,
@@ -831,12 +821,12 @@ QUnit.module("Fields", (hooks) => {
await click(target, ".o_external_button");
await editInput(
target,
".modal-body .o_field_widget[name='other_field'] input",
".o_dialog:not(.o_inactive_modal) .o_field_widget[name='other_field'] input",
"wood"
);
// save the modal and make sure an onchange is triggered
await clickSave(target.querySelector(".modal"));
await clickSave(target.querySelectorAll(".modal")[1]);
assert.verifySteps([
"get_views",
"read",
@@ -2570,7 +2560,7 @@ QUnit.module("Fields", (hooks) => {
};
serviceRegistry.add("action", fakeActionService, { force: true });
await makeView({
await makeViewInDialog({
type: "form",
resModel: "partner",
resId: 1,
@@ -2579,7 +2569,7 @@ QUnit.module("Fields", (hooks) => {
<form>
<field name="p">
<tree editable="bottom">
<field name="product_id" open_target="new" />
<field name="product_id"/>
</tree>
</field>
</form>`,
@@ -2590,11 +2580,14 @@ QUnit.module("Fields", (hooks) => {
},
});
assert.containsOnce(target, ".modal");
await click(target.querySelector("td.o_data_cell"));
await click(target, ".o_external_button");
assert.containsOnce(target, ".modal");
assert.containsN(target, ".modal", 2);
let buttons = target.querySelectorAll(".modal .o_form_statusbar button");
let buttons = target.querySelectorAll(
".o_dialog:not(.o_inactive_modal) .o_form_statusbar button"
);
await click(buttons[0]);
assert.verifySteps(["action"]);
@@ -2605,13 +2598,15 @@ QUnit.module("Fields", (hooks) => {
def.resolve();
await nextTick();
await clickDiscard(target.querySelector(".modal"));
assert.containsNone(target, ".modal");
await click(target, ".o_external_button");
await clickDiscard(target.querySelectorAll(".modal")[1]);
assert.containsOnce(target, ".modal");
buttons = target.querySelectorAll(".modal .o_form_statusbar button");
await click(target, ".o_external_button");
assert.containsN(target, ".modal", 2);
buttons = target.querySelectorAll(
".o_dialog:not(.o_inactive_modal) .o_form_statusbar button"
);
await click(buttons[1]);
assert.verifySteps(["object"]);
@@ -2667,7 +2662,7 @@ QUnit.module("Fields", (hooks) => {
</tree>`,
};
await makeView({
await makeViewInDialog({
type: "form",
resModel: "product",
resId: 37,
@@ -2677,7 +2672,7 @@ QUnit.module("Fields", (hooks) => {
<field name="name" />
<field name="turtle_ids" widget="one2many">
<tree editable="bottom">
<field name="type_id" open_target="new" />
<field name="type_id"/>
</tree>
</field>
</form>`,
@@ -2702,25 +2697,27 @@ QUnit.module("Fields", (hooks) => {
},
});
assert.containsOnce(target, ".modal");
await click(target, ".o_data_cell");
await click(target, ".o_external_button");
assert.containsOnce(target, ".modal", "There should be 1 modal opened");
assert.containsN(target, ".modal", 2);
assert.containsN(
target,
".modal .ui-sortable-handle",
target.querySelectorAll(".modal")[1],
".ui-sortable-handle",
2,
"There should be 2 sequence handlers"
);
await dragAndDrop(
".modal .o_data_row:nth-child(2) .ui-sortable-handle",
".modal tbody tr",
".o_dialog:not(.o_inactive_modal) .o_data_row:nth-child(2) .ui-sortable-handle",
".o_dialog:not(.o_inactive_modal) tbody tr",
"top"
);
// Saving the modal and then the original model
await clickSave(target.querySelector(".modal"));
await clickSave(target.querySelectorAll(".modal")[1]);
await clickSave(target);
assert.verifySteps(["onchange sequence", "partner_type write"]);
@@ -3174,14 +3171,14 @@ QUnit.module("Fields", (hooks) => {
</form>`,
};
await makeView({
await makeViewInDialog({
type: "form",
resModel: "partner",
serverData,
arch: `
<form>
<sheet>
<field name="product_id" can_create="false" can_write="false" open_target="new" />
<field name="product_id" can_create="false" can_write="false"/>
</sheet>
</form>`,
mockRPC(route) {
@@ -3191,6 +3188,8 @@ QUnit.module("Fields", (hooks) => {
},
});
assert.containsOnce(target, ".modal");
await click(target, ".o_field_many2one input");
assert.containsNone(
target,
@@ -3211,17 +3210,18 @@ QUnit.module("Fields", (hooks) => {
);
await click(target, ".o_field_many2one .o_external_button");
assert.containsN(target, ".modal", 2);
assert.containsOnce(
target,
".modal .o_form_view .o_form_readonly",
"there should be a readonly form view opened"
);
await click(target, ".modal .modal-footer .btn-primary");
await click(target, ".o_dialog:not(.o_inactive_modal) .modal-footer .btn-primary");
await editInput(target, ".o_field_many2one input", "new product");
await triggerEvent(target, ".o_field_many2one input", "blur");
assert.containsNone(target, ".modal", "should not display the create modal");
assert.containsOnce(target, ".modal");
});
QUnit.test("create_name_field option on a many2one", async function (assert) {
@@ -3980,7 +3980,7 @@ QUnit.module("Fields", (hooks) => {
});
QUnit.test("updating a many2one from a many2many", async function (assert) {
assert.expect(4);
assert.expect(5);
serverData.models.turtle.records[1].turtle_trululu = 1;
serverData.views = {
@@ -3990,7 +3990,7 @@ QUnit.module("Fields", (hooks) => {
</form>`,
};
await makeView({
await makeViewInDialog({
type: "form",
resModel: "partner",
resId: 1,
@@ -4000,7 +4000,7 @@ QUnit.module("Fields", (hooks) => {
<field name="turtles">
<tree editable="bottom">
<field name="display_name" />
<field name="turtle_trululu" open_target="new" />
<field name="turtle_trululu"/>
</tree>
</field>
</form>`,
@@ -4011,19 +4011,24 @@ QUnit.module("Fields", (hooks) => {
}
},
});
assert.containsOnce(target, ".modal");
// Opening the modal
await click(target.querySelectorAll(".o_data_row td")[1]);
await click(target, ".o_external_button");
assert.containsOnce(target, ".modal");
assert.containsN(target, ".modal", 2);
// Changing the 'trululu' value
await editInput(target, ".modal div[name=display_name] input", "test");
await clickSave(target.querySelector(".modal"));
await editInput(
target,
".o_dialog:not(.o_inactive_modal) div[name=display_name] input",
"test"
);
await clickSave(target.querySelectorAll(".modal")[1]);
assert.containsOnce(target, ".modal");
// Test whether the value has changed
assert.containsNone(target, ".modal");
assert.strictEqual(
target.querySelectorAll(".o_data_cell")[1].textContent,
"test",
@@ -4177,15 +4182,15 @@ 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" open_target="new" /></form>',
"partner,false,form": '<form><field name="trululu"/></form>',
};
await makeView({
await makeViewInDialog({
type: "form",
serverData,
resModel: "partner",
resId: 2,
arch: '<form><field name="trululu" open_target="new" /></form>',
arch: '<form><field name="trululu"/></form>',
mockRPC(route, { method }) {
if (method === "get_formview_id") {
return Promise.resolve(false);
@@ -4193,31 +4198,34 @@ QUnit.module("Fields", (hooks) => {
},
});
assert.containsOnce(target, ".modal");
assert.hasClass(document.body, "modal-open");
// Open many2one modal
await click(target, ".o_external_button");
const originalModal = target.querySelector(".modal");
const originalModal = target.querySelectorAll(".modal")[1];
assert.containsOnce(target, ".modal");
assert.containsN(target, ".modal", 2);
assert.doesNotHaveClass(originalModal, "o_inactive_modal");
assert.hasClass(document.body, "modal-open");
// Open many2one modal of field in many2one modal
await click(originalModal, ".o_external_button");
const nextModal = target.querySelectorAll(".modal")[1];
const nextModal = target.querySelectorAll(".modal")[2];
assert.containsN(target, ".modal", 2);
assert.containsN(target, ".modal", 3);
assert.doesNotHaveClass(nextModal, "o_inactive_modal");
assert.hasClass(document.body, "modal-open");
// Close second modal
await click(nextModal, "button[class='btn-close']");
assert.containsOnce(target, ".modal");
assert.containsN(target, ".modal", 2);
assert.strictEqual(
originalModal,
target.querySelector(".modal"),
target.querySelectorAll(".modal")[1],
"First modal is still opened"
);
assert.doesNotHaveClass(originalModal, "o_inactive_modal");
@@ -4225,8 +4233,7 @@ QUnit.module("Fields", (hooks) => {
// Close first modal
await click(originalModal, "button[class='btn-close']");
assert.containsNone(target, ".modal");
assert.doesNotHaveClass(document.body, "modal-open");
assert.containsOnce(target, ".modal");
});
QUnit.test("search more pager is reset when doing a new search", async function (assert) {
@@ -4367,9 +4374,9 @@ QUnit.module("Fields", (hooks) => {
);
});
QUnit.test("open_target prop = current", async function (assert) {
QUnit.test("external_button performs a doAction by default", async function (assert) {
serverData.views = {
"partner,false,form": '<form><field name="trululu" open_target="current"/></form>',
"partner,false,form": '<form><field name="trululu"/></form>',
"partner,false,search": "<search></search>",
};
serverData.actions = {
@@ -4408,12 +4415,12 @@ QUnit.module("Fields", (hooks) => {
assert.strictEqual(target.querySelector(".breadcrumb").textContent, "first recordNew");
});
QUnit.test("open_target prop = new", async function (assert) {
await makeView({
QUnit.test("external_button opens a FormViewDialog in dialogs", async function (assert) {
await makeViewInDialog({
type: "form",
resModel: "partner",
serverData,
arch: '<form><field name="trululu" open_target="new" /></form>',
arch: '<form><field name="trululu"/></form>',
mockRPC(route, { method }) {
if (method === "get_formview_id") {
assert.step("get_formview_id");
@@ -4421,12 +4428,115 @@ QUnit.module("Fields", (hooks) => {
}
},
});
assert.containsOnce(target, ".modal");
await selectDropdownItem(target, "trululu", "first record");
assert.containsOnce(target, ".o_field_widget .o_external_button.fa-external-link");
await click(target, ".o_field_widget .o_external_button");
assert.verifySteps(["get_formview_id"]);
assert.containsN(target, ".modal", 2);
});
QUnit.test("external_button opens a FormViewDialog in dialogs", async function (assert) {
await makeViewInDialog({
type: "form",
resModel: "partner",
serverData,
arch: '<form><field name="trululu"/></form>',
mockRPC(route, { method }) {
if (method === "get_formview_id") {
assert.step("get_formview_id");
return false;
}
},
});
assert.containsOnce(target, ".modal");
await selectDropdownItem(target, "trululu", "first record");
assert.containsOnce(target, ".o_field_widget .o_external_button.fa-external-link");
await click(target, ".o_field_widget .o_external_button");
assert.verifySteps(["get_formview_id"]);
assert.containsN(target, ".modal", 2);
});
QUnit.test("keep changes when editing related record in a dialog", async function (assert) {
serverData.views = {
"partner,98,form": '<form><field name="int_field"/></form>',
};
await makeViewInDialog({
type: "form",
resModel: "partner",
serverData,
arch: '<form><field name="foo"/><field name="trululu"/></form>',
mockRPC(route, { method }) {
if (method === "get_formview_id") {
return 98;
}
if (method === "write") {
assert.step("write");
}
},
});
assert.containsOnce(target, ".modal");
await editInput(target, ".o_field_widget[name=foo] input", "some value");
await selectDropdownItem(target, "trululu", "first record");
assert.containsOnce(target, ".o_field_widget .o_external_button.fa-external-link");
await click(target, ".o_field_widget .o_external_button");
assert.containsN(target, ".modal", 2);
const secondDialog = target.querySelector(".o_dialog:not(.o_inactive_modal)");
await editInput(secondDialog, ".o_field_widget[name=int_field] input", "5464");
await click(secondDialog.querySelector(".modal-footer .btn-primary:not(.d-none)"));
assert.containsOnce(target, ".modal");
assert.strictEqual(
target.querySelector(".o_field_widget[name=foo] input").value,
"some value"
);
assert.verifySteps(["write"]);
});
QUnit.test("create and edit, save and then discard", async function (assert) {
serverData.views = {
"partner,98,form": '<form><field name="name"/></form>',
};
await makeView({
type: "form",
resModel: "partner",
serverData,
arch: '<form><field name="trululu"/></form>',
resId: 1,
mockRPC(route, { method }) {
if (method === "get_formview_id") {
return 98;
}
},
});
assert.strictEqual(
target.querySelector(".o_field_widget[name=trululu] input").value,
"aaa"
);
await editInput(target, ".o_field_widget[name=trululu] input", "new m2o");
await click(target.querySelector(".o_field_widget[name=trululu] input"));
await selectDropdownItem(target, "trululu", "Create and edit...");
assert.containsOnce(target, ".modal");
await click(target.querySelector(".modal-footer .btn-primary:not(.d-none)"));
assert.containsNone(target, ".modal");
assert.strictEqual(
target.querySelector(".o_field_widget[name=trululu] input").value,
"new m2o"
);
await clickDiscard(target);
assert.strictEqual(
target.querySelector(".o_field_widget[name=trululu] input").value,
"aaa"
);
});
});
@@ -26,7 +26,7 @@ import BasicModel from "web.BasicModel";
import { browser } from "@web/core/browser/browser";
import { createWebClient, doAction } from "@web/../tests/webclient/helpers";
import { getNextTabableElement } from "@web/core/utils/ui";
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
import { makeView, makeViewInDialog, setupViewRegistries } from "@web/../tests/views/helpers";
import { registerCleanup } from "@web/../tests/helpers/cleanup";
import { registry } from "@web/core/registry";
import { session } from "@web/session";
@@ -6166,7 +6166,7 @@ QUnit.module("Fields", (hooks) => {
'<tree editable="bottom"><field name="display_name"/></tree>',
"partner_type,false,search": "<search></search>",
};
await makeView({
await makeViewInDialog({
type: "form",
resModel: "partner",
serverData,
@@ -6174,7 +6174,7 @@ QUnit.module("Fields", (hooks) => {
<form>
<field name="turtles">
<tree editable="bottom">
<field name="turtle_trululu" open_target="new"/>
<field name="turtle_trululu"/>
</tree>
</field>
</form>`,
@@ -6199,13 +6199,13 @@ QUnit.module("Fields", (hooks) => {
await click(target.querySelector(".o_external_button"));
// click on add, to add a new partner in the m2m
await addRow(target, ".modal");
await addRow(target.querySelectorAll(".modal")[1]);
// select the partner_type 'gold' (this closes the 2nd modal)
await click(target.querySelector("div:not(o_inactive_modal) .modal td.o_data_cell")); // select gold
// select the partner_type 'gold' (this closes the 3rd modal)
await click(target.querySelector(".o_dialog:not(.o_inactive_modal) td.o_data_cell")); // select gold
// confirm the changes in the modal
await clickSave(target.querySelector(".modal"));
await clickSave(target.querySelectorAll(".modal")[1]);
await clickSave(target);
});
@@ -13,7 +13,7 @@ import {
triggerHotkey,
nextTick,
} from "@web/../tests/helpers/utils";
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
import { makeView, makeViewInDialog, setupViewRegistries } from "@web/../tests/views/helpers";
let target;
let serverData;
@@ -399,7 +399,7 @@ QUnit.module("Fields", (hooks) => {
},
});
await makeView({
await makeViewInDialog({
type: "form",
resModel: "partner",
resId: 1,
@@ -408,7 +408,7 @@ QUnit.module("Fields", (hooks) => {
<form>
<sheet>
<group>
<field name="reference" string="custom label" open_target="new" />
<field name="reference" string="custom label"/>
</group>
</sheet>
</form>`,
@@ -479,11 +479,13 @@ QUnit.module("Fields", (hooks) => {
await click(target, ".o_external_button");
assert.strictEqual(
target.querySelector(".modal .modal-title").textContent.trim(),
target
.querySelector(".o_dialog:not(.o_inactive_modal) .modal-title")
.textContent.trim(),
"Open: custom label",
"dialog title should display the custom string label"
);
await click(target, ".modal .o_form_button_cancel");
await click(target, ".o_dialog:not(.o_inactive_modal) .o_form_button_cancel");
await editSelect(target, ".o_field_widget select", "partner_type");
assert.strictEqual(
@@ -503,41 +505,44 @@ QUnit.module("Fields", (hooks) => {
);
});
QUnit.test("computed reference field changed by onchange to 'False,0' value", async function (assert) {
assert.expect(1);
QUnit.test(
"computed reference field changed by onchange to 'False,0' value",
async function (assert) {
assert.expect(1);
serverData.models.partner.onchanges = {
bar(obj) {
if (!obj.bar) {
obj.reference_char = "False,0";
}
},
};
await makeView({
type: "form",
resModel: "partner",
serverData,
arch: `
serverData.models.partner.onchanges = {
bar(obj) {
if (!obj.bar) {
obj.reference_char = "False,0";
}
},
};
await makeView({
type: "form",
resModel: "partner",
serverData,
arch: `
<form>
<field name="bar"/>
<field name="reference_char" widget="reference"/>
</form>`,
mockRPC(route, { args, method }) {
if (method === "create") {
assert.deepEqual(args[0], {
bar: false,
reference_char: "False,0",
});
}
},
});
mockRPC(route, { args, method }) {
if (method === "create") {
assert.deepEqual(args[0], {
bar: false,
reference_char: "False,0",
});
}
},
});
// trigger the onchange to set a value for the reference field
await click(target, ".o_field_boolean input");
// trigger the onchange to set a value for the reference field
await click(target, ".o_field_boolean input");
// save
await clickSave(target);
});
// save
await clickSave(target);
}
);
QUnit.test("interact with reference field changed by onchange", async function (assert) {
assert.expect(2);
@@ -21,7 +21,7 @@ import {
triggerHotkey,
} from "@web/../tests/helpers/utils";
import { toggleActionMenu, toggleGroupByMenu, toggleMenuItem } from "@web/../tests/search/helpers";
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
import { makeView, makeViewInDialog, setupViewRegistries } from "@web/../tests/views/helpers";
import { createWebClient, doAction } from "@web/../tests/webclient/helpers";
import { browser } from "@web/core/browser/browser";
import { registry } from "@web/core/registry";
@@ -3705,7 +3705,7 @@ QUnit.module("Views", (hooks) => {
"partner,false,form": '<form><field name="display_name"/></form>',
};
await makeView({
await makeViewInDialog({
type: "form",
resModel: "user",
serverData,
@@ -3715,7 +3715,7 @@ QUnit.module("Views", (hooks) => {
<field name="partner_ids">
<tree editable="bottom">
<field name="display_name"/>
<field name="reference" open_target="new"/>
<field name="reference"/>
</tree>
</field>
</form>`,
@@ -3735,10 +3735,12 @@ QUnit.module("Views", (hooks) => {
// edit the record in the modal
await editInput(
target,
'.modal-body .o_field_widget[name="display_name"] input',
'.o_dialog:not(.o_inactive_modal) .o_field_widget[name="display_name"] input',
"New name"
);
await click(target.querySelector(".modal-dialog footer .o_form_button_save"));
await click(
target.querySelector(".o_dialog:not(.o_inactive_modal) footer .o_form_button_save")
);
assert.containsOnce(
target,
@@ -7819,7 +7821,7 @@ QUnit.module("Views", (hooks) => {
"partner,false,form": '<form><field name="trululu"/></form>',
};
await makeView({
await makeViewInDialog({
type: "form",
serverData,
resModel: "partner",
@@ -7833,7 +7835,7 @@ QUnit.module("Views", (hooks) => {
<field name="p"/>
<field name="timmy"/>
<field name="product_ids"/>
<field name="trululu" open_target="new"/>
<field name="trululu"/>
</group></sheet>
</form>`,
mockRPC(route, args) {
@@ -7846,6 +7848,7 @@ QUnit.module("Views", (hooks) => {
// we make the content height very small so we can test scrolling.
$(".o_content").css({ overflow: "auto", "max-height": "300px" });
assert.containsOnce(target, ".modal-dialog");
// Open many2one modal, lastActivatedFieldIndex will not set as we directly click on external button
assert.strictEqual($(".o_content").scrollTop(), 0, "scroll position should be 0");
// simply triggerEvent focus doesn't do the trick (doesn't scroll).
@@ -7855,15 +7858,14 @@ QUnit.module("Views", (hooks) => {
await click(target.querySelector(".o_external_button"));
// Close modal
await click(target, '.modal-dialog button[class="btn-close"]');
await click(target, '.o_dialog:not(.o_inactive_modal) button[class="btn-close"]');
assert.notStrictEqual(
$(".o_content").scrollTop(),
0,
"scroll position should not be 0 after closing modal"
);
assert.containsNone(target, ".modal-dialog", "There should be no modal");
assert.doesNotHaveClass(target, "modal-open", "Modal is not said opened");
assert.containsOnce(target, ".modal-dialog");
});
QUnit.test("correct amount of buttons", async function (assert) {
@@ -8205,12 +8207,12 @@ QUnit.module("Views", (hooks) => {
"product,false,list": '<tree><field name="display_name"/></tree>',
};
await makeView({
await makeViewInDialog({
type: "form",
resModel: "partner",
resId: 1,
serverData,
arch: `<form><field name="product_id" open_target="new"/></form>`,
arch: `<form><field name="product_id"/></form>`,
mockRPC(route, args) {
if (route === "/web/dataset/call_kw/product/get_formview_id") {
return false;
@@ -8218,22 +8220,15 @@ QUnit.module("Views", (hooks) => {
},
});
await click(target.querySelector(".o_external_button"));
assert.containsOnce(target, ".modal");
assert.containsOnce(
target,
".o_control_panel",
"control panel is present in the main form view"
);
assert.containsNone(
target,
".modal .o_control_panel",
"control panel is not present in dialogs"
);
assert.containsNone(target, ".o_control_panel");
await click(target.querySelector(".o_external_button"));
assert.containsN(target, ".modal", 2);
assert.containsNone(target, ".o_control_panel");
});
QUnit.test("check interactions between multiple FormViewDialogs", async function (assert) {
assert.expect(8);
assert.expect(9);
serverData.models.product.fields.product_ids = {
string: "one2many product",
@@ -8251,12 +8246,12 @@ QUnit.module("Views", (hooks) => {
"product,false,list": '<tree><field name="display_name"/></tree>',
};
await makeView({
await makeViewInDialog({
type: "form",
resModel: "partner",
resId: 1,
serverData,
arch: `<form><field name="product_id" open_target="new"/></form>`,
arch: `<form><field name="product_id"/></form>`,
mockRPC(route, args) {
if (route === "/web/dataset/call_kw/product/get_formview_id") {
return false;
@@ -8267,33 +8262,46 @@ QUnit.module("Views", (hooks) => {
},
});
assert.containsOnce(target, ".modal");
// Open first dialog
await click(target.querySelector(".o_external_button"));
assert.containsOnce(target, ".modal");
assert.containsN(target, ".modal", 2);
assert.strictEqual(
target.querySelector(".modal .modal-title").textContent.trim(),
target
.querySelector(".o_dialog:not(.o_inactive_modal) .modal-title")
.textContent.trim(),
"Open: Product"
);
assert.strictEqual(
target.querySelector(".modal .o_field_widget[name=display_name] input").value,
target.querySelector(
".o_dialog:not(.o_inactive_modal) .o_field_widget[name=display_name] input"
).value,
"xphone"
);
// Open second dialog
await click(target.querySelector(".modal .o_field_x2many_list_row_add a"));
assert.containsN(target, ".modal", 2);
// Open third dialog
await click(
target.querySelector(".o_dialog:not(.o_inactive_modal) .o_field_x2many_list_row_add a")
);
assert.containsN(target, ".modal", 3);
// Add new value
const secondModal = target.querySelectorAll(".modal")[1];
await editInput(secondModal, ".o_field_widget[name=display_name] input", "xtv");
await click(secondModal.querySelector(".modal-footer .btn-primary"));
assert.containsOnce(target, ".modal");
const thirdModal = target.querySelectorAll(".modal")[2];
await editInput(thirdModal, ".o_field_widget[name=display_name] input", "xtv");
await click(thirdModal.querySelector(".modal-footer .btn-primary"));
assert.containsN(target, ".modal", 2);
// Check that data in first dialog is correctly updated
assert.strictEqual(
target.querySelector(".modal .o_data_row .o_data_cell").textContent,
target.querySelector(".o_dialog:not(.o_inactive_modal) .o_data_row .o_data_cell")
.textContent,
"xtv"
);
await click(target.querySelector(".modal .modal-footer .btn-primary:not(.d-none"));
await click(
target.querySelector(
".o_dialog:not(.o_inactive_modal) .modal-footer .btn-primary:not(.d-none"
)
);
});
QUnit.test("fields and record contexts are not mixed", async function (assert) {
@@ -9217,7 +9225,7 @@ QUnit.module("Views", (hooks) => {
QUnit.test(
"translate event correctly handled with multiple controllers",
async function (assert) {
assert.expect(2);
assert.expect(3);
serverData.models.product.fields.name.translate = true;
serverData.models.partner.records[0].product_id = 37;
@@ -9233,13 +9241,13 @@ QUnit.module("Views", (hooks) => {
<sheet>
<group>
<field name="name"/>
<field name="partner_type_id" open_target="new"/>
<field name="partner_type_id"/>
</group>
</sheet>
</form>`,
};
await makeView({
await makeViewInDialog({
type: "form",
serverData,
resModel: "partner",
@@ -9248,7 +9256,7 @@ QUnit.module("Views", (hooks) => {
<form>
<sheet>
<group>
<field name="product_id" open_target="new"/>
<field name="product_id"/>
</group>
</sheet>
</form>`,
@@ -9276,14 +9284,18 @@ QUnit.module("Views", (hooks) => {
},
});
assert.containsOnce(target, ".o_dialog");
await click(target.querySelector('[name="product_id"] .o_external_button'));
assert.containsOnce(
target.querySelector(".modal-body"),
target.querySelector(".o_dialog:not(.o_inactive_modal)"),
"span.o_field_translate",
"there should be a translate button in the modal"
);
await click(target.querySelector(".modal-body span.o_field_translate"));
await click(
target.querySelector(".o_dialog:not(.o_inactive_modal) span.o_field_translate")
);
assert.strictEqual(nbTranslateCalls, 1, "should call_button translate once");
}
);
@@ -9642,11 +9654,11 @@ QUnit.module("Views", (hooks) => {
</sheet>
</form>`,
};
await makeView({
await makeViewInDialog({
type: "form",
resModel: "partner",
serverData,
arch: `<form><field name="trululu" open_target="new"/></form>`,
arch: `<form><field name="trululu"/></form>`,
resId: 1,
mockRPC(route, args) {
if (args.method === "get_formview_id") {
@@ -10072,17 +10084,17 @@ QUnit.module("Views", (hooks) => {
});
QUnit.test("reload event is handled only once", async function (assert) {
// In this test, several form controllers are nested (two of them are
// In this test, several form controllers are nested (all of them are
// opened in dialogs). When the users clicks on save in the last
// opened dialog, a 'reload' event is triggered up to reload the (direct)
// parent view. If this event isn't stopPropagated by the first controller
// catching it, it will crash when the other one will try to handle it,
// as this one doesn't know at all the dataPointID to reload.
const arch = `<form><field name="display_name"/><field name="trululu" open_target="new"/></form>`;
const arch = `<form><field name="display_name"/><field name="trululu"/></form>`;
serverData.views = {
"partner,false,form": arch,
};
await makeView({
await makeViewInDialog({
type: "form",
resModel: "partner",
serverData,
@@ -10096,20 +10108,25 @@ QUnit.module("Views", (hooks) => {
},
});
assert.containsOnce(target, ".o_dialog");
await click(target.querySelector(".o_external_button"));
await click(target.querySelector(".modal .o_external_button"));
assert.containsN(target, ".o_dialog", 2);
await click(target.querySelector(".o_dialog:not(.o_inactive_modal) .o_external_button"));
assert.containsN(target, ".o_dialog", 3);
await editInput(
target.querySelectorAll(".modal")[1],
target.querySelectorAll(".modal")[2],
".o_field_widget[name=display_name] input",
"new name"
);
await click(
target.querySelectorAll(".modal")[1].querySelector("footer .o_form_button_save")
target.querySelectorAll(".modal")[2].querySelector("footer .o_form_button_save")
);
assert.strictEqual(
target.querySelector(".modal .o_field_widget[name=trululu] input").value,
target
.querySelectorAll(".modal")[1]
.querySelector(".o_field_widget[name=trululu] input").value,
"new name",
"record should have been reloaded"
);
@@ -10188,11 +10205,11 @@ QUnit.module("Views", (hooks) => {
</field>
</form>`,
};
await makeView({
await makeViewInDialog({
type: "form",
resModel: "partner",
serverData,
arch: `<form><field name="trululu" open_target="new"/></form>`,
arch: `<form><field name="trululu"/></form>`,
resId: 1,
mockRPC(route, args) {
if (route === "/web/dataset/call_kw/partner/get_formview_id") {
@@ -10201,16 +10218,20 @@ QUnit.module("Views", (hooks) => {
},
});
await click(target.querySelector(".o_external_button"));
assert.deepEqual(getNodesTextContent(target.querySelectorAll(".modal-body .o_data_cell")), [
"yop",
"blip",
]);
assert.deepEqual(
getNodesTextContent(
target.querySelectorAll(".o_dialog:not(.o_inactive_modal) .o_data_cell")
),
["yop", "blip"]
);
await click(target.querySelector(".modal-body th.o_column_sortable"));
assert.deepEqual(getNodesTextContent(target.querySelectorAll(".modal-body .o_data_cell")), [
"blip",
"yop",
]);
await click(target.querySelector(".o_dialog:not(.o_inactive_modal) th.o_column_sortable"));
assert.deepEqual(
getNodesTextContent(
target.querySelectorAll(".o_dialog:not(.o_inactive_modal) .o_data_cell")
),
["blip", "yop"]
);
});
QUnit.test("rainbowman attributes correctly passed on button click", async function (assert) {
+46 -5
View File
@@ -1,8 +1,9 @@
/** @odoo-module **/
import { Dialog } from "@web/core/dialog/dialog";
import { registry } from "@web/core/registry";
import { makeTestEnv } from "@web/../tests/helpers/mock_env";
import { getFixture, mount } from "@web/../tests/helpers/utils";
import { getFixture, mount, nextTick } from "@web/../tests/helpers/utils";
import { getDefaultConfig, View } from "@web/views/view";
import { MainComponentsContainer } from "@web/core/main_components_container";
import {
@@ -20,11 +21,15 @@ import { commandService } from "@web/core/commands/command_service";
import { popoverService } from "@web/core/popover/popover_service";
import { createDebugContext } from "@web/core/debug/debug_context";
import { Component, useSubEnv, xml } from "@odoo/owl";
import { mapLegacyEnvToWowlEnv } from "@web/legacy/utils";
import makeTestEnvironment from "web.test_env";
const serviceRegistry = registry.category("services");
const rootDialogTemplate = xml`<Dialog><View t-props="props.viewProps"/></Dialog>`;
/**
* @typedef {{
* serverData: Object,
@@ -37,9 +42,10 @@ const serviceRegistry = registry.category("services");
/**
* @param {MakeViewParams} params
* @param {boolean} [inDialog=false]
* @returns {Component}
*/
export async function makeView(params) {
async function _makeView(params, inDialog = false) {
const props = { ...params };
const serverData = props.serverData;
const mockRPC = props.mockRPC;
@@ -86,10 +92,29 @@ export async function makeView(params) {
const target = getFixture();
const viewEnv = Object.assign(Object.create(env), { config });
const view = await mount(View, target, { env: viewEnv, props });
await mount(MainComponentsContainer, target, { env });
const viewNode = view.__owl__;
await mount(MainComponentsContainer, target, { env });
let viewNode;
if (inDialog) {
let root;
class RootDialog extends Component {
setup() {
root = this;
useSubEnv(viewEnv);
}
}
RootDialog.components = { Dialog, View };
RootDialog.template = rootDialogTemplate;
env.services.dialog.add(RootDialog, { viewProps: props });
await nextTick();
const rootNode = root.__owl__;
const dialogNode = Object.values(rootNode.children)[0];
viewNode = Object.values(dialogNode.children)[0];
} else {
const view = await mount(View, target, { env: viewEnv, props });
await nextTick();
viewNode = view.__owl__;
}
const withSearchNode = Object.values(viewNode.children)[0];
const concreteViewNode = Object.values(withSearchNode.children)[0];
const concreteView = concreteViewNode.component;
@@ -97,6 +122,22 @@ export async function makeView(params) {
return concreteView;
}
/**
* @param {MakeViewParams} params
* @returns {Component}
*/
export function makeView(params) {
return _makeView(params);
}
/**
* @param {MakeViewParams} params
* @returns {Component}
*/
export function makeViewInDialog(params) {
return _makeView(params, true);
}
export function setupViewRegistries() {
setupControlPanelFavoriteMenuRegistry();
setupControlPanelServiceRegistry();
@@ -61,7 +61,7 @@ import {
validateSearch,
} from "../search/helpers";
import { createWebClient, doAction, loadState } from "../webclient/helpers";
import { makeView, setupViewRegistries } from "./helpers";
import { makeView, makeViewInDialog, setupViewRegistries } from "./helpers";
import { Component, onWillStart, xml } from "@odoo/owl";
@@ -11766,16 +11766,13 @@ QUnit.module("Views", (hooks) => {
QUnit.test("editable list: edit many2one from external link", async function (assert) {
serverData.views = {
"bar,false,form": `
<form string="Bar">
<field name="display_name"/>
</form>`,
"bar,false,form": `<form><field name="display_name"/></form>`,
};
await makeView({
await makeViewInDialog({
arch: `
<tree editable="top" multi_edit="1">
<field name="m2o" open_target="new"/>
<field name="m2o"/>
</tree>`,
serverData,
mockRPC: async function (route, args) {
@@ -11787,7 +11784,8 @@ QUnit.module("Views", (hooks) => {
type: "list",
});
assert.containsNone(target, ".o_selected_row", "not in edit mode");
assert.containsOnce(target, ".o_dialog .o_list_view");
assert.containsNone(target, ".o_selected_row");
await click(target.querySelector("thead .o_list_record_selector input"));
await click(target.querySelector(".o_data_row .o_data_cell"));
assert.containsOnce(target, ".o_selected_row", "in edit mode");
@@ -11795,23 +11793,24 @@ QUnit.module("Views", (hooks) => {
// Clicking somewhere on the form dialog should not close it
// and should not leave edit mode
assert.containsOnce(target, ".modal[role='dialog']");
assert.containsN(target, ".modal[role='dialog']", 2);
await click(target.querySelector(".modal[role='dialog']"));
assert.containsOnce(target, ".modal[role='dialog']");
assert.containsN(target, ".modal[role='dialog']", 2);
assert.containsOnce(target, ".o_selected_row", "in edit mode");
// Change the M2O value in the Form dialog
await editInput(target, ".modal input", "OOF");
await click(target.querySelector(".modal .o_form_button_save"));
// Change the M2O value in the Form dialog (will open a confirmation dialog)
await editInput(target.querySelectorAll(".modal")[1], "input", "OOF");
await click(target.querySelectorAll(".modal")[1], ".o_form_button_save");
assert.containsN(target, ".modal[role='dialog']", 3);
const confirmationDialog = target.querySelectorAll(".modal")[2];
assert.strictEqual(
target.querySelector(".modal .o_field_widget[name=m2o]").innerText,
confirmationDialog.querySelector(".modal .o_field_widget[name=m2o]").innerText,
"OOF",
"Value of the m2o should be updated in the confirmation dialog"
);
// Close the confirmation dialog
await click(target.querySelectorAll(".modal")[1], ".btn-primary");
await click(confirmationDialog, ".btn-primary");
assert.strictEqual(
target.querySelector(".o_data_cell").innerText,
@@ -15332,8 +15331,16 @@ QUnit.module("Views", (hooks) => {
const widthsAfterSelectRow = [...target.querySelectorAll(".o_list_table th")].map(
(th) => th.offsetWidth
);
assert.strictEqual(widthsAfterResize[0], widthsAfterSelectRow[0], "Width must not have been changed after selecting a row");
assert.strictEqual(widthsAfterResize[1], widthsAfterSelectRow[1], "Width must not have been changed after selecting a row");
assert.strictEqual(
widthsAfterResize[0],
widthsAfterSelectRow[0],
"Width must not have been changed after selecting a row"
);
assert.strictEqual(
widthsAfterResize[1],
widthsAfterSelectRow[1],
"Width must not have been changed after selecting a row"
);
});
QUnit.test("list: resize column and toggle check all", async function (assert) {
@@ -15364,8 +15371,16 @@ QUnit.module("Views", (hooks) => {
const widthsAfterSelectAll = [...target.querySelectorAll(".o_list_table th")].map(
(th) => th.offsetWidth
);
assert.strictEqual(widthsAfterResize[0], widthsAfterSelectAll[0], "Width must not have been changed after selecting all");
assert.strictEqual(widthsAfterResize[1], widthsAfterSelectAll[1], "Width must not have been changed after selecting all");
assert.strictEqual(
widthsAfterResize[0],
widthsAfterSelectAll[0],
"Width must not have been changed after selecting all"
);
assert.strictEqual(
widthsAfterResize[1],
widthsAfterSelectAll[1],
"Width must not have been changed after selecting all"
);
});
QUnit.test("editable list: resize column headers", async function (assert) {
@@ -7,7 +7,7 @@ import {
patchWithCleanup,
triggerHotkey,
} from "@web/../tests/helpers/utils";
import { makeView } from "@web/../tests/views/helpers";
import { makeViewInDialog } from "@web/../tests/views/helpers";
import { createWebClient } from "@web/../tests/webclient/helpers";
import { FormViewDialog } from "@web/views/view_dialogs/form_view_dialog";
import { setupControlPanelServiceRegistry } from "@web/../tests/search/helpers";
@@ -203,15 +203,16 @@ QUnit.module("ViewDialogs", (hooks) => {
</tree>`,
};
await makeView({
await makeViewInDialog({
type: "form",
resModel: "partner",
resId: 1,
serverData,
arch: `<form>
arch: `
<form>
<field name="name"/>
<field name="instrument" context="{'tree_view_ref': 'some_tree_view'}" open_target="new"/>
</form>`,
<field name="instrument" context="{'tree_view_ref': 'some_tree_view'}"/>
</form>`,
mockRPC: function (route, args) {
if (args.method === "get_formview_id") {
return Promise.resolve(false);
@@ -490,7 +490,7 @@ QUnit.module("ActionManager", (hooks) => {
<form>
<group>
<field name="display_name" />
<field name="m2o" open_target="current" />
<field name="m2o" />
</group>
</form>
`;
@@ -1309,12 +1309,12 @@ QUnit.module("ActionManager", (hooks) => {
serverData.views["partner,false,form"] = `
<form>
<field name="foo"/>
<field name="bar" open_target="new"/>
<field name="bar"/>
</form>`;
const mockRPC = async (route, args) => {
assert.step(route);
if (args && args.method === "get_formview_id") {
return Promise.resolve(false);
if (args && args.method === "get_formview_action") {
return Promise.resolve(serverData.actions[24]);
}
};
const webClient = await createWebClient({ serverData, mockRPC });
@@ -1329,7 +1329,7 @@ QUnit.module("ActionManager", (hooks) => {
"/web/dataset/call_kw/partner/get_views",
"/web/dataset/call_kw/partner/web_search_read",
"/web/dataset/call_kw/partner/read",
"/web/dataset/call_kw/partner/get_formview_id",
"/web/dataset/call_kw/partner/get_formview_action",
"/web/dataset/call_kw/partner/get_views",
"/web/dataset/call_kw/partner/read",
]);
@@ -1919,8 +1919,7 @@ QUnit.module("ActionManager", (hooks) => {
res_id: 2,
views: [[44, "form"]],
};
serverData.views["partner,44,form"] =
'<form><field name="m2o" open_target="current"/></form>';
serverData.views["partner,44,form"] = '<form><field name="m2o"/></form>';
const mockRPC = async (route, args) => {
if (args.method === "get_formview_action") {
return Promise.resolve({
@@ -1965,8 +1964,7 @@ QUnit.module("ActionManager", (hooks) => {
res_id: 1,
views: [[44, "form"]],
};
serverData.views["partner,44,form"] =
'<form><field name="m2o" open_target="current"/></form>';
serverData.views["partner,44,form"] = '<form><field name="m2o"/></form>';
const mockRPC = async (route, { method }) => {
if (method === "get_formview_action") {
return Promise.resolve({ ...serverData.actions[999], res_id: 3 });