diff --git a/addons/mail/models/ir_model.py b/addons/mail/models/ir_model.py index faaea2b3d73..22363f555ca 100644 --- a/addons/mail/models/ir_model.py +++ b/addons/mail/models/ir_model.py @@ -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 diff --git a/addons/mail/static/tests/helpers/mock_server.js b/addons/mail/static/tests/helpers/mock_server.js index 254466b48e0..2e617f6f0de 100644 --- a/addons/mail/static/tests/helpers/mock_server.js +++ b/addons/mail/static/tests/helpers/mock_server.js @@ -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]); diff --git a/addons/mail/static/tests/qunit_suite_tests/components/activity_mark_done_popover_tests.js b/addons/mail/static/tests/qunit_suite_tests/components/activity_mark_done_popover_tests.js index 4d61f7da3f1..3b102c21717 100644 --- a/addons/mail/static/tests/qunit_suite_tests/components/activity_mark_done_popover_tests.js +++ b/addons/mail/static/tests/qunit_suite_tests/components/activity_mark_done_popover_tests.js @@ -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, diff --git a/addons/mail/static/tests/qunit_suite_tests/components/activity_tests.js b/addons/mail/static/tests/qunit_suite_tests/components/activity_tests.js index bd59a7d2eb4..5fde28978a9 100644 --- a/addons/mail/static/tests/qunit_suite_tests/components/activity_tests.js +++ b/addons/mail/static/tests/qunit_suite_tests/components/activity_tests.js @@ -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, diff --git a/addons/mail/static/tests/qunit_suite_tests/components/follow_button_tests.js b/addons/mail/static/tests/qunit_suite_tests/components/follow_button_tests.js index e95e99e8cfe..3b8d217bd08 100644 --- a/addons/mail/static/tests/qunit_suite_tests/components/follow_button_tests.js +++ b/addons/mail/static/tests/qunit_suite_tests/components/follow_button_tests.js @@ -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, diff --git a/addons/mail/static/tests/qunit_suite_tests/components/follower_tests.js b/addons/mail/static/tests/qunit_suite_tests/components/follower_tests.js index a7bccbaa53d..957297fd970 100644 --- a/addons/mail/static/tests/qunit_suite_tests/components/follower_tests.js +++ b/addons/mail/static/tests/qunit_suite_tests/components/follower_tests.js @@ -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, diff --git a/addons/web/__manifest__.py b/addons/web/__manifest__.py index 309545a97a6..81ede0229df 100644 --- a/addons/web/__manifest__.py +++ b/addons/web/__manifest__.py @@ -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', diff --git a/addons/web/static/tests/legacy/helpers/mock_server.js b/addons/web/static/tests/legacy/helpers/mock_server.js index 64ef35c9fcf..d10e54c8e8c 100644 --- a/addons/web/static/tests/legacy/helpers/mock_server.js +++ b/addons/web/static/tests/legacy/helpers/mock_server.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 diff --git a/addons/web/static/tests/mock_relational_fields_tests.js b/addons/web/static/tests/mock_relational_fields_tests.js new file mode 100644 index 00000000000..4c2ce9060f8 --- /dev/null +++ b/addons/web/static/tests/mock_relational_fields_tests.js @@ -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); +}); + +}); diff --git a/addons/website_slides/static/tests/qunit_suite_tests/components/activity_tests.js b/addons/website_slides/static/tests/qunit_suite_tests/components/activity_tests.js index e8219ca237f..794a6370c26 100644 --- a/addons/website_slides/static/tests/qunit_suite_tests/components/activity_tests.js +++ b/addons/website_slides/static/tests/qunit_suite_tests/components/activity_tests.js @@ -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,