diff --git a/addons/web/static/src/core/domain_selector/domain_selector.js b/addons/web/static/src/core/domain_selector/domain_selector.js
index 012884215ec..6bcc5115ef7 100644
--- a/addons/web/static/src/core/domain_selector/domain_selector.js
+++ b/addons/web/static/src/core/domain_selector/domain_selector.js
@@ -1,195 +1,29 @@
/** @odoo-module **/
-import { Domain } from "@web/core/domain";
-import { DomainSelectorRootNode } from "./domain_selector_root_node";
+import { Dropdown } from "@web/core/dropdown/dropdown";
+import { DropdownItem } from "@web/core/dropdown/dropdown_item";
-import { Component } from "@odoo/owl";
+import { Component, onWillStart, onWillUpdateProps, useState } from "@odoo/owl";
+import { Domain } from "@web/core/domain";
+import { DomainTreeBuilder } from "@web/core/domain_selector/domain_tree_builder";
+import {
+ getDefaultFieldValue,
+ getEditorInfo,
+ getOperatorsInfo,
+} from "@web/core/domain_selector/domain_selector_fields";
+import { BranchDomainNode } from "@web/core/domain_selector/domain_selector_nodes";
+import { findOperator } from "@web/core/domain_selector/domain_selector_operators";
+import { ModelFieldSelector } from "@web/core/model_field_selector/model_field_selector";
+import { useService } from "@web/core/utils/hooks";
export class DomainSelector extends Component {
- setup() {
- this.nextNodeId = 0;
- }
-
- buildTree() {
- try {
- const domain = new Domain(this.props.value);
- const ctx = {
- parent: null,
- index: 0,
- domain: domain.toList(),
- get currentElement() {
- return ctx.domain[ctx.index];
- },
- next() {
- ctx.index++;
- },
- getFullDomain() {
- return rootNode.computeDomain().toString();
- },
- };
-
- const rootNode = this.makeRootNode(ctx);
- ctx.parent = rootNode;
- this.traverseNode(ctx);
-
- return ctx.parent;
- } catch {
- // WOWL TODO: rethrow error when not the expected type
- return false;
- }
- }
-
- traverseNode(ctx) {
- if (ctx.index < ctx.domain.length) {
- if (typeof ctx.currentElement === "string" && ["&", "|"].includes(ctx.currentElement)) {
- this.traverseBranchNode(ctx);
- } else {
- this.traverseLeafNode(ctx);
- }
- }
- }
- traverseBranchNode(ctx) {
- if (ctx.parent.type !== "branch" || ctx.parent.operator !== ctx.currentElement) {
- const node = this.makeBranchNode(ctx, ctx.currentElement, []);
- ctx.parent.operands.push(node);
- ctx = Object.assign(Object.create(ctx), { parent: node });
- }
- ctx.next();
- this.traverseNode(ctx);
- ctx.next();
- this.traverseNode(ctx);
- }
- traverseLeafNode(ctx) {
- const condition = ctx.currentElement;
- const [leftOperand, operator, rightOperand] = condition;
- const node = this.makeLeafNode(ctx, operator, [leftOperand, rightOperand]);
- ctx.parent.operands.push(node);
- }
-
- makeBranchNode(ctx, operator, operands) {
- const updateDomain = () => this.props.update(ctx.getFullDomain());
- const makeFakeNode = this.makeFakeNode.bind(this);
-
- return {
- type: "branch",
- id: this.nextNodeId++,
- operator,
- operands,
- computeDomain() {
- return Domain.combine(
- this.operands.map((operand) => operand.computeDomain()),
- this.operator === "&" ? "AND" : "OR"
- );
- },
- update(operator) {
- this.operator = operator;
- updateDomain();
- },
- insert(newNodeType) {
- const newNode = makeFakeNode(ctx, newNodeType);
- const operands = ctx.parent.operands;
- operands.splice(operands.indexOf(this) + 1, 0, newNode);
- updateDomain();
- },
- delete() {
- const operands = ctx.parent.operands;
- operands.splice(operands.indexOf(this), 1);
- updateDomain();
- },
- };
- }
- makeLeafNode(ctx, operator, operands) {
- const updateDomain = () => this.props.update(ctx.getFullDomain());
- const makeFakeNode = this.makeFakeNode.bind(this);
-
- return {
- type: "leaf",
- id: this.nextNodeId++,
- operator,
- operands,
- computeDomain() {
- return new Domain([[this.operands[0], this.operator, this.operands[1]]]);
- },
- update(changes) {
- if ("fieldName" in changes) {
- this.operands[0] = changes.fieldName;
- }
- if ("operator" in changes) {
- this.operator = changes.operator;
- }
- if ("value" in changes) {
- this.operands[1] = changes.value;
- }
- updateDomain();
- },
- insert(newNodeType) {
- const newNode = makeFakeNode(ctx, newNodeType);
- const operands = ctx.parent.operands;
- operands.splice(operands.indexOf(this) + 1, 0, newNode);
- updateDomain();
- },
- delete() {
- const operands = ctx.parent.operands;
- operands.splice(operands.indexOf(this), 1);
- updateDomain();
- },
- };
- }
- makeRootNode(ctx) {
- const updateDomain = (...args) => this.props.update(...args);
- const makeFakeNode = this.makeFakeNode.bind(this);
-
- return {
- type: "root",
- id: this.nextNodeId++,
- operator: "&",
- operands: [],
- computeDomain() {
- return Domain.combine(
- this.operands.map((operand) => operand.computeDomain()),
- "AND"
- );
- },
- update(newValue, fromDebug) {
- if (typeof newValue === "string") {
- updateDomain(newValue, fromDebug);
- } else if (this.operands.length) {
- this.operands[0].update(newValue);
- }
- },
- insert(newNodeType) {
- const newNode = makeFakeNode(ctx, newNodeType);
- if (ctx.parent) {
- const operands = ctx.parent.operands;
- operands.splice(operands.indexOf(this) + 1, 0, newNode);
- } else {
- this.operands.push(newNode);
- }
- updateDomain(ctx.getFullDomain());
- },
- delete() {},
- };
- }
-
- makeFakeNode(ctx, type) {
- const [field, op, value] = this.props.defaultLeafValue;
- if (type === "branch") {
- return this.makeBranchNode(ctx, ctx.parent.operator === "&" ? "|" : "&", [
- this.makeLeafNode(ctx, op, [field, value]),
- this.makeLeafNode(ctx, op, [field, value]),
- ]);
- } else {
- return this.makeLeafNode(ctx, op, [field, value]);
- }
- }
-}
-
-Object.assign(DomainSelector, {
- template: "web._DomainSelector",
- components: {
- DomainSelectorRootNode,
- },
- props: {
+ static template = "web._DomainSelector";
+ static components = {
+ Dropdown,
+ DropdownItem,
+ ModelFieldSelector,
+ };
+ static props = {
className: { type: String, optional: true },
resModel: String,
value: String,
@@ -198,11 +32,186 @@ Object.assign(DomainSelector, {
update: { type: Function, optional: true },
isDebugMode: { type: Boolean, optional: true },
defaultLeafValue: { type: Array, optional: true },
- },
- defaultProps: {
+ };
+ static defaultProps = {
readonly: true,
update: () => {},
isDebugMode: false,
defaultLeafValue: ["id", "=", 1],
- },
-});
+ };
+
+ setup() {
+ this.view = useService("view");
+ this.treeBuilder = new DomainTreeBuilder();
+ this.tree = useState({
+ isSupported: false,
+ root: null,
+ });
+ onWillStart(() => this.onPropsUpdated(this.props));
+ onWillUpdateProps((np) => this.onPropsUpdated(np));
+ }
+
+ async onPropsUpdated(p) {
+ try {
+ // try to parse and execute the domain, if it fails then the domain is not supported.
+ const domain = new Domain(p.value);
+ domain.toList();
+ this.tree.root = this.treeBuilder.build(
+ domain,
+ await this.loadFieldDefs(p.resModel, this.extractFieldsFromDomain(domain))
+ );
+ this.defaultLeaf = this.treeBuilder.build(
+ new Domain([p.defaultLeafValue]),
+ await this.loadFieldDefs(p.resModel, [p.defaultLeafValue[0]])
+ ).children[0];
+ this.tree.isSupported = true;
+ } catch {
+ this.tree.isSupported = false;
+ this.tree.root = null;
+ }
+ }
+
+ extractFieldsFromDomain(domain) {
+ const fields = [];
+ for (const node of domain.ast.value) {
+ if ([4, 10].includes(node.type)) {
+ if (node.value[0].type === 1 || [0, 1].includes(node.value[0].value)) {
+ fields.push(node.value[0].value);
+ }
+ }
+ }
+ return fields;
+ }
+
+ notifyChanges() {
+ this.props.update(this.tree.root.toDomain().toString());
+ }
+
+ async loadFieldDefs(resModel, fields) {
+ const promises = [];
+ const fieldDefs = {};
+
+ for (const field of fields) {
+ promises.push(
+ this.loadFieldDef(resModel, field).then((info) => {
+ fieldDefs[field] = info;
+ })
+ );
+ }
+
+ await Promise.all(promises);
+ return fieldDefs;
+ }
+
+ async loadFieldDef(resModel, field) {
+ if ("01".includes(field.toString())) {
+ return { type: "integer" };
+ }
+
+ const names = field.length ? field.split(".") : [];
+ let currentModel = resModel;
+ let fieldDef = null;
+ for (const name of names) {
+ const model = await this.view.loadFields(currentModel);
+ fieldDef = model[name];
+ currentModel = fieldDef.relation;
+ }
+ return fieldDef;
+ }
+
+ createNewLeaf() {
+ return this.defaultLeaf.clone();
+ }
+
+ createNewBranch(operator) {
+ return new BranchDomainNode(operator, [this.createNewLeaf(), this.createNewLeaf()]);
+ }
+
+ insertRootLeaf(parent) {
+ parent.add(this.createNewLeaf());
+ this.notifyChanges();
+ }
+
+ insertLeaf(parent, node) {
+ parent.insertAfter(node.id, this.createNewLeaf());
+ this.notifyChanges();
+ }
+
+ insertBranch(parent, node) {
+ const nextOperator = parent.operator === "AND" ? "OR" : "AND";
+ parent.insertAfter(node.id, this.createNewBranch(nextOperator));
+ this.notifyChanges();
+ }
+
+ delete(parent, node) {
+ parent.delete(node.id);
+ this.notifyChanges();
+ }
+
+ updateBranchOperator(node, operator) {
+ node.operator = operator;
+ this.notifyChanges();
+ }
+
+ async updateField(node, field) {
+ const fieldDef = await this.loadFieldDef(this.props.resModel, field);
+ node.field = { ...fieldDef, name: field };
+ node.operator = getOperatorsInfo(fieldDef.type)[0];
+ node.value = getDefaultFieldValue(fieldDef);
+ this.notifyChanges();
+ }
+
+ updateLeafOperator(node, operator) {
+ const previousOperator = node.operator;
+ node.operator = findOperator(operator);
+ if (previousOperator.valueMode !== node.operator.valueMode) {
+ switch (node.operator.valueMode) {
+ case "none": {
+ node.value = false;
+ break;
+ }
+ case "multiple": {
+ node.value = previousOperator.valueMode === "none" ? [] : [node.value];
+ break;
+ }
+ default: {
+ if (previousOperator.valueMode === "none") {
+ node.value = getDefaultFieldValue(node.field);
+ } else {
+ node.value = node.value[0];
+ }
+ break;
+ }
+ }
+ }
+ this.notifyChanges();
+ }
+
+ updateLeafValue(node, value) {
+ node.value = value;
+ this.notifyChanges();
+ }
+
+ onDebugValueChange(value) {
+ return this.props.update(value, true);
+ }
+
+ getEditorInfo(node) {
+ return getEditorInfo(node.field.type, node.operator.key);
+ }
+
+ getOperatorsInfo(node) {
+ const operators = getOperatorsInfo(node.field.type);
+ if (!operators.some((op) => op.key === node.operator.key)) {
+ operators.push(node.operator);
+ }
+ return operators;
+ }
+
+ highlightNode(target, toggle, classNames) {
+ const nodeEl = target.closest(".o_domain_node");
+ for (const className of classNames.split(/\s+/i)) {
+ nodeEl.classList.toggle(className, toggle);
+ }
+ }
+}
diff --git a/addons/web/static/src/core/domain_selector/domain_selector.xml b/addons/web/static/src/core/domain_selector/domain_selector.xml
index 8aeccdf9b84..97e934ac10e 100644
--- a/addons/web/static/src/core/domain_selector/domain_selector.xml
+++ b/addons/web/static/src/core/domain_selector/domain_selector.xml
@@ -2,21 +2,178 @@
-
-
-
+
+
+
+
This domain is not supported.
+
+
+
+ Match all records
+
+
+
+
+
+ Match records with the following rule:
+
+
+
+ Match records with
+
+ of the following rules:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ all
+ any
+ none
+
+
+
+
+
+
+ all
+ any
+ none
+
+ all
+ any
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ is not set
+
+
+
+
+
+
+ ""
+
+
+ or
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/web/static/src/core/domain_selector/domain_selector_branch_node.js b/addons/web/static/src/core/domain_selector/domain_selector_branch_node.js
deleted file mode 100644
index b72c3b2fe06..00000000000
--- a/addons/web/static/src/core/domain_selector/domain_selector_branch_node.js
+++ /dev/null
@@ -1,30 +0,0 @@
-/** @odoo-module **/
-
-import { DomainSelectorBranchOperator } from "./domain_selector_branch_operator";
-import { DomainSelectorControlPanel } from "./domain_selector_control_panel";
-import { DomainSelectorLeafNode } from "./domain_selector_leaf_node";
-
-import { Component, useRef } from "@odoo/owl";
-
-export class DomainSelectorBranchNode extends Component {
- setup() {
- this.root = useRef("root");
- }
- onHoverDeleteNodeBtn(hovering) {
- this.root.el.classList.toggle("o_hover_btns", hovering);
- }
- onHoverInsertLeafNodeBtn(hovering) {
- this.root.el.classList.toggle("o_hover_add_node", hovering);
- }
- onHoverInsertBranchNodeBtn(hovering) {
- this.root.el.classList.toggle("o_hover_add_node", hovering);
- this.root.el.classList.toggle("o_hover_add_inset_node", hovering);
- }
-}
-DomainSelectorBranchNode.template = "web.DomainSelectorBranchNode";
-DomainSelectorBranchNode.components = {
- DomainSelectorBranchNode,
- DomainSelectorBranchOperator,
- DomainSelectorControlPanel,
- DomainSelectorLeafNode,
-};
diff --git a/addons/web/static/src/core/domain_selector/domain_selector_branch_node.xml b/addons/web/static/src/core/domain_selector/domain_selector_branch_node.xml
deleted file mode 100644
index 359ccb224c7..00000000000
--- a/addons/web/static/src/core/domain_selector/domain_selector_branch_node.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
-
-
-
-
- of:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/web/static/src/core/domain_selector/domain_selector_branch_operator.js b/addons/web/static/src/core/domain_selector/domain_selector_branch_operator.js
deleted file mode 100644
index 55b092d88f4..00000000000
--- a/addons/web/static/src/core/domain_selector/domain_selector_branch_operator.js
+++ /dev/null
@@ -1,24 +0,0 @@
-/** @odoo-module **/
-import { Dropdown } from "@web/core/dropdown/dropdown";
-import { DropdownItem } from "@web/core/dropdown/dropdown_item";
-
-import { Component } from "@odoo/owl";
-
-export class DomainSelectorBranchOperator extends Component {
- onOperatorSelected(operator) {
- this.props.node.update(operator);
- }
-}
-DomainSelectorBranchOperator.components = {
- Dropdown,
- DropdownItem,
-};
-DomainSelectorBranchOperator.template = "web.DomainSelectorBranchOperator";
-DomainSelectorBranchOperator.props = {
- node: Object,
- readonly: Boolean,
- showCaret: {
- type: Boolean,
- optional: true,
- },
-};
diff --git a/addons/web/static/src/core/domain_selector/domain_selector_branch_operator.xml b/addons/web/static/src/core/domain_selector/domain_selector_branch_operator.xml
deleted file mode 100644
index 35c3e67281c..00000000000
--- a/addons/web/static/src/core/domain_selector/domain_selector_branch_operator.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
-
-
-
-
- all
- any
- none
-
- all
- any
-
-
-
-
-
- all
- any
- none
-
-
-
-
-
diff --git a/addons/web/static/src/core/domain_selector/domain_selector_control_panel.js b/addons/web/static/src/core/domain_selector/domain_selector_control_panel.js
deleted file mode 100644
index a24119d2405..00000000000
--- a/addons/web/static/src/core/domain_selector/domain_selector_control_panel.js
+++ /dev/null
@@ -1,39 +0,0 @@
-/** @odoo-module **/
-
-import { Component, toRaw } from "@odoo/owl";
-
-export class DomainSelectorControlPanel extends Component {
- deleteNode() {
- this.props.node.delete();
- }
-
- insertNode(newNodeType) {
- toRaw(this.props.node).insert(newNodeType); // FIXME WOWL reactivity
- }
-
- onEnterDeleteNodeBtn() {
- this.props.onHoverDeleteNodeBtn(true);
- }
- onLeaveDeleteNodeBtn() {
- this.props.onHoverDeleteNodeBtn(false);
- }
- onEnterInsertLeafNodeBtn() {
- this.props.onHoverInsertLeafNodeBtn(true);
- }
- onLeaveInsertLeafNodeBtn() {
- this.props.onHoverInsertLeafNodeBtn(false);
- }
- onEnterInsertBranchNodeBtn() {
- this.props.onHoverInsertBranchNodeBtn(true);
- }
- onLeaveInsertBranchNodeBtn() {
- this.props.onHoverInsertBranchNodeBtn(false);
- }
-}
-DomainSelectorControlPanel.template = "web.DomainSelectorControlPanel";
-DomainSelectorControlPanel.props = {
- node: Object,
- onHoverDeleteNodeBtn: Function,
- onHoverInsertLeafNodeBtn: Function,
- onHoverInsertBranchNodeBtn: Function,
-};
diff --git a/addons/web/static/src/core/domain_selector/domain_selector_control_panel.xml b/addons/web/static/src/core/domain_selector/domain_selector_control_panel.xml
deleted file mode 100644
index c60a708ce69..00000000000
--- a/addons/web/static/src/core/domain_selector/domain_selector_control_panel.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/web/static/src/core/domain_selector/domain_selector_fields.js b/addons/web/static/src/core/domain_selector/domain_selector_fields.js
new file mode 100644
index 00000000000..5aff8aaaaa5
--- /dev/null
+++ b/addons/web/static/src/core/domain_selector/domain_selector_fields.js
@@ -0,0 +1,276 @@
+/** @odoo-module **/
+
+import { Component, useRef } from "@odoo/owl";
+import { DatePicker, DateTimePicker } from "@web/core/datepicker/datepicker";
+import { selectOperators } from "@web/core/domain_selector/domain_selector_operators";
+import {
+ deserializeDate,
+ deserializeDateTime,
+ serializeDate,
+ serializeDateTime,
+} from "@web/core/l10n/dates";
+import { _lt } from "@web/core/l10n/translation";
+import { evaluateExpr, formatAST } from "@web/core/py_js/py";
+import { toPyValue } from "@web/core/py_js/py_utils";
+import { registry } from "@web/core/registry";
+
+// ============================================================================
+
+class Input extends Component {
+ static props = ["value", "update"];
+ static template = "web.DomainSelector.Input";
+}
+
+class Select extends Component {
+ static props = ["value", "update", "options"];
+ static template = "web.DomainSelector.Select";
+
+ deserialize(value) {
+ return JSON.parse(value);
+ }
+ serialize(value) {
+ return JSON.stringify(value);
+ }
+}
+
+class TagInput extends Component {
+ static props = ["value", "update"];
+ static template = "web.DomainSelector.TagInput";
+
+ setup() {
+ this.inputRef = useRef("input");
+ }
+
+ removeTag(tagIndex) {
+ return this.props.update([
+ ...this.props.value.slice(0, tagIndex),
+ ...this.props.value.slice(tagIndex + 1),
+ ]);
+ }
+
+ addTag(value) {
+ return this.props.update([...this.props.value, value]);
+ }
+
+ onBtnClick() {
+ const value = this.inputRef.el.value;
+ this.inputRef.el.value = "";
+ return this.addTag(value);
+ }
+}
+
+// ============================================================================
+
+const parsers = registry.category("parsers");
+function parseValue(fieldType, value) {
+ const parser = parsers.get(fieldType, (value) => value);
+ try {
+ return parser(value);
+ } catch {
+ return value;
+ }
+}
+
+function makeEditor(component, props) {
+ return {
+ component,
+ props: props || (({ value, update }) => ({ value, update })),
+ };
+}
+
+const DEFAULT = {
+ operators: [
+ "equal",
+ "not_equal",
+ "greater_than",
+ "greater_equal",
+ "less_than",
+ "less_equal",
+ "ilike",
+ "not_ilike",
+ "like",
+ "not_like",
+ "equal_like",
+ "equal_ilike",
+ "child_of",
+ "parent_of",
+ "in",
+ "not_in",
+ "set",
+ "not_set",
+ ],
+ editors: { default: makeEditor(Input) },
+ defaultValue: () => "",
+};
+
+// ----------------------------------------------------------------------------
+
+const BOOLEAN = {
+ operators: ["is", "is_not"],
+ editors: {
+ default: makeEditor(Select, ({ value, update }) => ({
+ value,
+ update,
+ options: [
+ [true, _lt("set")],
+ [false, _lt("not set")],
+ ],
+ })),
+ },
+ defaultValue: () => true,
+};
+
+// ----------------------------------------------------------------------------
+
+const DATE = {
+ operators: [
+ "equal",
+ "not_equal",
+ "greater_than",
+ "greater_equal",
+ "less_than",
+ "less_equal",
+ "set",
+ "not_set",
+ ],
+ editors: {
+ default: makeEditor(DatePicker, ({ value, update }) => ({
+ date: deserializeDate(value),
+ onDateTimeChanged: (value) => {
+ if (!value.isValid) {
+ return;
+ }
+ return update(value ? serializeDate(value) : luxon.DateTime.local());
+ },
+ })),
+ },
+ defaultValue: () => serializeDate(luxon.DateTime.local()),
+};
+
+// ----------------------------------------------------------------------------
+
+const DATETIME = {
+ operators: [
+ "equal",
+ "not_equal",
+ "greater_than",
+ "greater_equal",
+ "less_than",
+ "less_equal",
+ "set",
+ "not_set",
+ ],
+ editors: {
+ default: makeEditor(DateTimePicker, ({ value, update }) => ({
+ date: deserializeDateTime(value),
+ onDateTimeChanged: (value) => {
+ if (!value.isValid) {
+ return;
+ }
+ return update(value ? serializeDateTime(value) : luxon.DateTime.local());
+ },
+ })),
+ },
+ defaultValue: () => serializeDateTime(luxon.DateTime.local()),
+};
+
+// ----------------------------------------------------------------------------
+
+const TEXT = {
+ operators: ["equal", "not_equal", "ilike", "not_ilike", "set", "not_set", "in", "not_in"],
+ editors: {
+ default: makeEditor(Input),
+ in: makeEditor(TagInput),
+ not_in: makeEditor(TagInput),
+ },
+ defaultValue: () => "",
+};
+
+// ----------------------------------------------------------------------------
+
+const NUMBER = {
+ operators: [
+ "equal",
+ "not_equal",
+ "greater_than",
+ "greater_equal",
+ "less_than",
+ "less_equal",
+ "ilike",
+ "not_ilike",
+ "set",
+ "not_set",
+ ],
+ editors: {
+ default: makeEditor(Input, ({ value, update, field }) => ({
+ value: `${value}`,
+ update: (value) => update(parseValue(field.type, value)),
+ })),
+ },
+ defaultValue: () => 1,
+};
+
+// ----------------------------------------------------------------------------
+
+const RELATIONAL = {
+ operators: ["equal", "not_equal", "ilike", "not_ilike", "set", "not_set"],
+ editors: { default: makeEditor(Input) },
+ defaultValue: (field) => (field.type === "many2one" ? 1 : []),
+};
+
+// ----------------------------------------------------------------------------
+
+const SELECTION_EDITOR_IN = makeEditor(Input, ({ value, update }) => ({
+ value: formatAST(toPyValue(value)),
+ update: (value) => update(evaluateExpr(value)),
+}));
+
+const SELECTION = {
+ operators: ["equal", "not_equal", "in", "not_in", "set", "not_set"],
+ editors: {
+ default: makeEditor(Select, ({ value, update, field }) => ({
+ value,
+ update,
+ options: field.selection,
+ })),
+ in: SELECTION_EDITOR_IN,
+ not_in: SELECTION_EDITOR_IN,
+ },
+ defaultValue: (field) => field.selection[0][0],
+};
+
+// ============================================================================
+
+export const FIELD_DESCRIPTIONS = {
+ boolean: BOOLEAN,
+ char: TEXT,
+ date: DATE,
+ datetime: DATETIME,
+ float: NUMBER,
+ html: TEXT,
+ integer: NUMBER,
+ many2many: RELATIONAL,
+ many2one: RELATIONAL,
+ monetary: NUMBER,
+ one2many: RELATIONAL,
+ selection: SELECTION,
+ text: TEXT,
+};
+
+export function getFieldInfo(fieldType) {
+ return FIELD_DESCRIPTIONS[fieldType] || DEFAULT;
+}
+
+export function getEditorInfo(fieldType, operator) {
+ const fieldInfo = getFieldInfo(fieldType);
+ return fieldInfo.editors[operator] || fieldInfo.editors.default;
+}
+
+export function getOperatorsInfo(fieldType) {
+ return selectOperators(getFieldInfo(fieldType).operators);
+}
+
+export function getDefaultFieldValue(field) {
+ const desc = getFieldInfo(field.type);
+ return desc.defaultValue(field);
+}
diff --git a/addons/web/static/src/core/domain_selector/domain_selector_fields.xml b/addons/web/static/src/core/domain_selector/domain_selector_fields.xml
new file mode 100644
index 00000000000..f60fe0894f1
--- /dev/null
+++ b/addons/web/static/src/core/domain_selector/domain_selector_fields.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/web/static/src/core/domain_selector/domain_selector_leaf_node.js b/addons/web/static/src/core/domain_selector/domain_selector_leaf_node.js
deleted file mode 100644
index b40a53335ce..00000000000
--- a/addons/web/static/src/core/domain_selector/domain_selector_leaf_node.js
+++ /dev/null
@@ -1,127 +0,0 @@
-/** @odoo-module **/
-
-import { useModelField } from "@web/core/model_field_selector/model_field_hook";
-import { ModelFieldSelector } from "@web/core/model_field_selector/model_field_selector";
-import { registry } from "@web/core/registry";
-import { DomainSelectorControlPanel } from "./domain_selector_control_panel";
-import { DomainSelectorDefaultField } from "./fields/domain_selector_default_field";
-
-import { Component, onWillStart, onWillUpdateProps, useRef } from "@odoo/owl";
-
-export class DomainSelectorLeafNode extends Component {
- setup() {
- this.root = useRef("root");
- this.modelField = useModelField();
- this.fieldInfo = {
- type: "integer",
- string: "ID",
- };
- onWillStart(async () => {
- this.fieldInfo = await this.loadField(this.props.resModel, this.props.node.operands[0]);
- });
- onWillUpdateProps(async (nextProps) => {
- this.fieldInfo = await this.loadField(nextProps.resModel, nextProps.node.operands[0]);
- });
- }
-
- get displayedOperator() {
- const op = this.getOperatorInfo(this.props.node.operator);
- return op ? op.label : "?";
- }
- get isValueHidden() {
- const op = this.getOperatorInfo(this.props.node.operator);
- return op ? op.hideValue : false;
- }
-
- async loadField(resModel, fieldName) {
- const chain = await this.modelField.loadChain(resModel, fieldName);
- if (!chain[chain.length - 1].field && chain.length > 1) {
- return chain[chain.length - 2].field;
- }
- return (
- chain[chain.length - 1].field || {
- type: "integer",
- string: "ID",
- }
- );
- }
-
- findOperator(operatorList, opToFind) {
- return operatorList.find((o) =>
- o.matches({
- field: this.fieldInfo,
- value: this.props.node.operands[1],
- operator: opToFind,
- })
- );
- }
-
- getOperators(field) {
- const operators = field.getOperators();
- if (this.findOperator(operators, this.props.node.operator)) {
- return operators;
- }
- return operators.concat(
- this.findOperator(
- registry.category("domain_selector/operator").getAll(),
- this.props.node.operator
- )
- );
- }
-
- getFieldComponent(type) {
- return registry.category("domain_selector/fields").get(type, DomainSelectorDefaultField);
- }
- getOperatorInfo(operator) {
- const op = this.findOperator(
- this.getFieldComponent(this.fieldInfo.type).getOperators(),
- operator
- );
- if (op) {
- return op;
- }
- return this.findOperator(
- registry.category("domain_selector/operator").getAll(),
- this.props.node.operator
- );
- }
-
- async onFieldChange(fieldName) {
- const changes = { fieldName };
- const fieldInfo = await this.loadField(this.props.resModel, fieldName);
- const component = this.getFieldComponent(fieldInfo.type);
- Object.assign(changes, component.onDidTypeChange(fieldInfo));
- this.props.node.update(changes);
- }
- onOperatorChange(ev) {
- const component = this.getFieldComponent(this.fieldInfo.type);
- const operatorInfo = component.getOperators()[parseInt(ev.target.value, 10)];
- const changes = { operator: operatorInfo.value };
- Object.assign(
- changes,
- operatorInfo.onDidChange(this.getOperatorInfo(this.props.node.operator), () =>
- component.onDidTypeChange(this.fieldInfo)
- )
- );
- this.props.node.update(changes);
- }
-
- onHoverDeleteNodeBtn(hovering) {
- this.root.el.classList.toggle("o_hover_btns", hovering);
- }
- onHoverInsertLeafNodeBtn(hovering) {
- this.root.el.classList.toggle("o_hover_add_node", hovering);
- }
- onHoverInsertBranchNodeBtn(hovering) {
- this.root.el.classList.toggle("o_hover_add_node", hovering);
- this.root.el.classList.toggle("o_hover_add_inset_node", hovering);
- }
-}
-
-Object.assign(DomainSelectorLeafNode, {
- template: "web.DomainSelectorLeafNode",
- components: {
- DomainSelectorControlPanel,
- ModelFieldSelector,
- },
-});
diff --git a/addons/web/static/src/core/domain_selector/domain_selector_leaf_node.xml b/addons/web/static/src/core/domain_selector/domain_selector_leaf_node.xml
deleted file mode 100644
index d4c7c4fdafc..00000000000
--- a/addons/web/static/src/core/domain_selector/domain_selector_leaf_node.xml
+++ /dev/null
@@ -1,71 +0,0 @@
-
-
-
-
-
-
-
-
-
-
- ""
-
-
-
-
-
-
- is
-
- not
-
- set
-
-
-
-
- ""
- or
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/web/static/src/core/domain_selector/domain_selector_nodes.js b/addons/web/static/src/core/domain_selector/domain_selector_nodes.js
new file mode 100644
index 00000000000..33e3ed22559
--- /dev/null
+++ b/addons/web/static/src/core/domain_selector/domain_selector_nodes.js
@@ -0,0 +1,66 @@
+/** @odoo-module **/
+
+import { Domain } from "@web/core/domain";
+
+class DomainNode {
+ static nextId = 0;
+
+ constructor(type) {
+ this.id = ++DomainNode.nextId;
+ this.type = type;
+ }
+
+ toDomain() {
+ return new Domain();
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+export class BranchDomainNode extends DomainNode {
+ constructor(operator, children = []) {
+ super("branch");
+ this.operator = operator;
+ this.children = children;
+ }
+
+ toDomain() {
+ return Domain.combine(
+ this.children.map((c) => c.toDomain()),
+ this.operator
+ );
+ }
+
+ add(node) {
+ this.children.push(node);
+ }
+
+ insertAfter(id, node) {
+ const childIndex = this.children.findIndex((c) => c.id === id);
+ this.children.splice(childIndex + 1, 0, node);
+ }
+
+ delete(id) {
+ const childIndex = this.children.findIndex((c) => c.id === id);
+ this.children.splice(childIndex, 1);
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+export class LeafDomainNode extends DomainNode {
+ constructor(field, operator, value) {
+ super("leaf");
+ this.field = field;
+ this.operator = operator;
+ this.value = value;
+ }
+
+ toDomain() {
+ return new Domain([[this.field.name, this.operator.symbol, this.value]]);
+ }
+
+ clone() {
+ return new LeafDomainNode(this.field, this.operator, this.value);
+ }
+}
diff --git a/addons/web/static/src/core/domain_selector/domain_selector_operators.js b/addons/web/static/src/core/domain_selector/domain_selector_operators.js
index f2fe1934f71..4a5abddc764 100644
--- a/addons/web/static/src/core/domain_selector/domain_selector_operators.js
+++ b/addons/web/static/src/core/domain_selector/domain_selector_operators.js
@@ -1,167 +1,41 @@
/** @odoo-module **/
import { _lt } from "@web/core/l10n/translation";
-import { registry } from "@web/core/registry";
-function onDidChange(action) {
- return function (oldOperator, fieldChange) {
- if (this.category !== oldOperator.category) {
- return action(fieldChange);
- }
- return {};
- };
+export const OPERATOR_DESCRIPTIONS = [
+ { key: "is", label: _lt("is"), symbol: "=" },
+ { key: "is_not", label: _lt("is not"), symbol: "!=" },
+ { key: "equal", label: "=", symbol: "=" },
+ { key: "not_equal", label: "!=", symbol: "!=" },
+ { key: "greater_than", label: ">", symbol: ">" },
+ { key: "greater_equal", label: ">=", symbol: ">=" },
+ { key: "less_than", label: "<", symbol: "<" },
+ { key: "less_equal", label: "<=", symbol: "<=" },
+ { key: "ilike", label: _lt("contains"), symbol: "ilike" },
+ { key: "not_ilike", label: _lt("does not contain"), symbol: "not ilike" },
+ { key: "like", label: _lt("like"), symbol: "like" },
+ { key: "not_like", label: _lt("not like"), symbol: "not like" },
+ { key: "equal_like", label: _lt("=like"), symbol: "=like" },
+ { key: "equal_ilike", label: _lt("=ilike"), symbol: "=ilike" },
+ { key: "child_of", label: _lt("child of"), symbol: "child_of" },
+ { key: "parent_of", label: _lt("parent of"), symbol: "parent_of" },
+ { key: "in", label: _lt("in"), symbol: "in", valueMode: "multiple" },
+ { key: "not_in", label: _lt("not in"), symbol: "not in", valueMode: "multiple" },
+ { key: "set", label: _lt("is set"), symbol: "!=", valueMode: "none" },
+ { key: "not_set", label: _lt("is not set"), symbol: "=", valueMode: "none" },
+];
+
+export function findOperator(key) {
+ return OPERATOR_DESCRIPTIONS.find((op) => op.key === key);
}
-function matchValue() {
- return function ({ operator }) {
- return operator === this.value;
- };
+export function selectOperators(keys) {
+ const operators = Object.fromEntries(OPERATOR_DESCRIPTIONS.map((op) => [op.key, op]));
+ return keys.map((key) => operators[key]);
}
-const dso = registry.category("domain_selector/operator");
-dso.add("=", {
- category: "equality",
- label: "=",
- value: "=",
- onDidChange: onDidChange((fieldChange) => fieldChange()),
- matches({ operator, value }) {
- return operator === this.value && typeof value !== "boolean";
- },
-});
-dso.add("!=", {
- category: "equality",
- label: _lt("is not ="),
- value: "!=",
- onDidChange: onDidChange((fieldChange) => fieldChange()),
- matches({ operator, value }) {
- return operator === this.value && typeof value !== "boolean";
- },
-});
-dso.add(">", {
- category: "comparison",
- label: ">",
- value: ">",
- onDidChange: onDidChange((fieldChange) => fieldChange()),
- matches: matchValue(),
-});
-dso.add(">=", {
- category: "comparison",
- label: ">=",
- value: ">=",
- onDidChange: onDidChange((fieldChange) => fieldChange()),
- matches: matchValue(),
-});
-dso.add("<", {
- category: "comparison",
- label: "<",
- value: "<",
- onDidChange: onDidChange((fieldChange) => fieldChange()),
- matches: matchValue(),
-});
-dso.add("<=", {
- category: "comparison",
- label: "<=",
- value: "<=",
- onDidChange: onDidChange((fieldChange) => fieldChange()),
- matches: matchValue(),
-});
-dso.add("ilike", {
- category: "like",
- label: _lt("contains"),
- value: "ilike",
- onDidChange: onDidChange(() => ({ value: "" })),
- matches: matchValue(),
-});
-dso.add("not ilike", {
- category: "like",
- label: _lt("does not contain"),
- value: "not ilike",
- onDidChange: onDidChange(() => ({ value: "" })),
- matches: matchValue(),
-});
-dso.add("like", {
- category: "like",
- label: _lt("like"),
- value: "like",
- onDidChange: onDidChange(() => ({ value: "" })),
- matches: matchValue(),
-});
-dso.add("not like", {
- category: "like",
- label: _lt("not like"),
- value: "not like",
- onDidChange: onDidChange(() => ({ value: "" })),
- matches: matchValue(),
-});
-dso.add("=like", {
- category: "like",
- label: _lt("=like"),
- value: "=like",
- onDidChange: onDidChange(() => ({ value: "" })),
- matches: matchValue(),
-});
-dso.add("=ilike", {
- category: "like",
- label: _lt("=ilike"),
- value: "=ilike",
- onDidChange: onDidChange(() => ({ value: "" })),
- matches: matchValue(),
-});
-dso.add("child_of", {
- category: "relation",
- label: _lt("child of"),
- value: "child_of",
- onDidChange: onDidChange(() => ({ value: 1 })),
- matches: matchValue(),
-});
-dso.add("parent_of", {
- category: "relation",
- label: _lt("parent of"),
- value: "parent_of",
- onDidChange: onDidChange(() => ({ value: 1 })),
- matches: matchValue(),
-});
-dso.add("in", {
- category: "in",
- label: _lt("in"),
- value: "in",
- onDidChange: onDidChange(() => ({ value: [] })),
- matches: matchValue(),
-});
-dso.add("not in", {
- category: "in",
- label: _lt("not in"),
- value: "not in",
- onDidChange: onDidChange(() => ({ value: [] })),
- matches: matchValue(),
-});
-dso.add("set", {
- category: "set",
- label: _lt("is set"),
- value: "set",
- hideValue: true,
- onDidChange() {
- return {
- operator: "!=",
- value: false,
- };
- },
- matches({ operator, value }) {
- return operator === "!=" && typeof value === "boolean";
- },
-});
-dso.add("not set", {
- category: "set",
- label: _lt("is not set"),
- value: "not set",
- hideValue: true,
- onDidChange() {
- return {
- operator: "=",
- value: false,
- };
- },
- matches({ operator, value }) {
- return operator === "=" && typeof value === "boolean";
- },
-});
+export function parseOperator(symbol) {
+ return OPERATOR_DESCRIPTIONS.filter(
+ (op) => !["is", "is_not", "set", "not_set"].includes(op.key)
+ ).find((op) => op.symbol === symbol);
+}
diff --git a/addons/web/static/src/core/domain_selector/domain_selector_root_node.js b/addons/web/static/src/core/domain_selector/domain_selector_root_node.js
deleted file mode 100644
index 198b2bf8850..00000000000
--- a/addons/web/static/src/core/domain_selector/domain_selector_root_node.js
+++ /dev/null
@@ -1,33 +0,0 @@
-/** @odoo-module **/
-
-import { DomainSelectorBranchNode } from "./domain_selector_branch_node";
-import { DomainSelectorBranchOperator } from "./domain_selector_branch_operator";
-import { DomainSelectorLeafNode } from "./domain_selector_leaf_node";
-
-import { Component } from "@odoo/owl";
-
-export class DomainSelectorRootNode extends Component {
- get hasNode() {
- return this.props.node.operands.length > 0;
- }
- get node() {
- return this.props.node.operands[0];
- }
-
- insertNode(newNodeType) {
- this.props.node.insert(newNodeType);
- }
-
- onOperatorSelected(ev) {
- this.props.node.update(ev.detail.payload.operator);
- }
- onChange(ev) {
- this.props.node.update(ev.target.value, true);
- }
-}
-DomainSelectorRootNode.template = "web.DomainSelectorRootNode";
-DomainSelectorRootNode.components = {
- DomainSelectorBranchNode,
- DomainSelectorBranchOperator,
- DomainSelectorLeafNode,
-};
diff --git a/addons/web/static/src/core/domain_selector/domain_selector_root_node.xml b/addons/web/static/src/core/domain_selector/domain_selector_root_node.xml
deleted file mode 100644
index a2f9e272042..00000000000
--- a/addons/web/static/src/core/domain_selector/domain_selector_root_node.xml
+++ /dev/null
@@ -1,53 +0,0 @@
-
-
-
-
-
-
- Match all records
-
-
-
-
-
-
- Match records with the following rule:
-
-
-
-
-
- Match records with
-
- of the following rules:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/web/static/src/core/domain_selector/domain_tree_builder.js b/addons/web/static/src/core/domain_selector/domain_tree_builder.js
new file mode 100644
index 00000000000..44a3ae54ded
--- /dev/null
+++ b/addons/web/static/src/core/domain_selector/domain_tree_builder.js
@@ -0,0 +1,102 @@
+/** @odoo-module **/
+
+import { BranchDomainNode, LeafDomainNode } from "@web/core/domain_selector/domain_selector_nodes";
+import { findOperator, parseOperator } from "@web/core/domain_selector/domain_selector_operators";
+
+export class DomainTreeBuilder {
+ build(domain, fieldDefs) {
+ const nodeIterator = domain.ast.value.values();
+ const nextNode = () => {
+ const it = nodeIterator.next();
+ return it.done ? null : it.value;
+ };
+
+ const rawNode = nextNode();
+ const node =
+ rawNode && this.isBranch(rawNode)
+ ? new BranchDomainNode(this.getBranchOperator(rawNode))
+ : new BranchDomainNode("AND");
+ if (rawNode) {
+ this.buildNode(node, rawNode, nextNode, fieldDefs);
+ }
+
+ return node;
+ }
+
+ /** @private */
+ buildNode(parent, rawNode, nextNode, fieldDefs) {
+ if (this.isBranch(rawNode)) {
+ this.buildBranch(parent, rawNode, nextNode, fieldDefs);
+ } else {
+ this.buildLeaf(parent, rawNode, fieldDefs);
+ }
+ }
+
+ /** @private */
+ buildLeaf(parent, rawNode, fieldDefs) {
+ const field = this.getLeafFieldName(rawNode);
+ const operatorInfo = this.getLeafOperatorInfo(rawNode, fieldDefs[field].type);
+ const value = this.isLeafValueArray(rawNode)
+ ? this.getLeafValue(rawNode).map((v) => v.value)
+ : this.getLeafValue(rawNode);
+ parent.add(
+ new LeafDomainNode({ ...fieldDefs[field], name: field.toString() }, operatorInfo, value)
+ );
+ }
+
+ /** @private */
+ buildBranch(parent, rawNode, nextNode, fieldDefs) {
+ let newParent = parent;
+ const currentOperator = this.getBranchOperator(rawNode);
+ if (currentOperator !== parent.operator) {
+ newParent = new BranchDomainNode(currentOperator);
+ parent.add(newParent);
+ }
+ this.buildNode(newParent, nextNode(), nextNode, fieldDefs);
+ this.buildNode(newParent, nextNode(), nextNode, fieldDefs);
+ }
+
+ /** @private */
+ isBranch(rawNode) {
+ return rawNode.type === 1 && ["&", "|", "!"].includes(rawNode.value);
+ }
+
+ /** @private */
+ getBranchOperator(rawNode) {
+ switch (rawNode.value) {
+ case "&":
+ return "AND";
+ case "|":
+ return "OR";
+ case "!":
+ return "NOT";
+ }
+ }
+
+ /** @private */
+ getLeafFieldName(rawNode) {
+ return rawNode.value[0].value;
+ }
+
+ /** @private */
+ getLeafOperatorInfo(rawNode, fieldType) {
+ const rawOperator = rawNode.value[1].value;
+ if (fieldType === "boolean") {
+ return findOperator(rawOperator === "=" ? "is" : "is_not");
+ } else if (rawNode.value[2].type === 2) {
+ return findOperator(rawOperator === "!=" ? "set" : "not_set");
+ } else {
+ return parseOperator(rawOperator);
+ }
+ }
+
+ /** @private */
+ getLeafValue(rawNode) {
+ return rawNode.value[2].value;
+ }
+
+ /** @private */
+ isLeafValueArray(rawNode) {
+ return [4, 10].includes(rawNode.value[2].type);
+ }
+}
diff --git a/addons/web/static/src/core/domain_selector/fields/domain_selector_boolean_field.js b/addons/web/static/src/core/domain_selector/fields/domain_selector_boolean_field.js
deleted file mode 100644
index baf99996dc6..00000000000
--- a/addons/web/static/src/core/domain_selector/fields/domain_selector_boolean_field.js
+++ /dev/null
@@ -1,47 +0,0 @@
-/** @odoo-module **/
-
-import { _lt } from "@web/core/l10n/translation";
-import { registry } from "@web/core/registry";
-
-import { Component } from "@odoo/owl";
-
-const dsf = registry.category("domain_selector/fields");
-
-export class DomainSelectorBooleanField extends Component {
- onChange(ev) {
- this.props.update({
- value: ev.target.value === "1",
- });
- }
-}
-Object.assign(DomainSelectorBooleanField, {
- template: "web.DomainSelectorBooleanField",
-
- onDidTypeChange() {
- return { value: true };
- },
- getOperators() {
- return [
- {
- category: "equality",
- label: _lt("is"),
- value: "=",
- onDidChange() {},
- matches({ operator }) {
- return operator === this.value;
- },
- },
- {
- category: "equality",
- label: _lt("is not"),
- value: "!=",
- onDidChange() {},
- matches({ operator }) {
- return operator === this.value;
- },
- },
- ];
- },
-});
-
-dsf.add("boolean", DomainSelectorBooleanField);
diff --git a/addons/web/static/src/core/domain_selector/fields/domain_selector_boolean_field.xml b/addons/web/static/src/core/domain_selector/fields/domain_selector_boolean_field.xml
deleted file mode 100644
index 7d2d8d0803e..00000000000
--- a/addons/web/static/src/core/domain_selector/fields/domain_selector_boolean_field.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/web/static/src/core/domain_selector/fields/domain_selector_datetime_field.js b/addons/web/static/src/core/domain_selector/fields/domain_selector_datetime_field.js
deleted file mode 100644
index fc9334c6087..00000000000
--- a/addons/web/static/src/core/domain_selector/fields/domain_selector_datetime_field.js
+++ /dev/null
@@ -1,52 +0,0 @@
-/** @odoo-module **/
-
-import { DatePicker, DateTimePicker } from "@web/core/datepicker/datepicker";
-import {
- deserializeDate,
- deserializeDateTime,
- serializeDate,
- serializeDateTime,
-} from "@web/core/l10n/dates";
-import { registry } from "@web/core/registry";
-
-import { Component } from "@odoo/owl";
-
-const dsf = registry.category("domain_selector/fields");
-const dso = registry.category("domain_selector/operator");
-
-export class DomainSelectorDateTimeField extends Component {
- get component() {
- const { DatePicker, DateTimePicker } = this.constructor.components;
- return this.props.field.type === "date" ? DatePicker : DateTimePicker;
- }
- get deserializedValue() {
- const deserialize =
- this.props.field.type === "date" ? deserializeDate : deserializeDateTime;
- return this.props.value ? deserialize(this.props.value) : luxon.DateTime.local();
- }
- onChange(value) {
- if (!this.deserializedValue.isValid && !value) {
- return;
- }
- const serialize = this.props.field.type === "date" ? serializeDate : serializeDateTime;
- this.props.update({ value: serialize(value || luxon.DateTime.local()) });
- }
-}
-Object.assign(DomainSelectorDateTimeField, {
- template: "web.DomainSelectorDateTimeField",
- components: {
- DatePicker,
- DateTimePicker,
- },
-
- onDidTypeChange(field) {
- const serialize = field.type === "date" ? serializeDate : serializeDateTime;
- return { value: serialize(luxon.DateTime.local()) };
- },
- getOperators() {
- return ["=", "!=", ">", "<", ">=", "<=", "set", "not set"].map((key) => dso.get(key));
- },
-});
-
-dsf.add("date", DomainSelectorDateTimeField);
-dsf.add("datetime", DomainSelectorDateTimeField);
diff --git a/addons/web/static/src/core/domain_selector/fields/domain_selector_datetime_field.xml b/addons/web/static/src/core/domain_selector/fields/domain_selector_datetime_field.xml
deleted file mode 100644
index bbd3c3e9a9c..00000000000
--- a/addons/web/static/src/core/domain_selector/fields/domain_selector_datetime_field.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/web/static/src/core/domain_selector/fields/domain_selector_default_field.js b/addons/web/static/src/core/domain_selector/fields/domain_selector_default_field.js
deleted file mode 100644
index e3a2ba44c78..00000000000
--- a/addons/web/static/src/core/domain_selector/fields/domain_selector_default_field.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/** @odoo-module **/
-
-import { registry } from "@web/core/registry";
-import { DomainSelectorFieldInput } from "./domain_selector_field_input";
-
-import { Component } from "@odoo/owl";
-
-const dso = registry.category("domain_selector/operator");
-
-export class DomainSelectorDefaultField extends Component {}
-Object.assign(DomainSelectorDefaultField, {
- template: "web.DomainSelectorDefaultField",
- components: {
- DomainSelectorFieldInput,
- },
-
- onDidTypeChange() {
- return { value: "" };
- },
- getOperators() {
- return dso.getAll();
- },
-});
diff --git a/addons/web/static/src/core/domain_selector/fields/domain_selector_default_field.xml b/addons/web/static/src/core/domain_selector/fields/domain_selector_default_field.xml
deleted file mode 100644
index 42daa775401..00000000000
--- a/addons/web/static/src/core/domain_selector/fields/domain_selector_default_field.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/web/static/src/core/domain_selector/fields/domain_selector_field_input.js b/addons/web/static/src/core/domain_selector/fields/domain_selector_field_input.js
deleted file mode 100644
index f95c31e95cc..00000000000
--- a/addons/web/static/src/core/domain_selector/fields/domain_selector_field_input.js
+++ /dev/null
@@ -1,22 +0,0 @@
-/** @odoo-module **/
-
-import { registry } from "@web/core/registry";
-
-import { Component } from "@odoo/owl";
-const parsers = registry.category("parsers");
-
-export class DomainSelectorFieldInput extends Component {
- parseValue(value) {
- const parser = parsers.get(this.props.field.type, (value) => value);
- try {
- return parser(value);
- } catch {
- return value;
- }
- }
-
- onChange(ev) {
- this.props.update({ value: this.parseValue(ev.target.value) });
- }
-}
-DomainSelectorFieldInput.template = "web.DomainSelectorFieldInput";
diff --git a/addons/web/static/src/core/domain_selector/fields/domain_selector_field_input.xml b/addons/web/static/src/core/domain_selector/fields/domain_selector_field_input.xml
deleted file mode 100644
index 9f5c3aed451..00000000000
--- a/addons/web/static/src/core/domain_selector/fields/domain_selector_field_input.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/addons/web/static/src/core/domain_selector/fields/domain_selector_field_input_with_tags.js b/addons/web/static/src/core/domain_selector/fields/domain_selector_field_input_with_tags.js
deleted file mode 100644
index 886ca674e85..00000000000
--- a/addons/web/static/src/core/domain_selector/fields/domain_selector_field_input_with_tags.js
+++ /dev/null
@@ -1,25 +0,0 @@
-/** @odoo-module **/
-
-import { Component, useRef } from "@odoo/owl";
-
-export class DomainSelectorFieldInputWithTags extends Component {
- setup() {
- this.inputRef = useRef("input");
- }
-
- removeTag(tagIndex) {
- const value = [...this.props.value];
- value.splice(tagIndex, 1);
- this.props.update({ value });
- }
- addTag(value) {
- this.props.update({ value: this.props.value.concat(value) });
- }
-
- onBtnClick() {
- const value = this.inputRef.el.value;
- this.inputRef.el.value = "";
- this.addTag(value);
- }
-}
-DomainSelectorFieldInputWithTags.template = "web.DomainSelectorFieldInputWithTags";
diff --git a/addons/web/static/src/core/domain_selector/fields/domain_selector_field_input_with_tags.xml b/addons/web/static/src/core/domain_selector/fields/domain_selector_field_input_with_tags.xml
deleted file mode 100644
index 298808a155d..00000000000
--- a/addons/web/static/src/core/domain_selector/fields/domain_selector_field_input_with_tags.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/addons/web/static/src/core/domain_selector/fields/domain_selector_number_field.js b/addons/web/static/src/core/domain_selector/fields/domain_selector_number_field.js
deleted file mode 100644
index fd4a3c70d3a..00000000000
--- a/addons/web/static/src/core/domain_selector/fields/domain_selector_number_field.js
+++ /dev/null
@@ -1,39 +0,0 @@
-/** @odoo-module **/
-
-import { registry } from "@web/core/registry";
-import { DomainSelectorFieldInput } from "./domain_selector_field_input";
-
-import { Component } from "@odoo/owl";
-
-const dsf = registry.category("domain_selector/fields");
-const dso = registry.category("domain_selector/operator");
-
-export class DomainSelectorNumberField extends Component {}
-Object.assign(DomainSelectorNumberField, {
- template: "web.DomainSelectorNumberField",
- components: {
- DomainSelectorFieldInput,
- },
-
- onDidTypeChange() {
- return { value: 0 };
- },
- getOperators() {
- return [
- "=",
- "!=",
- ">",
- "<",
- ">=",
- "<=",
- "ilike",
- "not ilike",
- "set",
- "not set",
- ].map((key) => dso.get(key));
- },
-});
-
-dsf.add("integer", DomainSelectorNumberField);
-dsf.add("float", DomainSelectorNumberField);
-dsf.add("monetary", DomainSelectorNumberField);
diff --git a/addons/web/static/src/core/domain_selector/fields/domain_selector_number_field.xml b/addons/web/static/src/core/domain_selector/fields/domain_selector_number_field.xml
deleted file mode 100644
index d6b94178301..00000000000
--- a/addons/web/static/src/core/domain_selector/fields/domain_selector_number_field.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/web/static/src/core/domain_selector/fields/domain_selector_relation_field.js b/addons/web/static/src/core/domain_selector/fields/domain_selector_relation_field.js
deleted file mode 100644
index ec37181dc1c..00000000000
--- a/addons/web/static/src/core/domain_selector/fields/domain_selector_relation_field.js
+++ /dev/null
@@ -1,29 +0,0 @@
-/** @odoo-module **/
-
-import { registry } from "@web/core/registry";
-import { DomainSelectorFieldInput } from "./domain_selector_field_input";
-
-import { Component } from "@odoo/owl";
-
-const dso = registry.category("domain_selector/operator");
-
-export class DomainSelectorRelationField extends Component {}
-Object.assign(DomainSelectorRelationField, {
- template: "web.DomainSelectorRelationField",
- components: {
- DomainSelectorFieldInput,
- },
-
- onDidTypeChange() {
- return { value: "0" };
- },
- getOperators() {
- return ["=", "!=", "ilike", "not ilike", "set", "not set"].map((key) => dso.get(key));
- },
-});
-
-registry
- .category("domain_selector/fields")
- .add("one2many", DomainSelectorRelationField)
- .add("many2one", DomainSelectorRelationField)
- .add("many2many", DomainSelectorRelationField);
diff --git a/addons/web/static/src/core/domain_selector/fields/domain_selector_relation_field.xml b/addons/web/static/src/core/domain_selector/fields/domain_selector_relation_field.xml
deleted file mode 100644
index 7d7b0728582..00000000000
--- a/addons/web/static/src/core/domain_selector/fields/domain_selector_relation_field.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/web/static/src/core/domain_selector/fields/domain_selector_selection_field.js b/addons/web/static/src/core/domain_selector/fields/domain_selector_selection_field.js
deleted file mode 100644
index fb3fdf1ef3c..00000000000
--- a/addons/web/static/src/core/domain_selector/fields/domain_selector_selection_field.js
+++ /dev/null
@@ -1,44 +0,0 @@
-/** @odoo-module **/
-
-import { evaluateExpr } from "@web/core/py_js/py";
-import { formatAST, toPyValue } from "@web/core/py_js/py_utils";
-import { registry } from "@web/core/registry";
-
-import { Component } from "@odoo/owl";
-
-const dsf = registry.category("domain_selector/fields");
-const dso = registry.category("domain_selector/operator");
-
-export class DomainSelectorSelectionField extends Component {
- get options() {
- return [
- [false, ""],
- ...this.props.field.selection.filter((option) => !this.props.value.includes(option[0])),
- ];
- }
-
- get formattedValue() {
- const ast = toPyValue(this.props.value);
- return formatAST(ast);
- }
-
- onChange(ev) {
- this.props.update({ value: ev.target.value });
- }
-
- onChangeMulti(ev) {
- this.props.update({ value: evaluateExpr(ev.target.value) });
- }
-}
-Object.assign(DomainSelectorSelectionField, {
- template: "web.DomainSelectorSelectionField",
-
- onDidTypeChange(field) {
- return { value: field.selection[0][0] };
- },
- getOperators() {
- return ["=", "!=", "set", "not set"].map((key) => dso.get(key));
- },
-});
-
-dsf.add("selection", DomainSelectorSelectionField);
diff --git a/addons/web/static/src/core/domain_selector/fields/domain_selector_selection_field.xml b/addons/web/static/src/core/domain_selector/fields/domain_selector_selection_field.xml
deleted file mode 100644
index 2d4f076f6cb..00000000000
--- a/addons/web/static/src/core/domain_selector/fields/domain_selector_selection_field.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/web/static/src/core/domain_selector/fields/domain_selector_text_field.js b/addons/web/static/src/core/domain_selector/fields/domain_selector_text_field.js
deleted file mode 100644
index 744c9054287..00000000000
--- a/addons/web/static/src/core/domain_selector/fields/domain_selector_text_field.js
+++ /dev/null
@@ -1,32 +0,0 @@
-/** @odoo-module **/
-
-import { registry } from "@web/core/registry";
-import { DomainSelectorFieldInput } from "./domain_selector_field_input";
-import { DomainSelectorFieldInputWithTags } from "./domain_selector_field_input_with_tags";
-
-import { Component } from "@odoo/owl";
-
-const dsf = registry.category("domain_selector/fields");
-const dso = registry.category("domain_selector/operator");
-
-export class DomainSelectorTextField extends Component {}
-Object.assign(DomainSelectorTextField, {
- template: "web.DomainSelectorTextField",
- components: {
- DomainSelectorFieldInput,
- DomainSelectorFieldInputWithTags,
- },
-
- onDidTypeChange() {
- return { value: "" };
- },
- getOperators() {
- return ["=", "!=", "ilike", "not ilike", "set", "not set", "in", "not in"].map((key) =>
- dso.get(key)
- );
- },
-});
-
-dsf.add("char", DomainSelectorTextField);
-dsf.add("html", DomainSelectorTextField);
-dsf.add("text", DomainSelectorTextField);
diff --git a/addons/web/static/src/core/domain_selector/fields/domain_selector_text_field.xml b/addons/web/static/src/core/domain_selector/fields/domain_selector_text_field.xml
deleted file mode 100644
index 07399ec1ef0..00000000000
--- a/addons/web/static/src/core/domain_selector/fields/domain_selector_text_field.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/web/static/src/core/model_field_selector/model_field_hook.js b/addons/web/static/src/core/model_field_selector/model_field_hook.js
index 2bf9bf4dd27..ffe44171df1 100644
--- a/addons/web/static/src/core/model_field_selector/model_field_hook.js
+++ b/addons/web/static/src/core/model_field_selector/model_field_hook.js
@@ -6,17 +6,7 @@ export function useModelField() {
const view = useService("view");
const loadModelFields = (resModel) => {
- return view.loadFields(resModel, {
- attributes: [
- "store",
- "searchable",
- "type",
- "string",
- "relation",
- "selection",
- "related",
- ],
- });
+ return view.loadFields(resModel);
};
const loadChain = async (resModel, fieldName) => {
diff --git a/addons/web/static/src/core/model_field_selector/model_field_selector.js b/addons/web/static/src/core/model_field_selector/model_field_selector.js
index 7ae1c969fa7..17c2f9a1b45 100644
--- a/addons/web/static/src/core/model_field_selector/model_field_selector.js
+++ b/addons/web/static/src/core/model_field_selector/model_field_selector.js
@@ -4,24 +4,30 @@ import { useModelField } from "./model_field_hook";
import { useUniquePopover } from "./unique_popover_hook";
import { ModelFieldSelectorPopover } from "./model_field_selector_popover";
-import { Component, onWillStart, onWillUpdateProps } from "@odoo/owl";
+import { Component, onWillStart, onWillUpdateProps, useState } from "@odoo/owl";
export class ModelFieldSelector extends Component {
setup() {
this.popover = useUniquePopover();
this.modelField = useModelField();
- this.chain = [];
+ this.state = useState({
+ chain: [],
+ fieldName: this.props.fieldName,
+ isDirty: false,
+ });
onWillStart(async () => {
- this.chain = await this.loadChain(this.props.resModel, this.props.fieldName);
+ this.state.chain = await this.loadChain(this.props.resModel, this.props.fieldName);
});
onWillUpdateProps(async (nextProps) => {
- this.chain = await this.loadChain(nextProps.resModel, nextProps.fieldName);
+ this.state.chain = await this.loadChain(nextProps.resModel, nextProps.fieldName);
+ this.state.fieldName = nextProps.fieldName;
+ this.state.isDirty = false;
});
}
get fieldNameChain() {
- return this.getFieldNameChain(this.props.fieldName);
+ return this.getFieldNameChain(this.state.fieldName ?? this.props.fieldName);
}
getFieldNameChain(fieldName) {
@@ -32,29 +38,16 @@ export class ModelFieldSelector extends Component {
if ("01".includes(fieldName)) {
return [{ resModel, field: { string: fieldName } }];
}
- const fieldNameChain = this.getFieldNameChain(fieldName);
- let currentNode = {
- resModel,
- field: null,
- };
- const chain = [currentNode];
- for (const fieldName of fieldNameChain) {
- const fieldsInfo = await this.modelField.loadModelFields(currentNode.resModel);
- Object.assign(currentNode, {
- field: { ...fieldsInfo[fieldName], name: fieldName },
- });
- if (fieldsInfo[fieldName].relation) {
- currentNode = {
- resModel: fieldsInfo[fieldName].relation,
- field: null,
- };
- chain.push(currentNode);
- }
- }
- return chain;
+ return this.modelField.loadChain(resModel, fieldName);
}
- update(chain) {
- this.props.update(chain.join("."));
+ async update(fieldName, isFieldSelected) {
+ this.state.fieldName = fieldName;
+ this.state.isDirty = !isFieldSelected;
+ if (isFieldSelected) {
+ await this.props.update(fieldName);
+ } else {
+ this.state.chain = await this.loadChain(this.props.resModel, fieldName);
+ }
}
onFieldSelectorClick(ev) {
@@ -65,7 +58,7 @@ export class ModelFieldSelector extends Component {
ev.currentTarget,
this.constructor.components.Popover,
{
- chain: this.chain,
+ chain: this.state.chain,
update: this.update.bind(this),
showSearchInput: this.props.showSearchInput,
isDebugMode: this.props.isDebugMode,
@@ -76,6 +69,11 @@ export class ModelFieldSelector extends Component {
{
closeOnClickAway: true,
popoverClass: "o_popover_field_selector",
+ onClose: () => {
+ if (this.state.isDirty) {
+ this.props.update(this.state.fieldName);
+ }
+ },
}
);
}
diff --git a/addons/web/static/src/core/model_field_selector/model_field_selector.xml b/addons/web/static/src/core/model_field_selector/model_field_selector.xml
index 713a8de381b..b12ab63dcaa 100644
--- a/addons/web/static/src/core/model_field_selector/model_field_selector.xml
+++ b/addons/web/static/src/core/model_field_selector/model_field_selector.xml
@@ -9,7 +9,7 @@
t-on-click="onFieldSelectorClick"
>
-
+
diff --git a/addons/web/static/src/core/model_field_selector/model_field_selector_popover.js b/addons/web/static/src/core/model_field_selector/model_field_selector_popover.js
index 57c5f5300f2..49643a6fc6c 100644
--- a/addons/web/static/src/core/model_field_selector/model_field_selector_popover.js
+++ b/addons/web/static/src/core/model_field_selector/model_field_selector_popover.js
@@ -62,12 +62,16 @@ export class ModelFieldSelectorPopover extends Component {
const keys = Object.keys(obj);
return sortBy(keys, (key) => obj[key].string);
}
- async update() {
- const fieldNameChain = this.fieldNameChain;
- this.fullFieldName = fieldNameChain.join(".");
- await this.props.update(fieldNameChain);
+ async update(isSelected) {
+ const fieldNameChain = this.fieldNameChain.join(".");
+ this.fullFieldName = fieldNameChain;
await this.loadFields();
- this.render();
+ await this.props.update(fieldNameChain, isSelected);
+ if (isSelected) {
+ this.props.close();
+ } else {
+ this.render();
+ }
}
async onInputKeydown(ev) {
@@ -147,11 +151,9 @@ export class ModelFieldSelectorPopover extends Component {
this.update();
} else if (this.props.needDefaultValue) {
this.isDefaultValueVisible = true;
- this.render();
this.update();
} else {
- this.update();
- this.props.close();
+ this.update(true);
this.props.validate(this.fieldNameChain, this.defaultValue);
}
}
@@ -159,8 +161,7 @@ export class ModelFieldSelectorPopover extends Component {
if (!acceptDefaultValue) {
this.defaultValue = "";
}
- this.props.close();
- this.update();
+ this.update(true);
this.props.validate(this.fieldNameChain, this.defaultValue);
}
async onFieldNameChange(ev) {
@@ -172,7 +173,7 @@ export class ModelFieldSelectorPopover extends Component {
} catch {
// WOWL TODO: rethrow error when not the expected type
this.chain = [{ resModel, field: null }];
- await this.props.update([]);
+ await this.props.update("");
this.render();
}
}
diff --git a/addons/web/static/tests/core/domain_selector_tests.js b/addons/web/static/tests/core/domain_selector_tests.js
index e8d27edcf3c..5727789fd05 100644
--- a/addons/web/static/tests/core/domain_selector_tests.js
+++ b/addons/web/static/tests/core/domain_selector_tests.js
@@ -1,18 +1,19 @@
/** @odoo-module **/
+import { Component, 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 { hotkeyService } from "@web/core/hotkeys/hotkey_service";
import { MainComponentsContainer } from "@web/core/main_components_container";
import { ormService } from "@web/core/orm_service";
-import { hotkeyService } from "@web/core/hotkeys/hotkey_service";
import { popoverService } from "@web/core/popover/popover_service";
import { registry } from "@web/core/registry";
import { uiService } from "@web/core/ui/ui_service";
import { viewService } from "@web/views/view_service";
+import { registerCleanup } from "../helpers/cleanup";
import { makeTestEnv } from "../helpers/mock_env";
-import { click, editInput, editSelect, getFixture, mount, triggerEvent } from "../helpers/utils";
import { makeFakeLocalizationService } from "../helpers/mock_services";
-
-import { Component, xml } from "@odoo/owl";
+import { click, editInput, editSelect, getFixture, mount, triggerEvent } from "../helpers/utils";
let serverData;
let target;
@@ -433,7 +434,10 @@ QUnit.module("Components", (hooks) => {
);
assert.strictEqual(target.querySelector(".o_field_selector_chain_part").innerText, "1");
- assert.strictEqual(target.querySelector(".o_domain_leaf_operator_select").value, "0"); // option "="
+ assert.strictEqual(
+ target.querySelector(".o_domain_leaf_operator_select").value,
+ "equal"
+ ); // option "="
assert.strictEqual(target.querySelector(".o_domain_leaf_value_input").value, "1");
newValue = `[(0, "=", 1)]`;
@@ -452,7 +456,10 @@ QUnit.module("Components", (hooks) => {
);
assert.strictEqual(target.querySelector(".o_field_selector_chain_part").innerText, "0");
- assert.strictEqual(target.querySelector(".o_domain_leaf_operator_select").value, "0"); // option "="
+ assert.strictEqual(
+ target.querySelector(".o_domain_leaf_operator_select").value,
+ "equal"
+ ); // option "="
assert.strictEqual(target.querySelector(".o_domain_leaf_value_input").value, "1");
}
);
@@ -469,13 +476,14 @@ QUnit.module("Components", (hooks) => {
});
QUnit.test("operator fallback in edit mode", async (assert) => {
- registry.category("domain_selector/operator").add("test", {
- category: "test",
+ OPERATOR_DESCRIPTIONS.push({
+ key: "test",
label: "test",
- value: "test",
- onDidChange: () => null,
- matches: ({ operator }) => operator === "test",
- hideValue: true,
+ symbol: "test",
+ valueMode: "none",
+ });
+ registerCleanup(() => {
+ OPERATOR_DESCRIPTIONS.pop();
});
await mountComponent(DomainSelector, {
@@ -542,13 +550,16 @@ QUnit.module("Components", (hooks) => {
await mountComponent(Parent);
assert.strictEqual(target.querySelector(".o_field_selector_chain_part").innerText, "State");
- assert.strictEqual(target.querySelector(".o_domain_leaf_operator_select").value, "2"); // option "!="
+ assert.strictEqual(
+ target.querySelector(".o_domain_leaf_operator_select").value,
+ "not_equal"
+ ); // option "!="
- await editSelect(target, ".o_domain_leaf_operator_select", 0);
+ await editSelect(target, ".o_domain_leaf_operator_select", "equal");
assert.strictEqual(target.querySelector(".o_field_selector_chain_part").innerText, "State");
- assert.strictEqual(target.querySelector(".o_domain_leaf_operator_select").value, "0"); // option "="
- assert.strictEqual(target.querySelector(".o_domain_leaf_value_input").value, "abc");
+ assert.strictEqual(target.querySelector(".o_domain_leaf_operator_select").value, "equal"); // option "="
+ assert.strictEqual(target.querySelector(".o_domain_leaf_value_input").value, `"abc"`);
});
QUnit.test("show correct operator", async (assert) => {
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 6c86ec3f27b..539f666c59c 100644
--- a/addons/web/static/tests/core/model_field_selector_tests.js
+++ b/addons/web/static/tests/core/model_field_selector_tests.js
@@ -79,6 +79,7 @@ QUnit.module("Components", (hooks) => {
this.fieldName = "";
}
onUpdate(value) {
+ assert.step(`update: ${value}`);
this.fieldName = value;
this.render();
}
@@ -136,6 +137,7 @@ QUnit.module("Components", (hooks) => {
"bar",
"the selected field should be correctly set"
);
+ assert.verifySteps(["update: bar"]);
// Focusing the input again should open the same popover
await click(target, ".o_field_selector");
@@ -182,12 +184,14 @@ QUnit.module("Components", (hooks) => {
"Product -> Product Name",
"field selector value should be displayed with two tags: 'Product' and 'Product Name'"
);
+ assert.verifySteps(["update: product_id.name"]);
// Remove the current selection and recreate it again
await click(target, ".o_field_selector");
await click(target, ".o_field_selector_prev_page");
await click(target, ".o_field_selector_prev_page");
await click(target, ".o_field_selector_close");
+ assert.verifySteps(["update: "]);
await click(target, ".o_field_selector");
assert.containsOnce(
@@ -210,6 +214,7 @@ QUnit.module("Components", (hooks) => {
"Product -> Product Name",
"field selector value should be displayed with two tags: 'Product' and 'Product Name'"
);
+ assert.verifySteps(["update: product_id.name"]);
});
QUnit.test("default field chain should set the page data correctly", async (assert) => {
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 674c006f93f..f4a64d422b9 100644
--- a/addons/web/static/tests/views/fields/domain_field_tests.js
+++ b/addons/web/static/tests/views/fields/domain_field_tests.js
@@ -812,7 +812,7 @@ QUnit.module("Fields", (hooks) => {
assert.verifySteps(["/web/webclient/load_menus"]);
await doAction(webClient, 1);
- assert.verifySteps(["/web/action/load", "get_views", "read", "search_count", "fields_get"]);
+ assert.verifySteps(["/web/action/load", "get_views", "read", "search_count"]);
assert.strictEqual(target.querySelector(".o_domain_debug_input").value, rawDomain);
assert.containsOnce(target, ".o_datepicker", "there should be a datepicker");