Odoo18-Base/addons/web/static/tests/views/fields/domain_field_tests.js
2025-03-10 11:12:23 +07:00

1023 lines
37 KiB
JavaScript

/** @odoo-module **/
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
import {
click,
clickDiscard,
clickSave,
editInput,
getFixture,
makeDeferred,
nextTick,
patchWithCleanup,
triggerEvent,
} from "@web/../tests/helpers/utils";
import { createWebClient, doAction } from "@web/../tests/webclient/helpers";
let serverData;
let target;
QUnit.module("Fields", (hooks) => {
hooks.beforeEach(() => {
target = getFixture();
serverData = {
models: {
partner: {
fields: {
date: { string: "A date", type: "date" },
display_name: { string: "Displayed name", type: "char" },
foo: {
string: "Foo",
type: "char",
default: "My little Foo Value",
},
bar: { string: "Bar", type: "boolean", default: true },
int_field: { string: "int_field", type: "integer" },
image: { string: "Picture", type: "binary", searchable: true },
},
records: [
{
id: 1,
date: "2017-02-03",
display_name: "first record",
bar: true,
foo: "yop",
int_field: 10,
},
{
id: 2,
display_name: "second record",
bar: true,
foo: "blip",
int_field: 0,
},
{
id: 4,
display_name: "aaa",
foo: "abc",
int_field: false,
},
{ id: 3, bar: true, foo: "gnap", int_field: 80 },
{ id: 5, bar: false, foo: "blop", int_field: -4 },
],
onchanges: {},
},
product: {
fields: {
name: { string: "Product Name", type: "char", searchable: true },
},
records: [
{
id: 37,
display_name: "xphone",
},
{
id: 41,
display_name: "xpad",
},
],
},
partner_type: {
fields: {
name: { string: "Partner Type", type: "char", searchable: true },
color: { string: "Color index", type: "integer", searchable: true },
},
records: [
{ id: 12, display_name: "gold", color: 2 },
{ id: 14, display_name: "silver", color: 5 },
],
},
},
};
setupViewRegistries();
});
QUnit.module("DomainField");
QUnit.test(
"The domain editor should not crash the view when given a dynamic filter",
async function (assert) {
// dynamic filters (containing variables, such as uid, parent or today)
// are not handled by the domain editor, but it shouldn't crash the view
serverData.models.partner.records[0].foo = `[("int_field", "=", uid)]`;
await makeView({
type: "form",
resModel: "partner",
resId: 1,
serverData,
arch: `
<form>
<field name="foo" widget="domain" options="{'model': 'partner'}" />
<field name="int_field" invisible="1" />
</form>`,
});
assert.strictEqual(
target.querySelector(".o_edit_mode").textContent,
" This domain is not supported. Reset domain",
"The widget should not crash the view, but gracefully admit its failure."
);
}
);
QUnit.test(
"The domain editor should not crash the view when given a dynamic filter ( datetime )",
async function (assert) {
// dynamic filters (containing variables, such as uid, parent or today)
// are not handled by the domain editor, but it shouldn't crash the view
serverData.models.partner.records[0].foo = `[("datetime", "=", context_today())]`;
serverData.models.partner.fields.datetime = { string: "A date", type: "datetime" };
await makeView({
type: "form",
resModel: "partner",
resId: 1,
serverData,
arch: `
<form>
<field name="foo" widget="domain" options="{'model': 'partner'}" />
</form>`,
});
// The input field should display that the date is invalid
assert.equal(target.querySelector(".o_datepicker_input").value, "Invalid DateTime");
// Change the date in the datepicker
await click(target, ".o_datepicker_input");
// Select a date in the datepicker
await click(
document.body.querySelector(
`.bootstrap-datetimepicker-widget :not(.today)[data-action="selectDay"]`
)
);
// Close the datepicker
await click(
document.body.querySelector(
`.bootstrap-datetimepicker-widget a[data-action="close"]`
)
);
await clickDiscard(target);
// Open the datepicker again
await click(target, ".o_datepicker_input");
}
);
QUnit.test("basic domain field usage is ok", async function (assert) {
serverData.models.partner.records[0].foo = "[]";
await makeView({
type: "form",
resModel: "partner",
resId: 1,
serverData,
arch: `
<form>
<sheet>
<group>
<field name="foo" widget="domain" options="{'model': 'partner_type'}" />
</group>
</sheet>
</form>`,
});
// As the domain is empty, there should be a button to add the first
// domain part
assert.containsOnce(
target,
".o_domain_add_first_node_button",
"there should be a button to create first domain element"
);
// Clicking on the button should add the [["id", "=", "1"]] domain, so
// there should be a field selector in the DOM
await click(target, ".o_domain_add_first_node_button");
assert.containsOnce(target, ".o_field_selector", "there should be a field selector");
// Focusing the field selector input should open the field selector
// popover
await click(target, ".o_field_selector");
assert.containsOnce(
document.body,
".o_field_selector_popover",
"field selector popover should be visible"
);
assert.containsOnce(
document.body,
".o_field_selector_search input",
"field selector popover should contain a search input"
);
// The popover should contain the list of partner_type fields and so
// there should be the "Color index" field
assert.strictEqual(
document.body.querySelector(".o_field_selector_item").textContent,
"Color index",
"field selector popover should contain 'Color index' field"
);
// Clicking on this field should close the popover, then changing the
// associated value should reveal one matched record
await click(document.body.querySelector(".o_field_selector_item"));
const input = target.querySelector(".o_domain_leaf_value_input");
input.value = 2;
await triggerEvent(input, null, "change");
assert.strictEqual(
target.querySelector(".o_domain_show_selection_button").textContent.trim().substr(0, 2),
"1 ",
"changing color value to 2 should reveal only one record"
);
// Saving the form view should show a readonly domain containing the
// "color" field
await clickSave(target);
assert.ok(
target.querySelector(".o_field_domain").textContent.includes("Color index"),
"field selector readonly value should now contain 'Color index'"
);
});
QUnit.test("using binary field in domain widget", async function (assert) {
assert.expect(0);
serverData.models.partner.records[0].foo = "[]";
await makeView({
type: "form",
resModel: "partner",
resId: 1,
serverData,
arch: `
<form>
<sheet>
<group>
<field name="foo" widget="domain" options="{'model': 'partner'}" />
</group>
</sheet>
</form>`,
});
await click(target, ".o_domain_add_first_node_button");
await click(target, ".o_field_selector");
await click(document.body.querySelector(".o_field_selector_item[data-name='image']"));
});
QUnit.test("domain field is correctly reset on every view change", async function (assert) {
serverData.models.partner.records[0].foo = `[("id", "=", 1)]`;
serverData.models.partner.fields.bar.type = "char";
serverData.models.partner.records[0].bar = "product";
await makeView({
type: "form",
resModel: "partner",
resId: 1,
serverData,
arch: `
<form>
<sheet>
<group>
<field name="bar" />
<field name="foo" widget="domain" options="{'model': 'bar'}" />
</group>
</sheet>
</form>`,
});
// As the domain is equal to [["id", "=", 1]] there should be a field
// selector to change this
assert.containsOnce(
target,
".o_field_domain .o_field_selector",
"there should be a field selector"
);
// Focusing its input should open the field selector popover
await click(target.querySelector(".o_field_selector"));
assert.containsOnce(
document.body,
".o_field_selector_popover",
"field selector popover should be visible"
);
// As the value of the "bar" field is "product", the field selector
// popover should contain the list of "product" fields
assert.containsOnce(
document.body,
".o_field_selector_item",
"field selector popover should contain only one field"
);
assert.strictEqual(
document.body.querySelector(".o_field_selector_item").textContent,
"Product Name",
"field selector popover should contain 'Product Name' field"
);
// Now change the value of the "bar" field to "partner_type"
await editInput(target, ".o_field_widget[name='bar'] input", "partner_type");
// Refocusing the field selector input should open the popover again
await click(target.querySelector(".o_field_selector"));
assert.containsOnce(
document.body,
".o_field_selector_popover",
"field selector popover should be visible"
);
// Now the list of fields should be the ones of the "partner_type" model
assert.containsN(
document.body,
".o_field_selector_item",
2,
"field selector popover should contain two fields"
);
assert.strictEqual(
document.body.querySelector(".o_field_selector_item").textContent,
"Color index",
"field selector popover should contain 'Color index' field"
);
});
QUnit.test(
"domain field can be reset with a new domain (from onchange)",
async function (assert) {
serverData.models.partner.records[0].foo = "[]";
serverData.models.partner.onchanges = {
display_name(obj) {
obj.foo = `[("id", "=", 1)]`;
},
};
await makeView({
type: "form",
resModel: "partner",
resId: 1,
serverData,
arch: `
<form>
<field name="display_name" />
<field name="foo" widget="domain" options="{'model': 'partner'}" />
</form>`,
});
assert.strictEqual(
target.querySelector(".o_domain_show_selection_button").textContent.trim(),
"5 record(s)",
"the domain being empty, there should be 5 records"
);
// update display_name to trigger the onchange and reset foo
await editInput(target, ".o_field_widget[name='display_name'] input", "new value");
assert.strictEqual(
target.querySelector(".o_domain_show_selection_button").textContent.trim(),
"1 record(s)",
"the domain has changed, there should be only 1 record"
);
}
);
QUnit.test("domain field: handle false domain as []", async function (assert) {
assert.expect(3);
serverData.models.partner.records[0].foo = false;
serverData.models.partner.fields.bar.type = "char";
serverData.models.partner.records[0].bar = "product";
await makeView({
type: "form",
resModel: "partner",
resId: 1,
serverData,
arch: `
<form>
<sheet>
<group>
<field name="bar" />
<field name="foo" widget="domain" options="{'model': 'bar'}" />
</group>
</sheet>
</form>`,
mockRPC(route, { args, method }) {
if (method === "search_count") {
assert.deepEqual(args[0], [], "should send a valid domain");
}
},
});
assert.containsOnce(
target,
".o_field_widget[name='foo']:not(.o_field_empty)",
"there should be a domain field, not considered empty"
);
assert.containsNone(
target,
".o_field_widget[name='foo'] .text-warning",
"should not display that the domain is invalid"
);
});
QUnit.test("basic domain field: show the selection", async function (assert) {
serverData.models.partner.records[0].foo = "[]";
serverData.views = {
"partner_type,false,list": `<tree><field name="display_name" /></tree>`,
"partner_type,false,search": `<search><field name="name" string="Name" /></search>`,
};
await makeView({
type: "form",
resModel: "partner",
resId: 1,
serverData,
arch: `
<form>
<sheet>
<group>
<field name="foo" widget="domain" options="{'model': 'partner_type'}" />
</group>
</sheet>
</form>`,
});
assert.equal(
target.querySelector(".o_domain_show_selection_button").textContent.trim().substr(0, 2),
"2 ",
"selection should contain 2 records"
);
// open the selection
await click(target, ".o_domain_show_selection_button");
assert.strictEqual(
target.querySelectorAll(".modal .o_list_view .o_data_row").length,
2,
"should have open a list view with 2 records in a dialog"
);
// click on a record -> should not open the record
// we don't actually check that it doesn't open the record because even
// if it tries to, it will crash as we don't define an arch in this test
await click(target, ".modal .o_list_view .o_data_row .o_data_cell[data-tooltip='gold']");
});
QUnit.test("field context is propagated when opening selection", async function (assert) {
serverData.models.partner.records[0].foo = "[]";
serverData.views = {
"partner_type,false,list": `<tree><field name="display_name" /></tree>`,
"partner_type,3,list": `<tree><field name="id" /></tree>`,
"partner_type,false,search": `<search><field name="name" string="Name" /></search>`,
};
await makeView({
type: "form",
resModel: "partner",
resId: 1,
serverData,
arch: `
<form>
<field name="foo" widget="domain" options="{'model': 'partner_type'}" context="{'tree_view_ref': 3}"/>
</form>`,
});
await click(target, ".o_domain_show_selection_button");
assert.deepEqual(
[...target.querySelectorAll(".modal .o_data_row")].map((x) => x.textContent),
["12", "14"],
"should have picked the correct list view"
);
});
QUnit.test("domain field: manually edit domain with textarea", async function (assert) {
patchWithCleanup(odoo, { debug: true });
serverData.models.partner.records[0].foo = false;
serverData.models.partner.fields.bar.type = "char";
serverData.models.partner.records[0].bar = "product";
serverData.views = {
"partner,false,form": `
<form>
<field name="bar"/>
<field name="foo" widget="domain" options="{'model': 'bar'}"/>
</form>`,
"partner,false,search": `<search />`,
};
serverData.actions = {
1: {
id: 1,
name: "test",
res_id: 1,
res_model: "partner",
type: "ir.actions.act_window",
views: [[false, "form"]],
},
};
const webClient = await createWebClient({
serverData,
mockRPC(route, { method, args }) {
if (method === "search_count") {
assert.step(JSON.stringify(args[0]));
}
if (route === "/web/domain/validate") {
return true;
}
},
});
await doAction(webClient, 1);
assert.verifySteps(["[]"]);
assert.strictEqual(
target.querySelector(".o_domain_show_selection_button").textContent.trim(),
"2 record(s)"
);
await editInput(target, ".o_domain_debug_input", "[['id', '<', 40]]");
// the count should not be re-computed when editing with the textarea
assert.strictEqual(
target.querySelector(".o_domain_show_selection_button").textContent.trim(),
"2 record(s)"
);
assert.verifySteps([]);
await clickSave(target);
assert.strictEqual(
target.querySelector(".o_domain_show_selection_button").textContent.trim(),
"1 record(s)"
);
assert.verifySteps(['[["id","<",40]]']);
});
QUnit.test(
"domain field: manually set an invalid domain with textarea",
async function (assert) {
patchWithCleanup(odoo, { debug: true });
serverData.models.partner.records[0].foo = false;
serverData.models.partner.fields.bar.type = "char";
serverData.models.partner.records[0].bar = "product";
serverData.views = {
"partner,false,form": `
<form>
<field name="bar"/>
<field name="foo" widget="domain" options="{'model': 'bar'}"/>
</form>`,
"partner,false,search": `<search />`,
};
serverData.actions = {
1: {
id: 1,
name: "test",
res_id: 1,
res_model: "partner",
type: "ir.actions.act_window",
views: [[false, "form"]],
},
};
const webClient = await createWebClient({
serverData,
mockRPC(route, { method, args }) {
if (method === "search_count") {
assert.step(JSON.stringify(args[0]));
}
if (method === "write") {
throw new Error("should not save");
}
if (route === "/web/domain/validate") {
return false;
}
},
});
await doAction(webClient, 1);
assert.verifySteps(["[]"]);
assert.strictEqual(
target.querySelector(".o_domain_show_selection_button").textContent.trim(),
"2 record(s)"
);
await editInput(target, ".o_domain_debug_input", "[['abc']]");
// the count should not be re-computed when editing with the textarea
assert.strictEqual(
target.querySelector(".o_domain_show_selection_button").textContent.trim(),
"2 record(s)"
);
assert.verifySteps([]);
await clickSave(target);
assert.hasClass(
target.querySelector(".o_field_domain"),
"o_field_invalid",
"the field is marked as invalid"
);
assert.containsOnce(
target,
".o_form_view .o_form_editable",
"the view is still in edit mode"
);
assert.verifySteps([]);
}
);
QUnit.test(
"domain field: reload count by clicking on the refresh button",
async function (assert) {
patchWithCleanup(odoo, { debug: true });
serverData.models.partner.records[0].foo = "[]";
serverData.models.partner.fields.bar.type = "char";
serverData.models.partner.records[0].bar = "product";
serverData.views = {
"partner,false,form": `
<form>
<field name="bar"/>
<field name="foo" widget="domain" options="{'model': 'bar'}"/>
</form>`,
"partner,false,search": `<search />`,
};
serverData.actions = {
1: {
id: 1,
name: "test",
res_id: 1,
res_model: "partner",
type: "ir.actions.act_window",
views: [[false, "form"]],
},
};
const webClient = await createWebClient({
serverData,
mockRPC(route, { method, args }) {
if (method === "search_count") {
assert.step(JSON.stringify(args[0]));
}
},
});
await doAction(webClient, 1);
assert.verifySteps(["[]"]);
assert.strictEqual(
target.querySelector(".o_domain_show_selection_button").textContent.trim(),
"2 record(s)"
);
await editInput(target, ".o_domain_debug_input", "[['id', '<', 40]]");
// the count should not be re-computed when editing with the textarea
assert.strictEqual(
target.querySelector(".o_domain_show_selection_button").textContent.trim(),
"2 record(s)"
);
// click on the refresh button
await click(target, ".o_refresh_count");
assert.strictEqual(
target.querySelector(".o_domain_show_selection_button").textContent.trim(),
"1 record(s)"
);
assert.verifySteps(['[["id","<",40]]']);
}
);
QUnit.test("domain field: does not wait for the count to render", async function (assert) {
serverData.models.partner.records[0].foo = "[]";
serverData.models.partner.fields.bar.type = "char";
serverData.models.partner.records[0].bar = "product";
const def = makeDeferred();
await makeView({
type: "form",
resModel: "partner",
resId: 1,
serverData,
arch: `
<form>
<field name="bar"/>
<field name="foo" widget="domain" options="{'model': 'bar'}"/>
</form>`,
async mockRPC(route, { method }) {
if (method === "search_count") {
await def;
}
},
});
assert.containsOnce(target, ".o_field_domain_panel .fa-circle-o-notch.fa-spin");
assert.containsNone(target, ".o_field_domain_panel .o_domain_show_selection_button");
def.resolve();
await nextTick();
assert.containsNone(target, ".o_field_domain_panel .fa-circle-o-notch .fa-spin");
assert.containsOnce(target, ".o_field_domain_panel .o_domain_show_selection_button");
assert.strictEqual(
target.querySelector(".o_domain_show_selection_button").textContent.trim(),
"2 record(s)"
);
});
QUnit.test("domain field: edit domain with dynamic content", async function (assert) {
assert.expect(3);
patchWithCleanup(odoo, { debug: true });
let rawDomain = `
[
["date", ">=", datetime.datetime.combine(context_today() + relativedelta(days = -365), datetime.time(0, 0, 0)).to_utc().strftime("%Y-%m-%d %H:%M:%S")]
]
`;
serverData.models.partner.records[0].foo = rawDomain;
serverData.models.partner.fields.bar.type = "char";
serverData.models.partner.records[0].bar = "partner";
serverData.views = {
"partner,false,form": `
<form>
<field name="bar"/>
<field name="foo" widget="domain" options="{'model': 'bar'}"/>
</form>`,
"partner,false,search": `<search />`,
};
serverData.actions = {
1: {
id: 1,
name: "test",
res_id: 1,
res_model: "partner",
type: "ir.actions.act_window",
views: [[false, "form"]],
},
};
const webClient = await createWebClient({
serverData,
mockRPC(route, { method, args }) {
if (method === "write") {
assert.strictEqual(args[1].foo, rawDomain);
}
if (route === "/web/domain/validate") {
return true;
}
},
});
await doAction(webClient, 1);
assert.strictEqual(target.querySelector(".o_domain_debug_input").value, rawDomain);
rawDomain = `
[
["date", ">=", datetime.datetime.combine(context_today() + relativedelta(days = -1), datetime.time(0, 0, 0)).to_utc().strftime("%Y-%m-%d %H:%M:%S")]
]
`;
await editInput(target, ".o_domain_debug_input", rawDomain);
assert.strictEqual(target.querySelector(".o_domain_debug_input").value, rawDomain);
await clickSave(target);
});
QUnit.test("domain field: edit through selector (dynamic content)", async function (assert) {
patchWithCleanup(odoo, { debug: true });
let rawDomain = `[("date", ">=", context_today())]`;
serverData.models.partner.records[0].foo = rawDomain;
serverData.models.partner.fields.bar.type = "char";
serverData.models.partner.records[0].bar = "partner";
serverData.views = {
"partner,false,form": `
<form>
<field name="bar"/>
<field name="foo" widget="domain" options="{'model': 'bar'}"/>
</form>`,
"partner,false,search": `<search />`,
};
serverData.actions = {
1: {
id: 1,
name: "test",
res_id: 1,
res_model: "partner",
type: "ir.actions.act_window",
views: [[false, "form"]],
},
};
const webClient = await createWebClient({
serverData,
mockRPC(route, { method }) {
assert.step(method || route);
},
});
assert.verifySteps(["/web/webclient/load_menus"]);
await doAction(webClient, 1);
assert.verifySteps(["/web/action/load", "get_views", "read", "search_count", "fields_get"]);
assert.strictEqual(target.querySelector(".o_domain_debug_input").value, rawDomain);
assert.containsOnce(target, ".o_datepicker", "there should be a datepicker");
// Open and close the datepicker
await click(target, ".o_datepicker_input");
assert.containsOnce(document.body, ".bootstrap-datetimepicker-widget");
await triggerEvent(window, null, "scroll");
assert.containsNone(document.body, ".bootstrap-datetimepicker-widget");
assert.strictEqual(target.querySelector(".o_domain_debug_input").value, rawDomain);
assert.verifySteps([]);
// Manually input a date
rawDomain = `[("date", ">=", "2020-09-09")]`;
await editInput(target, ".o_datepicker_input", "09/09/2020");
assert.verifySteps(["search_count"]);
assert.strictEqual(target.querySelector(".o_domain_debug_input").value, rawDomain);
// Save
await clickSave(target);
assert.verifySteps(["write", "read", "search_count"]);
assert.strictEqual(target.querySelector(".o_domain_debug_input").value, rawDomain);
});
QUnit.test("domain field without model", async function (assert) {
serverData.models.partner.fields.model_name = { string: "Model name", type: "char" };
await makeView({
type: "form",
resModel: "partner",
serverData,
arch: `
<form>
<field name="model_name"/>
<field name="display_name" widget="domain" options="{'model': 'model_name'}"/>
</form>`,
mockRPC(route, args) {
if (args.method === "search_count") {
assert.step(args.model);
}
},
});
assert.strictEqual(
target.querySelector('.o_field_widget[name="display_name"]').innerText,
"Select a model to add a filter.",
"should contain an error message saying the model is missing"
);
assert.verifySteps([]);
await editInput(target, ".o_field_widget[name=model_name] input", "test");
assert.notStrictEqual(
target.querySelector('.o_field_widget[name="display_name"]').innerText,
"Select a model to add a filter.",
"should not contain an error message anymore"
);
assert.verifySteps(["test"]);
});
QUnit.test("domain field in kanban view", async function (assert) {
serverData.models.partner.records[0].foo = "[]";
serverData.views = {
"partner_type,false,list": `<tree><field name="display_name" /></tree>`,
"partner_type,false,search": `<search><field name="name" string="Name" /></search>`,
};
await makeView({
type: "kanban",
resModel: "partner",
resId: 1,
serverData,
arch: `
<kanban>
<field name="bar" />
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_global_click">
<field name="foo" widget="domain" options="{'model': 'partner_type'}" />
</div>
</t>
</templates>
</kanban>`,
selectRecord: (resId) => {
assert.step(`open record ${resId}`);
},
});
assert.strictEqual(target.querySelector(".o_read_mode").textContent, "Match all records");
await click(target.querySelector(".o_domain_show_selection_button"));
assert.containsOnce(
target,
".o_dialog .o_list_view",
"selected records are listed in a dialog"
);
await click(target.querySelector(".o_domain_selector"));
assert.verifySteps(
["open record 1"],
"record should not open when clicked on the 'N record(s)' button"
);
});
QUnit.test("domain field with 'inDialog' options", async function (assert) {
await makeView({
type: "form",
resModel: "partner",
serverData,
arch: `
<form>
<field name="display_name" widget="domain" options="{'model': 'partner', 'in_dialog': True}"/>
</form>`,
});
assert.containsNone(target, ".o_domain_leaf");
assert.containsNone(target, ".modal");
await click(target, ".o_field_domain_dialog_button");
assert.containsOnce(target, ".modal");
await click(target, ".modal .o_domain_add_first_node_button");
await click(target, ".modal-footer .btn-primary");
assert.containsOnce(target, ".o_domain_leaf");
assert.strictEqual(target.querySelector(".o_domain_leaf").textContent, "ID = 1");
});
QUnit.test("invalid value in domain field with 'inDialog' options", async function (assert) {
serverData.models.partner.fields.display_name.default = "[]";
await makeView({
type: "form",
resModel: "partner",
serverData,
arch: `
<form>
<field name="display_name" widget="domain" options="{'model': 'partner', 'in_dialog': True}"/>
</form>`,
mockRPC: (route, args) => {
if (args.method === "search_count") {
const domain = args.args[0];
if (domain.length && domain[0][0] === "id" && domain[0][2] === "01/01/2002") {
throw new Error("Invalid Domain");
}
}
},
});
assert.containsNone(target, ".o_domain_leaf");
assert.containsNone(target, ".modal");
assert.containsNone(target, ".o_field_domain .text-warning");
await click(target, ".o_field_domain_dialog_button");
assert.containsOnce(target, ".modal");
await click(target, ".modal .o_domain_add_first_node_button");
await editInput(target, ".o_domain_leaf_value_input", "01/01/2002");
await click(target, ".modal-footer .btn-primary");
assert.containsOnce(target, ".o_domain_leaf");
assert.strictEqual(target.querySelector(".o_domain_leaf").textContent, 'ID = "01/01/2002"');
assert.strictEqual(
target.querySelector(".o_field_domain .text-warning").textContent.trim(),
"Invalid domain"
);
});
QUnit.test(
"quick check on save if domain has been edited via the debug input",
async function (assert) {
patchWithCleanup(odoo, { debug: true });
serverData.models.partner.fields.display_name.default = "[['id', '=', False]]";
await makeView({
type: "form",
resModel: "partner",
serverData,
arch: `
<form>
<field name="display_name" widget="domain" options="{'model': 'partner'}"/>
</form>`,
mockRPC: (route, args) => {
if (route === "/web/domain/validate") {
assert.step(route);
assert.deepEqual(args, {
domain: [["id", "!=", false]],
model: "partner",
});
return true;
}
},
});
assert.strictEqual(
target.querySelector(".o_domain_show_selection_button").textContent.trim(),
"0 record(s)"
);
await editInput(target, ".o_domain_debug_input", "[['id', '!=', False]]");
await click(target, "button.o_form_button_save");
assert.verifySteps(["/web/domain/validate"]);
assert.strictEqual(
target.querySelector(".o_domain_show_selection_button").textContent.trim(),
"6 record(s)"
);
}
);
});