[FIX] web: basic_model: applyOnChange with 2 consecutive one2many

The problem is generally due to the use of `var`, this one

https://github.com/odoo-dev/odoo/blob/58a046d2146e884d467cde827f89fa603ed98a3b/addons/web/static/src/js/views/basic/basic_model.js#L1780 followed by an asynchronous call to a function using it

We can therefore summarize the part of the `_applyOnChange` function
that interests us by

```js

for () {
  if (field. type === 'one2many' || field. type === 'many2many') {
    var list;
    ...
    //using list
    ...

    promiseFunction(list).then(function(){
      //using list
    })
  }
}
```

In the case where we have two one2many which follow each other,
this scenario occurs:
  - `list` is initially the first o2m
  - the `promiseFunction` is called with `list` (first o2m)
  - before the promise is resolved the loop continues
  - `list` is `Promise.then()` the second o2m
  - the first call to `promiseFunction` with `list` (first o2m) is done
    - we enter in `Promise.then()`
    - `list` is currently worth the value of the second o2m because
      `list` is a `var`
  - the second call to `promiseFunction` with `list` is finished
    - we enter the `Promise.then()`
    - `list` is currently worth the value of the second o2m because
      `list` is a `var`

Result: `Promise.then()` is called twice with the first o2m and never
with the second

We can see what happened with this code example:

```js
  const array = ["a", "b"];

  const asyncRead = async(value) => {
    console.log("async function arg", value)
    setTimeout(()=>{
      return Promise. resolve(value);
    }, 100)
  }

  const forFunction = () => {
    for (let i = 0; i < array.length; i++){
      var list = array[i];
      asyncRead(list).then(function (){
        console.log("then async function arg", list)
      })
    }
  }
  forFunction()
  /*
    async function arg a
    async function arg b
    then async function arg b
    then async function arg b
  */
```

While if:
  We use `let`
    by replacing `var list = array[i];` with `let list = array[i];`
  OR
  We force the use of `Promise.resolve()` argument
    by replacing `.then(function (){}` with `.then(function (list){}`

We will get:
```js
  /*
    async function arg a
    async function arg b
    then async function arg a
    then async function arg b
  */
 ```
opw-2694855

closes odoo/odoo#89160

X-original-commit: 99cb2f52e109ccf94ac8bd735cd776e84ef04260
Signed-off-by: Nicolas Lempereur (nle) <nle@odoo.com>
This commit is contained in:
Lucas Perais (lpe)
2022-04-25 18:15:01 +02:00
committed by Achraf (abz)
parent 75c4468b9a
commit 6af8b7f95e
2 changed files with 51 additions and 1 deletions
@@ -1909,7 +1909,7 @@ var BasicModel = AbstractModel.extend({
});
}
});
var def = self._readUngroupedList(list).then(function () {
var def = self._readUngroupedList(list).then(function (list) {
var x2ManysDef = self._fetchX2ManysBatched(list);
var referencesDef = self._fetchReferencesBatched(list);
return Promise.all([x2ManysDef, referencesDef]);
@@ -4178,6 +4178,56 @@ QUnit.module('relational_fields', {
form.destroy();
});
QUnit.test('Check onchange with two consecutive many2one', async function (assert) {
assert.expect(2);
this.data.product.fields.product_partner_ids = { string: "User", type: 'one2many', relation: 'partner' };
this.data.product.records[0].product_partner_ids = [1];
this.data.product.records[1].product_partner_ids = [2];
this.data.turtle.fields.product_ids = { string: "Product", type: "one2many", relation: 'product' };
this.data.turtle.fields.user_ids = { string: "Product", type: "one2many", relation: 'user' };
this.data.turtle.onchanges = {
turtle_trululu: function (record) {
record.product_ids = [37];
record.user_ids = [17, 19];
},
};
var form = await createView({
View: FormView,
model: 'turtle',
data: this.data,
arch:
'<form string="Turtles">' +
'<field string="Product" name="turtle_trululu"/>' +
'<field readonly="1" string="Related field" name="product_ids">' +
'<tree>' +
'<field widget="many2many_tags" name="product_partner_ids"/>' +
'</tree>' +
'</field>' +
'<field readonly="1" string="Second related field" name="user_ids">' +
'<tree>' +
'<field widget="many2many_tags" name="partner_ids"/>' +
'</tree>' +
'</field>' +
'</form>',
res_id: 1,
});
await testUtils.form.clickEdit(form);
await testUtils.fields.many2one.clickOpenDropdown("turtle_trululu");
await testUtils.fields.many2one.searchAndClickItem('turtle_trululu', {search: 'first record'});
const getElementTextContent = name => [...document.querySelectorAll(`.o_field_many2manytags[name="${name}"] .badge.o_tag_color_0 > span`)]
.map(x=>x.textContent);
assert.deepEqual(
getElementTextContent('product_partner_ids'),
['first record'],
"should have the correct value in the many2many tag widget");
assert.deepEqual(
getElementTextContent('partner_ids'),
['first record', 'second record'],
"should have the correct values in the many2many tag widget");
form.destroy();
});
});
});
});