[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:
@@ -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) {
|
||||
|
||||
@@ -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 });
|
||||
|
||||
Reference in New Issue
Block a user