[IMP] mail, *: make mockserver autofill relational fields' inverses

*: web, website_slides.

Specifying both side of a relation when seeding test data is cumbersome.
Let startServer method handle it.

task-2792108

closes odoo/odoo#86939

Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
tsm-odoo
2022-03-25 18:38:54 +01:00
parent 3ba3695642
commit 2e690b4aa3
10 changed files with 273 additions and 163 deletions
+21 -13
View File
@@ -106,25 +106,33 @@ class IrModel(models.Model):
for model_name in model_names_to_fetch:
model = self.env[model_name]
# get fields, relational fields are kept only if the related model is in model_names_to_fetch
fields_by_fname = {
fname: field
for fname, field in model.fields_get(
fields_data_by_fname = {
fname: field_data
for fname, field_data in model.fields_get(
attributes=['name', 'type', 'relation', 'required', 'readonly', 'selection', 'string']
).items()
if not field.get('relation') or field['relation'] in model_names_to_fetch
if not field_data.get('relation') or field_data['relation'] in model_names_to_fetch
}
# exclude date/datetime and binary default values: dates will always be wrong since they are dynamic
# and binary values are not needed for now
default_values_by_fname = model.default_get([
fname for fname, field in fields_by_fname.items()
if field['type'] not in ['binary', 'date', 'datetime']
fname for fname, field_data in fields_data_by_fname.items()
if field_data['type'] not in ['binary', 'date', 'datetime']
])
tracked_field_names = model._track_get_fields() if 'mail.thread' in model._inherit else []
for fname in tracked_field_names:
if fname in fields_by_fname:
fields_by_fname[fname]['tracking'] = True
for fname, value in default_values_by_fname.items():
if fname in fields_by_fname:
fields_by_fname[fname]['default'] = value
fields_by_model_names[model_name] = fields_by_fname
for fname, field_data in fields_data_by_fname.items():
if fname in tracked_field_names:
field_data['tracking'] = True
if fname in default_values_by_fname:
field_data['default'] = default_values_by_fname[fname]
if fname in model._fields:
inverse_fields = [
field for field in model.pool.field_inverses[model._fields[fname]]
if field.model_name in model_names_to_fetch
]
if inverse_fields:
field_data['inverse_fname_by_model_name'] = {field.model_name: field.name for field in inverse_fields}
if field_data['type'] == 'many2one_reference':
field_data['model_name_ref_fname'] = model._fields[fname].model_field
fields_by_model_names[model_name] = fields_data_by_fname
return fields_by_model_names
@@ -519,13 +519,7 @@ MockServer.include({
return res;
}
if (request_list.includes('activities')) {
const activities = this._mockSearchRead('mail.activity', [[
'|',
['id', 'in', thread.activity_ids || []],
'&',
['res_id', '=', thread.id],
['res_model', '=', thread_model],
]], {});
const activities = this._mockSearchRead('mail.activity', [[['id', 'in', thread.activity_ids || []]]], {});
res['activities'] = this._mockMailActivityActivityFormat(activities.map(activity => activity.id));
}
if (request_list.includes('attachments')) {
@@ -535,13 +529,7 @@ MockServer.include({
res['attachments'] = this._mockIrAttachment_attachmentFormat(attachments.map(attachment => attachment.id), true);
}
if (request_list.includes('followers')) {
const followers = this._mockSearchRead('mail.followers', [[
'|',
['id', 'in', thread.message_follower_ids || []],
'&',
['res_id', '=', thread.id],
['res_model', '=', thread_model],
]], {});
const followers = this._mockSearchRead('mail.followers', [[['id', 'in', thread.message_follower_ids || []]]], {});
// search read returns many2one relations as an array [id, display_name].
// But the original route does not. Thus, we need to change it now.
followers.forEach(follower => follower.partner_id = follower.partner_id[0]);
@@ -15,10 +15,7 @@ QUnit.module('activity_mark_done_popover_tests.js', {
QUnit.test('activity mark done popover simplest layout', async function (assert) {
assert.expect(6);
this.data['res.partner'].records.push({
activity_ids: [12],
id: 100,
});
this.data['res.partner'].records.push({ id: 100 });
this.data['mail.activity'].records.push({
activity_category: 'not_upload_file',
can_write: true,
@@ -68,10 +65,7 @@ QUnit.test('activity mark done popover simplest layout', async function (assert)
QUnit.test('activity with force next mark done popover simplest layout', async function (assert) {
assert.expect(6);
this.data['res.partner'].records.push({
activity_ids: [12],
id: 100,
});
this.data['res.partner'].records.push({ id: 100 });
this.data['mail.activity'].records.push({
activity_category: 'not_upload_file',
can_write: true,
@@ -122,10 +116,7 @@ QUnit.test('activity with force next mark done popover simplest layout', async f
QUnit.test('activity mark done popover mark done without feedback', async function (assert) {
assert.expect(7);
this.data['res.partner'].records.push({
activity_ids: [12],
id: 100,
});
this.data['res.partner'].records.push({ id: 100 });
this.data['mail.activity'].records.push({
activity_category: 'not_upload_file',
can_write: true,
@@ -167,10 +158,7 @@ QUnit.test('activity mark done popover mark done without feedback', async functi
QUnit.test('activity mark done popover mark done with feedback', async function (assert) {
assert.expect(7);
this.data['res.partner'].records.push({
activity_ids: [12],
id: 100,
});
this.data['res.partner'].records.push({ id: 100 });
this.data['mail.activity'].records.push({
activity_category: 'not_upload_file',
can_write: true,
@@ -221,10 +209,7 @@ QUnit.test('activity mark done popover mark done and schedule next', async funct
assert.step('activity_action');
throw new Error("The do-action event should not be triggered when the route doesn't return an action");
});
this.data['res.partner'].records.push({
activity_ids: [12],
id: 100,
});
this.data['res.partner'].records.push({ id: 100 });
this.data['mail.activity'].records.push({
activity_category: 'not_upload_file',
can_write: true,
@@ -279,10 +264,7 @@ QUnit.test('[technical] activity mark done & schedule next with new action', asy
"The content of the action should be correct"
);
});
this.data['res.partner'].records.push({
activity_ids: [12],
id: 100,
});
this.data['res.partner'].records.push({ id: 100 });
this.data['mail.activity'].records.push({
activity_category: 'not_upload_file',
can_write: true,
@@ -16,10 +16,7 @@ QUnit.module('activity_tests.js', {
QUnit.test('activity simplest layout', async function (assert) {
assert.expect(12);
this.data['res.partner'].records.push({
activity_ids: [12],
id: 100,
});
this.data['res.partner'].records.push({ id: 100 });
this.data['mail.activity'].records.push({
id: 12,
res_id: 100,
@@ -95,10 +92,7 @@ QUnit.test('activity simplest layout', async function (assert) {
QUnit.test('activity with note layout', async function (assert) {
assert.expect(3);
this.data['res.partner'].records.push({
activity_ids: [12],
id: 100,
});
this.data['res.partner'].records.push({ id: 100 });
this.data['mail.activity'].records.push({
id: 12,
note: 'There is no good or bad note',
@@ -133,10 +127,7 @@ QUnit.test('activity info layout when planned after tomorrow', async function (a
const today = new Date();
const fiveDaysFromNow = new Date();
fiveDaysFromNow.setDate(today.getDate() + 5);
this.data['res.partner'].records.push({
activity_ids: [12],
id: 100,
});
this.data['res.partner'].records.push({ id: 100 });
this.data['mail.activity'].records.push({
date_deadline: date_to_str(fiveDaysFromNow),
id: 12,
@@ -176,10 +167,7 @@ QUnit.test('activity info layout when planned tomorrow', async function (assert)
const today = new Date();
const tomorrow = new Date();
tomorrow.setDate(today.getDate() + 1);
this.data['res.partner'].records.push({
activity_ids: [12],
id: 100,
});
this.data['res.partner'].records.push({ id: 100 });
this.data['mail.activity'].records.push({
date_deadline: date_to_str(tomorrow),
id: 12,
@@ -216,10 +204,7 @@ QUnit.test('activity info layout when planned tomorrow', async function (assert)
QUnit.test('activity info layout when planned today', async function (assert) {
assert.expect(4);
this.data['res.partner'].records.push({
activity_ids: [12],
id: 100,
});
this.data['res.partner'].records.push({ id: 100 });
this.data['mail.activity'].records.push({
date_deadline: date_to_str(new Date()),
id: 12,
@@ -259,10 +244,7 @@ QUnit.test('activity info layout when planned yesterday', async function (assert
const today = new Date();
const yesterday = new Date();
yesterday.setDate(today.getDate() - 1);
this.data['res.partner'].records.push({
activity_ids: [12],
id: 100,
});
this.data['res.partner'].records.push({ id: 100 });
this.data['mail.activity'].records.push({
date_deadline: date_to_str(yesterday),
id: 12,
@@ -302,10 +284,7 @@ QUnit.test('activity info layout when planned before yesterday', async function
const today = new Date();
const fiveDaysBeforeNow = new Date();
fiveDaysBeforeNow.setDate(today.getDate() - 5);
this.data['res.partner'].records.push({
activity_ids: [12],
id: 100,
});
this.data['res.partner'].records.push({ id: 100 });
this.data['mail.activity'].records.push({
date_deadline: date_to_str(fiveDaysBeforeNow),
id: 12,
@@ -342,10 +321,7 @@ QUnit.test('activity info layout when planned before yesterday', async function
QUnit.test('activity with a summary layout', async function (assert) {
assert.expect(4);
this.data['res.partner'].records.push({
activity_ids: [12],
id: 100,
});
this.data['res.partner'].records.push({ id: 100 });
this.data['mail.activity'].records.push({
id: 12,
res_id: 100,
@@ -382,10 +358,7 @@ QUnit.test('activity with a summary layout', async function (assert) {
QUnit.test('activity without summary layout', async function (assert) {
assert.expect(5);
this.data['res.partner'].records.push({
activity_ids: [12],
id: 100,
});
this.data['res.partner'].records.push({ id: 100 });
this.data['mail.activity'].records.push({
activity_type_id: 1,
id: 12,
@@ -430,10 +403,7 @@ QUnit.test('activity details toggle', async function (assert) {
const today = new Date();
const tomorrow = new Date();
tomorrow.setDate(today.getDate() + 1);
this.data['res.partner'].records.push({
activity_ids: [12],
id: 100,
});
this.data['res.partner'].records.push({ id: 100 });
this.data['mail.activity'].records.push({
create_date: date_to_str(today),
create_uid: 1,
@@ -492,10 +462,7 @@ QUnit.test('activity details layout', async function (assert) {
id: 10,
name: 'Pauvre pomme',
});
this.data['res.partner'].records.push({
activity_ids: [12],
id: 100,
});
this.data['res.partner'].records.push({ id: 100 });
this.data['mail.activity'].records.push({
activity_type_id: 1,
create_date: date_to_str(today),
@@ -576,10 +543,7 @@ QUnit.test('activity details layout', async function (assert) {
QUnit.test('activity with mail template layout', async function (assert) {
assert.expect(8);
this.data['res.partner'].records.push({
activity_ids: [12],
id: 100,
});
this.data['res.partner'].records.push({ id: 100 });
this.data['mail.template'].records.push({
id: 1,
name: "Dummy mail template",
@@ -675,10 +639,7 @@ QUnit.test('activity with mail template: preview mail', async function (assert)
);
});
this.data['res.partner'].records.push({
activity_ids: [12],
id: 42,
});
this.data['res.partner'].records.push({ id: 42 });
this.data['mail.template'].records.push({
id: 1,
name: "Dummy mail template",
@@ -716,10 +677,7 @@ QUnit.test('activity with mail template: preview mail', async function (assert)
QUnit.test('activity with mail template: send mail', async function (assert) {
assert.expect(7);
this.data['res.partner'].records.push({
activity_ids: [12],
id: 42,
});
this.data['res.partner'].records.push({ id: 42 });
this.data['mail.template'].records.push({
id: 1,
name: "Dummy mail template",
@@ -770,10 +728,7 @@ QUnit.test('activity with mail template: send mail', async function (assert) {
QUnit.test('activity upload document is available', async function (assert) {
assert.expect(3);
this.data['res.partner'].records.push({
activity_ids: [12],
id: 100,
});
this.data['res.partner'].records.push({ id: 100, });
this.data['mail.activity'].records.push({
activity_category: 'upload_file',
activity_type_id: 28,
@@ -806,10 +761,7 @@ QUnit.test('activity upload document is available', async function (assert) {
QUnit.test('activity click on mark as done', async function (assert) {
assert.expect(4);
this.data['res.partner'].records.push({
activity_ids: [12],
id: 100,
});
this.data['res.partner'].records.push({ id: 100 });
this.data['mail.activity'].records.push({
activity_category: 'default',
activity_type_id: 1,
@@ -856,10 +808,7 @@ QUnit.test('activity click on mark as done', async function (assert) {
QUnit.test('activity mark as done popover should focus feedback input on open [REQUIRE FOCUS]', async function (assert) {
assert.expect(3);
this.data['res.partner'].records.push({
activity_ids: [12],
id: 100,
});
this.data['res.partner'].records.push({ id: 100 });
this.data['mail.activity'].records.push({
activity_category: 'default',
activity_type_id: 1,
@@ -927,10 +876,7 @@ QUnit.test('activity click on edit', async function (assert) {
);
});
this.data['res.partner'].records.push({
activity_ids: [12],
id: 42,
});
this.data['res.partner'].records.push({ id: 42 });
this.data['mail.template'].records.push({
id: 1,
name: "Dummy mail template",
@@ -969,10 +915,7 @@ QUnit.test('activity click on edit', async function (assert) {
QUnit.test('activity edition', async function (assert) {
assert.expect(14);
this.data['res.partner'].records.push({
activity_ids: [12],
id: 42,
});
this.data['res.partner'].records.push({ id: 42 });
this.data['mail.activity'].records.push({
can_write: true,
icon: 'fa-times',
@@ -1064,10 +1007,7 @@ QUnit.test('activity edition', async function (assert) {
QUnit.test('activity click on cancel', async function (assert) {
assert.expect(7);
this.data['res.partner'].records.push({
activity_ids: [12],
id: 100,
});
this.data['res.partner'].records.push({ id: 100 });
this.data['mail.activity'].records.push({
activity_type_id: 1,
can_write: true,
@@ -1121,10 +1061,7 @@ QUnit.test('activity mark done popover close on ESCAPE', async function (assert)
// This test is not in activity_mark_done_popover_tests.js as it requires the activity mark done
// component to have a parent in order to allow testing interactions the popover.
assert.expect(2);
this.data['res.partner'].records.push({
activity_ids: [12],
id: 100,
});
this.data['res.partner'].records.push({ id: 100 });
this.data['mail.activity'].records.push({
activity_category: 'default',
activity_type_id: 1,
@@ -1164,10 +1101,7 @@ QUnit.test('activity mark done popover click on discard', async function (assert
// component to have a parent in order to allow testing interactions the popover.
assert.expect(3);
this.data['res.partner'].records.push({
activity_ids: [12],
id: 100,
});
this.data['res.partner'].records.push({ id: 100 });
this.data['mail.activity'].records.push({
activity_category: 'default',
activity_type_id: 1,
@@ -1228,10 +1162,7 @@ QUnit.test('data-oe-id & data-oe-model link redirection on click', async functio
assert.step('do-action:openFormView_some.model_250');
});
this.data['res.partner'].records.push({
activity_ids: [12],
id: 100,
});
this.data['res.partner'].records.push({ id: 100 });
this.data['mail.activity'].records.push({
activity_category: 'default',
activity_type_id: 1,
@@ -1268,10 +1199,7 @@ QUnit.test('button related to file uploading is replaced when updating activity
assert.expect(2);
const activityId = 513;
this.data['res.partner'].records.push({
activity_ids: [activityId],
id: 100,
});
this.data['res.partner'].records.push({ id: 100 });
this.data['mail.activity'].records.push({
activity_category: 'upload_file',
activity_type_id: 28,
@@ -76,7 +76,7 @@ QUnit.test('base rendering editable', async function (assert) {
QUnit.test('hover following button', async function (assert) {
assert.expect(8);
this.data['res.partner'].records.push({ id: 100, message_follower_ids: [1] });
this.data['res.partner'].records.push({ id: 100 });
this.data['mail.followers'].records.push({
id: 1,
is_active: true,
@@ -143,7 +143,7 @@ QUnit.test('hover following button', async function (assert) {
QUnit.test('click on "follow" button', async function (assert) {
assert.expect(6);
this.data['res.partner'].records.push({ id: 100, message_follower_ids: [1] });
this.data['res.partner'].records.push({ id: 100 });
this.data['mail.followers'].records.push({
id: 1,
is_active: true,
@@ -197,7 +197,7 @@ QUnit.test('click on "follow" button', async function (assert) {
QUnit.test('click on "unfollow" button', async function (assert) {
assert.expect(7);
this.data['res.partner'].records.push({ id: 100, message_follower_ids: [1] });
this.data['res.partner'].records.push({ id: 100 });
this.data['mail.followers'].records.push({
id: 1,
is_active: true,
@@ -187,7 +187,7 @@ QUnit.test('click on partner follower details', async function (assert) {
QUnit.test('click on edit follower', async function (assert) {
assert.expect(5);
this.data['res.partner'].records.push({ id: 100, message_follower_ids: [2] });
this.data['res.partner'].records.push({ id: 100 });
this.data['mail.followers'].records.push({
id: 2,
is_active: true,
+1
View File
@@ -490,6 +490,7 @@ This module provides the core of the Odoo Web Client.
'web/static/tests/webclient/**/helpers.js',
'web/static/tests/qunit.js',
'web/static/tests/main.js',
'web/static/tests/mock_relational_fields_tests.js',
'web/static/tests/mock_server_tests.js',
'web/static/tests/setup.js',
@@ -52,6 +52,10 @@ var MockServer = Class.extend({
}
}
// fill relational fields' inverse.
for (const modelName in this.data) {
this.data[modelName].records.forEach(record => this._updateComodelRelationalFields(modelName, record.id, record));
}
this.debug = options.debug;
this.currentDate = options.currentDate || moment().format("YYYY-MM-DD");
@@ -578,6 +582,7 @@ var MockServer = Class.extend({
model.records.push(record);
this._applyDefaults(model, values);
this._writeRecord(modelName, values, id);
this._updateComodelRelationalFields(modelName, id, values);
return id;
},
/**
@@ -1846,14 +1851,18 @@ var MockServer = Class.extend({
return _.contains(ids, record.id);
});
// update value of one2many fields pointing to the deleted records
// update value of relationnal fields pointing to the deleted records
_.each(this.data, function (d) {
var relatedFields = _.pick(d.fields, function (field) {
return field.type === 'one2many' && field.relation === model;
return field.relation === model;
});
_.each(Object.keys(relatedFields), function (relatedField) {
_.each(d.records, function (record) {
record[relatedField] = _.difference(record[relatedField], ids);
if (Array.isArray(record[relatedField])) {
record[relatedField] = _.difference(record[relatedField], ids);
} else if (ids.includes(record[relatedField])) {
record[relatedField] = false;
}
});
});
});
@@ -1914,7 +1923,12 @@ var MockServer = Class.extend({
* @returns {boolean} currently, always return 'true'
*/
_mockWrite: function (model, args) {
_.each(args[0], this._writeRecord.bind(this, model, args[1]));
_.each(args[0], id => {
const originalRecord = this._mockSearchRead(model, [[['id', '=', id]]], {})[0];
this._writeRecord(model, args[1], id);
const updatedRecord = this.data[model].records.find(record => record.id === id);
this._updateComodelRelationalFields(model, id, updatedRecord, originalRecord);
});
return true;
},
/**
@@ -2076,6 +2090,66 @@ var MockServer = Class.extend({
_.each(tree.childNodes, function (c) { self._traverse(c, f); });
}
},
/**
* Fill all inverse fields of the relational fields present in the record
* to be created/updated.
*
* @param {string} modelName
* @param {Object} record record that have been created/updated.
* @param {Object|undefined} originalRecord record before update.
*/
_updateComodelRelationalFields(modelName, recordId, record, originalRecord) {
for (const fname in record) {
const field = this.data[modelName].fields[fname];
const comodelName = field.relation || record[field['model_name_ref_fname']];
const inverseFieldName = field['inverse_fname_by_model_name'] && field['inverse_fname_by_model_name'][comodelName];
if (!inverseFieldName) {
// field has no inverse, skip it.
continue;
}
const relatedRecordIds = Array.isArray(record[fname]) ? record[fname] : [record[fname]];
// we only want to set a value for comodel inverse field if the model field has a value.
if (record[fname]) {
for (const relatedRecordId of relatedRecordIds) {
let inverseFieldNewValue = recordId;
const relatedRecord = this.data[comodelName].records.find(record => record.id === relatedRecordId);
const relatedFieldValue = relatedRecord && relatedRecord[inverseFieldName];
if (
relatedFieldValue === undefined ||
relatedFieldValue === recordId ||
field.type !== 'one2many' && relatedFieldValue.includes(recordId)
) {
// related record does not exist or the related value is already up to date.
continue;
}
if (Array.isArray(relatedFieldValue)) {
inverseFieldNewValue = [...relatedFieldValue, recordId];
}
this._writeRecord(comodelName, { [inverseFieldName]: inverseFieldNewValue }, relatedRecordId);
}
} else if (field.type === 'many2one_reference') {
// we need to clean the many2one_field as well.
const comodel_inverse_field = this.data[comodelName].fields[inverseFieldName];
const model_many2one_field = comodel_inverse_field['inverse_fname_by_model_name'][modelName];
this._writeRecord(modelName, { [model_many2one_field]: false }, recordId);
}
// it's an update, get the records that were originally referenced but are not
// anymore and update their relational fields.
if (originalRecord) {
const originalRecordIds = Array.isArray(originalRecord[fname]) ? originalRecord[fname] : [originalRecord[fname]];
// search read returns [id, name], let's ensure the removedRecordIds are integers.
const removedRecordIds = originalRecordIds.filter(recordId => Number.isInteger(recordId) && !relatedRecordIds.includes(recordId));
for (const removedRecordId of removedRecordIds) {
const removedRecord = this.data[comodelName].records.find(record => record.id === removedRecordId);
let inverseFieldNewValue = false;
if (Array.isArray(removedRecord[inverseFieldName])) {
inverseFieldNewValue = removedRecord[inverseFieldName].filter(id => id !== recordId);
}
this._writeRecord(comodelName, { [inverseFieldName]: inverseFieldNewValue }, removedRecordId);
}
}
}
},
/**
* Write a record. The main difficulty is that we have to apply x2many
* commands
@@ -0,0 +1,135 @@
/** @odoo-module **/
import MockServer from 'web.MockServer';
QUnit.module('web', {}, function () {
QUnit.module('mock_relational_fields_tests.js', {
beforeEach() {
this.data = {
foo: {
fields: {
one2many_field: { type: 'one2many', relation: 'bar', inverse_fname_by_model_name: { bar: 'many2one_field' } },
many2one_field: { type: 'many2one', relation: 'bar', inverse_fname_by_model_name: { bar: 'one2many_field' } },
many2many_field: { type: 'many2many', relation: 'bar', inverse_fname_by_model_name: { bar: 'many2many_field' } },
many2one_reference: { type: 'many2one_reference', model_name_ref_fname: 'res_model', inverse_fname_by_model_name: { bar: 'one2many_field' } },
res_model: { type: 'char' },
},
records: [],
},
bar: {
fields: {
many2one_field: { type: 'many2one', relation: 'foo' },
one2many_field: { type: 'one2many', relation: 'foo', inverse_fname_by_model_name: { foo: 'many2one_field' } },
many2many_field: { type: 'many2many', relation: 'foo', inverse_fname_by_model_name: { foo: 'many2many_field' } },
},
records: [],
},
};
}
});
QUnit.test('many2one_ref should auto fill inverse field', async function (assert) {
this.data['bar'].records.push({ id: 1 });
this.data['foo'].records.push({
id: 2,
res_model: 'bar',
many2one_reference: 1,
});
const mockServer = new MockServer(this.data, {});
assert.deepEqual([2], mockServer.data['bar'].records[0].one2many_field);
mockServer._mockUnlink('foo', [2]);
assert.deepEqual([], mockServer.data['bar'].records[0].one2many_field);
});
QUnit.test('many2one should auto fill inverse field', async function (assert) {
this.data['bar'].records.push({ id: 1 });
this.data['foo'].records.push({
id: 2,
many2one_field: 1,
});
const mockServer = new MockServer(this.data, {});
assert.deepEqual([2], mockServer.data['bar'].records[0].one2many_field);
mockServer._mockUnlink('foo', [2]);
assert.deepEqual([], mockServer.data['bar'].records[0].one2many_field);
});
QUnit.test('one2many should auto fill inverse field', async function (assert) {
this.data['bar'].records.push({ id: 1 });
this.data['bar'].records.push({ id: 2 });
this.data['foo'].records.push({
id: 3,
one2many_field: [1, 2],
});
const mockServer = new MockServer(this.data, {});
assert.strictEqual(3, mockServer.data['bar'].records[0].many2one_field);
assert.strictEqual(3, mockServer.data['bar'].records[1].many2one_field);
mockServer._mockUnlink('foo', [3]);
assert.strictEqual(false, mockServer.data['bar'].records[0].many2one_field);
assert.strictEqual(false, mockServer.data['bar'].records[1].many2one_field);
});
QUnit.test('many2many should auto fill inverse field', async function (assert) {
this.data['bar'].records.push({ id: 1 });
this.data['foo'].records.push({
id: 2,
many2many_field: [1],
});
const mockServer = new MockServer(this.data, {});
assert.deepEqual([2], mockServer.data['bar'].records[0].many2many_field);
mockServer._mockUnlink('foo', [2]);
assert.deepEqual([], mockServer.data['bar'].records[0].many2many_field);
});
QUnit.test('one2many update should update inverse field', async function (assert) {
this.data['bar'].records.push({ id: 1 });
this.data['bar'].records.push({ id: 2 });
this.data['foo'].records.push({
id: 3,
one2many_field: [1, 2],
});
const mockServer = new MockServer(this.data, {});
mockServer._mockWrite('foo', [[3], { one2many_field: [1] }]);
assert.strictEqual(3, mockServer.data['bar'].records[0].many2one_field);
assert.strictEqual(false, mockServer.data['bar'].records[1].many2one_field);
});
QUnit.test('many2many update should update inverse field', async function (assert) {
this.data['bar'].records.push({ id: 1 });
this.data['foo'].records.push({
id: 2,
many2many_field: [1],
});
const mockServer = new MockServer(this.data, {});
mockServer._mockWrite('foo', [[2], { many2many_field: [] }]);
assert.deepEqual([], mockServer.data['bar'].records[0].many2many_field);
});
QUnit.test('many2one update should update inverse field', async function (assert) {
this.data['bar'].records.push({ id: 1 });
this.data['foo'].records.push({
id: 2,
many2one_field: 1,
});
const mockServer = new MockServer(this.data, {});
mockServer._mockWrite('foo', [[2], { many2one_field: false }]);
assert.deepEqual([], mockServer.data['bar'].records[0].one2many_field);
});
QUnit.test('many2one_ref update should update inverse field', async function (assert) {
this.data['bar'].records.push({ id: 1 });
this.data['foo'].records.push({
id: 2,
res_model: 'bar',
many2one_reference: 1,
});
const mockServer = new MockServer(this.data, {});
mockServer._mockWrite('foo', [[2], { many2one_reference: false }]);
assert.deepEqual([], mockServer.data['bar'].records[0].one2many_field);
});
});
@@ -14,10 +14,7 @@ QUnit.test('grant course access', async function (assert) {
assert.expect(8);
this.data['res.partner'].records.push({ id: 5 });
this.data['slide.channel'].records.push({
activity_ids: [12],
id: 100,
});
this.data['slide.channel'].records.push({ id: 100 });
this.data['mail.activity'].records.push({
can_write: true,
id: 12,
@@ -54,10 +51,7 @@ QUnit.test('refuse course access', async function (assert) {
assert.expect(8);
this.data['res.partner'].records.push({ id: 5 });
this.data['slide.channel'].records.push({
activity_ids: [12],
id: 100,
});
this.data['slide.channel'].records.push({ id: 100 });
this.data['mail.activity'].records.push({
can_write: true,
id: 12,