-
+
|
|
- Context:
-
- Modifiers:
-
+
+ Invisible:
+
+
+
+ Column invisible:
+
+
+
+ Required:
+
+
+
+ Readonly:
+
Special:
diff --git a/addons/web/static/src/views/view_compiler.js b/addons/web/static/src/views/view_compiler.js
index 96fb448a0f9..83c8f73f071 100644
--- a/addons/web/static/src/views/view_compiler.js
+++ b/addons/web/static/src/views/view_compiler.js
@@ -140,11 +140,7 @@ export function encodeObjectForTemplate(obj) {
* @returns {boolean | boolean[]}
*/
export function getModifier(el, modifierName) {
- // cf python side def transfer_node_to_modifiers
- // modifiers' string are evaluated to their boolean or array form
- const modifiers = JSON.parse(el.getAttribute("modifiers") || "{}");
- const mod = modifierName in modifiers ? modifiers[modifierName] : false;
- return typeof mod !== "boolean" ? mod : !!mod;
+ return el.getAttribute(modifierName);
}
/**
@@ -229,16 +225,16 @@ export class ViewCompiler {
* @returns {Element}
*/
applyInvisible(invisible, compiled, params) {
- if (!invisible) {
+ if (!invisible || invisible === "False") {
return compiled;
}
- if (typeof invisible === "boolean") {
+ if (invisible === "True" || invisible === "1") {
return;
}
const recordExpr = params.recordExpr || "__comp__.props.record";
- let isVisileExpr = `!__comp__.evalDomainFromRecord(${recordExpr},${JSON.stringify(
+ let isVisileExpr = `!__comp__.evaluateBooleanExpr(${JSON.stringify(
invisible
- )})`;
+ )},${recordExpr}.evalContext)`;
if (compiled.hasAttribute("t-if")) {
const formerTif = compiled.getAttribute("t-if");
isVisileExpr = `( ${formerTif} ) and ${isVisileExpr}`;
@@ -275,7 +271,7 @@ export class ViewCompiler {
let invisible;
if (evalInvisible) {
invisible = getModifier(node, "invisible");
- if (this.isAlwaysInvisible(invisible, params)) {
+ if (!params.compileInvisibleNodes && (invisible === "True" || invisible === "1")) {
return;
}
}
@@ -394,7 +390,7 @@ export class ViewCompiler {
*/
compileGenericNode(el, params) {
const compiled = createElement(el.nodeName.toLowerCase());
- const metaAttrs = ["modifiers", "attrs", "invisible", "readonly"];
+ const metaAttrs = ["column_invisible", "invisible", "readonly", "required"];
for (const attr of el.attributes) {
if (metaAttrs.includes(attr.name)) {
continue;
@@ -429,15 +425,6 @@ export class ViewCompiler {
return assignOwlDirectives(widget, el);
}
- /**
- * @param {any} invisibleModifer
- * @param {{ enableInvisible?: boolean }} params
- * @returns {boolean}
- */
- isAlwaysInvisible(invisibleModifer, params) {
- return !params.enableInvisible && typeof invisibleModifer === "boolean" && invisibleModifer;
- }
-
validateNode(node) {
// detect attributes not in whitelist, starting with t-
const attributes = Object.values(node.attributes).map((attr) => attr.name);
diff --git a/addons/web/static/src/views/widgets/widget.js b/addons/web/static/src/views/widgets/widget.js
index 0cb5a77f1c4..63903d4ed62 100644
--- a/addons/web/static/src/views/widgets/widget.js
+++ b/addons/web/static/src/views/widgets/widget.js
@@ -1,7 +1,6 @@
/* @odoo-module */
-import { evalDomain } from "@web/core/domain";
-import { evaluateExpr } from "@web/core/py_js/py";
+import { evaluateExpr, evaluateBooleanExpr } from "@web/core/py_js/py";
import { registry } from "@web/core/registry";
import { Component, xml } from "@odoo/owl";
@@ -45,8 +44,7 @@ export class Widget extends Component {
let propsFromNode = {};
if (this.props.widgetInfo) {
const widgetInfo = this.props.widgetInfo;
- const modifiers = widgetInfo.modifiers || {};
- readonlyFromModifiers = evalDomain(modifiers.readonly, evalContext);
+ readonlyFromModifiers = evaluateBooleanExpr(widgetInfo.attrs.readonly, evalContext);
propsFromNode = this.widget.extractProps ? this.widget.extractProps(widgetInfo) : {};
}
@@ -67,7 +65,6 @@ Widget.parseWidgetNode = function (node) {
const widget = viewWidgetRegistry.get(name);
const widgetInfo = {
name,
- modifiers: {},
widget,
options: {},
attrs: {},
@@ -78,9 +75,7 @@ Widget.parseWidgetNode = function (node) {
// avoid adding name and widget to attrs
continue;
}
- if (name === "modifiers") {
- widgetInfo.modifiers = JSON.parse(value);
- } else if (name === "options") {
+ if (name === "options") {
widgetInfo.options = evaluateExpr(value);
} else if (!name.startsWith("t-att")) {
// all other (non dynamic) attributes
diff --git a/addons/web/static/tests/core/py_js/py_date_tests.js b/addons/web/static/tests/core/py_js/py_date_tests.js
index 78c4e287620..3e60d970dac 100644
--- a/addons/web/static/tests/core/py_js/py_date_tests.js
+++ b/addons/web/static/tests/core/py_js/py_date_tests.js
@@ -333,7 +333,7 @@ QUnit.module("py", {}, () => {
QUnit.test("throws when period negative", (assert) => {
const matcher = (errorMessage) => {
return function match(err) {
- return err.message === errorMessage;
+ return err.message.includes(errorMessage);
};
};
diff --git a/addons/web/static/tests/core/py_js/py_interpreter_tests.js b/addons/web/static/tests/core/py_js/py_interpreter_tests.js
index 6d795c41375..41bc41ce980 100644
--- a/addons/web/static/tests/core/py_js/py_interpreter_tests.js
+++ b/addons/web/static/tests/core/py_js/py_interpreter_tests.js
@@ -1,6 +1,6 @@
/** @odoo-module **/
-import { evaluateExpr } from "@web/core/py_js/py";
+import { evaluateExpr, evaluateBooleanExpr } from "@web/core/py_js/py";
QUnit.module("py", {}, () => {
QUnit.module("interpreter", () => {
@@ -29,6 +29,8 @@ QUnit.module("py", {}, () => {
assert.strictEqual(evaluateExpr('""'), "");
assert.strictEqual(evaluateExpr('"foo"'), "foo");
assert.strictEqual(evaluateExpr("'foo'"), "foo");
+ assert.strictEqual(evaluateExpr("'FOO'.lower()"), "foo");
+ assert.strictEqual(evaluateExpr("'foo'.upper()"), "FOO");
});
QUnit.test("boolean", (assert) => {
@@ -96,6 +98,7 @@ QUnit.module("py", {}, () => {
assert.strictEqual(evaluateExpr("not False"), true);
assert.strictEqual(evaluateExpr("not foo", { foo: false }), true);
assert.strictEqual(evaluateExpr("not None"), true);
+ assert.strictEqual(evaluateExpr("not []"), true);
assert.strictEqual(evaluateExpr("True == False or True == True"), true);
assert.strictEqual(evaluateExpr("False == True and False"), false);
});
@@ -123,6 +126,7 @@ QUnit.module("py", {}, () => {
assert.strictEqual(evaluateExpr('None or "bar"'), "bar");
assert.strictEqual(evaluateExpr("False or None"), null);
assert.strictEqual(evaluateExpr("0 or 1"), 1);
+ assert.strictEqual(evaluateExpr("[] or False"), false);
});
QUnit.module("values from context");
@@ -321,6 +325,22 @@ QUnit.module("py", {}, () => {
assert.strictEqual(evaluateExpr("{'a': 1}.get('b', 54)"), 54);
});
+ QUnit.test("can get values from values 'context'", (assert) => {
+ assert.strictEqual(evaluateExpr("context.get('a')", { context: { a: 123 } }), 123);
+ const values = { context: { a: { b: { c: 321 } } } };
+ assert.strictEqual(evaluateExpr("context.get('a').b.c", values), 321);
+ assert.strictEqual(evaluateExpr("context.get('a', {'e': 5}).b.c", values), 321);
+ assert.strictEqual(evaluateExpr("context.get('d', 3)", values), 3);
+ assert.strictEqual(evaluateExpr("context.get('d', {'e': 5})['e']", values), 5);
+ });
+
+ QUnit.test("can check if a key is in the 'context'", (assert) => {
+ assert.strictEqual(evaluateExpr("'a' in context", { context: { a: 123 } }), true);
+ assert.strictEqual(evaluateExpr("'a' in context", { context: { b: 123 } }), false);
+ assert.strictEqual(evaluateExpr("'a' not in context", { context: { a: 123 } }), false);
+ assert.strictEqual(evaluateExpr("'a' not in context", { context: { b: 123 } }), true);
+ });
+
QUnit.module("objects");
QUnit.test("can read values from object", (assert) => {
@@ -350,5 +370,42 @@ QUnit.module("py", {}, () => {
QUnit.test("tuple in list", (assert) => {
assert.deepEqual(evaluateExpr("[(1 + 2,'foo', True)]"), [[3, "foo", true]]);
});
+
+ QUnit.module("evaluate to boolean");
+
+ QUnit.test("simple expression", (assert) => {
+ assert.strictEqual(evaluateBooleanExpr("12"), true);
+ assert.strictEqual(evaluateBooleanExpr("0"), false);
+ assert.strictEqual(evaluateBooleanExpr("0 + 3 - 1"), true);
+ assert.strictEqual(evaluateBooleanExpr("0 + 3 - 1 - 2"), false);
+ assert.strictEqual(evaluateBooleanExpr('"foo"'), true);
+ assert.strictEqual(evaluateBooleanExpr("[1]"), true);
+ assert.strictEqual(evaluateBooleanExpr("[]"), false);
+ });
+
+ QUnit.test("use contextual values", (assert) => {
+ assert.strictEqual(evaluateBooleanExpr("a", {a: 12}), true);
+ assert.strictEqual(evaluateBooleanExpr("a", {a: 0}), false);
+ assert.strictEqual(evaluateBooleanExpr("0 + 3 - a", {a: 1}), true);
+ assert.strictEqual(evaluateBooleanExpr("0 + 3 - a - 2", {a: 1}), false);
+ assert.strictEqual(evaluateBooleanExpr("0 + 3 - a - b", {a: 1, b: 2}), false);
+ assert.strictEqual(evaluateBooleanExpr('a', {a: "foo"}), true);
+ assert.strictEqual(evaluateBooleanExpr("a", {a: [1]}), true);
+ assert.strictEqual(evaluateBooleanExpr("a", {a: []}), false);
+ });
+
+ QUnit.test("throw if has missing value", (assert) => {
+ assert.throws(() => evaluateBooleanExpr("a", {b: 0}));
+ assert.strictEqual(evaluateBooleanExpr("1 or a"), true); // do not throw (lazy value)
+ assert.throws(() => evaluateBooleanExpr("0 or a"));
+ assert.throws(() => evaluateBooleanExpr("a or b", {b: true}));
+ assert.throws(() => evaluateBooleanExpr("a and b", {b: true}));
+ assert.throws(() => evaluateBooleanExpr("a()"));
+ assert.throws(() => evaluateBooleanExpr("a[0]"));
+ assert.throws(() => evaluateBooleanExpr("a.b"));
+ assert.throws(() => evaluateBooleanExpr("0 + 3 - a", {b: 1}));
+ assert.throws(() => evaluateBooleanExpr("0 + 3 - a - 2", {b: 1}));
+ assert.throws(() => evaluateBooleanExpr("0 + 3 - a - b", {b: 2}));
+ });
});
});
diff --git a/addons/web/static/tests/helpers/mock_server.js b/addons/web/static/tests/helpers/mock_server.js
index 3447eb802a4..591b09cab0a 100644
--- a/addons/web/static/tests/helpers/mock_server.js
+++ b/addons/web/static/tests/helpers/mock_server.js
@@ -10,7 +10,6 @@ import {
serializeDate,
serializeDateTime,
} from "@web/core/l10n/dates";
-import { evaluateExpr } from "@web/core/py_js/py";
import { registry } from "@web/core/registry";
import { intersection, unique } from "@web/core/utils/arrays";
import { deepCopy, pick } from "@web/core/utils/objects";
@@ -319,7 +318,6 @@ export class MockServer {
function isNodeProcessed(node) {
return processedNodes.findIndex((n) => n.isSameNode(node)) > -1;
}
- const modifiersNames = ["invisible", "readonly", "required"];
const onchanges = params.models[modelName].onchanges || {};
const fieldNodes = {};
const groupbyNodes = {};
@@ -332,8 +330,6 @@ export class MockServer {
}
const editableView = editable && this._editableNode(doc, modelName);
const onchangeAbleView = this._onchangeAbleView(doc);
- const modifiersFromModel = this._modifiersFromModel(doc);
- const inTreeView = ["tree", "list"].includes(doc.tagName);
const inFormView = doc.tagName === "form";
// mock _postprocess_access_rights
const isBaseModel = !context.base_model_name || modelName === context.base_model_name;
@@ -350,90 +346,30 @@ export class MockServer {
if (node.nodeType === Node.TEXT_NODE) {
return false;
}
- const modifiers = {};
+ ['required', 'readonly', 'invisible', 'column_invisible'].forEach((attr) => {
+ const value = node.getAttribute(attr);
+ if (value === '1' || value === 'true') {
+ node.setAttribute(attr, 'True');
+ }
+ });
const isField = node.tagName === "field";
const isGroupby = node.tagName === "groupby";
if (isField) {
const fieldName = node.getAttribute("name");
fieldNodes[fieldName] = {
node,
- isInvisible: node.getAttribute("invisible"),
+ isInvisible: node.getAttribute("invisible") === "True",
isEditable: editableView && this._editableNode(node, modelName),
};
- // 'transfer_field_to_modifiers' simulation
const field = fields[fieldName];
if (!field) {
throw new Error("Field " + fieldName + " does not exist");
}
- const defaultValues = {};
- const stateExceptions = {}; // what is this ?
-
- modifiersFromModel.forEach((attr) => {
- stateExceptions[attr] = [];
- defaultValues[attr] = !!field[attr];
- });
- // LPE: what is this ?
- /* for (const [state, modifs] of Object.entries(field.states || {})) {
- modifs.forEach((modif) => {
- if (defaultValues[modif[0]] !== modif[1]) {
- stateExceptions[modif[0]].append(state);
- }
- });
- });*/
- Object.entries(defaultValues).forEach(([attr, defaultValue]) => {
- if (stateExceptions[attr].length) {
- modifiers[attr] = [
- ["state", defaultValue ? "not in" : "in", stateExceptions[attr]],
- ];
- } else {
- modifiers[attr] = defaultValue;
- }
- });
} else if (isGroupby && !isNodeProcessed(node)) {
const groupbyName = node.getAttribute("name");
fieldNodes[groupbyName] = { node };
groupbyNodes[groupbyName] = node;
}
- // 'transfer_node_to_modifiers' simulation
- let attrs = node.getAttribute("attrs");
- node.removeAttribute("attrs");
- if (attrs) {
- attrs = evaluateExpr(attrs);
- Object.assign(modifiers, attrs);
- }
- const states = node.getAttribute("states");
- node.removeAttribute("states");
- if (states) {
- if (!modifiers.invisible) {
- modifiers.invisible = [];
- }
- modifiers.invisible.push(["state", "not in", states.split(",")]);
- }
- const inListHeader = inTreeView && node.closest("header");
- modifiersNames.forEach((attr) => {
- const mod = node.getAttribute(attr);
- node.removeAttribute(attr);
- if (mod) {
- const v = evaluateExpr(mod, context) ? true : false;
- if (inTreeView && !inListHeader && attr === "invisible") {
- modifiers.column_invisible = v;
- } else if (v || !(attr in modifiers) || !Array.isArray(modifiers[attr])) {
- modifiers[attr] = v;
- }
- }
- });
- modifiersNames.forEach((attr) => {
- if (
- attr in modifiers &&
- (!!modifiers[attr] === false ||
- (Array.isArray(modifiers[attr]) && !modifiers[attr].length))
- ) {
- delete modifiers[attr];
- }
- });
- if (Object.keys(modifiers).length) {
- node.setAttribute("modifiers", JSON.stringify(modifiers));
- }
if (isGroupby && !isNodeProcessed(node)) {
return false;
}
@@ -480,8 +416,7 @@ export class MockServer {
Array.from(node.children).forEach((childNode) => {
if (childNode.tagName) {
relFields = Object.assign({}, params.models[relModel].fields);
- // this is hackhish, but _getView modifies the subview document in place,
- // especially to generate the "modifiers" attribute
+ // this is hackhish, but _getView modifies the subview document in place
const { models } = this._getView({
models: params.models,
arch: childNode,
@@ -553,16 +488,7 @@ export class MockServer {
case "field": {
const fname = node.getAttribute("name");
const field = this.models[modelName].fields[fname];
- return (
- (!field.readonly ||
- (field.states &&
- Object.values(field.states).some((item) =>
- item.includes("readonly")
- ))) &&
- (!["1", "True"].includes(node.getAttribute("readonly")) ||
- Object.keys(evaluateExpr(node.getAttribute("attrs") || "{}") || {}).length >
- 0)
- );
+ return !field.readonly && node.getAttribute("readonly") !== "True" && node.getAttribute("readonly") !== "1";
}
default:
return false;
@@ -579,14 +505,6 @@ export class MockServer {
}
}
- _modifiersFromModel(node) {
- const modifiersNames = ["invisible"];
- if (["kanban", "tree", "form"].includes(node.tagName)) {
- modifiersNames.push(...["readonly", "required"]);
- }
- return modifiersNames;
- }
-
/**
* Converts an Object representing a record to actual return Object of the
* python `onchange` method.
diff --git a/addons/web/static/tests/mobile/views/form_view_tests.js b/addons/web/static/tests/mobile/views/form_view_tests.js
index c4b418a6594..873dce7fbe7 100644
--- a/addons/web/static/tests/mobile/views/form_view_tests.js
+++ b/addons/web/static/tests/mobile/views/form_view_tests.js
@@ -104,8 +104,8 @@ QUnit.module("Mobile Views", ({ beforeEach }) => {
arch: `
`,
@@ -12038,8 +12039,8 @@ QUnit.module("Fields", (hooks) => {
serverData.views = {
"partner,false,list": `
-
-
+
+
`,
};
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 9625433f11a..0430aecb7c2 100644
--- a/addons/web/static/tests/views/fields/properties_field_tests.js
+++ b/addons/web/static/tests/views/fields/properties_field_tests.js
@@ -1818,7 +1818,7 @@ QUnit.module("Fields", (hooks) => {
-
+
Properties not empty
@@ -1872,7 +1872,7 @@ QUnit.module("Fields", (hooks) => {
-
+
Properties not empty
diff --git a/addons/web/static/tests/views/fields/selection_field_tests.js b/addons/web/static/tests/views/fields/selection_field_tests.js
index 8d7b3f70d65..bd553f66b09 100644
--- a/addons/web/static/tests/views/fields/selection_field_tests.js
+++ b/addons/web/static/tests/views/fields/selection_field_tests.js
@@ -276,7 +276,7 @@ QUnit.module("Fields", (hooks) => {
arch: `
`,
});
@@ -329,7 +329,7 @@ QUnit.module("Fields", (hooks) => {
arch: `
`,
});
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 f014422f85d..64cba8d08a1 100644
--- a/addons/web/static/tests/views/fields/text_field_tests.js
+++ b/addons/web/static/tests/views/fields/text_field_tests.js
@@ -161,7 +161,7 @@ QUnit.module("Fields", (hooks) => {
arch: `
`,
});
diff --git a/addons/web/static/tests/views/fields/timezone_mismatch_field_tests.js b/addons/web/static/tests/views/fields/timezone_mismatch_field_tests.js
index ade66e060da..5e4a7ba986a 100644
--- a/addons/web/static/tests/views/fields/timezone_mismatch_field_tests.js
+++ b/addons/web/static/tests/views/fields/timezone_mismatch_field_tests.js
@@ -55,7 +55,7 @@ QUnit.module("Fields", (hooks) => {
resId: 1,
arch: /*xml*/ `
-
+
`,
diff --git a/addons/web/static/tests/views/fields/url_field_tests.js b/addons/web/static/tests/views/fields/url_field_tests.js
index 293cf7e4221..162fa55a7dc 100644
--- a/addons/web/static/tests/views/fields/url_field_tests.js
+++ b/addons/web/static/tests/views/fields/url_field_tests.js
@@ -276,7 +276,7 @@ QUnit.module("Fields", (hooks) => {
````
const arch = /*xml*/ `
`;
const expected = /*xml*/ `
-
+
`;
assert.areContentEquivalent(compileTemplate(arch), expected);
@@ -248,14 +248,14 @@ QUnit.module("Form Compiler", (hooks) => {
QUnit.test("compile invisible containing string as domain", async (assert) => {
const arch = /*xml*/ `
`;
const expected = /*xml*/ `
-
+
`;
assert.areContentEquivalent(compileTemplate(arch), expected);
});
@@ -350,14 +350,14 @@ QUnit.module("Form Renderer", (hooks) => {
};
});
- QUnit.test("compile form with modifiers and attrs - string as domain", async (assert) => {
+ QUnit.test("compile form with modifiers", async (assert) => {
serverData.views = {
"partner,1,form": /*xml*/ `
@@ -2099,8 +2103,8 @@ QUnit.module("Views", (hooks) => {
-
-
+
+
@@ -2129,7 +2133,7 @@ QUnit.module("Views", (hooks) => {
-
+
@@ -2356,8 +2360,8 @@ QUnit.module("Views", (hooks) => {
-
-
+
+
`,
mockRPC(route, args) {
@@ -2412,8 +2416,8 @@ QUnit.module("Views", (hooks) => {
-
-
+
+
`,
mockRPC(route, args) {
@@ -2466,8 +2470,8 @@ QUnit.module("Views", (hooks) => {
-
-
+
+
`,
mockRPC(route, args) {
@@ -2627,7 +2631,7 @@ QUnit.module("Views", (hooks) => {
-
+
@@ -2668,7 +2672,7 @@ QUnit.module("Views", (hooks) => {
serverData,
arch: `
-
+
`,
});
@@ -2722,7 +2726,7 @@ QUnit.module("Views", (hooks) => {
arch: `
-
+
@@ -2770,7 +2774,7 @@ QUnit.module("Views", (hooks) => {
-
+
@@ -2831,7 +2835,7 @@ QUnit.module("Views", (hooks) => {
-
+
`,
resId: 2,
@@ -2912,9 +2916,9 @@ QUnit.module("Views", (hooks) => {
-
-
-
+
+
+
`,
@@ -2947,7 +2951,7 @@ QUnit.module("Views", (hooks) => {
-
+
@@ -3063,7 +3067,7 @@ QUnit.module("Views", (hooks) => {
-
+
@@ -3252,7 +3256,7 @@ QUnit.module("Views", (hooks) => {
@@ -5899,7 +5903,7 @@ QUnit.module("Views", (hooks) => {
-
+
@@ -6339,7 +6343,7 @@ QUnit.module("Views", (hooks) => {
-
+
@@ -7580,7 +7584,7 @@ QUnit.module("Views", (hooks) => {
serverData,
arch: `
-
+
`,
resId: 1,
@@ -7664,7 +7668,7 @@ QUnit.module("Views", (hooks) => {
arch: `
-
+
`,
resId: 5,
});
@@ -7714,11 +7718,11 @@ QUnit.module("Views", (hooks) => {
"partner,false,form": `
- `,
@@ -8531,7 +8535,7 @@ QUnit.module("Views", (hooks) => {
-
+
@@ -9036,8 +9040,8 @@ QUnit.module("Views", (hooks) => {
-
-
+
+
`,
});
@@ -9953,7 +9957,7 @@ QUnit.module("Views", (hooks) => {
-
+
`,
@@ -10029,7 +10033,7 @@ QUnit.module("Views", (hooks) => {
arch: `
-
+
`,
});
@@ -10150,7 +10154,7 @@ QUnit.module("Views", (hooks) => {
arch: `
@@ -10168,7 +10172,7 @@ QUnit.module("Views", (hooks) => {
await nextTick();
assert.strictEqual(
target.querySelector(".o-tooltip").textContent,
- ` Button : ButtonThis is titleObject:partnerModifiers:{"readonly":[["display_name","=","readonly"]]}Button Type:objectMethod:some_method`
+ ` Button : ButtonThis is titleObject:partnerReadonly:display_name == 'readonly'Button Type:objectMethod:some_method`
);
await mouseEnter(target.querySelector("button[name='other_method']"));
@@ -10251,7 +10255,8 @@ QUnit.module("Views", (hooks) => {
-
+
+
`,
@@ -10272,7 +10277,7 @@ QUnit.module("Views", (hooks) => {
"partner,false,list": `
-
+
`,
};
await makeView({
@@ -10480,7 +10485,7 @@ QUnit.module("Views", (hooks) => {
arch: `
-
+
`,
});
@@ -10500,14 +10505,14 @@ QUnit.module("Views", (hooks) => {
);
assert.containsOnce(
target,
- ".o-tooltip--technical > li[data-item='modifiers']",
- "modifiers should be present for this field"
+ ".o-tooltip--technical > li[data-item='invisible']",
+ "invisible should be present for this field"
);
assert.strictEqual(
- target.querySelector('.o-tooltip--technical > li[data-item="modifiers"]').lastChild
+ target.querySelector('.o-tooltip--technical > li[data-item="invisible"]').lastChild
.textContent,
- '{"invisible":[["product_id","=",33]]}',
- "modifiers should be properly stringified"
+ 'product_id == 33',
+ "invisible should be properly stringified"
);
assert.containsOnce(
@@ -12507,7 +12512,7 @@ QUnit.module("Views", (hooks) => {
-
+
`,
diff --git a/addons/web/static/tests/views/graph_view_tests.js b/addons/web/static/tests/views/graph_view_tests.js
index a99bf33d7bf..dca8c95ffb2 100644
--- a/addons/web/static/tests/views/graph_view_tests.js
+++ b/addons/web/static/tests/views/graph_view_tests.js
@@ -2416,8 +2416,8 @@ QUnit.module("Views", (hooks) => {
-
-
+
+
diff --git a/addons/web/static/tests/views/kanban/kanban_view_tests.js b/addons/web/static/tests/views/kanban/kanban_view_tests.js
index 513511881bb..5f53fda8d34 100644
--- a/addons/web/static/tests/views/kanban/kanban_view_tests.js
+++ b/addons/web/static/tests/views/kanban/kanban_view_tests.js
@@ -2619,7 +2619,7 @@ QUnit.module("Views", (hooks) => {
serverData.views["partner,some_view_ref,form"] =
" " +
'' +
- '' +
+ '' +
"";
await makeView({
@@ -8047,11 +8047,8 @@ QUnit.module("Views", (hooks) => {
' ' +
' ' +
' ' +
- '" +
- '' +
+ '' +
+ '' +
" " +
"",
});
@@ -8221,7 +8218,7 @@ QUnit.module("Views", (hooks) => {
-
+
@@ -8396,7 +8393,7 @@ QUnit.module("Views", (hooks) => {
arch: `
-
+
@@ -8563,7 +8560,7 @@ QUnit.module("Views", (hooks) => {
-
+
diff --git a/addons/web/static/tests/views/list_view_tests.js b/addons/web/static/tests/views/list_view_tests.js
index 163d2042b03..45eefc1c77b 100644
--- a/addons/web/static/tests/views/list_view_tests.js
+++ b/addons/web/static/tests/views/list_view_tests.js
@@ -746,10 +746,10 @@ QUnit.module("Views", (hooks) => {
arch: `
-
-
-
+
@@ -786,7 +786,7 @@ QUnit.module("Views", (hooks) => {
arch: `
-
+
@@ -839,9 +839,9 @@ QUnit.module("Views", (hooks) => {
arch: `
-
-
-
+
+
+
`,
});
@@ -1405,7 +1405,7 @@ QUnit.module("Views", (hooks) => {
arch: `
-
+
`,
});
@@ -1422,8 +1422,9 @@ QUnit.module("Views", (hooks) => {
serverData,
arch: `
-
-
+
+
+
`,
context: {
invisible: true,
@@ -1437,6 +1438,81 @@ QUnit.module("Views", (hooks) => {
}
);
+ QUnit.test(
+ "invisible column based on the context are correctly displayed in o2m",
+ async function (assert) {
+ serverData.models.foo.fields.foo_o2m = {
+ string: "Foo O2M",
+ type: "one2many",
+ relation: "foo",
+ };
+ await makeView({
+ type: "form",
+ resModel: "foo",
+ serverData,
+ resId: 1,
+ arch: `
+
+
+
+
+
+
+
+
+
+ `,
+ context: {
+ invisible: true,
+ notInvisible: false,
+ },
+ });
+
+ // 1 for 1 visible column (foo), 1 th for delete button
+ assert.containsN(target, "th", 2, "should have 2 th");
+ assert.strictEqual(target.querySelectorAll("th")[0].dataset.name, "foo");
+ }
+ );
+
+ QUnit.test(
+ "invisible column based on the parent are correctly displayed in o2m",
+ async function (assert) {
+ serverData.models.foo.fields.foo_o2m = {
+ string: "Foo O2M",
+ type: "one2many",
+ relation: "foo",
+ };
+ await makeView({
+ type: "form",
+ resModel: "foo",
+ serverData,
+ resId: 1,
+ arch: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ });
+
+ // 1 for 2 visible column (foo, properties), 1 th for delete button
+ assert.containsN(target, "th", 3, "should have 3 th");
+ assert.strictEqual(target.querySelectorAll("th")[0].dataset.name, "foo");
+ assert.strictEqual(target.querySelectorAll("th")[1].dataset.name, "amount");
+ }
+ );
+
QUnit.test("save a record with an invisible required field", async function (assert) {
serverData.models.foo.fields.foo.required = true;
@@ -1446,7 +1522,7 @@ QUnit.module("Views", (hooks) => {
serverData,
arch: `
-
+
`,
mockRPC(_, { args, method }) {
@@ -1625,7 +1701,7 @@ QUnit.module("Views", (hooks) => {
arch: `
-
+
`,
});
@@ -1642,7 +1718,7 @@ QUnit.module("Views", (hooks) => {
serverData,
arch: `
-
+
`,
});
@@ -1662,7 +1738,7 @@ QUnit.module("Views", (hooks) => {
arch: `
-
+
`,
mockRPC(route) {
assert.step(route.split("/").pop());
@@ -3993,8 +4069,8 @@ QUnit.module("Views", (hooks) => {
serverData,
arch: `
-
-
+
+
`,
@@ -4905,7 +4981,7 @@ QUnit.module("Views", (hooks) => {
-
+
@@ -5437,7 +5513,7 @@ QUnit.module("Views", (hooks) => {
-
+
`,
@@ -7144,7 +7220,7 @@ QUnit.module("Views", (hooks) => {
resModel: "foo",
serverData,
arch: `
-
+
`,
@@ -7871,7 +7947,7 @@ QUnit.module("Views", (hooks) => {
-
+
`,
mockRPC(route, args) {
@@ -7918,7 +7994,7 @@ QUnit.module("Views", (hooks) => {
-
+
`,
mockRPC(route, args) {
@@ -7950,7 +8026,7 @@ QUnit.module("Views", (hooks) => {
-
+
`,
groupBy: ["currency_id"],
@@ -7972,7 +8048,7 @@ QUnit.module("Views", (hooks) => {
-
+
`,
groupBy: ["currency_id"],
@@ -8046,7 +8122,7 @@ QUnit.module("Views", (hooks) => {
-
+
`,
groupBy: ["currency_id"],
@@ -8422,10 +8498,10 @@ QUnit.module("Views", (hooks) => {
serverData,
arch: `
-
+
-
+
`,
});
@@ -8459,7 +8535,7 @@ QUnit.module("Views", (hooks) => {
-
+
`,
});
@@ -8874,7 +8950,7 @@ QUnit.module("Views", (hooks) => {
serverData,
arch: `
-
+
`,
});
@@ -8927,7 +9003,7 @@ QUnit.module("Views", (hooks) => {
serverData,
arch: `
-
+
`,
});
@@ -8984,7 +9060,7 @@ QUnit.module("Views", (hooks) => {
serverData,
arch: `
-
+
`,
});
@@ -9059,7 +9135,7 @@ QUnit.module("Views", (hooks) => {
-
+
@@ -10096,7 +10172,7 @@ QUnit.module("Views", (hooks) => {
arch: `
-
+
`,
resId: 1,
@@ -10260,7 +10336,7 @@ QUnit.module("Views", (hooks) => {
-
+
`,
});
patchWithCleanup(session, { currencies });
@@ -10962,7 +11038,7 @@ QUnit.module("Views", (hooks) => {
serverData,
arch: `
-
+
`,
groupBy: ["foo"],
@@ -11039,7 +11115,7 @@ QUnit.module("Views", (hooks) => {
serverData,
arch: `
-
+
`,
@@ -12053,7 +12129,7 @@ QUnit.module("Views", (hooks) => {
-
+
`,
mockRPC(route, args) {
if (args.method === "write") {
@@ -12637,8 +12713,8 @@ QUnit.module("Views", (hooks) => {
arch: `
-
-
+
+
`,
});
@@ -13288,9 +13364,9 @@ QUnit.module("Views", (hooks) => {
serverData,
arch: `
-
-
-
+
+
+
`,
});
@@ -13330,7 +13406,7 @@ QUnit.module("Views", (hooks) => {
arch: `
-
+
`,
});
@@ -17131,7 +17207,7 @@ QUnit.module("Views", (hooks) => {
-
+
@@ -17993,7 +18069,7 @@ QUnit.module("Views", (hooks) => {
serverData,
arch: `
-
+
`,
@@ -19039,7 +19115,7 @@ QUnit.module("Views", (hooks) => {
arch: `
-
+
`,
mockRPC(route, { method, args }) {
diff --git a/addons/web/static/tests/views/view_dialogs/form_view_dialog_tests.js b/addons/web/static/tests/views/view_dialogs/form_view_dialog_tests.js
index b2494e24ef2..d4cdd1e98fc 100644
--- a/addons/web/static/tests/views/view_dialogs/form_view_dialog_tests.js
+++ b/addons/web/static/tests/views/view_dialogs/form_view_dialog_tests.js
@@ -107,11 +107,11 @@ QUnit.module("ViewDialogs", (hooks) => {
"partner,false,form": `
- `,
diff --git a/addons/web/static/tests/webclient/helpers.js b/addons/web/static/tests/webclient/helpers.js
index 7eb40ce363a..c70c381513a 100644
--- a/addons/web/static/tests/webclient/helpers.js
+++ b/addons/web/static/tests/webclient/helpers.js
@@ -340,7 +340,7 @@ export function getActionManagerServerData() {
"partner,666,form": `
- |