-
-
+
+
+
+
+
+
+
@@ -32,16 +35,13 @@
role="img"
aria-label="Refresh"
title="Refresh"
- t-on-click="() => this.loadCount(props)"
+ t-on-click="() => this.checkProps()"
/>
-
-
-
-
-
-
+
+
+
diff --git a/addons/web/static/src/views/fields/properties/property_definition.xml b/addons/web/static/src/views/fields/properties/property_definition.xml
index aa0ef389683..cabc008a102 100644
--- a/addons/web/static/src/views/fields/properties/property_definition.xml
+++ b/addons/web/static/src/views/fields/properties/property_definition.xml
@@ -133,11 +133,11 @@
diff --git a/addons/web/static/src/views/standard_view_props.js b/addons/web/static/src/views/standard_view_props.js
index e01e2575ce1..06c298cca08 100644
--- a/addons/web/static/src/views/standard_view_props.js
+++ b/addons/web/static/src/views/standard_view_props.js
@@ -21,7 +21,6 @@ export const standardViewProps = {
bannerRoute: { type: String, optional: true },
className: { type: String, optional: true },
comparison: { type: [Object, { value: null }], optional: true },
- // Issue OWL: https://github.com/odoo/owl/issues/910
context: { type: Object },
createRecord: { type: Function, optional: true },
display: { type: Object, optional: true },
diff --git a/addons/web/static/tests/core/debug/debug_manager_tests.js b/addons/web/static/tests/core/debug/debug_manager_tests.js
index 408e333b9e5..f26df5affef 100644
--- a/addons/web/static/tests/core/debug/debug_manager_tests.js
+++ b/addons/web/static/tests/core/debug/debug_manager_tests.js
@@ -30,6 +30,7 @@ import {
import { createWebClient, doAction, getActionManagerServerData } from "../../webclient/helpers";
import { openViewItem } from "@web/webclient/debug_items";
import { editSearchView, editView, setDefaults, viewMetadata } from "@web/views/debug_items";
+import { fieldService } from "@web/core/field_service";
import { Component, xml } from "@odoo/owl";
const { prepareRegistriesWithCleanup } = utils;
@@ -56,6 +57,7 @@ QUnit.module("DebugMenu", (hooks) => {
.add("orm", ormService)
.add("dialog", makeFakeDialogService())
.add("localization", makeFakeLocalizationService())
+ .add("field", fieldService)
.add("command", fakeCommandService);
const mockRPC = async (route, args) => {
if (args.method === "check_access_rights") {
diff --git a/addons/web/static/tests/core/domain_selector_tests.js b/addons/web/static/tests/core/domain_selector_tests.js
index 869cc531fe1..509539aada1 100644
--- a/addons/web/static/tests/core/domain_selector_tests.js
+++ b/addons/web/static/tests/core/domain_selector_tests.js
@@ -1,19 +1,29 @@
/** @odoo-module **/
-import { Component, xml } from "@odoo/owl";
+import {
+ click,
+ editInput,
+ editSelect,
+ getFixture,
+ mount,
+ nextTick,
+ patchDate,
+ patchTimeZone,
+ triggerEvent,
+} from "../helpers/utils";
+import { Component, useState, xml } from "@odoo/owl";
import { DomainSelector } from "@web/core/domain_selector/domain_selector";
-import { OPERATOR_DESCRIPTIONS } from "@web/core/domain_selector/domain_selector_operators";
import { fieldService } from "@web/core/field_service";
import { hotkeyService } from "@web/core/hotkeys/hotkey_service";
import { MainComponentsContainer } from "@web/core/main_components_container";
+import { makeFakeLocalizationService } from "../helpers/mock_services";
+import { makeTestEnv } from "../helpers/mock_env";
import { ormService } from "@web/core/orm_service";
+import { OPERATOR_DESCRIPTIONS } from "@web/core/domain_selector/domain_selector_operators";
import { popoverService } from "@web/core/popover/popover_service";
+import { registerCleanup } from "../helpers/cleanup";
import { registry } from "@web/core/registry";
import { uiService } from "@web/core/ui/ui_service";
-import { registerCleanup } from "../helpers/cleanup";
-import { makeTestEnv } from "../helpers/mock_env";
-import { makeFakeLocalizationService } from "../helpers/mock_services";
-import { click, editInput, editSelect, getFixture, mount, triggerEvent } from "../helpers/utils";
import { getPickerApplyButton, getPickerCell } from "./datetime/datetime_test_helpers";
import { openModelFieldSelectorPopover } from "./model_field_selector_tests";
@@ -26,6 +36,36 @@ async function mountComponent(Component, params = {}) {
return mount(Component, target, { env, props: params.props || {} });
}
+async function makeDomainSelector(params = {}) {
+ const props = { ...params };
+ const mockRPC = props.mockRPC;
+ delete props.mockRPC;
+
+ class Parent extends Component {
+ setup() {
+ this.domainSelectorProps = {
+ resModel: "partner",
+ readonly: false,
+ domain: "[]",
+ ...props,
+ update: (domain, fromDebug) => {
+ if (props.update) {
+ props.update(domain, fromDebug);
+ }
+ this.domainSelectorProps.domain = domain;
+ this.render();
+ },
+ };
+ }
+ }
+ Parent.components = { DomainSelector };
+ Parent.template = xml` `;
+
+ const env = await makeTestEnv({ serverData, mockRPC });
+ await mount(MainComponentsContainer, target, { env });
+ return mount(Parent, target, { env, props });
+}
+
QUnit.module("Components", (hooks) => {
hooks.beforeEach(async () => {
serverData = {
@@ -41,6 +81,7 @@ QUnit.module("Components", (hooks) => {
searchable: true,
},
datetime: { string: "Date Time", type: "datetime", searchable: true },
+ int: { string: "Integer", type: "integer", searchable: true },
},
records: [
{ id: 1, foo: "yop", bar: true, product_id: 37 },
@@ -74,30 +115,9 @@ QUnit.module("Components", (hooks) => {
QUnit.module("DomainSelector");
QUnit.test("creating a domain from scratch", async (assert) => {
- assert.expect(12);
-
- class Parent extends Component {
- setup() {
- this.value = "[]";
- }
- onUpdate(newValue) {
- this.value = newValue;
- this.render();
- }
- }
- Parent.components = { DomainSelector };
- Parent.template = xml`
-
- `;
-
- // Create the domain selector and its mock environment
- await mountComponent(Parent);
+ await makeDomainSelector({
+ isDebugMode: true,
+ });
// As we gave an empty domain, there should be a visible button to add
// the first domain part
@@ -151,11 +171,11 @@ QUnit.module("Components", (hooks) => {
await click(target, ".fa.fa-plus");
assert.strictEqual(
target.querySelector(".o_domain_debug_input").value,
- `["&", ("bar", "=", True), ("id", "=", 1)]`,
+ `["&", ("bar", "=", True), ("bar", "=", True)]`,
"the domain input should contain a domain with 'bar' and 'id'"
);
- // There should be two "..." buttons to add a domain group; clicking on
+ // There should be two "add group" buttons to add a domain group; clicking on
// the first one, should add this group with defaults "['id', '=', 1]"
// domains and the "|" operator
assert.containsN(target, ".fa.fa-sitemap", 2, "there should be two '...' buttons");
@@ -163,7 +183,7 @@ QUnit.module("Components", (hooks) => {
await click(target.querySelector(".fa.fa-sitemap"));
assert.strictEqual(
target.querySelector(".o_domain_debug_input").value,
- `["&", ("bar", "=", True), "&", "|", ("id", "=", 1), ("id", "=", 1), ("id", "=", 1)]`,
+ `["&", "&", ("bar", "=", True), "|", ("id", "=", 1), ("id", "=", 1), ("bar", "=", True)]`,
"the domain input should contain a domain with 'bar', 'id' and a subgroup"
);
@@ -189,34 +209,17 @@ QUnit.module("Components", (hooks) => {
QUnit.test("building a domain with a datetime", async (assert) => {
assert.expect(4);
-
- class Parent extends Component {
- setup() {
- this.value = `[("datetime", "=", "2017-03-27 15:42:00")]`;
- }
- onUpdate(newValue) {
+ await makeDomainSelector({
+ domain: `[("datetime", "=", "2017-03-27 15:42:00")]`,
+ isDebugMode: true,
+ update(domain) {
assert.strictEqual(
- newValue,
+ domain,
`[("datetime", "=", "2017-02-26 15:42:00")]`,
"datepicker value should have changed"
);
- this.value = newValue;
- this.render();
- }
- }
- Parent.components = { DomainSelector };
- Parent.template = xml`
-
- `;
-
- // Create the domain selector and its mock environment
- await mountComponent(Parent);
+ },
+ });
// Check that there is a datepicker to choose the date
assert.containsOnce(target, ".o_datetime_input", "there should be a datepicker");
@@ -232,51 +235,121 @@ QUnit.module("Components", (hooks) => {
assert.equal(target.querySelector(".o_datetime_input").value, "02/26/2017 16:42:00");
});
- QUnit.test("building a domain with a datetime: context_today()", async (assert) => {
- // Create the domain selector and its mock environment
- await mountComponent(DomainSelector, {
- props: {
- resModel: "partner",
- value: `[("datetime", "=", context_today())]`,
- readonly: false,
- update: () => {
- assert.step("SHOULD NEVER BE CALLED");
- },
+ QUnit.test("building a domain with an invalid path", async (assert) => {
+ await makeDomainSelector({
+ domain: `[("fooooooo", "=", "abc")]`,
+ update(domain) {
+ assert.strictEqual(domain, `[("bar", "=", True)]`);
},
});
- // Check that there is a datepicker to choose the date
- assert.containsOnce(target, ".o_datetime_input", "there should be a datepicker");
- // The input field should display that the date is invalid
- assert.equal(target.querySelector(".o_datetime_input").value, "Invalid DateTime");
+ assert.strictEqual(target.querySelector(".o_model_field_selector").innerText, "fooooooo");
+ assert.containsOnce(target, ".o_model_field_selector_warning");
+ assert.strictEqual(
+ target.querySelector(".o_model_field_selector_warning").title,
+ "Invalid field chain"
+ );
+ assert.containsN(target, ".o_domain_leaf_operator_select option", 18);
+ assert.strictEqual(target.querySelector(".o_domain_leaf_operator_select").value, "equal");
+ assert.strictEqual(target.querySelector(".o_domain_leaf_value_input").value, "abc");
- // Open and close the datepicker
- await click(target, ".o_datetime_input");
- await click(target);
+ await click(target, ".o_model_field_selector");
+ await click(target.querySelector(".o_model_field_selector_popover_item_name"));
- // The input field should continue displaying 'Invalid DateTime'.
- // The value is still invalid.
- assert.equal(target.querySelector(".o_datetime_input").value, "Invalid DateTime");
- assert.verifySteps([]);
+ assert.strictEqual(target.querySelector(".o_model_field_selector").innerText, "Bar");
+ assert.strictEqual(target.querySelector(".o_domain_leaf_operator_select").value, "is");
+ assert.strictEqual(target.querySelector(".o_domain_leaf_value_input").value, "true");
+ });
+
+ QUnit.test("building a domain with an invalid operator", async (assert) => {
+ await makeDomainSelector({
+ domain: `[("foo", "!!!!=!!!!", "abc")]`,
+ update(domain) {
+ assert.strictEqual(domain, `[("foo", "=", "")]`);
+ },
+ });
+
+ assert.strictEqual(target.querySelector(".o_model_field_selector").innerText, "Foo");
+ assert.containsNone(target, ".o_model_field_selector_warning");
+ assert.containsN(target, ".o_domain_leaf_operator_select option", 8 + 1);
+ assert.strictEqual(
+ target.querySelector(".o_domain_leaf_operator_select").value,
+ "!!!!=!!!!"
+ );
+ assert.containsNone(
+ target,
+ ".o_domain_leaf_value_input",
+ "do not show editor if operator is invalid"
+ );
+
+ await editSelect(target, ".o_domain_leaf_operator_select", "equal");
+
+ assert.strictEqual(target.querySelector(".o_model_field_selector").innerText, "Foo");
+ assert.containsNone(target, ".o_model_field_selector_warning");
+ assert.containsN(target, ".o_domain_leaf_operator_select option", 8);
+ assert.strictEqual(target.querySelector(".o_domain_leaf_operator_select").value, "equal");
+ assert.strictEqual(target.querySelector(".o_domain_leaf_value_input").value, "");
+ });
+
+ QUnit.test("building a domain with an expression for value", async (assert) => {
+ patchDate(2023, 3, 20, 17, 0, 0);
+ await makeDomainSelector({
+ domain: `[("datetime", ">=", context_today())]`,
+ update(domain) {
+ assert.strictEqual(domain, `[("datetime", ">=", "2023-04-20 16:00:00")]`);
+ },
+ });
+
+ assert.containsNone(target, ".o_ds_value_cell input");
+ assert.containsOnce(target, ".o_ds_expr_value");
+ assert.strictEqual(target.querySelector(".o_ds_expr_value").textContent, "context_today()");
+
+ await click(target, ".o_ds_expr_value button");
+ assert.containsOnce(target, ".o_ds_value_cell input");
+ assert.containsNone(target, ".o_ds_expr_value");
+ assert.strictEqual(
+ target.querySelector(".o_ds_value_cell input").value,
+ "04/20/2023 17:00:00"
+ );
+ });
+
+ QUnit.test("building a domain with an expression in value", async (assert) => {
+ await makeDomainSelector({
+ domain: `[("int", "=", id)]`,
+ update(domain) {
+ assert.strictEqual(domain, `[("int", "<", id)]`);
+ },
+ });
+
+ assert.strictEqual(target.querySelector(".o_model_field_selector").innerText, "Integer");
+ assert.strictEqual(target.querySelector(".o_domain_leaf_operator_select").value, "equal");
+ assert.containsNone(target, ".o_ds_value_cell input");
+ assert.containsOnce(target, ".o_ds_expr_value");
+ assert.strictEqual(target.querySelector(".o_ds_expr_value").textContent, "id");
+
+ await editSelect(target, ".o_domain_leaf_operator_select", "less_than");
+ assert.strictEqual(target.querySelector(".o_model_field_selector").innerText, "Integer");
+ assert.strictEqual(
+ target.querySelector(".o_domain_leaf_operator_select").value,
+ "less_than"
+ );
+ assert.containsNone(target, ".o_ds_value_cell input");
+ assert.containsOnce(target, ".o_ds_expr_value");
+ assert.strictEqual(target.querySelector(".o_ds_expr_value").textContent, "id");
});
QUnit.test("building a domain with a m2o without following the relation", async (assert) => {
assert.expect(1);
- // Create the domain selector and its mock environment
- await mountComponent(DomainSelector, {
- props: {
- resModel: "partner",
- value: `[("product_id", "ilike", 1)]`,
- readonly: false,
- isDebugMode: true,
- update: (newValue) => {
- assert.strictEqual(
- newValue,
- `[("product_id", "ilike", "pad")]`,
- "string should have been allowed as m2o value"
- );
- },
+ await makeDomainSelector({
+ domain: `[("product_id", "ilike", 1)]`,
+ isDebugMode: true,
+ update: (domain) => {
+ assert.strictEqual(
+ domain,
+ `[("product_id", "ilike", "pad")]`,
+ "string should have been allowed as m2o value"
+ );
},
});
@@ -286,41 +359,29 @@ QUnit.module("Components", (hooks) => {
});
QUnit.test("editing a domain with `parent` key", async (assert) => {
- // Create the domain selector and its mock environment
- await mountComponent(DomainSelector, {
- props: {
- resModel: "product",
- value: `[("name", "=", parent.foo)]`,
- readonly: false,
- },
+ await makeDomainSelector({
+ resModel: "product",
+ domain: `[("name", "=", parent.foo)]`,
+ isDebugMode: true,
});
- assert.strictEqual(
- target.lastElementChild.innerText,
- `This domain is not supported. RESET DOMAIN`
- );
+ assert.containsOnce(target, ".o_ds_expr_value");
+ assert.strictEqual(target.querySelector(".o_ds_expr_value").textContent, "parent.foo");
});
QUnit.test("creating a domain with a default option", async (assert) => {
assert.expect(1);
-
// Create the domain selector and its mock environment
- await mountComponent(DomainSelector, {
- props: {
- resModel: "partner",
- value: "[]",
- readonly: false,
- isDebugMode: true,
- defaultLeafValue: ["foo", "=", "kikou"],
- update: (newValue) => {
- assert.strictEqual(
- newValue,
- `[("foo", "=", "kikou")]`,
- "the domain input should contain the default domain"
- );
- },
+ await makeDomainSelector({
+ isDebugMode: true,
+ defaultLeafValue: ["foo", "=", "kikou"],
+ update: (domain) => {
+ assert.strictEqual(
+ domain,
+ `[("foo", "=", "kikou")]`,
+ "the domain input should contain the default domain"
+ );
},
});
-
// Clicking on the button should add a visible field selector in the
// widget so that the user can change the field chain
await click(target, ".o_domain_add_first_node_button");
@@ -329,53 +390,34 @@ QUnit.module("Components", (hooks) => {
QUnit.test("edit a domain with the debug textarea", async (assert) => {
assert.expect(5);
- let newValue;
-
- class Parent extends Component {
- setup() {
- this.value = `[("product_id", "ilike", 1)]`;
- }
- onUpdate(value, fromDebug) {
- assert.strictEqual(value, newValue);
+ let newDomain;
+ await makeDomainSelector({
+ domain: `[("product_id", "ilike", 1)]`,
+ isDebugMode: true,
+ update(domain, fromDebug) {
+ assert.strictEqual(domain, newDomain);
assert.ok(fromDebug);
- }
- }
- Parent.components = { DomainSelector };
- Parent.template = xml`
-
- `;
- // Create the domain selector and its mock environment
- await mountComponent(Parent);
-
+ },
+ });
assert.containsOnce(
target,
".o_domain_node.o_domain_leaf",
"should have a single domain node"
);
- newValue = `
-[
- ['product_id', 'ilike', 1],
- ['id', '=', 0]
-]`;
- const input = target.querySelector(".o_domain_debug_input");
- input.value = newValue;
- await triggerEvent(input, null, "change");
+
+ newDomain = `
+ [
+ ['product_id', 'ilike', 1],
+ ['id', '=', 0]
+ ]
+ `;
+ await editInput(target, ".o_domain_debug_input", newDomain);
assert.strictEqual(
target.querySelector(".o_domain_debug_input").value,
- newValue,
+ newDomain,
"the domain should not have been formatted"
);
- assert.containsOnce(
- target,
- ".o_domain_node.o_domain_leaf",
- "should still have a single domain node"
- );
+ assert.containsN(target, ".o_domain_node.o_domain_leaf", 2);
});
QUnit.test(
@@ -383,44 +425,27 @@ QUnit.module("Components", (hooks) => {
async (assert) => {
assert.expect(15);
- let newValue;
-
- class Parent extends Component {
- setup() {
- this.value = `[("product_id", "ilike", 1)]`;
- }
- onUpdate(value, fromDebug) {
- this.value = value;
- assert.strictEqual(value, newValue);
+ let newDomain;
+ await makeDomainSelector({
+ domain: `[("product_id", "ilike", 1)]`,
+ isDebugMode: true,
+ update(domain, fromDebug) {
+ assert.strictEqual(domain, newDomain);
assert.ok(fromDebug);
- this.render();
- }
- }
- Parent.components = { DomainSelector };
- Parent.template = xml`
-
- `;
- // Create the domain selector and its mock environment
- await mountComponent(Parent);
-
+ },
+ });
assert.containsOnce(
target,
".o_domain_node.o_domain_leaf",
"should have a single domain node"
);
- newValue = `[(1, "=", 1)]`;
+ newDomain = `[(1, "=", 1)]`;
let input = target.querySelector(".o_domain_debug_input");
- input.value = newValue;
+ input.value = newDomain;
await triggerEvent(input, null, "change");
assert.strictEqual(
target.querySelector(".o_domain_debug_input").value,
- newValue,
+ newDomain,
"the domain should not have been formatted"
);
assert.containsOnce(
@@ -439,13 +464,13 @@ QUnit.module("Components", (hooks) => {
); // option "="
assert.strictEqual(target.querySelector(".o_domain_leaf_value_input").value, "1");
- newValue = `[(0, "=", 1)]`;
+ newDomain = `[(0, "=", 1)]`;
input = target.querySelector(".o_domain_debug_input");
- input.value = newValue;
+ input.value = newDomain;
await triggerEvent(input, null, "change");
assert.strictEqual(
target.querySelector(".o_domain_debug_input").value,
- newValue,
+ newDomain,
"the domain should not have been formatted"
);
assert.containsOnce(
@@ -466,18 +491,15 @@ QUnit.module("Components", (hooks) => {
}
);
- QUnit.test("operator fallback", async (assert) => {
- await mountComponent(DomainSelector, {
- props: {
- resModel: "partner",
- value: "[['foo', 'like', 'kikou']]",
- },
+ QUnit.test("operator fallback (readonly mode)", async (assert) => {
+ await makeDomainSelector({
+ domain: `[['foo', 'like', 'kikou']]`,
+ readonly: true,
});
-
assert.strictEqual(target.querySelector(".o_domain_leaf").textContent, `Foo like "kikou"`);
});
- QUnit.test("operator fallback in edit mode", async (assert) => {
+ QUnit.test("operator fallback (edit mode)", async (assert) => {
OPERATOR_DESCRIPTIONS.push({
key: "test",
label: "test",
@@ -488,31 +510,20 @@ QUnit.module("Components", (hooks) => {
OPERATOR_DESCRIPTIONS.pop();
});
- await mountComponent(DomainSelector, {
- props: {
- readonly: false,
- resModel: "partner",
- value: "[['foo', 'test', 'kikou']]",
- },
- });
-
+ await makeDomainSelector({ domain: "[['foo', 'test', 'kikou']]" });
// check that the DomainSelector does not crash
assert.containsOnce(target, ".o_domain_selector");
assert.containsN(target, ".o_domain_leaf_edition > div", 2, "value should be hidden");
});
QUnit.test("cache fields_get", async (assert) => {
- await mountComponent(DomainSelector, {
- mockRPC(route, { method }) {
+ await makeDomainSelector({
+ domain: "['&', ['foo', '=', 'kikou'], ['bar', '=', 'true']]",
+ mockRPC(_, { method }) {
if (method === "fields_get") {
assert.step("fields_get");
}
},
- props: {
- readonly: false,
- resModel: "partner",
- value: "['&', ['foo', '=', 'kikou'], ['bar', '=', 'true']]",
- },
});
assert.verifySteps(["fields_get"]);
@@ -529,36 +540,13 @@ QUnit.module("Components", (hooks) => {
],
};
- class Parent extends Component {
- setup() {
- this.value = `[['state', '!=', false]]`;
- }
- onUpdate(newValue) {
- this.value = newValue;
- this.render();
- }
- }
- Parent.components = { DomainSelector };
- Parent.template = xml`
-
- `;
-
- // Create the domain selector and its mock environment
- await mountComponent(Parent);
+ await makeDomainSelector({ domain: `[['state', '!=', False]]` });
assert.strictEqual(
target.querySelector(".o_model_field_selector_chain_part").innerText,
"State"
);
- assert.strictEqual(
- target.querySelector(".o_domain_leaf_operator_select").value,
- "not_equal"
- ); // option "!="
+ assert.strictEqual(target.querySelector(".o_domain_leaf_operator_select").value, "set"); // option "!="
await editSelect(target, ".o_domain_leaf_operator_select", "equal");
@@ -580,15 +568,7 @@ QUnit.module("Components", (hooks) => {
["ghi", "GHI"],
],
};
-
- await mountComponent(DomainSelector, {
- props: {
- resModel: "partner",
- value: `[['state', 'in', ['abc']]]`,
- readonly: false,
- },
- });
-
+ await makeDomainSelector({ domain: `[['state', 'in', ['abc']]]` });
const select = target.querySelector(".o_domain_leaf_operator_select");
assert.strictEqual(select.options[select.options.selectedIndex].text, "in");
});
@@ -606,10 +586,10 @@ QUnit.module("Components", (hooks) => {
class Parent extends Component {
setup() {
- this.value = `[("state", "in", ["a", "b", "c"])]`;
+ this.domain = `[("state", "in", ["a", "b", "c"])]`;
}
- onUpdate(newValue) {
- this.value = newValue;
+ onUpdate(domain) {
+ this.domain = domain;
this.render();
}
}
@@ -617,9 +597,9 @@ QUnit.module("Components", (hooks) => {
Parent.template = xml`
`;
@@ -627,28 +607,28 @@ QUnit.module("Components", (hooks) => {
const comp = await mountComponent(Parent);
assert.containsOnce(target, ".o_domain_leaf_value_input");
- assert.strictEqual(comp.value, `[("state", "in", ["a", "b", "c"])]`);
+ assert.strictEqual(comp.domain, `[("state", "in", ["a", "b", "c"])]`);
assert.strictEqual(
target.querySelector(".o_domain_leaf_value_input").value,
`["a", "b", "c"]`
);
await editInput(target, ".o_domain_leaf_value_input", `[]`);
- assert.strictEqual(comp.value, `[("state", "in", [])]`);
+ assert.strictEqual(comp.domain, `[("state", "in", [])]`);
await editInput(target, ".o_domain_leaf_value_input", `["b"]`);
- assert.strictEqual(comp.value, `[("state", "in", ["b"])]`);
+ assert.strictEqual(comp.domain, `[("state", "in", ["b"])]`);
});
QUnit.test("parse -1", async (assert) => {
class Parent extends Component {
setup() {
- this.value = `[("id", "=", -1)]`;
+ this.domain = `[("id", "=", -1)]`;
}
}
Parent.components = { DomainSelector };
Parent.template = xml`
-
+
`;
await mountComponent(Parent);
assert.strictEqual(target.querySelector(".o_domain_leaf_value_input").value, "-1");
@@ -657,22 +637,22 @@ QUnit.module("Components", (hooks) => {
QUnit.test("parse 3-1", async (assert) => {
class Parent extends Component {
setup() {
- this.value = `[("id", "=", 3-1)]`;
+ this.domain = `[("id", "=", 3-1)]`;
}
}
Parent.components = { DomainSelector };
Parent.template = xml`
-
+
`;
await mountComponent(Parent);
- assert.strictEqual(target.querySelector(".o_domain_leaf_value_input").value, "undefined");
+ assert.strictEqual(target.querySelector(".o_ds_expr_value").innerText, "3 - 1");
});
QUnit.test("domain not supported (mode readonly)", async (assert) => {
await mountComponent(DomainSelector, {
props: {
resModel: "partner",
- value: `[`,
+ domain: `[`,
readonly: true,
isDebugMode: false,
},
@@ -685,7 +665,7 @@ QUnit.module("Components", (hooks) => {
await mountComponent(DomainSelector, {
props: {
resModel: "partner",
- value: `[`,
+ domain: `[`,
readonly: true,
isDebugMode: true,
},
@@ -699,7 +679,7 @@ QUnit.module("Components", (hooks) => {
await mountComponent(DomainSelector, {
props: {
resModel: "partner",
- value: `[`,
+ domain: `[`,
readonly: false,
isDebugMode: false,
},
@@ -712,7 +692,7 @@ QUnit.module("Components", (hooks) => {
await mountComponent(DomainSelector, {
props: {
resModel: "partner",
- value: `[`,
+ domain: `[`,
readonly: false,
isDebugMode: true,
},
@@ -725,11 +705,11 @@ QUnit.module("Components", (hooks) => {
QUnit.test("reset domain", async (assert) => {
class Parent extends Component {
setup() {
- this.value = `[`;
+ this.domain = `[`;
}
onUpdate(domain) {
assert.step(domain);
- this.value = domain;
+ this.domain = domain;
this.render();
}
}
@@ -737,7 +717,7 @@ QUnit.module("Components", (hooks) => {
Parent.template = xml`
@@ -753,21 +733,21 @@ QUnit.module("Components", (hooks) => {
await click(target, ".o_reset_domain_button");
assert.strictEqual(
target.querySelector(".o_domain_selector").innerText.toLowerCase(),
- "match all records add filter"
+ "match all records add condition"
);
assert.containsNone(target, ".o_reset_domain_button");
assert.containsOnce(target, ".o_domain_add_first_node_button");
assert.verifySteps(["[]"]);
});
- QUnit.test("incorrect path in debug input in model field selector popover", async (assert) => {
+ QUnit.test("debug input in model field selector popover", async (assert) => {
class Parent extends Component {
setup() {
- this.value = `[("id", "=", 1)]`;
+ this.domain = `[("id", "=", 1)]`;
}
onUpdate(domain) {
assert.step(domain);
- this.value = domain;
+ this.domain = domain;
this.render();
}
}
@@ -775,7 +755,7 @@ QUnit.module("Components", (hooks) => {
Parent.template = xml`
{
await openModelFieldSelectorPopover(target);
await editInput(target, ".o_model_field_selector_debug", "a");
await click(target, ".o_model_field_selector_popover_close");
- assert.verifySteps([`[("", "=", 1)]`]);
- assert.strictEqual(
- target.querySelector(".o_model_field_selector_chain_part").innerText,
- "-"
- );
+ assert.verifySteps([`[("a", "=", "")]`]);
+
+ assert.strictEqual(target.querySelector(".o_model_field_selector").innerText, "a");
assert.containsOnce(target, ".o_model_field_selector_warning");
+ assert.containsN(target, ".o_domain_leaf_operator_select option", 18);
+ assert.strictEqual(target.querySelector(".o_domain_leaf_operator_select").value, "equal");
+ assert.strictEqual(target.querySelector(".o_domain_leaf_value_input").value, "");
+ assert.strictEqual(target.querySelector(".o_domain_debug_input").value, `[("a", "=", "")]`);
+ });
+
+ QUnit.test("between operator", async (assert) => {
+ patchTimeZone(0);
+ await makeDomainSelector({
+ domain: `["&", ("datetime", ">=", "2023-01-01 00:00:00"), ("datetime", "<=", "2023-01-10 00:00:00")]`,
+ isDebugMode: true,
+ update(domain) {
+ assert.step(domain);
+ },
+ });
+
+ assert.containsOnce(target, ".o_domain_leaf");
+ assert.strictEqual(target.querySelector(".o_domain_leaf_operator_select").value, "between");
+ assert.containsN(target, ".o_datetime_input", 2);
+
+ await editInput(
+ target.querySelectorAll(".o_datetime_input")[0],
+ null,
+ "2023-01-02 00:00:00"
+ );
+ assert.verifySteps([
+ `["&", ("datetime", ">=", "2023-01-02 00:00:00"), ("datetime", "<=", "2023-01-10 00:00:00")]`,
+ ]);
+
+ await editInput(
+ target.querySelectorAll(".o_datetime_input")[1],
+ null,
+ "2023-01-08 00:00:00"
+ );
+ assert.verifySteps([
+ `["&", ("datetime", ">=", "2023-01-02 00:00:00"), ("datetime", "<=", "2023-01-08 00:00:00")]`,
+ ]);
+ });
+
+ QUnit.test("between operator (2)", async (assert) => {
+ patchTimeZone(0);
+ await makeDomainSelector({
+ domain: `["&", "&", ("foo", "=", "abc"), ("datetime", ">=", "2023-01-01 00:00:00"), ("datetime", "<=", "2023-01-10 00:00:00")]`,
+ update(domain) {
+ assert.step(domain);
+ },
+ });
+ assert.containsN(target, ".o_domain_leaf", 2);
+ assert.strictEqual(
+ target.querySelector(".o_domain_leaf .o_domain_leaf_operator_select ").value,
+ "equal"
+ );
+ assert.strictEqual(
+ target.querySelector(".o_domain_leaf:nth-child(2) .o_domain_leaf_operator_select ")
+ .value,
+ "between"
+ );
+ assert.containsN(target, ".o_datetime_input", 2);
+ });
+
+ QUnit.test("between operator (3)", async (assert) => {
+ patchTimeZone(0);
+ await makeDomainSelector({
+ domain: `["&", "&", ("datetime", ">=", "2023-01-01 00:00:00"), ("datetime", "<=", "2023-01-10 00:00:00"), ("foo", "=", "abc")]`,
+ update(domain) {
+ assert.step(domain);
+ },
+ });
+ assert.containsN(target, ".o_domain_leaf", 2);
+ assert.strictEqual(
+ target.querySelector(".o_domain_leaf .o_domain_leaf_operator_select ").value,
+ "between"
+ );
+ assert.strictEqual(
+ target.querySelector(".o_domain_leaf:nth-child(2) .o_domain_leaf_operator_select ")
+ .value,
+ "equal"
+ );
+ assert.containsN(target, ".o_datetime_input", 2);
+ });
+
+ QUnit.test("between operator (4)", async (assert) => {
+ patchTimeZone(0);
+ await makeDomainSelector({
+ domain: `["&", ("datetime", ">=", "2023-01-01 00:00:00"), "&", ("datetime", "<=", "2023-01-10 00:00:00"), ("foo", "=", "abc")]`,
+ update(domain) {
+ assert.step(domain);
+ },
+ });
+ assert.containsN(target, ".o_domain_leaf", 2);
+ assert.strictEqual(
+ target.querySelector(".o_domain_leaf .o_domain_leaf_operator_select ").value,
+ "between"
+ );
+ assert.strictEqual(
+ target.querySelector(".o_domain_leaf:nth-child(2) .o_domain_leaf_operator_select ")
+ .value,
+ "equal"
+ );
+ assert.containsN(target, ".o_datetime_input", 2);
+ });
+
+ QUnit.test("between operator (5)", async (assert) => {
+ patchTimeZone(0);
+ await makeDomainSelector({
+ domain: `["|", "&", ("create_date", ">=", "2023-04-01 00:00:00"), ("create_date", "<=", "2023-04-30 23:59:59"), (0, "=", 1)]`,
+ readonly: true,
+ update(domain) {
+ assert.step(domain);
+ },
+ });
+ assert.strictEqual(
+ target.querySelector(".o_domain_selector").innerText,
+ `Match records with any of the following rules:\ncreate_date\nis between "2023-04-01 00:00:00" and "2023-04-30 23:59:59"\n0\n= 1`
+ );
+ });
+
+ QUnit.test("expressions in between operator", async (assert) => {
+ patchTimeZone(0);
+ patchDate(2023, 0, 1, 0, 0, 0);
+ await makeDomainSelector({
+ domain: `["&", ("datetime", ">=", context_today()), ("datetime", "<=", "2023-01-10 00:00:00")]`,
+ update(domain) {
+ assert.step(domain);
+ },
+ });
+
+ assert.containsOnce(target, ".o_domain_leaf");
+ assert.strictEqual(target.querySelector(".o_domain_leaf_operator_select").value, "between");
+ assert.containsOnce(target, ".o_ds_expr_value");
+ assert.containsOnce(target, ".o_datetime_input");
+
+ await click(target, ".o_ds_expr_value button");
+ assert.verifySteps([
+ `["&", ("datetime", ">=", "2023-01-01 00:00:00"), ("datetime", "<=", "2023-01-10 00:00:00")]`,
+ ]);
+ });
+
+ QUnit.test("support of connector '!' (readonly mode)", async (assert) => {
+ const toTest = [
+ {
+ domain: `["!", ("foo", "=", "abc")]`,
+ result: `Match records with the following rule:\nFoo\n!= "abc"`,
+ },
+ {
+ domain: `["!", "!", ("foo", "=", "abc")]`,
+ result: `Match records with the following rule:\nFoo\n= "abc"`,
+ },
+ {
+ domain: `["!", "!", "!", ("foo", "=", "abc")]`,
+ result: `Match records with the following rule:\nFoo\n!= "abc"`,
+ },
+ {
+ domain: `["!", "&", ("foo", "=", "abc"), ("foo", "=", "def")]`,
+ result: `Match records with any of the following rules:\nFoo\n!= "abc"\nFoo\n!= "def"`,
+ },
+ {
+ domain: `["!", "|", ("foo", "=", "abc"), ("foo", "=", "def")]`,
+ result: `Match records with all of the following rules:\nFoo\n!= "abc"\nFoo\n!= "def"`,
+ },
+ {
+ domain: `["&", "!", ("foo", "=", "abc"), ("foo", "=", "def")]`,
+ result: `Match records with all of the following rules:\nFoo\n!= "abc"\nFoo\n= "def"`,
+ },
+ {
+ domain: `["&", "!", "!", ("foo", "=", "abc"), ("foo", "=", "def")]`,
+ result: `Match records with all of the following rules:\nFoo\n= "abc"\nFoo\n= "def"`,
+ },
+ {
+ domain: `["&", ("foo", "=", "abc"), "!", ("foo", "=", "def")]`,
+ result: `Match records with all of the following rules:\nFoo\n= "abc"\nFoo\n!= "def"`,
+ },
+ {
+ domain: `["&", ("foo", "=", "abc"), "!", "!", ("foo", "=", "def")]`,
+ result: `Match records with all of the following rules:\nFoo\n= "abc"\nFoo\n= "def"`,
+ },
+ {
+ domain: `["|", "!", ("foo", "=", "abc"), ("foo", "=", "def")]`,
+ result: `Match records with any of the following rules:\nFoo\n!= "abc"\nFoo\n= "def"`,
+ },
+ {
+ domain: `["|", "!", "!", ("foo", "=", "abc"), ("foo", "=", "def")]`,
+ result: `Match records with any of the following rules:\nFoo\n= "abc"\nFoo\n= "def"`,
+ },
+ {
+ domain: `["|", ("foo", "=", "abc"), "!", ("foo", "=", "def")]`,
+ result: `Match records with any of the following rules:\nFoo\n= "abc"\nFoo\n!= "def"`,
+ },
+ {
+ domain: `["|", ("foo", "=", "abc"), "!", "!", ("foo", "=", "def")]`,
+ result: `Match records with any of the following rules:\nFoo\n= "abc"\nFoo\n= "def"`,
+ },
+ {
+ domain: `["&", "!", "&", ("foo", "=", "abc"), ("foo", "=", "def"), ("foo", "=", "ghi")]`,
+ result: `Match records with all of the following rules:\nany\nof:\nFoo\n!= "abc"\nFoo\n!= "def"\nFoo\n= "ghi"`,
+ },
+ {
+ domain: `["&", "!", "|", ("foo", "=", "abc"), ("foo", "=", "def"), ("foo", "=", "ghi")]`,
+ result: `Match records with all of the following rules:\nFoo\n!= "abc"\nFoo\n!= "def"\nFoo\n= "ghi"`,
+ },
+ {
+ domain: `["|", "!", "&", ("foo", "=", "abc"), ("foo", "=", "def"), ("foo", "=", "ghi")]`,
+ result: `Match records with any of the following rules:\nFoo\n!= "abc"\nFoo\n!= "def"\nFoo\n= "ghi"`,
+ },
+ {
+ domain: `["|", "!", "|", ("foo", "=", "abc"), ("foo", "=", "def"), ("foo", "=", "ghi")]`,
+ result: `Match records with any of the following rules:\nall\nof:\nFoo\n!= "abc"\nFoo\n!= "def"\nFoo\n= "ghi"`,
+ },
+ {
+ domain: `["!", "&", "&", ("foo", "=", "abc"), ("foo", "=", "def"), ("foo", "=", "ghi")]`,
+ result: `Match records with any of the following rules:\nFoo\n!= "abc"\nFoo\n!= "def"\nFoo\n!= "ghi"`,
+ },
+ {
+ domain: `["!", "|", "|", ("foo", "=", "abc"), ("foo", "=", "def"), ("foo", "=", "ghi")]`,
+ result: `Match records with all of the following rules:\nFoo\n!= "abc"\nFoo\n!= "def"\nFoo\n!= "ghi"`,
+ },
+ {
+ domain: `["!", "&", "|", ("foo", "=", "abc"), "!", ("foo", "=", "def"), ("foo", "=", "ghi")]`,
+ result: `Match records with any of the following rules:\nall\nof:\nFoo\n!= "abc"\nFoo\n= "def"\nFoo\n!= "ghi"`,
+ },
+ {
+ domain: `["!", "|", "&", ("foo", "=", "abc"), ("foo", "=", "def"), ("foo", "=", "ghi")]`,
+ result: `Match records with all of the following rules:\nany\nof:\nFoo\n!= "abc"\nFoo\n!= "def"\nFoo\n!= "ghi"`,
+ },
+ {
+ domain: `["!", "&", ("foo", "=", "abc"), "|", ("foo", "=", "def"), ("foo", "=", "ghi")]`,
+ result: `Match records with any of the following rules:\nFoo\n!= "abc"\nall\nof:\nFoo\n!= "def"\nFoo\n!= "ghi"`,
+ },
+ {
+ domain: `["!", "|", ("foo", "=", "abc"), "&", ("foo", "=", "def"), ("foo", "!=", "ghi")]`,
+ result: `Match records with all of the following rules:\nFoo\n!= "abc"\nany\nof:\nFoo\n!= "def"\nFoo\n= "ghi"`,
+ },
+ {
+ domain: `["!", "|", ("foo", "=", "abc"), "&", ("foo", "!=", "def"), "!", ("foo", "=", "ghi")]`,
+ result: `Match records with all of the following rules:\nFoo\n!= "abc"\nany\nof:\nFoo\n= "def"\nFoo\n= "ghi"`,
+ },
+ ];
+
+ class Parent extends Component {
+ setup() {
+ this.state = useState({ domain: `[]` });
+ }
+ }
+ Parent.components = { DomainSelector };
+ Parent.template = xml``;
+
+ const parent = await mountComponent(Parent);
+
+ for (const { domain, result } of toTest) {
+ parent.state.domain = domain;
+ await nextTick();
+ assert.strictEqual(target.querySelector(".o_domain_selector").innerText, result);
+ }
+ });
+
+ QUnit.test("support of connector '!' (debug mode)", async (assert) => {
+ const toTest = [
+ {
+ domain: `["!", ("foo", "=", "abc")]`,
+ result: `Match records with the following rule:\nFoo\n!= "abc"`,
+ },
+ {
+ domain: `["!", "!", ("foo", "=", "abc")]`,
+ result: `Match records with the following rule:\nFoo\n= "abc"`,
+ },
+ {
+ domain: `["!", "!", "!", ("foo", "=", "abc")]`,
+ result: `Match records with the following rule:\nFoo\n!= "abc"`,
+ },
+ {
+ domain: `["!", "&", ("foo", "=", "abc"), ("foo", "=", "def")]`,
+ result: `Match records with not all of the following rules:\nFoo\n= "abc"\nFoo\n= "def"`,
+ },
+ {
+ domain: `["!", "|", ("foo", "=", "abc"), ("foo", "=", "def")]`,
+ result: `Match records with none of the following rules:\nFoo\n= "abc"\nFoo\n= "def"`,
+ },
+ {
+ domain: `["&", "!", ("foo", "=", "abc"), ("foo", "=", "def")]`,
+ result: `Match records with all of the following rules:\nFoo\n!= "abc"\nFoo\n= "def"`,
+ },
+ {
+ domain: `["&", "!", "!", ("foo", "=", "abc"), ("foo", "=", "def")]`,
+ result: `Match records with all of the following rules:\nFoo\n= "abc"\nFoo\n= "def"`,
+ },
+ {
+ domain: `["&", ("foo", "=", "abc"), "!", ("foo", "=", "def")]`,
+ result: `Match records with all of the following rules:\nFoo\n= "abc"\nFoo\n!= "def"`,
+ },
+ {
+ domain: `["&", ("foo", "=", "abc"), "!", "!", ("foo", "=", "def")]`,
+ result: `Match records with all of the following rules:\nFoo\n= "abc"\nFoo\n= "def"`,
+ },
+ {
+ domain: `["|", "!", ("foo", "=", "abc"), ("foo", "=", "def")]`,
+ result: `Match records with any of the following rules:\nFoo\n!= "abc"\nFoo\n= "def"`,
+ },
+ {
+ domain: `["|", "!", "!", ("foo", "=", "abc"), ("foo", "=", "def")]`,
+ result: `Match records with any of the following rules:\nFoo\n= "abc"\nFoo\n= "def"`,
+ },
+ {
+ domain: `["|", ("foo", "=", "abc"), "!", ("foo", "=", "def")]`,
+ result: `Match records with any of the following rules:\nFoo\n= "abc"\nFoo\n!= "def"`,
+ },
+ {
+ domain: `["|", ("foo", "=", "abc"), "!", "!", ("foo", "=", "def")]`,
+ result: `Match records with any of the following rules:\nFoo\n= "abc"\nFoo\n= "def"`,
+ },
+ {
+ domain: `["&", "!", "&", ("foo", "=", "abc"), ("foo", "=", "def"), ("foo", "=", "ghi")]`,
+ result: `Match records with all of the following rules:\nnot all\nof:\nFoo\n= "abc"\nFoo\n= "def"\nFoo\n= "ghi"`,
+ },
+ {
+ domain: `["&", "!", "|", ("foo", "=", "abc"), ("foo", "=", "def"), ("foo", "=", "ghi")]`,
+ result: `Match records with all of the following rules:\nnone\nof:\nFoo\n= "abc"\nFoo\n= "def"\nFoo\n= "ghi"`,
+ },
+ {
+ domain: `["|", "!", "&", ("foo", "=", "abc"), ("foo", "=", "def"), ("foo", "=", "ghi")]`,
+ result: `Match records with any of the following rules:\nnot all\nof:\nFoo\n= "abc"\nFoo\n= "def"\nFoo\n= "ghi"`,
+ },
+ {
+ domain: `["|", "!", "|", ("foo", "=", "abc"), ("foo", "=", "def"), ("foo", "=", "ghi")]`,
+ result: `Match records with any of the following rules:\nnone\nof:\nFoo\n= "abc"\nFoo\n= "def"\nFoo\n= "ghi"`,
+ },
+ {
+ domain: `["!", "&", "&", ("foo", "=", "abc"), ("foo", "=", "def"), ("foo", "=", "ghi")]`,
+ result: `Match records with not all of the following rules:\nFoo\n= "abc"\nFoo\n= "def"\nFoo\n= "ghi"`,
+ },
+ {
+ domain: `["!", "|", "|", ("foo", "=", "abc"), ("foo", "=", "def"), ("foo", "=", "ghi")]`,
+ result: `Match records with none of the following rules:\nFoo\n= "abc"\nFoo\n= "def"\nFoo\n= "ghi"`,
+ },
+ {
+ domain: `["!", "&", "|", ("foo", "=", "abc"), "!", ("foo", "=", "def"), ("foo", "=", "ghi")]`,
+ result: `Match records with not all of the following rules:\nany\nof:\nFoo\n= "abc"\nFoo\n!= "def"\nFoo\n= "ghi"`,
+ },
+ {
+ domain: `["!", "|", "&", ("foo", "=", "abc"), ("foo", "=", "def"), ("foo", "=", "ghi")]`,
+ result: `Match records with none of the following rules:\nall\nof:\nFoo\n= "abc"\nFoo\n= "def"\nFoo\n= "ghi"`,
+ },
+ {
+ domain: `["!", "&", ("foo", "=", "abc"), "|", ("foo", "=", "def"), ("foo", "=", "ghi")]`,
+ result: `Match records with not all of the following rules:\nFoo\n= "abc"\nany\nof:\nFoo\n= "def"\nFoo\n= "ghi"`,
+ },
+ {
+ domain: `["!", "|", ("foo", "=", "abc"), "&", ("foo", "=", "def"), ("foo", "=", "ghi")]`,
+ result: `Match records with none of the following rules:\nFoo\n= "abc"\nall\nof:\nFoo\n= "def"\nFoo\n= "ghi"`,
+ },
+ {
+ domain: `["!", "|", ("foo", "=", "abc"), "&", ("foo", "=", "def"), "!", ("foo", "=", "ghi")]`,
+ result: `Match records with none of the following rules:\nFoo\n= "abc"\nall\nof:\nFoo\n= "def"\nFoo\n!= "ghi"`,
+ },
+ ];
+
+ class Parent extends Component {
+ setup() {
+ this.state = useState({ domain: `[]` });
+ }
+ }
+ Parent.components = { DomainSelector };
+ Parent.template = xml``;
+
+ const parent = await mountComponent(Parent);
+
+ for (const { domain, result } of toTest) {
+ parent.state.domain = domain;
+ await nextTick();
+ assert.strictEqual(target.querySelector(".o_domain_selector").innerText, result);
+ }
});
QUnit.test("support properties", async (assert) => {
@@ -821,7 +1169,7 @@ QUnit.module("Components", (hooks) => {
static template = xml`
{
`;
static components = { DomainSelector };
setup() {
- this.value = expectedDomain;
+ this.domain = expectedDomain;
}
onUpdate(domain) {
assert.strictEqual(domain, expectedDomain);
- this.value = domain;
+ this.domain = domain;
this.render();
}
}
diff --git a/addons/web/static/tests/core/model_field_selector_tests.js b/addons/web/static/tests/core/model_field_selector_tests.js
index b495693b516..a46afde6837 100644
--- a/addons/web/static/tests/core/model_field_selector_tests.js
+++ b/addons/web/static/tests/core/model_field_selector_tests.js
@@ -32,8 +32,9 @@ async function mountComponent(Component, params = {}) {
return mount(Component, target, { env, props: params.props || {} });
}
-export async function openModelFieldSelectorPopover(target) {
- await click(target, ".o_model_field_selector");
+export async function openModelFieldSelectorPopover(target, index = 0) {
+ const el = target.querySelectorAll(".o_model_field_selector")[index];
+ await click(el);
}
function getDisplayedFieldNames(target) {
@@ -154,7 +155,7 @@ QUnit.module("Components", (hooks) => {
resModel="'partner'"
path="path"
isDebugMode="false"
- update="(pathInfo) => this.onUpdate(pathInfo)"
+ update="(path) => this.onUpdate(path)"
/>
`;
@@ -392,7 +393,7 @@ QUnit.module("Components", (hooks) => {
readonly="false"
resModel="'partner'"
path="path"
- update="(pathInfo) => this.onUpdate(pathInfo)"
+ update="(path) => this.onUpdate(path)"
/>
`;
diff --git a/addons/web/static/tests/search/advanced_search_dialog_tests.js b/addons/web/static/tests/search/advanced_search_dialog_tests.js
deleted file mode 100644
index 60a1f08866b..00000000000
--- a/addons/web/static/tests/search/advanced_search_dialog_tests.js
+++ /dev/null
@@ -1,657 +0,0 @@
-/** @odoo-module **/
-
-import { Component, xml } from "@odoo/owl";
-import {
- click,
- editInput,
- getFixture,
- getNodesTextContent,
- patchDate,
- patchWithCleanup,
-} from "@web/../tests/helpers/utils";
-import { browser } from "@web/core/browser/browser";
-import { fieldService } from "@web/core/field_service";
-import { registry } from "@web/core/registry";
-import { ControlPanel } from "@web/search/control_panel/control_panel";
-import {
- editFavoriteName,
- getFacetTexts,
- makeWithSearch,
- openAdvancedSearchDialog,
- saveFavorite,
- setupControlPanelFavoriteMenuRegistry,
- setupControlPanelServiceRegistry,
- toggleComparisonMenu,
- toggleFavoriteMenu,
- toggleFilterMenu,
- toggleGroupByMenu,
- toggleMenuItem,
- toggleMenuItemOption,
- toggleSaveFavorite,
-} from "./helpers";
-
-async function makeTestComponent(props) {
- class TestComponent extends Component {
- setup() {
- this.id = 1; // used for t-key. We want to be sure that the control panel is rendered.
- }
- }
- TestComponent.components = { ControlPanel };
- TestComponent.template = xml`
-
-
-
- `;
- return makeWithSearch({
- serverData,
- resModel: "foo",
- Component: TestComponent,
- searchViewId: 1,
- searchMenuTypes: ["filter", "groupBy", "comparison", "favorite"],
- ...props,
- });
-}
-
-let target;
-let serverData;
-
-QUnit.module("Search", (hooks) => {
- hooks.beforeEach(async () => {
- serverData = {
- models: {
- foo: {
- fields: {
- date_field: {
- name: "date_field",
- string: "A date",
- type: "date",
- searchable: true,
- },
- date_time_field: {
- name: "date_time_field",
- string: "DateTime",
- type: "datetime",
- searchable: true,
- },
- boolean_field: {
- name: "boolean_field",
- string: "Boolean Field",
- type: "boolean",
- default: true,
- searchable: true,
- },
- binary_field: {
- name: "binary_field",
- string: "Binary Field",
- type: "binary",
- searchable: true,
- },
- char_field: {
- name: "char_field",
- string: "Char Field",
- type: "char",
- default: "foo",
- trim: true,
- searchable: true,
- },
- float_field: {
- name: "float_field",
- string: "Floaty McFloatface",
- type: "float",
- searchable: true,
- },
- color: {
- name: "color",
- string: "Color",
- type: "selection",
- selection: [
- ["black", "Black"],
- ["white", "White"],
- ],
- searchable: true,
- },
- user_id: {
- string: "User",
- type: "many2one",
- relation: "user",
- store: true,
- sortable: true,
- },
- },
- },
- user: {
- fields: {
- id: { string: "Id", type: "integer" },
- name: { string: "Product Name", type: "char" },
- },
- records: [
- {
- id: 37,
- display_name: "Me",
- },
- ],
- },
- },
- views: {
- "foo,1,search": `
-
-
-
-
-
-
-
-
- `,
- },
- };
- setupControlPanelServiceRegistry();
- setupControlPanelFavoriteMenuRegistry();
- registry.category("services").add("field", fieldService);
- target = getFixture();
- patchWithCleanup(browser, {
- setTimeout: (fn) => fn(),
- clearTimeout: () => {},
- });
- });
-
- QUnit.module("AdvancedSearchDialog");
-
- QUnit.test("basic rendering", async function (assert) {
- await makeTestComponent();
- await toggleFilterMenu(target);
- assert.containsNone(target, ".modal");
- assert.containsOnce(target, ".o_filter_menu .dropdown-menu");
-
- await openAdvancedSearchDialog(target);
- assert.containsNone(target, ".o_filter_menu .dropdown-menu");
- assert.containsOnce(target, ".modal");
- assert.containsOnce(target, ".modal .modal-header h4");
- assert.strictEqual(target.querySelector(".modal header h4").innerText, "Advanced Search");
- assert.containsN(target, ".modal footer button", 2);
- assert.deepEqual(getNodesTextContent(target.querySelectorAll(".modal footer button")), [
- "Search",
- "Discard",
- ]);
-
- await click(target, ".modal footer button:nth-child(2)");
- assert.containsNone(target, ".modal");
-
- await toggleFilterMenu(target);
- await openAdvancedSearchDialog(target);
- assert.containsOnce(target, ".modal");
-
- await click(target, ".modal footer button:nth-child(1)");
- assert.containsNone(target, ".modal");
- });
-
- QUnit.test("start with an empty query", async function (assert) {
- await makeTestComponent();
- await toggleFilterMenu(target);
- await openAdvancedSearchDialog(target);
- assert.containsOnce(target, ".modal");
- assert.containsOnce(target, ".modal .modal-body .o_domain_selector");
- assert.containsOnce(target, ".modal .modal-body .o_domain_selector span");
- assert.strictEqual(
- target.querySelector(".modal .modal-body .o_domain_selector span").innerText,
- "Match all records"
- );
- assert.containsOnce(
- target,
- ".modal .modal-body .o_domain_selector button.o_domain_add_first_node_button"
- );
- });
-
- QUnit.test("start with an empty query and a global domain", async function (assert) {
- await makeTestComponent({ domain: [[0, "=", 1]] });
- await toggleFilterMenu(target);
- await openAdvancedSearchDialog(target);
- // the global domain should not be part of the domain passed to domain selector
- assert.containsOnce(target, ".modal");
- assert.containsOnce(target, ".modal .modal-body .o_domain_selector");
- assert.containsOnce(target, ".modal .modal-body .o_domain_selector span");
- assert.strictEqual(
- target.querySelector(".modal .modal-body .o_domain_selector span").innerText,
- "Match all records"
- );
- assert.containsOnce(
- target,
- ".modal .modal-body .o_domain_selector button.o_domain_add_first_node_button"
- );
- });
-
- QUnit.test("start with a simple domain and modify it", async function (assert) {
- const testComponent = await makeTestComponent({
- context: { search_default_simple_filter: true },
- });
- await toggleFilterMenu(target);
- assert.deepEqual(getFacetTexts(target), ["Simple Filter"]);
- assert.containsNone(target, ".o_domain_selector");
-
- await openAdvancedSearchDialog(target);
- assert.containsOnce(target, ".modal");
- assert.containsOnce(target, ".modal .modal-body .o_domain_selector");
- assert.containsOnce(target, ".o_model_field_selector");
- assert.strictEqual(
- target.querySelector(".o_model_field_selector").innerText,
- "Floaty McFloatface"
- );
- assert.containsOnce(target, ".o_domain_leaf_operator_select");
- assert.strictEqual(target.querySelector(".o_domain_leaf_operator_select").value, "equal");
- assert.containsOnce(target, ".o_domain_leaf_value_input");
- assert.strictEqual(target.querySelector(".o_domain_leaf_value_input").value, "2");
-
- await editInput(target, ".o_domain_leaf_value_input", 4);
- assert.strictEqual(target.querySelector(".o_domain_leaf_value_input").value, "4");
- assert.deepEqual(testComponent.props.domain, [["float_field", "=", 2]]);
-
- await click(target, ".modal footer button:nth-child(1)");
- assert.deepEqual(getFacetTexts(target), ["Advanced Search"]);
- assert.deepEqual(testComponent.props.domain, [["float_field", "=", 4]]);
- });
-
- QUnit.test("start with a dynamic domain (evaluation is done)", async function (assert) {
- const testComponent = await makeTestComponent({
- context: {
- search_default_dynamic_filter: true,
- },
- mockRPC(_, { args, method }) {
- if (method === "create_or_replace") {
- assert.strictEqual(args[0].domain, `[("user_id", "=", 4)]`); // the domain is not saved dynamically
- return 9; // fake serverId to simulate the creation of
- // the favorite in db.
- }
- },
- });
- assert.deepEqual(getFacetTexts(target), ["Dynamic Filter"]);
- assert.deepEqual(testComponent.props.domain, [["user_id", "=", 7]]);
-
- await toggleFilterMenu(target);
- await openAdvancedSearchDialog(target);
-
- assert.strictEqual(target.querySelector(".o_model_field_selector").innerText, "User");
- assert.strictEqual(target.querySelector(".o_domain_leaf_operator_select").value, "equal");
- assert.strictEqual(target.querySelector(".o_ds_value_cell input").value, "7");
- });
-
- QUnit.test(
- "start with a simple domain and a simple group by, group by not lost after search",
- async function (assert) {
- const testComponent = await makeTestComponent({
- context: {
- search_default_simple_filter: true,
- search_default_simple_group_by: true,
- },
- });
- assert.deepEqual(getFacetTexts(target), ["Simple Filter", "Simple GroupBy"]);
-
- await toggleFilterMenu(target);
- await openAdvancedSearchDialog(target);
- assert.strictEqual(target.querySelector(".o_domain_leaf_value_input").value, "2");
- assert.deepEqual(testComponent.props.domain, [["float_field", "=", 2]]);
- assert.deepEqual(testComponent.props.groupBy, ["boolean_field"]);
-
- await editInput(target, ".o_domain_leaf_value_input", 4);
- await click(target, ".modal footer button:nth-child(1)");
- assert.deepEqual(getFacetTexts(target), ["Simple GroupBy", "Advanced Search"]);
- assert.deepEqual(testComponent.props.domain, [["float_field", "=", 4]]);
- assert.deepEqual(testComponent.props.groupBy, ["boolean_field"]);
- }
- );
-
- QUnit.test("start with a favorite only", async function (assert) {
- const irFilters = [
- {
- context: "{ 'some_key': 'some_value', 'group_by': ['color'] }",
- domain: "[('char_field', 'ilike', 'abc')]",
- id: 1,
- is_default: true,
- name: "My favorite",
- sort: "[]",
- user_id: [2, "Mitchell Admin"],
- },
- ];
- const testComponent = await makeTestComponent({
- irFilters,
- });
- assert.deepEqual(getFacetTexts(target), ["My favorite"]);
- assert.deepEqual(testComponent.props.domain, [["char_field", "ilike", "abc"]]);
- assert.deepEqual(testComponent.props.groupBy, ["color"]);
- assert.strictEqual(testComponent.props.context.some_key, "some_value");
-
- await toggleFilterMenu(target);
- await openAdvancedSearchDialog(target);
- await editInput(target.querySelector(".o_domain_leaf_value_input"), null, "xyz");
- assert.strictEqual(target.querySelector(".o_domain_leaf_value_input").value, "xyz");
-
- await click(target, ".modal footer button:nth-child(1)");
- assert.deepEqual(getFacetTexts(target), ["Color", "Advanced Search"]);
- assert.deepEqual(testComponent.props.domain, [["char_field", "ilike", "xyz"]]);
- assert.deepEqual(testComponent.props.groupBy, ["color"]);
- assert.strictEqual(testComponent.props.context.some_key, "some_value");
- });
-
- QUnit.test("group_bys retrieved from favorite", async function (assert) {
- const irFilters = [
- {
- context: "{ 'some_key': 'some_value', 'group_by': ['color'] }",
- domain: "[('char_field', 'ilike', 'abc')]",
- id: 1,
- is_default: true,
- name: "My favorite",
- sort: "[]",
- user_id: [2, "Mitchell Admin"],
- },
- ];
- const testComponent = await makeTestComponent({
- irFilters,
- });
-
- await toggleGroupByMenu(target);
- await toggleMenuItem(target, "Simple GroupBy");
- assert.deepEqual(getFacetTexts(target), ["My favorite", "Simple GroupBy"]);
-
- await toggleFilterMenu(target);
- await toggleMenuItem(target, "Simple Filter");
- assert.deepEqual(getFacetTexts(target), ["My favorite", "Simple GroupBy", "Simple Filter"]);
- assert.deepEqual(testComponent.props.domain, [
- "&",
- ["char_field", "ilike", "abc"],
- ["float_field", "=", 2],
- ]);
- assert.deepEqual(testComponent.props.groupBy, ["color", "boolean_field"]);
- assert.strictEqual(testComponent.props.context.some_key, "some_value");
-
- await toggleFilterMenu(target);
- await openAdvancedSearchDialog(target);
- await editInput(target.querySelector(".o_domain_leaf_value_input"), null, "xyz");
- assert.strictEqual(target.querySelector(".o_domain_leaf_value_input").value, "xyz");
-
- await click(target, ".modal footer button:nth-child(1)");
- assert.deepEqual(getFacetTexts(target), ["Color>Simple GroupBy", "Advanced Search"]);
- assert.deepEqual(testComponent.props.domain, [
- "&",
- ["char_field", "ilike", "xyz"],
- ["float_field", "=", 2],
- ]);
- assert.deepEqual(testComponent.props.groupBy, ["color", "boolean_field"]);
- assert.strictEqual(testComponent.props.context.some_key, "some_value");
- });
-
- QUnit.test(
- "comparison retrieved from favorite (comparison in searchMenuTypes)",
- async function (assert) {
- patchDate(2023, 2, 1, 14, 0, 0);
- const testComponent = await makeTestComponent({
- async mockRPC(_, { method }) {
- if (method === "create_or_replace") {
- return 9;
- }
- },
- });
- await toggleFilterMenu(target);
- await toggleMenuItem(target, "Date Filter");
- await toggleMenuItemOption(target, "Date Filter", "March");
-
- await toggleComparisonMenu(target);
- await toggleMenuItem(target, "Date Filter: Previous Period");
-
- await toggleFavoriteMenu(target);
- await toggleSaveFavorite(target);
- await editFavoriteName(target, "My new favorite");
- await saveFavorite(target, "My new favorite");
-
- assert.deepEqual(getFacetTexts(target), ["My new favorite"]);
- assert.deepEqual(testComponent.props.domain, []);
- assert.deepEqual(testComponent.props.comparison, {
- domains: [
- {
- arrayRepr: [
- "&",
- ["date_field", ">=", "2023-03-01"],
- ["date_field", "<=", "2023-03-31"],
- ],
- description: "March 2023",
- },
- {
- arrayRepr: [
- "&",
- ["date_field", ">=", "2023-02-01"],
- ["date_field", "<=", "2023-02-28"],
- ],
- description: "February 2023",
- },
- ],
- fieldName: "date_field",
- });
- assert.deepEqual(testComponent.props.groupBy, []);
- assert.strictEqual(testComponent.props.context.comparison, undefined);
-
- await toggleFilterMenu(target);
- await openAdvancedSearchDialog(target);
- await click(target, ".modal footer button:nth-child(1)");
-
- assert.deepEqual(getFacetTexts(target), ["Advanced Search"]);
- assert.deepEqual(testComponent.props.domain, [
- "&",
- ["date_field", ">=", "2023-03-01"],
- ["date_field", "<=", "2023-03-31"],
- ]);
- assert.deepEqual(testComponent.props.groupBy, []);
- assert.strictEqual(testComponent.props.context.comparison, undefined);
- }
- );
-
- QUnit.test(
- "comparison retrieved from favorite (comparison not in searchMenuTypes)",
- async function (assert) {
- const testComponent = await makeTestComponent({
- irFilters: [
- {
- context: `{
- 'group_by': [],
- 'comparison': {
- 'comparisonId': 'previous_period',
- 'fieldName': 'create_date',
- 'fieldDescription':'Creation Date',
- 'range': ['&', ['create_date', '>=', '2023-02-28 23:00:00'], ['create_date', '<=', '2023-03-31 21:59:59']],
- 'rangeDescription': 'March 2023',
- 'comparisonRange': ['&', ['create_date', '>=', '2023-01-31 23:00:00'], ['create_date', '<=', '2023-02-28 22:59:59']],
- 'comparisonRangeDescription': 'February 2023'}
- }`,
- domain: "[]",
- id: 1,
- is_default: true,
- name: "My favorite",
- sort: "[]",
- user_id: [2, "Mitchell Admin"],
- },
- ],
- searchMenuTypes: ["filter", "favorite"],
- });
-
- assert.deepEqual(getFacetTexts(target), ["My favorite"]);
- assert.deepEqual(testComponent.props.domain, []); // a problem with comparison?
- assert.strictEqual(testComponent.props.comparison, null);
- assert.deepEqual(testComponent.props.groupBy, []);
- assert.strictEqual(testComponent.props.context.comparison, undefined);
-
- await toggleFilterMenu(target);
- await openAdvancedSearchDialog(target);
- await click(target, ".modal footer button:nth-child(1)");
-
- assert.deepEqual(getFacetTexts(target), ["Advanced Search"]);
- assert.deepEqual(testComponent.props.domain, []);
- assert.deepEqual(testComponent.props.groupBy, []);
- assert.strictEqual(testComponent.props.context.comparison, undefined);
- }
- );
-
- QUnit.test("search items created for advanced search are invisible", async function (assert) {
- const irFilters = [
- {
- context: "{ 'group_by': ['color'] }",
- domain: "[]]",
- id: 1,
- is_default: true,
- name: "My favorite",
- sort: "[]",
- user_id: [2, "Mitchell Admin"],
- },
- ];
- const testComponent = await makeTestComponent({
- irFilters,
- });
- assert.deepEqual(getFacetTexts(target), ["My favorite"]);
- assert.deepEqual(testComponent.props.domain, []);
- assert.deepEqual(testComponent.props.groupBy, ["color"]);
-
- await toggleGroupByMenu(target);
- const visibleGroupBys = getNodesTextContent(
- target.querySelectorAll(".o_group_by_menu .dropdown-item")
- );
-
- await toggleFilterMenu(target);
- const visibleFilters = getNodesTextContent(
- target.querySelectorAll(".o_filter_menu .dropdown-item")
- );
- await openAdvancedSearchDialog(target);
- await click(target, ".modal footer button:nth-child(1)");
- assert.deepEqual(getFacetTexts(target), ["Color", "Advanced Search"]);
- assert.deepEqual(testComponent.props.domain, []);
- assert.deepEqual(testComponent.props.groupBy, ["color"]);
-
- await toggleGroupByMenu(target);
- assert.deepEqual(
- getNodesTextContent(target.querySelectorAll(".o_group_by_menu .dropdown-item")),
- visibleGroupBys
- );
-
- await toggleFilterMenu(target);
- assert.deepEqual(
- getNodesTextContent(target.querySelectorAll(".o_filter_menu .dropdown-item")),
- visibleFilters
- );
- });
-
- QUnit.test("Several date filters and a comparison", async function (assert) {
- patchDate(2023, 3, 1, 0, 0, 0);
- serverData.views["foo,1,search"] = `
-
-
-
-
- `;
- const testComponent = await makeTestComponent({
- domain: [[0, "=", 1]],
- context: {
- search_default_date_filter: true,
- search_default_date_time_filter: true,
- },
- });
- await toggleComparisonMenu(target);
- await toggleMenuItem(target, "Date: Previous Period");
-
- assert.deepEqual(getFacetTexts(target), [
- "Date: April 2023orDatetime: April 2023",
- "Date: Previous Period",
- ]);
- assert.deepEqual(testComponent.props.domain, [
- "&",
- [0, "=", 1],
- "&",
- ["date_time_field", ">=", "2023-03-31 23:00:00"],
- ["date_time_field", "<=", "2023-04-30 22:59:59"],
- ]);
- assert.deepEqual(testComponent.props.comparison, {
- domains: [
- {
- arrayRepr: [
- "&",
- "&",
- [0, "=", 1],
- "&",
- ["date_time_field", ">=", "2023-03-31 23:00:00"],
- ["date_time_field", "<=", "2023-04-30 22:59:59"],
- "&",
- ["date_field", ">=", "2023-04-01"],
- ["date_field", "<=", "2023-04-30"],
- ],
- description: "April 2023",
- },
- {
- arrayRepr: [
- "&",
- "&",
- [0, "=", 1],
- "&",
- ["date_time_field", ">=", "2023-03-31 23:00:00"],
- ["date_time_field", "<=", "2023-04-30 22:59:59"],
- "&",
- ["date_field", ">=", "2023-03-01"],
- ["date_field", "<=", "2023-03-31"],
- ],
- description: "March 2023",
- },
- ],
- fieldName: "date_field",
- });
-
- await toggleFilterMenu(target);
- await openAdvancedSearchDialog(target);
-
- assert.deepEqual(
- getNodesTextContent(target.querySelectorAll(".o_model_field_selector_chain_part")),
- ["DateTime", "DateTime", "A date", "A date"]
- ); // 0 not found!
-
- await click(target, ".modal footer button:nth-child(1)");
-
- assert.deepEqual(testComponent.props.domain, [
- "&",
- [0, "=", 1],
- "&",
- "&",
- ["date_time_field", ">=", "2023-03-31 23:00:00"],
- ["date_time_field", "<=", "2023-04-30 22:59:59"],
- "&",
- ["date_field", ">=", "2023-04-01"],
- ["date_field", "<=", "2023-04-30"],
- ]);
- });
-
- QUnit.test("an invalid domain cannot be saved", async function (assert) {
- assert.expect(9);
- patchWithCleanup(odoo, {
- debug: true,
- });
- registry.category("services").add(
- "notification",
- {
- start() {
- return {
- add(message, options) {
- assert.strictEqual(message, "Domain is invalid. Please correct it");
- assert.deepEqual(options, { type: "danger" });
- },
- };
- },
- },
- { force: true }
- );
- const testComponent = await makeTestComponent({});
- assert.deepEqual(testComponent.props.domain, []);
- assert.deepEqual(getFacetTexts(target), []);
- assert.containsNone(target, ".modal");
-
- await toggleFilterMenu(target);
- await openAdvancedSearchDialog(target);
- assert.containsOnce(target, ".modal");
-
- await editInput(target, "textarea.o_domain_debug_input", "[");
- await click(target, ".modal footer button:nth-child(1)");
- assert.containsOnce(target, ".modal");
- assert.deepEqual(testComponent.props.domain, []);
- assert.deepEqual(getFacetTexts(target), []);
- });
-});
diff --git a/addons/web/static/tests/search/filter_menu_tests.js b/addons/web/static/tests/search/filter_menu_tests.js
index d9dc16c471b..3b53af105f0 100644
--- a/addons/web/static/tests/search/filter_menu_tests.js
+++ b/addons/web/static/tests/search/filter_menu_tests.js
@@ -1,6 +1,8 @@
/** @odoo-module **/
import {
+ click,
+ editInput,
getFixture,
getNodesTextContent,
patchDate,
@@ -13,14 +15,12 @@ import {
isItemSelected,
isOptionSelected,
makeWithSearch,
- openAdvancedSearchDialog,
+ openAddCustomFilterDialog,
setupControlPanelServiceRegistry,
toggleFilterMenu,
toggleMenuItem,
toggleMenuItemOption,
} from "./helpers";
-import { registry } from "@web/core/registry";
-import { AdvancedSearchDialog } from "@web/search/filter_menu/advanced_search_dialog";
function getDomain(controlPanel) {
return controlPanel.env.searchModel.domain;
@@ -47,7 +47,7 @@ QUnit.module("Search", (hooks) => {
},
foo: { string: "Foo", type: "char", store: true, sortable: true },
},
- records: {},
+ records: [],
},
},
views: {
@@ -76,7 +76,7 @@ QUnit.module("Search", (hooks) => {
assert.containsNone(target, ".o_menu_item");
assert.containsNone(target, ".dropdown-divider");
assert.containsOnce(target, ".dropdown-item");
- assert.strictEqual(target.querySelector(".dropdown-item").innerText, "Advanced Search");
+ assert.strictEqual(target.querySelector(".dropdown-item").innerText, "Add Custom Filter");
});
QUnit.test("simple rendering with a single filter", async function (assert) {
@@ -101,7 +101,7 @@ QUnit.module("Search", (hooks) => {
assert.containsOnce(target, ".dropdown-item:not(.o_menu_item)");
assert.strictEqual(
target.querySelector(".dropdown-item:not(.o_menu_item)").innerText,
- "Advanced Search"
+ "Add Custom Filter"
);
});
@@ -596,27 +596,77 @@ QUnit.module("Search", (hooks) => {
});
});
- QUnit.test("Open 'Advanced Search' dialog", async function (assert) {
- assert.expect(3);
- registry.category("services").add(
- "dialog",
- {
- start() {
- return {
- add: (dialogClass, props) => {
- assert.strictEqual(dialogClass, AdvancedSearchDialog);
- assert.deepEqual(Object.keys(props), [
- "domain",
- "onConfirm",
- "resModel",
- "isDebugMode",
- ]);
- },
- };
- },
- },
- { force: true }
+ QUnit.test("Open 'Add Custom Filter' dialog", async function (assert) {
+ await makeWithSearch({
+ serverData,
+ resModel: "foo",
+ Component: ControlPanel,
+ searchMenuTypes: ["filter"],
+ });
+
+ await toggleFilterMenu(target);
+ assert.deepEqual(
+ getNodesTextContent(target.querySelectorAll(".o_filter_menu .dropdown-item")),
+ ["Add Custom Filter"]
);
+ assert.containsNone(target, ".modal");
+
+ await openAddCustomFilterDialog(target);
+ assert.containsOnce(target, ".modal");
+ assert.strictEqual(target.querySelector(".modal header").innerText, "Add Custom Filter");
+ assert.containsOnce(target, ".modal .o_domain_selector");
+ assert.containsOnce(target, ".modal .o_domain_selector .o_domain_leaf");
+ assert.deepEqual(getNodesTextContent(target.querySelectorAll(".modal footer button")), [
+ "Add",
+ "Cancel",
+ ]);
+ });
+
+ QUnit.test(
+ "Default leaf in 'Add Custom Filter' dialog is based on ID (if no special fields on model)",
+ async function (assert) {
+ await makeWithSearch({
+ serverData,
+ resModel: "foo",
+ Component: ControlPanel,
+ searchMenuTypes: ["filter"],
+ });
+ await toggleFilterMenu(target);
+ await openAddCustomFilterDialog(target);
+ assert.containsOnce(target, ".modal .o_domain_selector .o_domain_leaf");
+ assert.containsOnce(target, ".o_domain_leaf .o_model_field_selector_chain_part");
+ assert.strictEqual(
+ target.querySelector(".o_domain_leaf .o_model_field_selector_chain_part").innerText,
+ "ID"
+ );
+ }
+ );
+
+ QUnit.test(
+ "Default leaf in 'Add Custom Filter' dialog is based on first special field (if any special fields on model)",
+ async function (assert) {
+ serverData.models.foo.fields.country_id = {
+ string: "Country",
+ type: "many2one",
+ };
+ await makeWithSearch({
+ serverData,
+ resModel: "foo",
+ Component: ControlPanel,
+ searchMenuTypes: ["filter"],
+ });
+ await toggleFilterMenu(target);
+ await openAddCustomFilterDialog(target);
+ assert.containsOnce(target, ".modal .o_domain_selector .o_domain_leaf");
+ assert.containsOnce(target, ".o_domain_leaf .o_model_field_selector_chain_part");
+ assert.strictEqual(
+ target.querySelector(".o_domain_leaf .o_model_field_selector_chain_part").innerText,
+ "Country"
+ );
+ }
+ );
+
+ QUnit.test("Default connector is '|' (any)", async function (assert) {
await makeWithSearch({
serverData,
resModel: "foo",
@@ -624,10 +674,127 @@ QUnit.module("Search", (hooks) => {
searchMenuTypes: ["filter"],
});
await toggleFilterMenu(target);
- assert.deepEqual(
- getNodesTextContent(target.querySelectorAll(".o_filter_menu .dropdown-item")),
- ["Advanced Search"]
+ await openAddCustomFilterDialog(target);
+ assert.containsOnce(target, ".modal .o_domain_selector .o_domain_leaf");
+ assert.containsOnce(target, ".o_domain_leaf .o_model_field_selector_chain_part");
+ assert.strictEqual(
+ target.querySelector(".o_domain_leaf .o_model_field_selector_chain_part").innerText,
+ "ID"
);
- await openAdvancedSearchDialog(target);
+ assert.containsNone(target, "button.o_domain_tree_connector_caret");
+
+ await click(target, ".o_domain_add_node_button .fa-plus");
+ assert.containsOnce(target, "button.o_domain_tree_connector_caret");
+ assert.strictEqual(
+ target.querySelector("button.o_domain_tree_connector_caret").innerText,
+ "any"
+ );
+ assert.containsN(target, ".modal .o_domain_selector .o_domain_leaf", 2);
+ });
+
+ QUnit.test("Add a custom filter", async function (assert) {
+ const controlPanel = await makeWithSearch({
+ serverData,
+ resModel: "foo",
+ Component: ControlPanel,
+ searchMenuTypes: ["filter"],
+ searchViewId: false,
+ searchViewArch: `
+
+
+
+ `,
+ context: {
+ search_default_filter: true,
+ },
+ });
+ assert.deepEqual(getFacetTexts(target), ["Filter"]);
+ assert.deepEqual(getDomain(controlPanel), [["foo", "=", "abc"]]);
+
+ await toggleFilterMenu(target);
+ await openAddCustomFilterDialog(target);
+ await click(target, ".o_domain_add_node_button .fa-plus");
+
+ const connectorEl = target.querySelector(".o_domain_tree_connector_selector");
+ await click(connectorEl.querySelector("button.o_domain_tree_connector_caret"));
+ await click(
+ [...target.querySelectorAll(".o_domain_tree_connector_selector .dropdown-item")].find(
+ (el) => el.innerText === "all"
+ )
+ );
+
+ await click([...target.querySelectorAll(".o_domain_add_node_button .fa-sitemap")].at(-1));
+ await click([...target.querySelectorAll(".o_domain_add_node_button .fa-sitemap")].at(-1));
+ await click(target.querySelector(".modal footer button"));
+
+ assert.deepEqual(getFacetTexts(target), [
+ "Filter",
+ "ID = 1",
+ "ID = 1",
+ "ID = 1 or ID = 1 or ( ID = 1 and ID = 1 )",
+ ]);
+ assert.deepEqual(getDomain(controlPanel), [
+ "&",
+ ["foo", "=", "abc"],
+ "&",
+ ["id", "=", 1],
+ "&",
+ ["id", "=", 1],
+ "|",
+ "|",
+ ["id", "=", 1],
+ ["id", "=", 1],
+ "&",
+ ["id", "=", 1],
+ ["id", "=", 1],
+ ]);
+ });
+
+ QUnit.test("Add a custom filter containing an expression", async function (assert) {
+ patchWithCleanup(odoo, { debug: true });
+
+ const controlPanel = await makeWithSearch({
+ serverData,
+ resModel: "foo",
+ Component: ControlPanel,
+ searchMenuTypes: ["filter"],
+ searchViewId: false,
+ searchViewArch: ``,
+ });
+ assert.deepEqual(getFacetTexts(target), []);
+ assert.deepEqual(getDomain(controlPanel), []);
+
+ await toggleFilterMenu(target);
+ await openAddCustomFilterDialog(target);
+ await editInput(target, ".o_domain_debug_input", `[("foo", "in", [uid, 1, "a"])]`);
+ await click(target.querySelector(".modal footer button"));
+
+ assert.deepEqual(getFacetTexts(target), [`Foo in uid or 1 or "a"`]);
+ assert.deepEqual(getDomain(controlPanel), [
+ ["foo", "in", [7, 1, "a"]], // uid = 7
+ ]);
+ });
+
+ QUnit.test("Add a custom filter containing a between operator", async function (assert) {
+ patchWithCleanup(odoo, { debug: true });
+
+ const controlPanel = await makeWithSearch({
+ serverData,
+ resModel: "foo",
+ Component: ControlPanel,
+ searchMenuTypes: ["filter"],
+ searchViewId: false,
+ searchViewArch: ``,
+ });
+ assert.deepEqual(getFacetTexts(target), []);
+ assert.deepEqual(getDomain(controlPanel), []);
+
+ await toggleFilterMenu(target);
+ await openAddCustomFilterDialog(target);
+ await editInput(target, ".o_domain_debug_input", `[("id", "between", [0, 10])]`);
+ await click(target.querySelector(".modal footer button"));
+
+ assert.deepEqual(getFacetTexts(target), [`ID is between 0 and 10`]);
+ assert.deepEqual(getDomain(controlPanel), ["&", ["id", ">=", 0], ["id", "<=", 10]]);
});
});
diff --git a/addons/web/static/tests/search/helpers.js b/addons/web/static/tests/search/helpers.js
index 0883ab40e21..55cc640c057 100644
--- a/addons/web/static/tests/search/helpers.js
+++ b/addons/web/static/tests/search/helpers.js
@@ -13,6 +13,7 @@ import {
} from "@web/../tests/helpers/utils";
import { commandService } from "@web/core/commands/command_service";
import { dialogService } from "@web/core/dialog/dialog_service";
+import { fieldService } from "@web/core/field_service";
import { hotkeyService } from "@web/core/hotkeys/hotkey_service";
import { notificationService } from "@web/core/notifications/notification_service";
import { ormService } from "@web/core/orm_service";
@@ -31,6 +32,7 @@ const favoriteMenuRegistry = registry.category("favoriteMenu");
export function setupControlPanelServiceRegistry() {
serviceRegistry.add("action", actionService);
serviceRegistry.add("dialog", dialogService);
+ serviceRegistry.add("field", fieldService);
serviceRegistry.add("hotkey", hotkeyService);
serviceRegistry.add("notification", notificationService);
serviceRegistry.add("orm", ormService);
@@ -203,7 +205,7 @@ export async function toggleFilterMenu(el) {
await click(findItem(el, `.o_filter_menu button.dropdown-toggle`));
}
-export async function openAdvancedSearchDialog(el) {
+export async function openAddCustomFilterDialog(el) {
await click(findItem(el, `.o_filter_menu .dropdown-item:not(.o_menu_item)`));
}
diff --git a/addons/web/static/tests/search/search_bar_tests.js b/addons/web/static/tests/search/search_bar_tests.js
index a4f7281112d..5ae0304fae8 100644
--- a/addons/web/static/tests/search/search_bar_tests.js
+++ b/addons/web/static/tests/search/search_bar_tests.js
@@ -4,8 +4,10 @@ import { ControlPanel } from "@web/search/control_panel/control_panel";
import {
click,
getFixture,
+ getNodesTextContent,
makeDeferred,
nextTick,
+ patchDate,
patchTimeZone,
patchWithCleanup,
triggerEvent,
@@ -16,9 +18,16 @@ import {
makeWithSearch,
removeFacet,
setupControlPanelServiceRegistry,
+ toggleComparisonMenu,
+ toggleGroupByMenu,
+ toggleMenuItem,
validateSearch,
} from "./helpers";
+function getContext(controlPanel) {
+ return controlPanel.env.searchModel.context;
+}
+
function getDomain(controlPanel) {
return controlPanel.env.searchModel.domain;
}
@@ -1413,4 +1422,271 @@ QUnit.module("Search", (hooks) => {
assert.strictEqual(items.length, 2, "Should show the search result");
assert.strictEqual(items[1].innerText, "My Text (Bar 2) for: a");
});
+
+ QUnit.test("edit a filter", async function (assert) {
+ await makeWithSearch({
+ serverData,
+ resModel: "partner",
+ Component: ControlPanel,
+ searchMenuTypes: ["groupBy"], // we need it to have facet (see facets getter in search_model)
+ searchViewId: false,
+ searchViewArch: `
+
+
+
+
+ `,
+ context: {
+ search_default_filter: true,
+ search_default_bool: true,
+ },
+ });
+ assert.deepEqual(getFacetTexts(target), ["Filter", "Bool"]);
+ assert.containsN(target, ".o_searchview_facet .o_searchview_facet_label", 2);
+ assert.containsOnce(
+ target,
+ ".o_searchview_facet.o_facet_with_domain .o_searchview_facet_label"
+ );
+ assert.containsNone(target, ".modal");
+
+ await click(target, ".o_facet_with_domain .o_searchview_facet_label");
+ assert.containsOnce(target, ".modal");
+ assert.strictEqual(target.querySelector(".modal header").innerText, "Modify Condition");
+ assert.containsOnce(target, ".modal .o_domain_selector");
+ assert.containsOnce(target, ".o_domain_leaf");
+ assert.deepEqual(getNodesTextContent(target.querySelectorAll(".modal footer button")), [
+ "Confirm",
+ "Discard",
+ ]);
+ assert.strictEqual(
+ target.querySelector(".o_model_field_selector_value").innerText,
+ "Birthday"
+ );
+ assert.strictEqual(
+ target.querySelector(".o_domain_leaf_operator_select").value,
+ "greater_equal"
+ );
+ assert.strictEqual(target.querySelector(".o_ds_expr_value").innerText, "context_today()");
+ assert.notOk(target.querySelector(".modal footer button").disabled);
+
+ await click(target, ".o_domain_delete_node_button");
+ assert.containsNone(target, ".o_domain_leaf");
+ assert.ok(target.querySelector(".modal footer button").disabled);
+
+ await click(target, ".modal .o_domain_add_first_node_button");
+ assert.containsOnce(target, ".o_domain_leaf");
+ assert.strictEqual(target.querySelector(".o_model_field_selector_value").innerText, "ID");
+ assert.strictEqual(target.querySelector(".o_domain_leaf_operator_select").value, "equal");
+ assert.strictEqual(target.querySelector(".o_ds_value_cell .o_input").value, "1");
+
+ await click(target.querySelector(".modal footer button"));
+ assert.containsNone(target, ".modal");
+ assert.deepEqual(getFacetTexts(target), ["Bool", "ID = 1"]);
+ });
+
+ QUnit.test(
+ "edit a filter with context: context is kept after edition",
+ async function (assert) {
+ const controlPanel = await makeWithSearch({
+ serverData,
+ resModel: "partner",
+ Component: ControlPanel,
+ searchViewId: false,
+ searchViewArch: `
+
+
+
+ `,
+ context: {
+ search_default_filter: true,
+ },
+ });
+ assert.deepEqual(getFacetTexts(target), ["Filter"]);
+ assert.deepEqual(getContext(controlPanel).specialKey, "abc");
+
+ await click(target, ".o_facet_with_domain .o_searchview_facet_label");
+ await click(target.querySelector(".modal footer button"));
+
+ assert.deepEqual(getFacetTexts(target), [`Foo = "abc"`]);
+ assert.deepEqual(getContext(controlPanel).specialKey, "abc");
+ }
+ );
+
+ QUnit.test("edit a favorite", async function (assert) {
+ const irFilters = [
+ {
+ context: "{ 'some_key': 'some_value', 'group_by': ['bool'] }",
+ domain: "[('foo', 'ilike', 'abc')]",
+ id: 1,
+ is_default: true,
+ name: "My favorite",
+ sort: "[]",
+ user_id: [2, "Mitchell Admin"],
+ },
+ ];
+ await makeWithSearch({
+ serverData,
+ resModel: "partner",
+ Component: ControlPanel,
+ searchMenuTypes: ["groupBy"], // we need it to have facet (see facets getter in search_model)
+ searchViewId: false,
+ searchViewArch: `
+
+
+
+ `,
+ irFilters,
+ });
+ assert.deepEqual(getFacetTexts(target), ["My favorite"]);
+ assert.containsOnce(
+ target,
+ ".o_searchview_facet.o_facet_with_domain .o_searchview_facet_label"
+ );
+
+ await toggleGroupByMenu(target);
+ await toggleMenuItem(target, "Company");
+
+ assert.deepEqual(getFacetTexts(target), ["My favorite", "Company"]);
+
+ assert.containsN(target, ".o_searchview_facet .o_searchview_facet_label", 2);
+ assert.containsOnce(
+ target,
+ ".o_searchview_facet.o_facet_with_domain .o_searchview_facet_label"
+ );
+
+ await click(target, ".o_facet_with_domain .o_searchview_facet_label");
+ assert.containsOnce(target, ".modal");
+ assert.strictEqual(target.querySelector(".o_model_field_selector_value").innerText, "Foo");
+ assert.strictEqual(target.querySelector(".o_domain_leaf_operator_select").value, "ilike");
+ assert.strictEqual(target.querySelector(".o_ds_value_cell .o_input").value, "abc");
+
+ await click(target.querySelector(".modal footer button"));
+ assert.containsNone(target, ".modal");
+ assert.deepEqual(getFacetTexts(target), ["Bool>Company", 'Foo contains "abc"']);
+ });
+
+ QUnit.test("edit a date filter with comparison active", async function (assert) {
+ patchDate(2023, 3, 28, 13, 40, 0);
+ await makeWithSearch({
+ serverData,
+ resModel: "partner",
+ Component: ControlPanel,
+ searchMenuTypes: ["filter", "comparison"],
+ searchViewId: false,
+ searchViewArch: `
+
+
+
+ `,
+ context: {
+ search_default_birthday: true,
+ },
+ });
+ assert.deepEqual(getFacetTexts(target), ["Birthday: April 2023"]);
+ assert.containsOnce(
+ target,
+ ".o_searchview_facet.o_facet_with_domain .o_searchview_facet_label"
+ );
+
+ await toggleComparisonMenu(target);
+ await toggleMenuItem(target);
+
+ assert.deepEqual(getFacetTexts(target), [
+ "Birthday: April 2023",
+ "Birthday: Previous Period",
+ ]);
+ assert.containsOnce(
+ target,
+ ".o_searchview_facet.o_facet_with_domain .o_searchview_facet_label"
+ );
+
+ await click(target, ".o_facet_with_domain .o_searchview_facet_label");
+ assert.containsOnce(target, ".modal");
+ assert.containsOnce(target, ".o_domain_leaf");
+ assert.strictEqual(
+ target.querySelector(".o_model_field_selector_value").innerText,
+ "Birthday"
+ );
+ assert.strictEqual(target.querySelector(".o_domain_leaf_operator_select").value, "between");
+ assert.strictEqual(
+ target.querySelector(".o_ds_value_cell .o_datetime_input:nth-child(1)").value,
+ "04/01/2023"
+ );
+ assert.strictEqual(
+ target.querySelector(".o_ds_value_cell .o_datetime_input:nth-child(3)").value,
+ "04/30/2023"
+ );
+
+ await click(target.querySelector(".modal footer button"));
+ assert.containsNone(target, ".modal");
+ assert.deepEqual(getFacetTexts(target), [
+ `Birthday is between "2023-04-01" and "2023-04-30"`,
+ ]);
+ });
+
+ QUnit.test("edit a field", async function (assert) {
+ await makeWithSearch({
+ serverData,
+ resModel: "partner",
+ Component: ControlPanel,
+ searchViewId: false,
+ searchViewArch: `
+
+
+
+ `,
+ context: {
+ search_default_foo: "abc",
+ },
+ });
+ assert.deepEqual(getFacetTexts(target), ["Foo\nabc"]);
+ assert.containsOnce(
+ target,
+ ".o_searchview_facet.o_facet_with_domain .o_searchview_facet_label"
+ );
+
+ await editSearch(target, "def");
+ const searchInput = target.querySelector(".o_searchview input");
+ await triggerEvent(searchInput, null, "keydown", { key: "ArrowDown" });
+ await triggerEvent(searchInput, null, "keydown", { key: "Enter" }); // select
+
+ assert.deepEqual(getFacetTexts(target), ["Foo\nabcordef"]);
+
+ await click(target, ".o_facet_with_domain .o_searchview_facet_label");
+ assert.containsN(target, ".o_domain_leaf", 2);
+
+ assert.strictEqual(
+ target.querySelector(".o_domain_leaf:nth-child(1) .o_model_field_selector_value")
+ .innerText,
+ "Foo"
+ );
+ assert.strictEqual(
+ target.querySelector(".o_domain_leaf:nth-child(1) .o_domain_leaf_operator_select")
+ .value,
+ "ilike"
+ );
+ assert.strictEqual(
+ target.querySelector(".o_domain_leaf:nth-child(1) .o_domain_leaf_value_input").value,
+ "abc"
+ );
+
+ assert.strictEqual(
+ target.querySelector(".o_domain_leaf:nth-child(2) .o_model_field_selector_value")
+ .innerText,
+ "Foo"
+ );
+ assert.strictEqual(
+ target.querySelector(".o_domain_leaf:nth-child(2) .o_domain_leaf_operator_select")
+ .value,
+ "ilike"
+ );
+ assert.strictEqual(
+ target.querySelector(".o_domain_leaf:nth-child(2) .o_domain_leaf_value_input").value,
+ "def"
+ );
+
+ await click(target.querySelector(".modal footer button"));
+
+ assert.deepEqual(getFacetTexts(target), [`Foo contains "abc" or Foo contains "def"`]);
+ });
});
diff --git a/addons/web/static/tests/views/fields/domain_field_tests.js b/addons/web/static/tests/views/fields/domain_field_tests.js
index f8cd180964a..47e5dadf546 100644
--- a/addons/web/static/tests/views/fields/domain_field_tests.js
+++ b/addons/web/static/tests/views/fields/domain_field_tests.js
@@ -9,12 +9,11 @@ import {
getFixture,
makeDeferred,
nextTick,
+ patchDate,
patchWithCleanup,
triggerEvent,
} from "@web/../tests/helpers/utils";
import { createWebClient, doAction } from "@web/../tests/webclient/helpers";
-import { fieldService } from "@web/core/field_service";
-import { registry } from "@web/core/registry";
import { getPickerCell } from "../../core/datetime/datetime_test_helpers";
let serverData;
@@ -92,9 +91,7 @@ QUnit.module("Fields", (hooks) => {
},
},
};
-
setupViewRegistries();
- registry.category("services").add("field", fieldService);
});
QUnit.module("DomainField");
@@ -103,7 +100,7 @@ QUnit.module("Fields", (hooks) => {
"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
+ // are handled by the domain editor
serverData.models.partner.records[0].foo = `[("int_field", "=", uid)]`;
await makeView({
@@ -119,9 +116,9 @@ QUnit.module("Fields", (hooks) => {
});
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."
+ target.querySelector(".o_ds_value_cell").textContent,
+ "uid",
+ "The widget should show the dynamic filter."
);
}
);
@@ -129,10 +126,8 @@ QUnit.module("Fields", (hooks) => {
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" };
+ serverData.models.partner.records[0].foo = `[("datetime", "=", context_today())]`;
await makeView({
type: "form",
@@ -145,8 +140,12 @@ QUnit.module("Fields", (hooks) => {
`,
});
- // The input field should display that the date is invalid
- assert.equal(target.querySelector(".o_datetime_input").value, "Invalid DateTime");
+ assert.equal(
+ target.querySelector(".o_ds_value_cell .o_ds_expr_value").innerText,
+ "context_today()"
+ );
+
+ await click(target, ".o_ds_value_cell .fa.fa-times");
// Change the date in the datepicker
await click(target, ".o_datetime_input");
@@ -157,7 +156,10 @@ QUnit.module("Fields", (hooks) => {
await clickDiscard(target);
// Open the datepicker again
- await click(target, ".o_datetime_input");
+ assert.equal(
+ target.querySelector(".o_ds_value_cell .o_ds_expr_value").innerText,
+ "context_today()"
+ );
}
);
@@ -181,11 +183,7 @@ QUnit.module("Fields", (hooks) => {
// 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"
- );
+ assert.containsOnce(target, ".o_domain_add_first_node_button");
// Clicking on the button should add the [["id", "=", "1"]] domain, so
// there should be a field selector in the DOM
@@ -195,23 +193,14 @@ QUnit.module("Fields", (hooks) => {
// Focusing the field selector input should open the field selector
// popover
await click(target, ".o_model_field_selector");
- assert.containsOnce(
- document.body,
- ".o_model_field_selector_popover",
- "field selector popover should be visible"
- );
- assert.containsOnce(
- document.body,
- ".o_model_field_selector_popover_search input",
- "field selector popover should contain a search input"
- );
+ assert.containsOnce(document.body, ".o_model_field_selector_popover");
+ assert.containsOnce(document.body, ".o_model_field_selector_popover_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_model_field_selector_popover_item").textContent,
- "Color index",
- "field selector popover should contain 'Color index' field"
+ document.body.querySelector(".o_model_field_selector_popover_item_name").textContent,
+ "Color index"
);
// Clicking on this field should close the popover, then changing the
@@ -231,10 +220,7 @@ QUnit.module("Fields", (hooks) => {
// 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'"
- );
+ assert.ok(target.querySelector(".o_field_domain").textContent.includes("Color index"));
});
QUnit.test("using binary field in domain widget", async function (assert) {
@@ -405,16 +391,8 @@ QUnit.module("Fields", (hooks) => {
}
},
});
- 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"
- );
+ assert.containsOnce(target, ".o_field_widget[name='foo']:not(.o_field_empty)");
+ assert.containsNone(target, ".o_field_widget[name='foo'] .text-warning");
});
QUnit.test("basic domain field: show the selection", async function (assert) {
@@ -726,11 +704,7 @@ QUnit.module("Fields", (hooks) => {
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")]
- ]
- `;
+ 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";
@@ -767,11 +741,7 @@ QUnit.module("Fields", (hooks) => {
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")]
- ]
- `;
+ 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);
@@ -780,6 +750,7 @@ QUnit.module("Fields", (hooks) => {
QUnit.test("domain field: edit through selector (dynamic content)", async function (assert) {
patchWithCleanup(odoo, { debug: true });
+ patchDate(2020, 8, 5, 0, 0, 0);
let rawDomain = `[("date", ">=", context_today())]`;
serverData.models.partner.records[0].foo = rawDomain;
@@ -818,7 +789,12 @@ QUnit.module("Fields", (hooks) => {
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_datetime_input", "there should be a datepicker input");
+ assert.containsOnce(target, ".o_ds_expr_value", "there should be an expression");
+
+ await click(target, ".o_ds_expr_value button");
+ rawDomain = `[("date", ">=", "2020-09-05")]`;
+ assert.containsOnce(target, ".o_datetime_input", "there should be a datepicker");
+ assert.verifySteps(["search_count"]);
// Open and close the datepicker
await click(target, ".o_datetime_input");
@@ -865,13 +841,15 @@ QUnit.module("Fields", (hooks) => {
"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"
+
+ await editInput(target, ".o_field_widget[name=model_name] input", "partner");
+ assert.strictEqual(
+ target
+ .querySelector('.o_field_widget[name="display_name"] .o_field_domain_panel')
+ .innerText.toLowerCase(),
+ "5 record(s)"
);
- assert.verifySteps(["test"]);
+ assert.verifySteps(["partner"]);
});
QUnit.test("domain field in kanban view", async function (assert) {
@@ -968,11 +946,6 @@ QUnit.module("Fields", (hooks) => {
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"
- );
+ assert.containsOnce(target, ".modal", "the domain is invalid: the dialog is not closed");
});
});
diff --git a/addons/web/static/tests/views/fields/properties_field_tests.js b/addons/web/static/tests/views/fields/properties_field_tests.js
index 753a131ce30..0ad4f8b87d2 100644
--- a/addons/web/static/tests/views/fields/properties_field_tests.js
+++ b/addons/web/static/tests/views/fields/properties_field_tests.js
@@ -15,8 +15,6 @@ import { Many2XAutocomplete } from "@web/views/fields/relational_utils";
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
import { SelectCreateDialog } from "@web/views/view_dialogs/select_create_dialog";
import { browser } from "@web/core/browser/browser";
-import { fieldService } from "@web/core/field_service";
-import { registry } from "@web/core/registry";
import {
getPickerApplyButton,
getPickerCell,
@@ -230,7 +228,6 @@ QUnit.module("Fields", (hooks) => {
};
setupViewRegistries();
- registry.category("services").add("field", fieldService);
patchWithCleanup(browser, {
setTimeout: (fn) => fn(),
diff --git a/addons/web/static/tests/views/fields/text_field_tests.js b/addons/web/static/tests/views/fields/text_field_tests.js
index 0fee824b325..c91d7f5df53 100644
--- a/addons/web/static/tests/views/fields/text_field_tests.js
+++ b/addons/web/static/tests/views/fields/text_field_tests.js
@@ -13,7 +13,6 @@ import {
triggerHotkey,
} from "@web/../tests/helpers/utils";
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
-import { fieldService } from "@web/core/field_service";
const serviceRegistry = registry.category("services");
@@ -595,8 +594,6 @@ QUnit.module("Fields", (hooks) => {
});
QUnit.test("field text with dynamic placeholder", async (assert) => {
- registry.category("services").add("field", fieldService);
-
serverData.models.partner.fields.model_reference_field = {
string: "Model Reference Field",
type: "char",
diff --git a/addons/web/static/tests/views/helpers.js b/addons/web/static/tests/views/helpers.js
index fd2035dc2c5..c82dca7fd85 100644
--- a/addons/web/static/tests/views/helpers.js
+++ b/addons/web/static/tests/views/helpers.js
@@ -5,7 +5,6 @@ import { getFixture, mount, nextTick } from "@web/../tests/helpers/utils";
import { createDebugContext } from "@web/core/debug/debug_context";
import { Dialog } from "@web/core/dialog/dialog";
import { MainComponentsContainer } from "@web/core/main_components_container";
-import { popoverService } from "@web/core/popover/popover_service";
import { registry } from "@web/core/registry";
import { View, getDefaultConfig } from "@web/views/view";
import {
@@ -145,8 +144,7 @@ export function setupViewRegistries() {
{ force: true }
);
serviceRegistry.add("router", makeFakeRouterService(), { force: true });
- serviceRegistry.add("localization", makeFakeLocalizationService()), { force: true };
- serviceRegistry.add("popover", popoverService, { force: true });
+ serviceRegistry.add("localization", makeFakeLocalizationService());
serviceRegistry.add("company", fakeCompanyService);
}
diff --git a/addons/web/static/tests/webclient/helpers.js b/addons/web/static/tests/webclient/helpers.js
index e040a14a9a4..ff072288930 100644
--- a/addons/web/static/tests/webclient/helpers.js
+++ b/addons/web/static/tests/webclient/helpers.js
@@ -58,6 +58,7 @@ import { standaloneAdapter } from "web.OwlCompatibility";
import { overlayService } from "@web/core/overlay/overlay_service";
import { Component, onMounted, xml } from "@odoo/owl";
+import { fieldService } from "@web/core/field_service";
const actionRegistry = registry.category("actions");
const serviceRegistry = registry.category("services");
@@ -87,6 +88,7 @@ export function setupWebClientRegistries() {
command: () => commandService,
dialog: () => dialogService,
effect: () => effectService,
+ field: () => fieldService,
hotkey: () => hotkeyService,
http: () => makeFakeHTTPService(),
legacy_service_provider: () => legacyServiceProvider,
|