From b2406f72ad95b64c3e036b1ce720b2faa18b704f Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Fri, 2 Feb 2024 11:28:18 +0100 Subject: [PATCH] [FIX] web: statusbar field: load status with correct domain Before this commit, there was a race condition with the statusbar field, and more specifically with useRecordObserver. The issue could be reproduced in the form view of project.task. In an existing task, belonging to a project with some stages, change the project to another project with its own stages. It might happen that the displayed stages weren't the ones of the newly set project. If this didn't happen directly, this happened upon saving (i.e. the correct stages are displayed just after switching the project, but as soon as the user saves the record, the former stages are back). This happens because useRecordObserver used an outdated version of the props to get the domain (i.e. the props of the component have been updated, but useRecordObserver still used the old version, in particular the old props.domain). This commit fixes the issue by ensuring that we always use the last version of the props. opw 3693113 closes odoo/odoo#152433 Signed-off-by: Francois Georis (fge) --- .../src/model/relational_model/utils.js | 13 ++-- .../views/fields/statusbar_field_tests.js | 77 +++++++++++++++++++ 2 files changed, 85 insertions(+), 5 deletions(-) diff --git a/addons/web/static/src/model/relational_model/utils.js b/addons/web/static/src/model/relational_model/utils.js index e877d59ed6e..0a7e8602b20 100644 --- a/addons/web/static/src/model/relational_model/utils.js +++ b/addons/web/static/src/model/relational_model/utils.js @@ -619,7 +619,8 @@ export function isRelational(field) { export function useRecordObserver(callback) { const component = useComponent(); let alive = true; - const fct = (props) => { + let props = component.props; + const fct = () => { const def = new Deferred(); let firstCall = true; effect( @@ -652,10 +653,12 @@ export function useRecordObserver(callback) { onWillDestroy(() => { alive = false; }); - onWillStart(() => fct(component.props)); - onWillUpdateProps((props) => { - if (props.record.id !== component.props.record.id) { - return fct(props); + onWillStart(() => fct()); + onWillUpdateProps((nextProps) => { + const currentRecordId = props.record.id; + props = nextProps; + if (props.record.id !== currentRecordId) { + return fct(); } }); } diff --git a/addons/web/static/tests/views/fields/statusbar_field_tests.js b/addons/web/static/tests/views/fields/statusbar_field_tests.js index bfdd934af45..23cc40c2a42 100644 --- a/addons/web/static/tests/views/fields/statusbar_field_tests.js +++ b/addons/web/static/tests/views/fields/statusbar_field_tests.js @@ -3,10 +3,13 @@ import { makeFakeNotificationService } from "@web/../tests/helpers/mock_services"; import { click, + clickSave, editInput, getFixture, + getNodesTextContent, nextTick, patchWithCleanup, + selectDropdownItem, triggerHotkey, } from "@web/../tests/helpers/utils"; import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; @@ -816,4 +819,78 @@ QUnit.module("Fields", (hooks) => { assert.hasClass(button, "o_first"); } ); + + QUnit.test("correctly load statusbar when dynamic domain changes", async function (assert) { + serverData.models = { + stage: { + fields: { + name: { string: "Name", type: "char" }, + folded: { string: "Folded", type: "boolean", default: false }, + project_ids: { string: "Project", type: "many2many", relation: "project" }, + }, + records: [ + { id: 1, name: "Stage Project 1", project_ids: [1] }, + { id: 2, name: "Stage Project 2", project_ids: [2] }, + ], + }, + project: { + fields: { + display_name: { string: "Name", type: "char" }, + }, + records: [ + { id: 1, display_name: "Project 1" }, + { id: 2, display_name: "Project 2" }, + ], + }, + task: { + fields: { + status: { string: "Status", type: "many2one", relation: "stage" }, + project_id: { string: "Project", type: "many2one", relation: "project" }, + }, + records: [{ id: 1, project_id: 1, status: 1 }], + }, + }; + serverData.models.task.onchanges = { + project_id: (obj) => { + obj.status = obj.project_id === 1 ? 1 : 2; + }, + }; + + await makeView({ + type: "form", + resModel: "task", + resId: 1, + serverData, + arch: ` +
+
+ +
+ + `, + mockRPC(route, args) { + if (args.method === "search_read") { + assert.step(JSON.stringify(args.kwargs.domain)); + } + }, + }); + + assert.deepEqual( + getNodesTextContent(target.querySelectorAll(".o_statusbar_status button:not(.d-none)")), + ["Stage Project 1"] + ); + assert.verifySteps(['["|",["id","=",1],["project_ids","in",1]]']); + await selectDropdownItem(target, "project_id", "Project 2"); + assert.deepEqual( + getNodesTextContent(target.querySelectorAll(".o_statusbar_status button:not(.d-none)")), + ["Stage Project 2"] + ); + assert.verifySteps(['["|",["id","=",2],["project_ids","in",2]]']); + await clickSave(target); + assert.deepEqual( + getNodesTextContent(target.querySelectorAll(".o_statusbar_status button:not(.d-none)")), + ["Stage Project 2"] + ); + assert.verifySteps([]); + }); });