diff --git a/addons/hr/static/tests/helpers/mock_models.js b/addons/hr/static/tests/helpers/mock_models.js
index d4ae7c86035..51fa5d5ee50 100644
--- a/addons/hr/static/tests/helpers/mock_models.js
+++ b/addons/hr/static/tests/helpers/mock_models.js
@@ -2,33 +2,32 @@ odoo.define('hr/static/tests/helpers/mock_models.js', function (require) {
'use strict';
const MockModels = require('mail/static/tests/helpers/mock_models.js');
+const { patch } = require('web.utils');
-MockModels.patch('hr/static/tests/helpers/mock_models.js', T =>
- class extends T {
+patch(MockModels, 'hr/static/tests/helpers/mock_models.js', {
- //----------------------------------------------------------------------
- // Public
- //----------------------------------------------------------------------
+ //----------------------------------------------------------------------
+ // Public
+ //----------------------------------------------------------------------
- /**
- * @override
- */
- static generateData() {
- const data = super.generateData(...arguments);
- Object.assign(data, {
- 'hr.employee.public': {
- fields: {
- display_name: { string: "Name", type: "char" },
- user_id: { string: "User", type: "many2one", relation: 'res.users' },
- user_partner_id: { string: "Partner", type: "many2one", relation: 'res.partner' },
- },
- records: [],
+ /**
+ * @override
+ */
+ generateData() {
+ const data = this._super(...arguments);
+ Object.assign(data, {
+ 'hr.employee.public': {
+ fields: {
+ display_name: { string: "Name", type: "char" },
+ user_id: { string: "User", type: "many2one", relation: 'res.users' },
+ user_partner_id: { string: "Partner", type: "many2one", relation: 'res.partner' },
},
- });
- return data;
- }
-
- }
-);
+ records: [],
+ },
+ });
+ return data;
+ },
+
+});
});
diff --git a/addons/hr_holidays/static/src/components/thread_view/thread_view.js b/addons/hr_holidays/static/src/components/thread_view/thread_view.js
index 73ca28d4d0c..12ec1fb01b8 100644
--- a/addons/hr_holidays/static/src/components/thread_view/thread_view.js
+++ b/addons/hr_holidays/static/src/components/thread_view/thread_view.js
@@ -7,7 +7,7 @@ const components = {
const { patch } = require('web.utils');
-patch(components.ThreadView, 'hr_holidays/static/src/components/thread_view/thread_view.js', {
+patch(components.ThreadView.prototype, 'hr_holidays/static/src/components/thread_view/thread_view.js', {
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
diff --git a/addons/hr_holidays/static/tests/helpers/mock_models.js b/addons/hr_holidays/static/tests/helpers/mock_models.js
index 8513f29d2bf..a53657edb30 100644
--- a/addons/hr_holidays/static/tests/helpers/mock_models.js
+++ b/addons/hr_holidays/static/tests/helpers/mock_models.js
@@ -2,27 +2,26 @@ odoo.define('hr_holidays/static/tests/helpers/mock_models.js', function (require
'use strict';
const MockModels = require('mail/static/tests/helpers/mock_models.js');
+const { patch } = require('web.utils');
-MockModels.patch('hr_holidays/static/tests/helpers/mock_models.js', T =>
- class extends T {
+patch(MockModels, 'hr_holidays/static/tests/helpers/mock_models.js', {
- //----------------------------------------------------------------------
- // Public
- //----------------------------------------------------------------------
+ //----------------------------------------------------------------------
+ // Public
+ //----------------------------------------------------------------------
- /**
- * @override
- */
- static generateData() {
- const data = super.generateData(...arguments);
- Object.assign(data['res.partner'].fields, {
- // Not a real field but ease the testing
- out_of_office_date_end: { type: 'datetime' },
- });
- return data;
- }
-
- }
-);
+ /**
+ * @override
+ */
+ generateData() {
+ const data = this._super(...arguments);
+ Object.assign(data['res.partner'].fields, {
+ // Not a real field but ease the testing
+ out_of_office_date_end: { type: 'datetime' },
+ });
+ return data;
+ },
+
+});
});
diff --git a/addons/im_livechat/static/src/components/discuss/discuss.js b/addons/im_livechat/static/src/components/discuss/discuss.js
index 5b3c266430a..c3458d20e96 100644
--- a/addons/im_livechat/static/src/components/discuss/discuss.js
+++ b/addons/im_livechat/static/src/components/discuss/discuss.js
@@ -7,7 +7,7 @@ const components = {
const { patch } = require('web.utils');
-patch(components.Discuss, 'im_livechat/static/src/components/discuss/discuss.js', {
+patch(components.Discuss.prototype, 'im_livechat/static/src/components/discuss/discuss.js', {
//--------------------------------------------------------------------------
// Public
diff --git a/addons/im_livechat/static/src/components/discuss_sidebar/discuss_sidebar.js b/addons/im_livechat/static/src/components/discuss_sidebar/discuss_sidebar.js
index 35070cb8e6d..7481805e94e 100644
--- a/addons/im_livechat/static/src/components/discuss_sidebar/discuss_sidebar.js
+++ b/addons/im_livechat/static/src/components/discuss_sidebar/discuss_sidebar.js
@@ -7,7 +7,7 @@ const components = {
const { patch } = require('web.utils');
-patch(components.DiscussSidebar, 'im_livechat/static/src/components/discuss_sidebar/discuss_sidebar.js', {
+patch(components.DiscussSidebar.prototype, 'im_livechat/static/src/components/discuss_sidebar/discuss_sidebar.js', {
//--------------------------------------------------------------------------
// Public
diff --git a/addons/im_livechat/static/src/components/discuss_sidebar_item/discuss_sidebar_item.js b/addons/im_livechat/static/src/components/discuss_sidebar_item/discuss_sidebar_item.js
index b2273d9989b..856f100a8a5 100644
--- a/addons/im_livechat/static/src/components/discuss_sidebar_item/discuss_sidebar_item.js
+++ b/addons/im_livechat/static/src/components/discuss_sidebar_item/discuss_sidebar_item.js
@@ -7,7 +7,7 @@ const components = {
const { patch } = require('web.utils');
-patch(components.DiscussSidebarItem, 'im_livechat/static/src/components/discuss_sidebar_item/discuss_sidebar_item.js', {
+patch(components.DiscussSidebarItem.prototype, 'im_livechat/static/src/components/discuss_sidebar_item/discuss_sidebar_item.js', {
//--------------------------------------------------------------------------
// Public
diff --git a/addons/im_livechat/static/src/components/notification_list/notification_list.js b/addons/im_livechat/static/src/components/notification_list/notification_list.js
index c0ab76b28f3..9e1f1f59afa 100644
--- a/addons/im_livechat/static/src/components/notification_list/notification_list.js
+++ b/addons/im_livechat/static/src/components/notification_list/notification_list.js
@@ -9,7 +9,7 @@ const { patch } = require('web.utils');
components.NotificationList._allowedFilters.push('livechat');
-patch(components.NotificationList, 'im_livechat/static/src/components/notification_list/notification_list.js', {
+patch(components.NotificationList.prototype, 'im_livechat/static/src/components/notification_list/notification_list.js', {
//--------------------------------------------------------------------------
// Public
diff --git a/addons/im_livechat/static/src/components/thread_needaction_preview/thread_needaction_preview.js b/addons/im_livechat/static/src/components/thread_needaction_preview/thread_needaction_preview.js
index 3cd95824634..45deb68109b 100644
--- a/addons/im_livechat/static/src/components/thread_needaction_preview/thread_needaction_preview.js
+++ b/addons/im_livechat/static/src/components/thread_needaction_preview/thread_needaction_preview.js
@@ -7,7 +7,7 @@ const components = {
const { patch } = require('web.utils');
-patch(components.ThreadNeedactionPreview, 'thread_needaction_preview', {
+patch(components.ThreadNeedactionPreview.prototype, 'thread_needaction_preview', {
//--------------------------------------------------------------------------
// Public
diff --git a/addons/im_livechat/static/src/components/thread_preview/thread_preview.js b/addons/im_livechat/static/src/components/thread_preview/thread_preview.js
index ea752b56936..61554d71530 100644
--- a/addons/im_livechat/static/src/components/thread_preview/thread_preview.js
+++ b/addons/im_livechat/static/src/components/thread_preview/thread_preview.js
@@ -7,7 +7,7 @@ const components = {
const { patch } = require('web.utils');
-patch(components.ThreadPreview, 'im_livechat/static/src/components/thread_preview/thread_preview.js', {
+patch(components.ThreadPreview.prototype, 'im_livechat/static/src/components/thread_preview/thread_preview.js', {
//--------------------------------------------------------------------------
// Public
diff --git a/addons/im_livechat/static/tests/helpers/mock_models.js b/addons/im_livechat/static/tests/helpers/mock_models.js
index db18e8f776b..4ee59970de2 100644
--- a/addons/im_livechat/static/tests/helpers/mock_models.js
+++ b/addons/im_livechat/static/tests/helpers/mock_models.js
@@ -2,41 +2,40 @@ odoo.define('im_livechat/static/tests/helpers/mock_models.js', function (require
'use strict';
const MockModels = require('mail/static/tests/helpers/mock_models.js');
+const { patch } = require('web.utils');
-MockModels.patch('im_livechat/static/tests/helpers/mock_models.js', T =>
- class extends T {
+patch(MockModels, 'im_livechat/static/tests/helpers/mock_models.js', {
- //----------------------------------------------------------------------
- // Public
- //----------------------------------------------------------------------
+ //----------------------------------------------------------------------
+ // Public
+ //----------------------------------------------------------------------
- /**
- * @override
- */
- static generateData() {
- const data = super.generateData(...arguments);
- Object.assign(data, {
- 'im_livechat.channel': {
- fields: {
- user_ids: { string: "Operators", type: 'many2many', relation: 'res.users' }
- },
- records: [],
- }
- });
- Object.assign(data['mail.channel'].fields, {
- anonymous_name: { string: "Anonymous Name", type: 'char' },
- country_id: { string: "Country", type: 'many2one', relation: 'res.country' },
- livechat_active: { string: "Is livechat ongoing?", type: 'boolean', default: false },
- livechat_channel_id: { string: "Channel", type: 'many2one', relation: 'im_livechat.channel' },
- livechat_operator_id: { string: "Operator", type: 'many2one', relation: 'res.partner' },
- });
- Object.assign(data['res.users'].fields, {
- livechat_username: { string: 'Livechat Username', type: 'string' },
- });
- return data;
- }
-
- }
-);
+ /**
+ * @override
+ */
+ generateData() {
+ const data = this._super(...arguments);
+ Object.assign(data, {
+ 'im_livechat.channel': {
+ fields: {
+ user_ids: { string: "Operators", type: 'many2many', relation: 'res.users' }
+ },
+ records: [],
+ }
+ });
+ Object.assign(data['mail.channel'].fields, {
+ anonymous_name: { string: "Anonymous Name", type: 'char' },
+ country_id: { string: "Country", type: 'many2one', relation: 'res.country' },
+ livechat_active: { string: "Is livechat ongoing?", type: 'boolean', default: false },
+ livechat_channel_id: { string: "Channel", type: 'many2one', relation: 'im_livechat.channel' },
+ livechat_operator_id: { string: "Operator", type: 'many2one', relation: 'res.partner' },
+ });
+ Object.assign(data['res.users'].fields, {
+ livechat_username: { string: 'Livechat Username', type: 'string' },
+ });
+ return data;
+ },
+
+});
});
diff --git a/addons/mail/static/src/components/chat_window/chat_window.js b/addons/mail/static/src/components/chat_window/chat_window.js
index f9271523651..6811eea6c6b 100644
--- a/addons/mail/static/src/components/chat_window/chat_window.js
+++ b/addons/mail/static/src/components/chat_window/chat_window.js
@@ -11,8 +11,6 @@ const useStore = require('mail/static/src/component_hooks/use_store/use_store.js
const useUpdate = require('mail/static/src/component_hooks/use_update/use_update.js');
const { isEventHandled } = require('mail/static/src/utils/utils.js');
-const patchMixin = require('web.patchMixin');
-
const { Component } = owl;
const { useRef } = owl.hooks;
@@ -358,6 +356,6 @@ Object.assign(ChatWindow, {
template: 'mail.ChatWindow',
});
-return patchMixin(ChatWindow);
+return ChatWindow;
});
diff --git a/addons/mail/static/src/components/dialog/dialog.js b/addons/mail/static/src/components/dialog/dialog.js
index 65902c9ce23..cf64116bf04 100644
--- a/addons/mail/static/src/components/dialog/dialog.js
+++ b/addons/mail/static/src/components/dialog/dialog.js
@@ -4,8 +4,6 @@ odoo.define('mail/static/src/components/dialog/dialog.js', function (require) {
const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
-const patchMixin = require('web.patchMixin');
-
const { Component } = owl;
const { useRef } = owl.hooks;
@@ -114,6 +112,6 @@ Object.assign(Dialog, {
template: 'mail.Dialog',
});
-return patchMixin(Dialog);
+return Dialog;
});
diff --git a/addons/mail/static/src/components/discuss/discuss.js b/addons/mail/static/src/components/discuss/discuss.js
index e303e495015..0ad12e48409 100644
--- a/addons/mail/static/src/components/discuss/discuss.js
+++ b/addons/mail/static/src/components/discuss/discuss.js
@@ -15,8 +15,6 @@ const components = {
const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
-const patchMixin = require('web.patchMixin');
-
const { Component } = owl;
const { useRef } = owl.hooks;
@@ -308,6 +306,6 @@ Object.assign(Discuss, {
template: 'mail.Discuss',
});
-return patchMixin(Discuss);
+return Discuss;
});
diff --git a/addons/mail/static/src/components/messaging_menu/messaging_menu.js b/addons/mail/static/src/components/messaging_menu/messaging_menu.js
index 9eb7fd71a49..e7c93d2cb66 100644
--- a/addons/mail/static/src/components/messaging_menu/messaging_menu.js
+++ b/addons/mail/static/src/components/messaging_menu/messaging_menu.js
@@ -9,8 +9,6 @@ const components = {
const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
-const patchMixin = require('web.patchMixin');
-
const { Component } = owl;
class MessagingMenu extends Component {
@@ -229,6 +227,6 @@ Object.assign(MessagingMenu, {
template: 'mail.MessagingMenu',
});
-return patchMixin(MessagingMenu);
+return MessagingMenu;
});
diff --git a/addons/mail/static/src/js/custom_filter_item.js b/addons/mail/static/src/js/custom_filter_item.js
index abe15edab94..5dc590be354 100644
--- a/addons/mail/static/src/js/custom_filter_item.js
+++ b/addons/mail/static/src/js/custom_filter_item.js
@@ -2,8 +2,9 @@ odoo.define('mail.CustomFilterItem', function (require) {
"use strict";
const CustomFilterItem = require('web.CustomFilterItem');
+ const { patch } = require('web.utils');
- CustomFilterItem.patch('mail.CustomFilterItem', T => class extends T {
+ patch(CustomFilterItem.prototype, 'mail.CustomFilterItem', {
/**
* With the `mail` module installed, we want to filter out some of the
@@ -11,10 +12,10 @@ odoo.define('mail.CustomFilterItem', function (require) {
* @override
*/
_validateField(field) {
- return super._validateField(...arguments) &&
+ return this._super(...arguments) &&
field.relation !== 'mail.message' &&
field.name !== 'message_ids';
- }
+ },
});
return CustomFilterItem;
diff --git a/addons/mail/static/src/js/views/activity/activity_renderer.js b/addons/mail/static/src/js/views/activity/activity_renderer.js
index e62d416ea28..8f36231194d 100644
--- a/addons/mail/static/src/js/views/activity/activity_renderer.js
+++ b/addons/mail/static/src/js/views/activity/activity_renderer.js
@@ -7,7 +7,6 @@ const ActivityRecord = require('mail.ActivityRecord');
const { ComponentAdapter } = require('web.OwlCompatibility');
const core = require('web.core');
const KanbanColumnProgressBar = require('web.KanbanColumnProgressBar');
-const patchMixin = require('web.patchMixin');
const QWeb = require('web.QWeb');
const session = require('web.session');
const utils = require('web.utils');
@@ -205,6 +204,6 @@ ActivityRenderer.components = {
};
ActivityRenderer.template = 'mail.ActivityRenderer';
-return patchMixin(ActivityRenderer);
+return ActivityRenderer;
});
diff --git a/addons/mail/static/src/utils/utils.js b/addons/mail/static/src/utils/utils.js
index f502fc8f326..06a99ed11c8 100644
--- a/addons/mail/static/src/utils/utils.js
+++ b/addons/mail/static/src/utils/utils.js
@@ -124,7 +124,7 @@ function patchClassMethods(Class, patchName, patch) {
* @returns {function} unpatch function
*/
function patchInstanceMethods(Class, patchName, patch) {
- return webUtilsPatch(Class, patchName, patch);
+ return webUtilsPatch(Class.prototype, patchName, patch);
}
/**
@@ -158,7 +158,7 @@ function unpatchClassMethods(Class, patchName) {
* @param {string} patchName
*/
function unpatchInstanceMethods(Class, patchName) {
- return webUtilsUnpatch(Class, patchName);
+ return webUtilsUnpatch(Class.prototype, patchName);
}
//------------------------------------------------------------------------------
diff --git a/addons/mail/static/tests/helpers/mock_models.js b/addons/mail/static/tests/helpers/mock_models.js
index 873b5b0be70..4ce50094ac7 100644
--- a/addons/mail/static/tests/helpers/mock_models.js
+++ b/addons/mail/static/tests/helpers/mock_models.js
@@ -1,8 +1,6 @@
odoo.define('mail/static/tests/helpers/mock_models.js', function (require) {
'use strict';
-const patchMixin = require('web.patchMixin');
-
/**
* Allows to generate mocked models that will be used by the mocked server.
* This is defined as a class to allow patches by dependent modules and a new
@@ -253,6 +251,6 @@ class MockModels {
}
-return patchMixin(MockModels);
+return MockModels;
});
diff --git a/addons/sms/static/src/components/notification_group/notification_group.js b/addons/sms/static/src/components/notification_group/notification_group.js
index 053fedc5bd2..973948992c6 100644
--- a/addons/sms/static/src/components/notification_group/notification_group.js
+++ b/addons/sms/static/src/components/notification_group/notification_group.js
@@ -7,7 +7,7 @@ const components = {
const { patch } = require('web.utils');
-patch(components.NotificationGroup, 'sms/static/src/components/notification_group/notification_group.js', {
+patch(components.NotificationGroup.prototype, 'sms/static/src/components/notification_group/notification_group.js', {
//--------------------------------------------------------------------------
// Public
diff --git a/addons/snailmail/static/src/components/message/message.js b/addons/snailmail/static/src/components/message/message.js
index 1033c901367..a51d131d2a4 100644
--- a/addons/snailmail/static/src/components/message/message.js
+++ b/addons/snailmail/static/src/components/message/message.js
@@ -16,7 +16,7 @@ Object.assign(components.Message.components, {
SnailmailNotificationPopover: components.SnailmailNotificationPopover,
});
-patch(components.Message, 'snailmail/static/src/components/message/message.js', {
+patch(components.Message.prototype, 'snailmail/static/src/components/message/message.js', {
/**
* @override
*/
diff --git a/addons/snailmail/static/src/components/notification_group/notification_group.js b/addons/snailmail/static/src/components/notification_group/notification_group.js
index 00cdebe15d9..97254dbea22 100644
--- a/addons/snailmail/static/src/components/notification_group/notification_group.js
+++ b/addons/snailmail/static/src/components/notification_group/notification_group.js
@@ -7,7 +7,7 @@ const components = {
const { patch } = require('web.utils');
-patch(components.NotificationGroup, 'snailmail/static/src/components/notification_group/notification_group.js', {
+patch(components.NotificationGroup.prototype, 'snailmail/static/src/components/notification_group/notification_group.js', {
//--------------------------------------------------------------------------
// Public
diff --git a/addons/snailmail/static/tests/helpers/mock_models.js b/addons/snailmail/static/tests/helpers/mock_models.js
index d338504d024..ee1a442b5a3 100644
--- a/addons/snailmail/static/tests/helpers/mock_models.js
+++ b/addons/snailmail/static/tests/helpers/mock_models.js
@@ -2,31 +2,30 @@ odoo.define('snailmail/static/tests/helpers/mock_models.js', function (require)
'use strict';
const MockModels = require('mail/static/tests/helpers/mock_models.js');
+const { patch } = require('web.utils');
-MockModels.patch('snailmail/static/tests/helpers/mock_models.js', T =>
- class extends T {
+patch(MockModels, 'snailmail/static/tests/helpers/mock_models.js', {
- //----------------------------------------------------------------------
- // Public
- //----------------------------------------------------------------------
+ //----------------------------------------------------------------------
+ // Public
+ //----------------------------------------------------------------------
- /**
- * @override
- */
- static generateData() {
- const data = super.generateData(...arguments);
- Object.assign(data, {
- 'snailmail.letter': {
- fields: {
- message_id: { string: 'Snailmail Status Message', type: 'many2one', relation: 'mail.message' },
- },
- records: [],
+ /**
+ * @override
+ */
+ generateData() {
+ const data = this._super(...arguments);
+ Object.assign(data, {
+ 'snailmail.letter': {
+ fields: {
+ message_id: { string: 'Snailmail Status Message', type: 'many2one', relation: 'mail.message' },
},
- });
- return data;
- }
-
- }
-);
+ records: [],
+ },
+ });
+ return data;
+ },
+
+});
});
diff --git a/addons/stock/static/tests/stock_traceability_report_backend_tests.js b/addons/stock/static/tests/stock_traceability_report_backend_tests.js
index 5ab69403544..08181d8535c 100644
--- a/addons/stock/static/tests/stock_traceability_report_backend_tests.js
+++ b/addons/stock/static/tests/stock_traceability_report_backend_tests.js
@@ -5,6 +5,7 @@ odoo.define('stock.stock_traceability_report_backend_tests', function (require)
const dom = require('web.dom');
const StockReportGeneric = require('stock.stock_report_generic');
const testUtils = require('web.test_utils');
+ const { patch, unpatch } = require('web.utils');
const { createActionManager, dom: domUtils } = testUtils;
@@ -77,20 +78,18 @@ odoo.define('stock.stock_traceability_report_backend_tests', function (require)
let mountCount = 0;
- ControlPanel.patch('test.ControlPanel', T => {
- class ControlPanelPatchTest extends T {
- mounted() {
- mountCount = mountCount + 1;
- this.__uniqueId = mountCount;
- assert.step(`mounted ${this.__uniqueId}`);
- super.mounted(...arguments);
- }
- willUnmount() {
- assert.step(`willUnmount ${this.__uniqueId}`);
- super.mounted(...arguments);
- }
- }
- return ControlPanelPatchTest;
+ patch(ControlPanel.prototype, 'test.ControlPanel', {
+ mounted() {
+ mountCount = mountCount + 1;
+ this.__uniqueId = mountCount;
+ assert.step(`mounted ${this.__uniqueId}`);
+ this.__superMounted = this._super.bind(this);
+ this.__superMounted(...arguments);
+ },
+ willUnmount() {
+ assert.step(`willUnmount ${this.__uniqueId}`);
+ this.__superMounted(...arguments);
+ },
});
const actionManager = await createActionManager({
@@ -145,7 +144,7 @@ odoo.define('stock.stock_traceability_report_backend_tests', function (require)
'willUnmount 3',
]);
- ControlPanel.unpatch('test.ControlPanel');
+ unpatch(ControlPanel.prototype, 'test.ControlPanel');
});
});
});
diff --git a/addons/web/static/src/js/control_panel/control_panel.js b/addons/web/static/src/js/control_panel/control_panel.js
index 60e26f0b280..16cb905f683 100644
--- a/addons/web/static/src/js/control_panel/control_panel.js
+++ b/addons/web/static/src/js/control_panel/control_panel.js
@@ -7,7 +7,6 @@ odoo.define('web.ControlPanel', function (require) {
const FavoriteMenu = require('web.FavoriteMenu');
const FilterMenu = require('web.FilterMenu');
const GroupByMenu = require('web.GroupByMenu');
- const patchMixin = require('web.patchMixin');
const Pager = require('web.Pager');
const SearchBar = require('web.SearchBar');
const { useModel } = require('web.Model');
@@ -217,5 +216,5 @@ odoo.define('web.ControlPanel', function (require) {
};
ControlPanel.template = 'web.ControlPanel';
- return patchMixin(ControlPanel);
+ return ControlPanel;
});
diff --git a/addons/web/static/src/js/control_panel/custom_filter_item.js b/addons/web/static/src/js/control_panel/custom_filter_item.js
index 7aeb81fe4ea..29fc35084a5 100644
--- a/addons/web/static/src/js/control_panel/custom_filter_item.js
+++ b/addons/web/static/src/js/control_panel/custom_filter_item.js
@@ -6,7 +6,6 @@ odoo.define('web.CustomFilterItem', function (require) {
const DropdownMenuItem = require('web.DropdownMenuItem');
const { FIELD_OPERATORS, FIELD_TYPES } = require('web.searchUtils');
const field_utils = require('web.field_utils');
- const patchMixin = require('web.patchMixin');
const { useModel } = require('web.Model');
/**
@@ -262,5 +261,5 @@ odoo.define('web.CustomFilterItem', function (require) {
};
CustomFilterItem.template = 'web.CustomFilterItem';
- return patchMixin(CustomFilterItem);
+ return CustomFilterItem;
});
diff --git a/addons/web/static/src/js/core/owl_dialog.js b/addons/web/static/src/js/core/owl_dialog.js
index 62c6d239dda..9c5dc93a7b3 100644
--- a/addons/web/static/src/js/core/owl_dialog.js
+++ b/addons/web/static/src/js/core/owl_dialog.js
@@ -1,8 +1,6 @@
odoo.define('web.OwlDialog', function (require) {
"use strict";
- const patchMixin = require('web.patchMixin');
-
const { Component, hooks, misc } = owl;
const { Portal } = misc;
const { useExternalListener, useRef } = hooks;
@@ -271,5 +269,5 @@ odoo.define('web.OwlDialog', function (require) {
};
Dialog.template = 'web.OwlDialog';
- return patchMixin(Dialog);
+ return Dialog;
});
diff --git a/addons/web/static/src/js/core/patch_mixin.js b/addons/web/static/src/js/core/patch_mixin.js
deleted file mode 100644
index 99f06f56588..00000000000
--- a/addons/web/static/src/js/core/patch_mixin.js
+++ /dev/null
@@ -1,80 +0,0 @@
-odoo.define("web.patchMixin", function () {
- "use strict";
-
- /**
- * This module defines and exports the 'patchMixin' function. This function
- * returns a 'monkey-patchable' version of the ES6 Class given in arguments.
- *
- * const patchMixin = require('web.patchMixin');
- * class MyClass {
- * print() {
- * console.log('MyClass');
- * }
- * }
- * const MyPatchedClass = patchMixin(MyClass);
- *
- *
- * A patchable class has a 'patch' function, allowing to define a patch:
- *
- * MyPatchedClass.patch("module_name.key", T =>
- * class extends T {
- * print() {
- * console.log('MyPatchedClass');
- * super.print();
- * }
- * }
- * );
- *
- * const myPatchedClass = new MyPatchedClass();
- * myPatchedClass.print(); // displays "MyPatchedClass" and "MyClass"
- *
- *
- * The 'unpatch' function can be used to remove a patch, given its key:
- *
- * MyPatchedClass.unpatch("module_name.key");
- */
- function patchMixin(OriginalClass) {
- let unpatchList = [];
- class PatchableClass extends OriginalClass {}
-
- PatchableClass.patch = function (name, patch) {
- if (unpatchList.find(x => x.name === name)) {
- throw new Error(`Class ${OriginalClass.name} already has a patch ${name}`);
- }
- if (!Object.prototype.hasOwnProperty.call(this, 'patch')) {
- throw new Error(`Class ${this.name} is not patchable`);
- }
- const SubClass = patch(Object.getPrototypeOf(this));
- unpatchList.push({
- name: name,
- elem: this,
- prototype: this.prototype,
- origProto: Object.getPrototypeOf(this),
- origPrototype: Object.getPrototypeOf(this.prototype),
- patch: patch,
- });
- Object.setPrototypeOf(this, SubClass);
- Object.setPrototypeOf(this.prototype, SubClass.prototype);
- };
-
- PatchableClass.unpatch = function (name) {
- if (!unpatchList.find(x => x.name === name)) {
- throw new Error(`Class ${OriginalClass.name} does not have any patch ${name}`);
- }
- const toUnpatch = unpatchList.reverse();
- unpatchList = [];
- for (let unpatch of toUnpatch) {
- Object.setPrototypeOf(unpatch.elem, unpatch.origProto);
- Object.setPrototypeOf(unpatch.prototype, unpatch.origPrototype);
- }
- for (let u of toUnpatch.reverse()) {
- if (u.name !== name) {
- PatchableClass.patch(u.name, u.patch);
- }
- }
- };
- return PatchableClass;
- }
-
- return patchMixin;
-});
diff --git a/addons/web/static/src/js/core/popover.js b/addons/web/static/src/js/core/popover.js
index d2102b56d3c..9796916ab87 100644
--- a/addons/web/static/src/js/core/popover.js
+++ b/addons/web/static/src/js/core/popover.js
@@ -1,8 +1,6 @@
odoo.define('web.Popover', function (require) {
'use strict';
- const patchMixin = require('web.patchMixin');
-
const { Component, hooks, misc, QWeb } = owl;
const { Portal } = misc;
const { useRef, useState } = hooks;
@@ -318,5 +316,5 @@ odoo.define('web.Popover', function (require) {
QWeb.registerComponent('Popover', Popover);
- return patchMixin(Popover);
+ return Popover;
});
diff --git a/addons/web/static/src/js/core/utils.js b/addons/web/static/src/js/core/utils.js
index aa085e9b525..15f3f973f24 100644
--- a/addons/web/static/src/js/core/utils.js
+++ b/addons/web/static/src/js/core/utils.js
@@ -611,57 +611,80 @@ var utils = {
}, []);
},
/**
- * Patch a class and return a function that remove the patch
+ * Patch an object and return a function that remove the patch
* when called.
*
- * This function is the last resort solution for monkey-patching an
- * ES6 Class, for people that do not control the code defining the Class
- * to patch (e.g. partners), and when that Class isn't patchable already
- * (i.e. when it doesn't have a 'patch' function, defined by the 'web.patchMixin').
- *
- * @param {Class} C Class to patch
+ * @param {Object} obj Object to patch
* @param {string} patchName
* @param {Object} patch
- * @returns {Function}
*/
- patch: function (C, patchName, patch) {
- let metadata = patchMap.get(C.prototype);
- if (!metadata) {
- metadata = {
- origMethods: {},
- patches: {},
- current: []
- };
- patchMap.set(C.prototype, metadata);
- }
- const proto = C.prototype;
- if (metadata.patches[patchName]) {
- throw new Error(`Patch [${patchName}] already exists`);
- }
- metadata.patches[patchName] = patch;
- applyPatch(proto, patch);
- metadata.current.push(patchName);
-
- function applyPatch(proto, patch) {
- Object.keys(patch).forEach(function (methodName) {
- const method = patch[methodName];
- if (typeof method === "function") {
- const original = proto[methodName];
- if (!(methodName in metadata.origMethods)) {
- metadata.origMethods[methodName] = original;
- }
- proto[methodName] = function (...args) {
- const previousSuper = this._super;
- this._super = original;
- const res = method.call(this, ...args);
- this._super = previousSuper;
- return res;
- };
- }
+ patch: function (obj, patchName, patch) {
+ if (!patchMap.has(obj)) {
+ patchMap.set(obj, {
+ original: {},
+ patches: [],
});
}
+ const objDesc = patchMap.get(obj);
+ if (objDesc.patches.some(p => p.name === patchName)) {
+ throw new Error(`Class ${obj.name} already has a patch ${patchName}`);
+ }
+ objDesc.patches.push({
+ name: patchName,
+ patch,
+ });
- return utils.unpatch.bind(null, C, patchName);
+ for (const k in patch) {
+ let prevDesc = null;
+ let proto = obj;
+ do {
+ prevDesc = Object.getOwnPropertyDescriptor(proto, k);
+ proto = Object.getPrototypeOf(proto);
+ } while (!prevDesc && proto);
+
+ const newDesc = Object.getOwnPropertyDescriptor(patch, k);
+ if (!objDesc.original.hasOwnProperty(k)) {
+ objDesc.original[k] = Object.getOwnPropertyDescriptor(obj, k);
+ }
+
+ if (prevDesc) {
+ const patchedFnName = `${k} (patch ${patchName})`;
+
+ if (prevDesc.value && typeof newDesc.value === "function") {
+ makeIntermediateFunction("value", prevDesc, newDesc, patchedFnName);
+ }
+ if (prevDesc.get || prevDesc.set) {
+ // get and set are defined together. If they are both defined
+ // in the previous descriptor but only one in the new descriptor
+ // then the other will be undefined so we need to apply the
+ // previous descriptor in the new one.
+ newDesc.get = newDesc.get || prevDesc.get;
+ newDesc.set = newDesc.set || prevDesc.set;
+ if (prevDesc.get && typeof newDesc.get === "function") {
+ makeIntermediateFunction("get", prevDesc, newDesc, patchedFnName);
+ }
+ if (prevDesc.set && typeof newDesc.set === "function") {
+ makeIntermediateFunction("set", prevDesc, newDesc, patchedFnName);
+ }
+ }
+ }
+
+ Object.defineProperty(obj, k, newDesc);
+ }
+
+ function makeIntermediateFunction(key, prevDesc, newDesc, patchedFnName) {
+ const _superFn = prevDesc[key];
+ const patchFn = newDesc[key];
+ newDesc[key] = {
+ [patchedFnName](...args) {
+ const prevSuper = this._super;
+ this._super = _superFn.bind(this);
+ const result = patchFn.call(this, ...args);
+ this._super = prevSuper;
+ return result;
+ }
+ }[patchedFnName];
+ }
},
/**
* performs a half up rounding with a fixed amount of decimals, correcting for float loss of precision
@@ -872,26 +895,29 @@ var utils = {
* We define here an unpatch function. This is mostly useful if we want to
* remove a patch. For example, for testing purposes
*
- * @param {Class} C
+ * @param {Object} obj
* @param {string} patchName
*/
- unpatch: function (C, patchName) {
- const proto = C.prototype;
- let metadata = patchMap.get(proto);
- if (!metadata) {
- return;
+ unpatch: function (obj, patchName) {
+ const objDesc = patchMap.get(obj);
+ if (!objDesc.patches.some(p => p.name === patchName)) {
+ throw new Error(`Class ${obj.name} does not have any patch ${patchName}`);
}
- patchMap.delete(proto);
+ patchMap.delete(obj);
- // reset to original
- for (let k in metadata.origMethods) {
- proto[k] = metadata.origMethods[k];
+ // Restore original methods on the prototype and the class.
+ for (const k in objDesc.original) {
+ if (objDesc.original[k] === undefined) {
+ delete obj[k];
+ } else {
+ Object.defineProperty(obj, k, objDesc.original[k]);
+ }
}
- // apply other patches
- for (let name of metadata.current) {
- if (name !== patchName) {
- utils.patch(C, name, metadata.patches[name]);
+ // Re-apply the patches except the one to remove.
+ for (const patchDesc of objDesc.patches) {
+ if (patchDesc.name !== patchName) {
+ utils.patch(obj, patchDesc.name, patchDesc.patch);
}
}
},
diff --git a/addons/web/static/src/js/views/graph/graph_renderer.js b/addons/web/static/src/js/views/graph/graph_renderer.js
index d8cc39a29df..45944e6ac63 100644
--- a/addons/web/static/src/js/views/graph/graph_renderer.js
+++ b/addons/web/static/src/js/views/graph/graph_renderer.js
@@ -5,7 +5,6 @@ odoo.define("web/static/src/js/views/graph/graph_renderer", function (require) {
const { device } = require("web.config");
const { DateClasses } = require("web.dataComparisonUtils");
const fieldUtils = require("web.field_utils");
- const patchMixin = require("web.patchMixin");
const { sortBy } = require("web.utils");
const COLORS = [
@@ -1048,6 +1047,6 @@ odoo.define("web/static/src/js/views/graph/graph_renderer", function (require) {
withSearchPanel: { type: Boolean, optional: 1 },
};
- return patchMixin(GraphRenderer);
+ return GraphRenderer;
});
diff --git a/addons/web/static/src/js/views/pivot/pivot_renderer.js b/addons/web/static/src/js/views/pivot/pivot_renderer.js
index ed9df45c098..07801fca0b9 100644
--- a/addons/web/static/src/js/views/pivot/pivot_renderer.js
+++ b/addons/web/static/src/js/views/pivot/pivot_renderer.js
@@ -2,7 +2,6 @@
import OwlAbstractRenderer from '../abstract_renderer_owl';
import field_utils from 'web.field_utils';
- import patchMixin from 'web.patchMixin';
const { useExternalListener, useState, onMounted, onPatched } = owl.hooks;
@@ -196,4 +195,4 @@
PivotRenderer.template = 'web.PivotRenderer';
- export default patchMixin(PivotRenderer);
+ export default PivotRenderer;
diff --git a/addons/web/static/src/js/views/search_panel.js b/addons/web/static/src/js/views/search_panel.js
index 270d1ad181c..e5936260e86 100644
--- a/addons/web/static/src/js/views/search_panel.js
+++ b/addons/web/static/src/js/views/search_panel.js
@@ -1,7 +1,6 @@
/** @odoo-module alias=web.searchPanel **/
import { Model, useModel } from "web.Model";
- import patchMixin from "web.patchMixin";
const { Component, hooks } = owl;
const { useState, useSubEnv } = hooks;
@@ -17,9 +16,7 @@
* @extends Component
*/
class SearchPanel extends Component {
- constructor() {
- super(...arguments);
-
+ setup() {
useSubEnv({ searchModel: this.props.searchModel });
this.state = useState({
@@ -209,4 +206,4 @@
};
SearchPanel.template = "web.SearchPanel";
- export default patchMixin(SearchPanel);
+ export default SearchPanel;
diff --git a/addons/web/static/tests/core/patch_mixin_tests.js b/addons/web/static/tests/core/patch_mixin_tests.js
deleted file mode 100644
index 7329a9607d4..00000000000
--- a/addons/web/static/tests/core/patch_mixin_tests.js
+++ /dev/null
@@ -1,994 +0,0 @@
-odoo.define("web.patchMixin_tests", function (require) {
-"use strict";
-
-const patchMixin = require('web.patchMixin');
-
-QUnit.module('core', {}, function () {
-
- QUnit.module('patchMixin', {}, function () {
-
- QUnit.test('basic use', function (assert) {
- assert.expect(4);
-
- const A = patchMixin(
- class {
- constructor() {
- assert.step('A.constructor');
- }
- f() {
- assert.step('A.f');
- }
- }
- );
-
- const a = new A();
- a.f();
-
- assert.ok(a instanceof A);
- assert.verifySteps([
- 'A.constructor',
- 'A.f',
- ]);
- });
-
- QUnit.test('simple patch', function (assert) {
- assert.expect(5);
-
- const A = patchMixin(
- class {
- constructor() {
- assert.step('A.constructor');
- }
- f() {
- assert.step('A.f');
- }
- }
- );
-
- A.patch('patch', T =>
- class extends T {
- constructor() {
- super();
- assert.step('patch.constructor');
- }
- f() {
- super.f();
- assert.step('patch.f');
- }
- }
- );
-
- (new A()).f();
-
- assert.verifySteps([
- 'A.constructor',
- 'patch.constructor',
- 'A.f',
- 'patch.f',
- ]);
- });
-
- QUnit.test('two patches on same base class', function (assert) {
- assert.expect(7);
-
- const A = patchMixin(
- class {
- constructor() {
- assert.step('A.constructor');
- }
- f() {
- assert.step('A.f');
- }
- }
- );
-
- A.patch('patch1', T =>
- class extends T {
- constructor() {
- super();
- assert.step('patch1.constructor');
- }
- f() {
- super.f();
- assert.step('patch1.f');
- }
- }
- );
-
- A.patch('patch2', T =>
- class extends T {
- constructor() {
- super();
- assert.step('patch2.constructor');
- }
- f() {
- super.f();
- assert.step('patch2.f');
- }
- }
- );
-
- (new A()).f();
-
- assert.verifySteps([
- 'A.constructor',
- 'patch1.constructor',
- 'patch2.constructor',
- 'A.f',
- 'patch1.f',
- 'patch2.f',
- ]);
- });
-
- QUnit.test('two patches with same name on same base class', function (assert) {
- assert.expect(1);
-
- const A = patchMixin(class {});
-
- A.patch('patch', T => class extends T {});
-
- // keys should be unique
- assert.throws(() => {
- A.patch('patch', T => class extends T {});
- });
- });
-
- QUnit.test('unpatch', function (assert) {
- assert.expect(8);
-
- const A = patchMixin(
- class {
- constructor() {
- assert.step('A.constructor');
- }
- f() {
- assert.step('A.f');
- }
- }
- );
-
- A.patch('patch', T =>
- class extends T {
- constructor() {
- super();
- assert.step('patch.constructor');
- }
- f() {
- super.f();
- assert.step('patch.f');
- }
- }
- );
-
- (new A()).f();
-
- assert.verifySteps([
- 'A.constructor',
- 'patch.constructor',
- 'A.f',
- 'patch.f',
- ]);
-
- A.unpatch('patch');
-
- (new A()).f();
-
- assert.verifySteps([
- 'A.constructor',
- 'A.f',
- ]);
- });
-
- QUnit.test('unpatch 2', function (assert) {
- assert.expect(12);
-
- const A = patchMixin(
- class {
- constructor() {
- assert.step('A.constructor');
- }
- f() {
- assert.step('A.f');
- }
- }
- );
-
- A.patch('patch1', T =>
- class extends T {
- constructor() {
- super();
- assert.step('patch1.constructor');
- }
- f() {
- super.f();
- assert.step('patch1.f');
- }
- }
- );
-
- A.patch('patch2', T =>
- class extends T {
- constructor() {
- super();
- assert.step('patch2.constructor');
- }
- f() {
- super.f();
- assert.step('patch2.f');
- }
- }
- );
-
- (new A()).f();
-
- assert.verifySteps([
- 'A.constructor',
- 'patch1.constructor',
- 'patch2.constructor',
- 'A.f',
- 'patch1.f',
- 'patch2.f',
- ]);
-
- A.unpatch('patch1');
-
- (new A()).f();
-
- assert.verifySteps([
- 'A.constructor',
- 'patch2.constructor',
- 'A.f',
- 'patch2.f',
- ]);
- });
-
- QUnit.test('unpatch inexistent', function (assert) {
- assert.expect(1);
-
- const A = patchMixin(class {});
- A.patch('patch', T => class extends T {});
-
- A.unpatch('patch');
- assert.throws(() => {
- A.unpatch('inexistent-patch');
- });
- });
-
- QUnit.test('patch for specialization', function (assert) {
- assert.expect(1);
-
- let args = [];
-
- const A = patchMixin(
- class {
- constructor() {
- args = ['A', ...arguments];
- }
- }
- );
-
- A.patch('patch', T =>
- class extends T {
- constructor() {
- super('patch', ...arguments);
- }
- }
- );
-
- new A('instantiation');
-
- assert.deepEqual(args, ['A', 'patch', 'instantiation']);
- });
-
- QUnit.test('instance fields', function (assert) {
- assert.expect(1);
-
- const A = patchMixin(
- class {
- constructor() {
- this.x = ['A'];
- }
- }
- );
-
- A.patch('patch', T =>
- class extends T {
- constructor() {
- super();
- this.x.push('patch');
- }
- }
- );
-
- const a = new A();
- assert.deepEqual(a.x, ['A', 'patch']);
- });
-
- QUnit.test('call instance method defined in patch', function (assert) {
- assert.expect(3);
-
- const A = patchMixin(
- class {}
- );
-
- assert.notOk((new A()).f);
-
- A.patch('patch', T =>
- class extends T {
- f() {
- assert.step('patch.f');
- }
- }
- );
-
- (new A()).f();
- assert.verifySteps(['patch.f']);
- });
-
- QUnit.test('class methods', function (assert) {
- assert.expect(7);
-
- const A = patchMixin(
- class {
- static f() {
- assert.step('A');
- }
- }
- );
-
- A.f();
- assert.verifySteps(['A']);
-
- A.patch('patch', T =>
- class extends T {
- static f() {
- super.f();
- assert.step('patch');
- }
- }
- );
-
- A.f();
- assert.verifySteps(['A', 'patch']);
-
- A.unpatch('patch');
-
- A.f();
- assert.verifySteps(['A']);
- });
-
- QUnit.test('class fields', function (assert) {
- assert.expect(4);
-
- class A {}
- A.foo = ['A'];
- A.bar = 'A';
-
- const PatchableA = patchMixin(A);
-
- PatchableA.patch('patch', T => {
- class Patch extends T {}
-
- Patch.foo = [...T.foo, 'patched A'];
- Patch.bar = 'patched A';
-
- return Patch;
- });
-
- assert.deepEqual(PatchableA.foo, ['A', 'patched A']);
- assert.strictEqual(PatchableA.bar, 'patched A');
-
- PatchableA.unpatch('patch');
-
- assert.deepEqual(PatchableA.foo, ['A']);
- assert.strictEqual(PatchableA.bar, 'A');
- });
-
- QUnit.test('lazy patch', function (assert) {
- assert.expect(4);
-
- const A = patchMixin(
- class {
- constructor() {
- assert.step('A.constructor');
- }
- f() {
- assert.step('A.f');
- }
- }
- );
-
- const a = new A();
-
- A.patch('patch', T =>
- class extends T {
- constructor() {
- super();
- // will not be called
- assert.step('patch.constructor');
- }
- f() {
- super.f();
- assert.step('patch.f');
- }
- }
- );
-
- a.f();
-
- assert.verifySteps([
- 'A.constructor',
- 'A.f',
- 'patch.f',
- ]);
- });
-
-
- QUnit.module('inheritance');
-
- QUnit.test('inheriting a patchable class', function (assert) {
- assert.expect(8);
-
- const A = patchMixin(
- class {
- constructor() {
- assert.step('A.constructor');
- }
- f() {
- assert.step('A.f');
- }
- }
- );
-
- class B extends A {
- constructor() {
- super();
- assert.step('B.constructor');
- }
- f() {
- super.f();
- assert.step('B.f');
- }
- }
-
- (new A()).f();
-
- assert.verifySteps([
- 'A.constructor',
- 'A.f',
- ]);
-
- (new B()).f();
-
- assert.verifySteps([
- 'A.constructor',
- 'B.constructor',
- 'A.f',
- 'B.f',
- ]);
- });
-
- QUnit.test('inheriting a patchable class that has patch', function (assert) {
- assert.expect(12);
-
- const A = patchMixin(
- class {
- constructor() {
- assert.step('A.constructor');
- }
- f() {
- assert.step('A.f');
- }
- }
- );
-
- A.patch('patch', T =>
- class extends T {
- constructor() {
- super();
- assert.step('patch.constructor');
- }
- f() {
- super.f();
- assert.step('patch.f');
- }
- }
- );
-
- class B extends A {
- constructor() {
- super();
- assert.step('B.constructor');
- }
- f() {
- super.f();
- assert.step('B.f');
- }
- }
-
- (new A()).f();
-
- assert.verifySteps([
- 'A.constructor',
- 'patch.constructor',
- 'A.f',
- 'patch.f',
- ]);
-
- (new B()).f();
-
- assert.verifySteps([
- 'A.constructor',
- 'patch.constructor',
- 'B.constructor',
- 'A.f',
- 'patch.f',
- 'B.f',
- ]);
- });
-
- QUnit.test('patch inherited patchable class', function (assert) {
- assert.expect(10);
-
- const A = patchMixin(
- class {
- constructor() {
- assert.step('A.constructor');
- }
- f() {
- assert.step('A.f');
- }
- }
- );
-
- const B = patchMixin(
- class extends A {
- constructor() {
- super();
- assert.step('B.constructor');
- }
- f() {
- super.f();
- assert.step('B.f');
- }
- }
- );
-
- B.patch('patch', T =>
- class extends T {
- constructor() {
- super();
- assert.step('patch.constructor');
- }
- f() {
- super.f();
- assert.step('patch.f');
- }
- }
- );
-
- (new A()).f();
-
- assert.verifySteps([
- 'A.constructor',
- 'A.f',
- ]);
-
- (new B()).f();
-
- assert.verifySteps([
- 'A.constructor',
- 'B.constructor',
- 'patch.constructor',
- 'A.f',
- 'B.f',
- 'patch.f',
- ]);
- });
-
- QUnit.test('patch inherited patched class', function (assert) {
- assert.expect(14);
-
- const A = patchMixin(
- class {
- constructor() {
- assert.step('A.constructor');
- }
- f() {
- assert.step('A.f');
- }
- }
- );
-
- A.patch('patch', T =>
- class extends T {
- constructor() {
- super();
- assert.step('A.patch.constructor');
- }
- f() {
- super.f();
- assert.step('A.patch.f');
- }
- }
- );
-
- /**
- * /!\ WARNING /!\
- *
- * If you want to patch class B, make it patchable
- * otherwise it will patch class A!
- */
- const B = patchMixin(
- class extends A {
- constructor() {
- super();
- assert.step('B.constructor');
- }
- f() {
- super.f();
- assert.step('B.f');
- }
- }
- );
-
- B.patch('patch', T =>
- class extends T {
- constructor() {
- super();
- assert.step('B.patch.constructor');
- }
- f() {
- super.f();
- assert.step('B.patch.f');
- }
- }
- );
-
- const a = new A();
- a.f();
-
- assert.verifySteps([
- 'A.constructor',
- 'A.patch.constructor',
- 'A.f',
- 'A.patch.f',
- ]);
-
- const b = new B();
- b.f();
-
- assert.verifySteps([
- 'A.constructor',
- 'A.patch.constructor',
- 'B.constructor',
- 'B.patch.constructor',
- 'A.f',
- 'A.patch.f',
- 'B.f',
- 'B.patch.f',
- ]);
- });
-
- QUnit.test('unpatch inherited patched class', function (assert) {
- assert.expect(15);
-
- const A = patchMixin(
- class {
- constructor() {
- assert.step('A.constructor');
- }
- f() {
- assert.step('A.f');
- }
- }
- );
-
- A.patch('patch', T =>
- class extends T {
- constructor() {
- super();
- assert.step('A.patch.constructor');
- }
- f() {
- super.f();
- assert.step('A.patch.f');
- }
- }
- );
-
- const B = patchMixin(
- class extends A {
- constructor() {
- super();
- assert.step('B.constructor');
- }
- f() {
- super.f();
- assert.step('B.f');
- }
- }
- );
-
- B.patch('patch', T =>
- class extends T {
- constructor() {
- super();
- assert.step('B.patch.constructor');
- }
- f() {
- super.f();
- assert.step('B.patch.f');
- }
- }
- );
-
- A.unpatch('patch');
-
- (new A()).f();
-
- assert.verifySteps([
- 'A.constructor',
- 'A.f',
- ]);
-
- (new B()).f();
-
- assert.verifySteps([
- 'A.constructor',
- 'B.constructor',
- 'B.patch.constructor',
- 'A.f',
- 'B.f',
- 'B.patch.f',
- ]);
-
- B.unpatch('patch');
-
- (new B()).f();
-
- assert.verifySteps([
- 'A.constructor',
- 'B.constructor',
- 'A.f',
- 'B.f',
- ]);
- });
-
- QUnit.test('unpatch inherited patched class 2', function (assert) {
- assert.expect(12);
-
- const A = patchMixin(
- class {
- constructor() {
- assert.step('A.constructor');
- }
- f() {
- assert.step('A.f');
- }
- }
- );
-
- A.patch('patch', T =>
- class extends T {
- constructor() {
- super();
- assert.step('A.patch.constructor');
- }
- f() {
- super.f();
- assert.step('A.patch.f');
- }
- }
- );
-
- const B = patchMixin(
- class extends A {
- constructor() {
- super();
- assert.step('B.constructor');
- }
- f() {
- super.f();
- assert.step('B.f');
- }
- }
- );
-
- B.patch('patch', T =>
- class extends T {
- constructor() {
- super();
- assert.step('B.patch.constructor');
- }
- f() {
- super.f();
- assert.step('B.patch.f');
- }
- }
- );
-
- B.unpatch('patch');
-
- (new B()).f();
-
- assert.verifySteps([
- 'A.constructor',
- 'A.patch.constructor',
- 'B.constructor',
- 'A.f',
- 'A.patch.f',
- 'B.f',
- ]);
-
- A.unpatch('patch');
-
- (new B()).f();
-
- assert.verifySteps([
- 'A.constructor',
- 'B.constructor',
- 'A.f',
- 'B.f',
- ]);
- });
-
- QUnit.test('class methods', function (assert) {
- assert.expect(12);
-
- const A = patchMixin(
- class {
- static f() {
- assert.step('A');
- }
- }
- );
-
- const B = patchMixin(
- class extends A {
- static f() {
- super.f();
- assert.step('B');
- }
- }
- );
-
- A.patch('patch', T =>
- class extends T {
- static f() {
- super.f();
- assert.step('A.patch');
- }
- }
- );
-
- B.patch('patch', T =>
- class extends T {
- static f() {
- super.f();
- assert.step('B.patch');
- }
- }
- );
-
- B.f();
- assert.verifySteps(['A', 'A.patch', 'B', 'B.patch']);
-
- A.unpatch('patch');
-
- B.f();
- assert.verifySteps(['A', 'B', 'B.patch']);
-
- B.unpatch('patch');
-
- B.f();
- assert.verifySteps(['A', 'B']);
- });
-
- QUnit.test('class fields', function (assert) {
- assert.expect(3);
-
- class A {}
- A.foo = ['A'];
- A.bar = 'A';
-
- const PatchableA = patchMixin(A);
-
- class B extends PatchableA {}
- // /!\ This is not dynamic
- // so if A.foo is patched after this assignment
- // B.foo won't have the patches of A.foo
- B.foo = [...PatchableA.foo, 'B'];
- B.bar = 'B';
-
- const PatchableB = patchMixin(B);
-
- PatchableA.patch('patch', T => {
- class Patch extends T {}
-
- Patch.foo = [...T.foo, 'patched A'];
- Patch.bar = 'patched A';
-
- return Patch;
- });
-
- PatchableB.patch('patch', T => {
- class Patch extends T {}
-
- Patch.foo = [...T.foo, 'patched B'];
- Patch.bar = 'patched B';
-
- return Patch;
- });
-
- assert.deepEqual(PatchableB.foo, [ 'A', /* 'patched A', */ 'B', 'patched B' ]);
- assert.deepEqual(PatchableA.foo, [ 'A', 'patched A' ]);
- assert.strictEqual(PatchableB.bar, 'patched B');
- });
-
- QUnit.test('inheritance and lazy patch', function (assert) {
- assert.expect(6);
-
- const A = patchMixin(
- class {
- constructor() {
- assert.step('A.constructor');
- }
- f() {
- assert.step('A.f');
- }
- }
- );
-
- class B extends A {
- constructor() {
- super();
- assert.step('B.constructor');
- }
- f() {
- super.f();
- assert.step('B.f');
- }
- }
-
- const b = new B();
-
- A.patch('patch', T =>
- class extends T {
- constructor() {
- super();
- // will not be called
- assert.step('patch.constructor');
- }
- f() {
- super.f();
- assert.step('patch.f');
- }
- }
- );
-
- b.f();
-
- assert.verifySteps([
- 'A.constructor',
- 'B.constructor',
- 'A.f',
- 'patch.f',
- 'B.f',
- ]);
- });
-
- QUnit.test('patch not patchable class that inherits patchable class', function (assert) {
- assert.expect(1);
-
- const A = patchMixin(class {});
- class B extends A {}
-
- // class B is not patchable
- assert.throws(() => {
- B.patch('patch', T => class extends T {});
- });
- });
- });
-});
-});
diff --git a/addons/web/static/tests/core/patch_tests.js b/addons/web/static/tests/core/patch_tests.js
new file mode 100644
index 00000000000..d1b8a340fe5
--- /dev/null
+++ b/addons/web/static/tests/core/patch_tests.js
@@ -0,0 +1,1161 @@
+odoo.define("web.patch_tests", function (require) {
+"use strict";
+
+const { patch, unpatch } = require('web.utils');
+
+function makeBaseClass(assert, assertInSetup) {
+ class BaseClass {
+ constructor() {
+ this.setup();
+ }
+ setup() {
+ this._dynamic = "base";
+
+ this.str = "base";
+ this.obj = { base: "base" };
+ this.arr = ["base"];
+
+ if (assertInSetup !== false) {
+ assert.step("base.setup");
+ }
+ }
+ fn() {
+ assert.step("base.fn");
+ }
+ async asyncFn() {
+ // also check this binding
+ assert.step(`base.${this.str}`);
+ }
+ get dynamic() {
+ return this._dynamic;
+ }
+ set dynamic(value) {
+ this._dynamic = value;
+ }
+ }
+
+ BaseClass.staticStr = "base";
+ BaseClass.staticObj = { base: "base" };
+ BaseClass.staticArr = ["base"];
+ BaseClass.staticFn = function () {
+ assert.step("base.staticFn");
+ };
+
+ return BaseClass;
+}
+
+QUnit.module('core', {}, function () {
+
+ QUnit.module('patch', {}, function () {
+ QUnit.test('simple patch', async function (assert) {
+ assert.expect(5);
+
+ const BaseClass = makeBaseClass(assert);
+
+ patch(BaseClass.prototype, 'patch', {
+ setup() {
+ this._super();
+ assert.step('patch.setup');
+ },
+ fn() {
+ this._super();
+ assert.step('patch.fn');
+ },
+ });
+
+ (new BaseClass()).fn();
+
+ assert.verifySteps([
+ 'base.setup',
+ 'patch.setup',
+ 'base.fn',
+ 'patch.fn',
+ ]);
+ });
+
+ QUnit.test('two patches on same base class', async function (assert) {
+ assert.expect(7);
+
+ const BaseClass = makeBaseClass(assert);
+
+ patch(BaseClass.prototype, 'patch1', {
+ setup() {
+ this._super();
+ assert.step('patch1.setup');
+ },
+ fn() {
+ this._super();
+ assert.step('patch1.fn');
+ },
+ });
+
+ patch(BaseClass.prototype, 'patch2', {
+ setup() {
+ this._super();
+ assert.step('patch2.setup');
+ },
+ fn() {
+ this._super();
+ assert.step('patch2.fn');
+ },
+ });
+
+ (new BaseClass()).fn();
+
+ assert.verifySteps([
+ 'base.setup',
+ 'patch1.setup',
+ 'patch2.setup',
+ 'base.fn',
+ 'patch1.fn',
+ 'patch2.fn',
+ ]);
+ });
+
+ QUnit.test('two patches with same name on same base class', async function (assert) {
+ assert.expect(1);
+
+ const A = class {};
+
+ patch(A.prototype, 'patch');
+
+ // keys should be unique
+ assert.throws(() => {
+ patch(A.prototype, 'patch');
+ });
+ });
+
+ QUnit.test('unpatch', async function (assert) {
+ assert.expect(8);
+
+ const BaseClass = makeBaseClass(assert);
+
+ patch(BaseClass.prototype, 'patch', {
+ setup() {
+ this._super();
+ assert.step('patch.setup');
+ },
+ fn() {
+ this._super();
+ assert.step('patch.fn');
+ },
+ });
+
+ (new BaseClass()).fn();
+
+ assert.verifySteps([
+ 'base.setup',
+ 'patch.setup',
+ 'base.fn',
+ 'patch.fn',
+ ]);
+
+ unpatch(BaseClass.prototype, 'patch');
+
+ (new BaseClass()).fn();
+
+ assert.verifySteps([
+ 'base.setup',
+ 'base.fn',
+ ]);
+ });
+
+ QUnit.test('unpatch two patches on the same base class', async function (assert) {
+ assert.expect(15);
+
+ const BaseClass = makeBaseClass(assert);
+
+ patch(BaseClass.prototype, 'patch1', {
+ setup() {
+ this._super();
+ assert.step('patch1.setup');
+ },
+ fn() {
+ this._super();
+ assert.step('patch1.fn');
+ },
+ });
+
+ patch(BaseClass.prototype, 'patch2', {
+ setup() {
+ this._super();
+ assert.step('patch2.setup');
+ },
+ fn() {
+ this._super();
+ assert.step('patch2.fn');
+ },
+ });
+
+ (new BaseClass()).fn();
+
+ assert.verifySteps([
+ 'base.setup',
+ 'patch1.setup',
+ 'patch2.setup',
+ 'base.fn',
+ 'patch1.fn',
+ 'patch2.fn',
+ ]);
+
+ unpatch(BaseClass.prototype, 'patch1');
+
+ (new BaseClass()).fn();
+
+ assert.verifySteps([
+ 'base.setup',
+ 'patch2.setup',
+ 'base.fn',
+ 'patch2.fn',
+ ]);
+
+ unpatch(BaseClass.prototype, 'patch2');
+
+ (new BaseClass()).fn();
+
+ assert.verifySteps([
+ 'base.setup',
+ 'base.fn',
+ ]);
+ });
+
+ QUnit.test('unpatch two patches on the same base class (returned by patch)', async function (assert) {
+ assert.expect(15);
+
+ const BaseClass = makeBaseClass(assert);
+
+ patch(BaseClass.prototype, 'patch1', {
+ setup() {
+ this._super();
+ assert.step('patch1.setup');
+ },
+ fn() {
+ this._super();
+ assert.step('patch1.fn');
+ },
+ });
+
+ patch(BaseClass.prototype, 'patch2', {
+ setup() {
+ this._super();
+ assert.step('patch2.setup');
+ },
+ fn() {
+ this._super();
+ assert.step('patch2.fn');
+ },
+ });
+
+ (new BaseClass()).fn();
+
+ assert.verifySteps([
+ 'base.setup',
+ 'patch1.setup',
+ 'patch2.setup',
+ 'base.fn',
+ 'patch1.fn',
+ 'patch2.fn',
+ ]);
+
+ unpatch(BaseClass.prototype, 'patch1');
+
+ (new BaseClass()).fn();
+
+ assert.verifySteps([
+ 'base.setup',
+ 'patch2.setup',
+ 'base.fn',
+ 'patch2.fn',
+ ]);
+
+ unpatch(BaseClass.prototype, 'patch2');
+
+ (new BaseClass()).fn();
+
+ assert.verifySteps([
+ 'base.setup',
+ 'base.fn',
+ ]);
+ });
+
+ QUnit.test('unpatch twice the same patch name', async function (assert) {
+ assert.expect(1);
+
+ const A = class {};
+ patch(A.prototype, 'patch');
+
+ unpatch(A.prototype, 'patch');
+ assert.throws(() => {
+ unpatch(A.prototype, 'patch');
+ });
+ });
+
+ QUnit.test('patch for specialization', async function (assert) {
+ assert.expect(1);
+
+ let args = [];
+
+ const A = class {
+ constructor() {
+ this.setup(...arguments);
+ }
+ setup() {
+ args = ['A', ...arguments];
+ }
+ };
+
+ patch(A.prototype, 'patch', {
+ setup() {
+ this._super('patch', ...arguments);
+ },
+ });
+
+ new A('instantiation');
+
+ assert.deepEqual(args, ['A', 'patch', 'instantiation']);
+ });
+
+ QUnit.test('instance fields', async function (assert) {
+ assert.expect(3);
+
+ const BaseClass = makeBaseClass(assert, false);
+ patch(BaseClass.prototype, 'patch', {
+ setup() {
+ this._super(...arguments);
+
+ this.str += 'patch';
+ this.arr.push('patch');
+ this.obj.patch = 'patch';
+ }
+ });
+
+ const instance = new BaseClass();
+ assert.strictEqual(instance.str, 'basepatch');
+ assert.deepEqual(instance.arr, ['base', 'patch']);
+ assert.deepEqual(instance.obj, { base: 'base', patch: 'patch' });
+ });
+
+ QUnit.test('call instance method defined in patch', async function (assert) {
+ assert.expect(4);
+
+ const BaseClass = makeBaseClass(assert, false);
+
+ assert.notOk((new BaseClass()).f);
+
+ patch(BaseClass.prototype, 'patch', {
+ f() {
+ assert.step('patch.f');
+ },
+ });
+
+ (new BaseClass()).f();
+ assert.verifySteps(['patch.f']);
+
+ unpatch(BaseClass.prototype, 'patch');
+
+ assert.notOk((new BaseClass()).f);
+ });
+
+ QUnit.test('class methods', async function (assert) {
+ assert.expect(7);
+
+ const BaseClass = makeBaseClass(assert);
+
+ BaseClass.staticFn();
+ assert.verifySteps(['base.staticFn']);
+
+ patch(BaseClass, 'patch', {
+ staticFn() {
+ this._super();
+ assert.step('patch.staticFn');
+ }
+ });
+
+ BaseClass.staticFn();
+ assert.verifySteps(['base.staticFn', 'patch.staticFn']);
+
+ unpatch(BaseClass, 'patch');
+
+ BaseClass.staticFn();
+ assert.verifySteps(['base.staticFn']);
+ });
+
+ QUnit.test('class fields', async function (assert) {
+ assert.expect(6);
+
+ const BaseClass = makeBaseClass(assert);
+
+ patch(BaseClass, 'patch', {
+ staticStr: BaseClass.staticStr + 'patch',
+ staticArr: [...BaseClass.staticArr, 'patch'],
+ staticObj: {...BaseClass.staticObj, patch: 'patch'},
+ });
+
+ assert.strictEqual(BaseClass.staticStr, 'basepatch');
+ assert.deepEqual(BaseClass.staticArr, ['base', 'patch']);
+ assert.deepEqual(BaseClass.staticObj, { base: 'base', patch: 'patch' });
+
+ unpatch(BaseClass, 'patch');
+
+ assert.strictEqual(BaseClass.staticStr, 'base');
+ assert.deepEqual(BaseClass.staticArr, ['base']);
+ assert.deepEqual(BaseClass.staticObj, { base: 'base' });
+ });
+
+ QUnit.test('lazy patch', async function (assert) {
+ assert.expect(6);
+
+ const BaseClass = makeBaseClass(assert);
+ const instance = new BaseClass();
+
+ patch(BaseClass.prototype, 'patch', {
+ setup() {
+ this._super();
+ // will not be called
+ assert.step('patch.setup');
+ },
+ fn() {
+ this._super();
+ assert.step('patch.fn');
+ },
+ });
+
+ instance.fn();
+ assert.verifySteps([
+ 'base.setup',
+ 'base.fn',
+ 'patch.fn',
+ ]);
+
+ unpatch(BaseClass.prototype, 'patch');
+
+ instance.fn();
+ assert.verifySteps([
+ 'base.fn',
+ ]);
+ });
+
+ QUnit.test('getter', async function (assert) {
+ assert.expect(2);
+
+ const BaseClass = makeBaseClass(assert, false);
+
+ patch(BaseClass.prototype, 'patch', {
+ get dynamic() {
+ return this._super() + 'patch';
+ },
+ });
+
+ const instance = new BaseClass();
+ assert.strictEqual(instance.dynamic, 'basepatch');
+
+ unpatch(BaseClass.prototype, 'patch');
+ assert.strictEqual(instance.dynamic, 'base');
+ });
+
+ QUnit.test('setter', async function (assert) {
+ assert.expect(3);
+
+ const BaseClass = makeBaseClass(assert, false);
+
+ patch(BaseClass.prototype, 'patch', {
+ set dynamic(value) {
+ this._super('patch:' + value);
+ },
+ });
+
+ const instance = new BaseClass();
+
+ assert.strictEqual(instance.dynamic, 'base');
+ instance.dynamic = 'patch';
+ assert.strictEqual(instance.dynamic, 'patch:patch');
+
+ unpatch(BaseClass.prototype, 'patch');
+
+ instance.dynamic = 'base';
+ assert.strictEqual(instance.dynamic, 'base');
+ });
+
+ QUnit.test('async function', async function (assert) {
+ assert.expect(3);
+
+ const BaseClass = makeBaseClass(assert, false);
+
+ patch(BaseClass.prototype, "patch", {
+ async asyncFn() {
+ const _super = this._super;
+ await Promise.resolve();
+ await _super(...arguments);
+ assert.step('patch.asyncFn');
+ },
+ });
+
+ const instance = new BaseClass();
+ instance.str = "asyncFn";
+ await instance.asyncFn();
+
+ assert.verifySteps([
+ 'base.asyncFn',
+ 'patch.asyncFn',
+ ]);
+ });
+
+ QUnit.test('async function (multiple patches)', async function (assert) {
+ assert.expect(4);
+
+ const BaseClass = makeBaseClass(assert, false);
+
+ patch(BaseClass.prototype, "patch1", {
+ async asyncFn() {
+ const _super = this._super;
+ await Promise.resolve();
+ await _super(...arguments);
+ // also check this binding
+ assert.step(`patch1.${this.str}`);
+ },
+ });
+ patch(BaseClass.prototype, "patch2", {
+ async asyncFn() {
+ const _super = this._super;
+ await Promise.resolve();
+ await _super(...arguments);
+ // also check this binding
+ assert.step(`patch2.${this.str}`);
+ },
+ });
+
+ const instance = new BaseClass();
+ instance.str = "asyncFn";
+ await instance.asyncFn();
+
+ assert.verifySteps([
+ 'base.asyncFn',
+ 'patch1.asyncFn',
+ 'patch2.asyncFn',
+ ]);
+ });
+
+ QUnit.module('inheritance');
+
+ QUnit.test('inherit a patched class (extends before patch)', async function (assert) {
+ assert.expect(12);
+
+ const BaseClass = makeBaseClass(assert);
+
+ class Extension extends BaseClass {
+ setup() {
+ super.setup();
+ assert.step('extension.setup');
+ }
+ fn() {
+ super.fn();
+ assert.step('extension.fn');
+ }
+ }
+
+ (new Extension()).fn();
+ assert.verifySteps([
+ 'base.setup',
+ 'extension.setup',
+ 'base.fn',
+ 'extension.fn',
+ ]);
+
+ patch(BaseClass.prototype, 'patch', {
+ setup() {
+ this._super();
+ assert.step('patch.setup');
+ },
+ fn() {
+ this._super();
+ assert.step('patch.fn');
+ },
+ });
+
+ (new Extension()).fn();
+ assert.verifySteps([
+ 'base.setup',
+ 'patch.setup',
+ 'extension.setup',
+ 'base.fn',
+ 'patch.fn',
+ 'extension.fn',
+ ]);
+ });
+
+ QUnit.test('inherit a patched class (extends after patch)', async function (assert) {
+ assert.expect(7);
+
+ const BaseClass = makeBaseClass(assert);
+
+ patch(BaseClass.prototype, 'patch', {
+ setup() {
+ this._super();
+ assert.step('patch.setup');
+ },
+ fn() {
+ this._super();
+ assert.step('patch.fn');
+ },
+ });
+
+ class Extension extends BaseClass {
+ setup() {
+ super.setup();
+ assert.step('extension.setup');
+ }
+ fn() {
+ super.fn();
+ assert.step('extension.fn');
+ }
+ }
+
+ (new Extension()).fn();
+
+ assert.verifySteps([
+ 'base.setup',
+ 'patch.setup',
+ 'extension.setup',
+ 'base.fn',
+ 'patch.fn',
+ 'extension.fn',
+ ]);
+ });
+
+ QUnit.test('patch an inherited class', async function (assert) {
+ assert.expect(12);
+
+ const BaseClass = makeBaseClass(assert);
+
+ class Extension extends BaseClass {
+ setup() {
+ super.setup();
+ assert.step('extension.setup');
+ }
+ fn() {
+ super.fn();
+ assert.step('extension.fn');
+ }
+ }
+
+ (new Extension()).fn();
+ assert.verifySteps([
+ 'base.setup',
+ 'extension.setup',
+ 'base.fn',
+ 'extension.fn',
+ ]);
+
+ patch(Extension.prototype, 'patch', {
+ setup() {
+ this._super();
+ assert.step('patch.setup');
+ },
+ fn() {
+ this._super();
+ assert.step('patch.fn');
+ },
+ });
+
+ (new Extension()).fn();
+ assert.verifySteps([
+ 'base.setup',
+ 'extension.setup',
+ 'patch.setup',
+ 'base.fn',
+ 'extension.fn',
+ 'patch.fn',
+ ]);
+ });
+
+ QUnit.test('patch an inherited patched class', async function (assert) {
+ assert.expect(9);
+
+ const BaseClass = makeBaseClass(assert);
+
+ patch(BaseClass.prototype, 'patch', {
+ setup() {
+ this._super();
+ assert.step('patch.setup');
+ },
+ fn() {
+ this._super();
+ assert.step('patch.fn');
+ },
+ });
+
+ class Extension extends BaseClass {
+ setup() {
+ super.setup();
+ assert.step('extension.setup');
+ }
+ fn() {
+ super.fn();
+ assert.step('extension.fn');
+ }
+ }
+
+ patch(Extension.prototype, 'patch', {
+ setup() {
+ this._super();
+ assert.step('patch.extension.setup');
+ },
+ fn() {
+ this._super();
+ assert.step('patch.extension.fn');
+ },
+ });
+
+ (new Extension()).fn();
+ assert.verifySteps([
+ 'base.setup',
+ 'patch.setup',
+ 'extension.setup',
+ 'patch.extension.setup',
+ 'base.fn',
+ 'patch.fn',
+ 'extension.fn',
+ 'patch.extension.fn',
+ ]);
+ });
+
+ QUnit.test('unpatch base class', async function (assert) {
+ assert.expect(12);
+
+ const BaseClass = makeBaseClass(assert);
+
+ class Extension extends BaseClass {
+ setup() {
+ super.setup();
+ assert.step('extension.setup');
+ }
+ fn() {
+ super.fn();
+ assert.step('extension.fn');
+ }
+ }
+
+ patch(BaseClass.prototype, 'patch', {
+ setup() {
+ this._super();
+ assert.step('patch.setup');
+ },
+ fn() {
+ this._super();
+ assert.step('patch.fn');
+ },
+ });
+
+ (new Extension()).fn();
+ assert.verifySteps([
+ 'base.setup',
+ 'patch.setup',
+ 'extension.setup',
+ 'base.fn',
+ 'patch.fn',
+ 'extension.fn',
+ ]);
+
+ unpatch(BaseClass.prototype, 'patch');
+
+ (new Extension()).fn();
+ assert.verifySteps([
+ 'base.setup',
+ 'extension.setup',
+ 'base.fn',
+ 'extension.fn',
+ ]);
+ });
+
+ QUnit.test('unpatch an inherited class', async function (assert) {
+ assert.expect(12);
+
+ const BaseClass = makeBaseClass(assert);
+
+ class Extension extends BaseClass {
+ setup() {
+ super.setup();
+ assert.step('extension.setup');
+ }
+ fn() {
+ super.fn();
+ assert.step('extension.fn');
+ }
+ }
+
+ patch(Extension.prototype, 'patch', {
+ setup() {
+ this._super();
+ assert.step('patch.setup');
+ },
+ fn() {
+ this._super();
+ assert.step('patch.fn');
+ },
+ });
+
+ (new Extension()).fn();
+ assert.verifySteps([
+ 'base.setup',
+ 'extension.setup',
+ 'patch.setup',
+ 'base.fn',
+ 'extension.fn',
+ 'patch.fn',
+ ]);
+
+ unpatch(Extension.prototype, 'patch');
+
+ (new Extension()).fn();
+ assert.verifySteps([
+ 'base.setup',
+ 'extension.setup',
+ 'base.fn',
+ 'extension.fn',
+ ]);
+ });
+
+ QUnit.test('unpatch an inherited class and its base class (unpatch base first)', async function (assert) {
+ assert.expect(21);
+
+ const BaseClass = makeBaseClass(assert);
+
+ patch(BaseClass.prototype, 'patch.BaseClass', {
+ setup() {
+ this._super();
+ assert.step('patch.setup');
+ },
+ fn() {
+ this._super();
+ assert.step('patch.fn');
+ },
+ });
+
+ class Extension extends BaseClass {
+ setup() {
+ super.setup();
+ assert.step('extension.setup');
+ }
+ fn() {
+ super.fn();
+ assert.step('extension.fn');
+ }
+ }
+
+ patch(Extension.prototype, 'patch.Extension', {
+ setup() {
+ this._super();
+ assert.step('patch.extension.setup');
+ },
+ fn() {
+ this._super();
+ assert.step('patch.extension.fn');
+ },
+ });
+
+ (new Extension()).fn();
+ assert.verifySteps([
+ 'base.setup',
+ 'patch.setup',
+ 'extension.setup',
+ 'patch.extension.setup',
+ 'base.fn',
+ 'patch.fn',
+ 'extension.fn',
+ 'patch.extension.fn',
+ ]);
+
+ unpatch(BaseClass.prototype, 'patch.BaseClass');
+
+ (new Extension()).fn();
+ assert.verifySteps([
+ 'base.setup',
+ 'extension.setup',
+ 'patch.extension.setup',
+ 'base.fn',
+ 'extension.fn',
+ 'patch.extension.fn',
+ ]);
+
+ unpatch(Extension.prototype, 'patch.Extension');
+
+ (new Extension()).fn();
+ assert.verifySteps([
+ 'base.setup',
+ 'extension.setup',
+ 'base.fn',
+ 'extension.fn',
+ ]);
+ });
+
+ QUnit.test('unpatch an inherited class and its base class (unpatch extension first)', async function (assert) {
+ assert.expect(21);
+
+ const BaseClass = makeBaseClass(assert);
+
+ patch(BaseClass.prototype, 'patch.BaseClass', {
+ setup() {
+ this._super();
+ assert.step('patch.setup');
+ },
+ fn() {
+ this._super();
+ assert.step('patch.fn');
+ },
+ });
+
+ class Extension extends BaseClass {
+ setup() {
+ super.setup();
+ assert.step('extension.setup');
+ }
+ fn() {
+ super.fn();
+ assert.step('extension.fn');
+ }
+ }
+
+ patch(Extension.prototype, 'patch.Extension', {
+ setup() {
+ this._super();
+ assert.step('patch.extension.setup');
+ },
+ fn() {
+ this._super();
+ assert.step('patch.extension.fn');
+ },
+ });
+
+ (new Extension()).fn();
+ assert.verifySteps([
+ 'base.setup',
+ 'patch.setup',
+ 'extension.setup',
+ 'patch.extension.setup',
+ 'base.fn',
+ 'patch.fn',
+ 'extension.fn',
+ 'patch.extension.fn',
+ ]);
+
+ unpatch(Extension.prototype, 'patch.Extension');
+
+ (new Extension()).fn();
+ assert.verifySteps([
+ 'base.setup',
+ 'patch.setup',
+ 'extension.setup',
+ 'base.fn',
+ 'patch.fn',
+ 'extension.fn',
+ ]);
+
+ unpatch(BaseClass.prototype, 'patch.BaseClass');
+
+ (new Extension()).fn();
+ assert.verifySteps([
+ 'base.setup',
+ 'extension.setup',
+ 'base.fn',
+ 'extension.fn',
+ ]);
+ });
+
+ QUnit.test('class methods', async function (assert) {
+ assert.expect(12);
+
+ const BaseClass = makeBaseClass(assert);
+
+ class Extension extends BaseClass {
+ static staticFn() {
+ super.staticFn();
+ assert.step('extension.staticFn');
+ }
+ }
+
+ patch(BaseClass, 'patch.BaseClass', {
+ staticFn() {
+ this._super();
+ assert.step('patch.staticFn');
+ }
+ });
+
+ patch(Extension, 'patch.Extension', {
+ staticFn() {
+ this._super();
+ assert.step('patch.extension.staticFn');
+ }
+ });
+
+ Extension.staticFn();
+ assert.verifySteps([
+ 'base.staticFn',
+ 'patch.staticFn',
+ 'extension.staticFn',
+ 'patch.extension.staticFn',
+ ]);
+
+ unpatch(BaseClass, 'patch.BaseClass');
+
+ Extension.staticFn();
+ assert.verifySteps([
+ 'base.staticFn',
+ 'extension.staticFn',
+ 'patch.extension.staticFn',
+ ]);
+
+ unpatch(Extension, 'patch.Extension');
+
+ Extension.staticFn();
+ assert.verifySteps([
+ 'base.staticFn',
+ 'extension.staticFn',
+ ]);
+ });
+
+ QUnit.test('class fields (patch before inherit)', async function (assert) {
+ assert.expect(6);
+
+ const BaseClass = makeBaseClass(assert);
+
+ patch(BaseClass, 'patch.BaseClass', {
+ staticStr: BaseClass.staticStr + 'patch',
+ staticArr: [...BaseClass.staticArr, 'patch'],
+ staticObj: {...BaseClass.staticObj, patch: 'patch'},
+ });
+
+ class Extension extends BaseClass {}
+ Extension.staticStr = BaseClass.staticStr + 'extension';
+ Extension.staticArr = [...BaseClass.staticArr, 'extension'];
+ Extension.staticObj = {...BaseClass.staticObj, extension: 'extension'};
+
+ assert.strictEqual(Extension.staticStr, 'basepatchextension');
+ assert.deepEqual(Extension.staticArr, ['base', 'patch', 'extension']);
+ assert.deepEqual(Extension.staticObj,
+ { base: 'base', patch: 'patch', extension: 'extension' });
+
+ unpatch(BaseClass, 'patch.BaseClass');
+
+ // /!\ WARNING /!\
+ // If inherit comes after the patch then extension will still have
+ // the patched data when unpatching.
+ assert.strictEqual(Extension.staticStr, 'basepatchextension');
+ assert.deepEqual(Extension.staticArr, ['base', 'patch', 'extension']);
+ assert.deepEqual(Extension.staticObj,
+ { base: 'base', patch: 'patch', extension: 'extension' });
+ });
+
+ QUnit.test('class fields (inherit before patch)', async function (assert) {
+ assert.expect(6);
+
+ const BaseClass = makeBaseClass(assert);
+
+ class Extension extends BaseClass {}
+ Extension.staticStr = BaseClass.staticStr + 'extension';
+ Extension.staticArr = [...BaseClass.staticArr, 'extension'];
+ Extension.staticObj = {...BaseClass.staticObj, extension: 'extension'};
+
+ // /!\ WARNING /!\
+ // If patch comes after the inherit then extension won't have
+ // the patched data.
+ patch(BaseClass, 'patch.BaseClass', {
+ staticStr: BaseClass.staticStr + 'patch',
+ staticArr: [...BaseClass.staticArr, 'patch'],
+ staticObj: {...BaseClass.staticObj, patch: 'patch'},
+ });
+
+ assert.strictEqual(Extension.staticStr, 'baseextension');
+ assert.deepEqual(Extension.staticArr, ['base', 'extension']);
+ assert.deepEqual(Extension.staticObj,
+ { base: 'base', extension: 'extension' });
+
+ unpatch(BaseClass, 'patch.BaseClass');
+
+ assert.strictEqual(Extension.staticStr, 'baseextension');
+ assert.deepEqual(Extension.staticArr, ['base', 'extension']);
+ assert.deepEqual(Extension.staticObj,
+ { base: 'base', extension: 'extension' });
+ });
+
+ QUnit.test('lazy patch', async function (assert) {
+ assert.expect(9);
+
+ const BaseClass = makeBaseClass(assert);
+ class Extension extends BaseClass {
+ setup() {
+ super.setup();
+ assert.step('extension.setup');
+ }
+ fn() {
+ super.fn();
+ assert.step('extension.fn');
+ }
+ }
+
+ const instance = new Extension();
+
+ patch(BaseClass.prototype, 'patch', {
+ setup() {
+ this._super();
+ // will not be called
+ assert.step('patch.setup');
+ },
+ fn() {
+ this._super();
+ assert.step('patch.fn');
+ },
+ });
+
+ instance.fn();
+ assert.verifySteps([
+ 'base.setup',
+ 'extension.setup',
+ 'base.fn',
+ 'patch.fn',
+ 'extension.fn',
+ ]);
+
+ unpatch(BaseClass.prototype, 'patch');
+
+ instance.fn();
+ assert.verifySteps([
+ 'base.fn',
+ 'extension.fn',
+ ]);
+ });
+
+ QUnit.module('other');
+
+ QUnit.test('patch an object', async function (assert) {
+ assert.expect(7);
+
+ const obj = {
+ var: "obj",
+ fn() {
+ assert.step("obj");
+ },
+ };
+
+ patch(obj, "patch", {
+ var: obj.var + "patch",
+ fn() {
+ this._super(...arguments);
+ assert.step("patch");
+ }
+ });
+
+ assert.strictEqual(obj.var, "objpatch");
+
+ obj.fn();
+ assert.verifySteps([ "obj", "patch" ]);
+
+ unpatch(obj, "patch");
+
+ assert.strictEqual(obj.var, "obj");
+
+ obj.fn();
+ assert.verifySteps([ "obj" ]);
+ });
+ });
+});
+});
diff --git a/addons/web/static/tests/core/util_tests.js b/addons/web/static/tests/core/util_tests.js
index 61ab088d137..6ebed595447 100644
--- a/addons/web/static/tests/core/util_tests.js
+++ b/addons/web/static/tests/core/util_tests.js
@@ -153,41 +153,6 @@ QUnit.module('core', {}, function () {
assert.strictEqual(human_number(-1.012e+43, 2, 2), '-1.01e+43');
});
- QUnit.test('patch a class', function(assert) {
- assert.expect(4);
-
- class Parent {
- foo() {
- return 'Parent foo';
- }
- }
-
- class Child extends Parent {
- bar() {
- return 'Child bar';
- }
- }
-
- const removePatch = utils.patch(Child, 'patch', {
- foo() {
- return this._super() + ' patch foo';
- },
- bar() {
- return this._super() + ' patch bar';
- }
- })
-
- const child = new Child();
-
- assert.strictEqual(child.foo(), 'Parent foo patch foo')
- assert.strictEqual(child.bar(), 'Child bar patch bar')
-
- removePatch();
-
- assert.strictEqual(child.foo(), 'Parent foo');
- assert.strictEqual(child.bar(), 'Child bar');
- })
-
QUnit.test('round_decimals', function (assert) {
assert.expect(21);
diff --git a/addons/web/static/tests/fields/relational_fields/field_one2many_tests.js b/addons/web/static/tests/fields/relational_fields/field_one2many_tests.js
index 54606151e4b..f3424f18502 100644
--- a/addons/web/static/tests/fields/relational_fields/field_one2many_tests.js
+++ b/addons/web/static/tests/fields/relational_fields/field_one2many_tests.js
@@ -13,6 +13,7 @@ var RamStorage = require('web.RamStorage');
var relationalFields = require('web.relational_fields');
var testUtils = require('web.test_utils');
var fieldUtils = require('web.field_utils');
+const { patch, unpatch } = require('web.utils');
const cpHelpers = testUtils.controlPanel;
var createView = testUtils.createView;
@@ -9602,24 +9603,23 @@ QUnit.module('fields', {}, function () {
const prom = new Promise(r => {
resolveCP = r;
});
- ControlPanel.patch('cp_patch_mock', T =>
- class extends T {
- constructor() {
- super(...arguments);
- owl.hooks.onMounted(() => {
- assert.step('mounted');
- });
- owl.hooks.onWillUnmount(() => {
- assert.step('willUnmount');
- });
- }
- async update() {
- // the issue is a race condition, so we manually delay the update to turn it deterministic
- await prom;
- super.update(...arguments);
- }
- }
- );
+ patch(ControlPanel.prototype, 'cp_patch_mock', {
+ setup() {
+ this._super(...arguments);
+ owl.hooks.onMounted(() => {
+ assert.step('mounted');
+ });
+ owl.hooks.onWillUnmount(() => {
+ assert.step('willUnmount');
+ });
+ },
+ async update() {
+ const _super = this._super.bind(this);
+ // the issue is a race condition, so we manually delay the update to turn it deterministic
+ await prom;
+ _super.update(...arguments);
+ },
+ });
const form = await createView({
View: FormView,
@@ -9644,7 +9644,7 @@ QUnit.module('fields', {}, function () {
assert.verifySteps([]);
- ControlPanel.unpatch('cp_patch_mock');
+ unpatch(ControlPanel.prototype, 'cp_patch_mock');
delete fieldRegistry.map.pad_like;
form.destroy();
diff --git a/addons/web/static/tests/report/client_action_tests.js b/addons/web/static/tests/report/client_action_tests.js
index c537f6c2540..29123183fab 100644
--- a/addons/web/static/tests/report/client_action_tests.js
+++ b/addons/web/static/tests/report/client_action_tests.js
@@ -4,6 +4,7 @@ odoo.define('web/static/tests/report/client_action_tests', function (require) {
const ControlPanel = require('web.ControlPanel');
const ReportClientAction = require('report.client_action');
const testUtils = require("web.test_utils");
+ const { patch, unpatch } = require('web.utils');
const { createActionManager, dom, mock } = testUtils;
@@ -29,20 +30,18 @@ odoo.define('web/static/tests/report/client_action_tests', function (require) {
}
});
- ControlPanel.patch('test.ControlPanel', T => {
- class ControlPanelPatchTest extends T {
- mounted() {
- mountCount = mountCount + 1;
- this.__uniqueId = mountCount;
- assert.step(`mounted ${this.__uniqueId}`);
- super.mounted(...arguments);
- }
- willUnmount() {
- assert.step(`willUnmount ${this.__uniqueId}`);
- super.mounted(...arguments);
- }
- }
- return ControlPanelPatchTest;
+ patch(ControlPanel.prototype, 'test.ControlPanel', {
+ mounted() {
+ mountCount = mountCount + 1;
+ this.__uniqueId = mountCount;
+ assert.step(`mounted ${this.__uniqueId}`);
+ this.__superMounted = this._super.bind(this);
+ this.__superMounted(...arguments);
+ },
+ willUnmount() {
+ assert.step(`willUnmount ${this.__uniqueId}`);
+ this.__superMounted(...arguments);
+ },
});
const actionManager = await createActionManager({
actions: [
@@ -103,7 +102,7 @@ odoo.define('web/static/tests/report/client_action_tests', function (require) {
'willUnmount 3',
]);
- ControlPanel.unpatch('test.ControlPanel');
+ unpatch(ControlPanel.prototype, 'test.ControlPanel');
mock.unpatch(ReportClientAction);
});
});
diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js
index 0fb6446aa9f..49390b3fc02 100644
--- a/addons/web/static/tests/views/list_tests.js
+++ b/addons/web/static/tests/views/list_tests.js
@@ -16,6 +16,7 @@ var mixins = require('web.mixins');
var NotificationService = require('web.NotificationService');
var RamStorage = require('web.RamStorage');
var testUtils = require('web.test_utils');
+const { patch, unpatch } = require('web.utils');
var widgetRegistry = require('web.widget_registry');
var Widget = require('web.Widget');
@@ -11226,15 +11227,12 @@ QUnit.module('Views', {
let mountedCounterCall = 0;
- ControlPanel.patch('test.ControlPanel', T => {
- class ControlPanelPatchTest extends T {
- mounted() {
- mountedCounterCall = mountedCounterCall + 1;
- assert.step(`mountedCounterCall-${mountedCounterCall}`);
- super.mounted(...arguments);
- }
- }
- return ControlPanelPatchTest;
+ patch(ControlPanel.prototype, 'test.ControlPanel', {
+ mounted() {
+ mountedCounterCall = mountedCounterCall + 1;
+ assert.step(`mountedCounterCall-${mountedCounterCall}`);
+ this._super(...arguments);
+ },
});
const MyListView = ListView.extend({
@@ -11261,7 +11259,7 @@ QUnit.module('Views', {
'mountedCounterCall-1',
]);
- ControlPanel.unpatch('test.ControlPanel');
+ unpatch(ControlPanel.prototype, 'test.ControlPanel');
list.destroy();
});
diff --git a/addons/web/views/webclient_templates.xml b/addons/web/views/webclient_templates.xml
index 60982a48392..dec1da40b4e 100644
--- a/addons/web/views/webclient_templates.xml
+++ b/addons/web/views/webclient_templates.xml
@@ -147,7 +147,6 @@
-
@@ -739,7 +738,7 @@
-
+
diff --git a/addons/website/static/tests/dashboard_tests.js b/addons/website/static/tests/dashboard_tests.js
index 04d9ce89176..26778ee4070 100644
--- a/addons/website/static/tests/dashboard_tests.js
+++ b/addons/website/static/tests/dashboard_tests.js
@@ -4,6 +4,7 @@ odoo.define('website/static/tests/dashboard_tests', function (require) {
const ControlPanel = require('web.ControlPanel');
const Dashboard = require('website.backend.dashboard');
const testUtils = require("web.test_utils");
+const { patch, unpatch } = require('web.utils');
const { createParent, nextTick, prepareTarget } = testUtils;
@@ -13,18 +14,16 @@ QUnit.module('Website Backend Dashboard', {
// This test can be removed as soon as we don't mix legacy and owl layers anymore.
assert.expect(5);
- ControlPanel.patch('test.ControlPanel', T => {
- class ControlPanelPatchTest extends T {
- mounted() {
- assert.step('mounted');
- super.mounted(...arguments);
- }
- willUnmount() {
- assert.step('willUnmount');
- super.mounted(...arguments);
- }
- }
- return ControlPanelPatchTest;
+ patch(ControlPanel.prototype, 'test.ControlPanel', {
+ mounted() {
+ this.__superMounted = this._super.bind(this);
+ assert.step('mounted');
+ this.__superMounted(...arguments);
+ },
+ willUnmount() {
+ assert.step('willUnmount');
+ this.__superMounted(...arguments);
+ },
});
const params = {
@@ -60,7 +59,7 @@ QUnit.module('Website Backend Dashboard', {
dashboard.destroy();
assert.verifySteps(['willUnmount']);
- ControlPanel.unpatch('test.ControlPanel');
+ unpatch(ControlPanel.prototype, 'test.ControlPanel');
});
});
diff --git a/addons/website_livechat/static/src/components/discuss/discuss.js b/addons/website_livechat/static/src/components/discuss/discuss.js
index 778b1513730..17ce3bdc07a 100644
--- a/addons/website_livechat/static/src/components/discuss/discuss.js
+++ b/addons/website_livechat/static/src/components/discuss/discuss.js
@@ -5,24 +5,23 @@ const components = {
Discuss: require('mail/static/src/components/discuss/discuss.js'),
VisitorBanner: require('website_livechat/static/src/components/visitor_banner/visitor_banner.js'),
};
+const { patch } = require('web.utils');
-components.Discuss.patch('website_livechat/static/src/components/discuss/discuss.js', T =>
- class extends T {
+patch(components.Discuss.prototype, 'website_livechat/static/src/components/discuss/discuss.js', {
- /**
- * @override
- */
- _useStoreSelector(props) {
- const res = super._useStoreSelector(...arguments);
- const thread = res.thread;
- const visitor = thread && thread.visitor;
- return Object.assign({}, res, {
- visitor,
- });
- }
+ /**
+ * @override
+ */
+ _useStoreSelector(props) {
+ const res = this._super(...arguments);
+ const thread = res.thread;
+ const visitor = thread && thread.visitor;
+ return Object.assign({}, res, {
+ visitor,
+ });
+ },
- }
-);
+});
Object.assign(components.Discuss.components, {
VisitorBanner: components.VisitorBanner,
diff --git a/addons/website_livechat/static/tests/helpers/mock_models.js b/addons/website_livechat/static/tests/helpers/mock_models.js
index bc9f1d3b830..b0da281cbd2 100644
--- a/addons/website_livechat/static/tests/helpers/mock_models.js
+++ b/addons/website_livechat/static/tests/helpers/mock_models.js
@@ -2,44 +2,43 @@ odoo.define('website_livechat/static/tests/helpers/mock_models.js', function (re
'use strict';
const MockModels = require('mail/static/tests/helpers/mock_models.js');
+const { patch } = require('web.utils');
-MockModels.patch('website_livechat/static/tests/helpers/mock_models.js', T =>
- class extends T {
+patch(MockModels, 'website_livechat/static/tests/helpers/mock_models.js', {
- //----------------------------------------------------------------------
- // Public
- //----------------------------------------------------------------------
+ //----------------------------------------------------------------------
+ // Public
+ //----------------------------------------------------------------------
- /**
- * @override
- */
- static generateData() {
- const data = super.generateData(...arguments);
- Object.assign(data, {
- 'website.visitor': {
- fields: {
- country_id: { string: "Country", type: 'many2one', relation: 'res.country' },
- display_name: { string: "Display name", type: 'string' },
- // Represent the browsing history of the visitor as a string.
- // To ease testing this allows tests to set it directly instead
- // of implementing the computation made on server.
- // This should normally not be a field.
- history: { string: "History", type: 'string'},
- is_connected: { string: "Is connected", type: 'boolean' },
- lang_name: { string: "Language name", type: 'string'},
- partner_id: {string: "partner", type: "many2one", relation: 'res.partner'},
- website_name: { string: "Website name", type: 'string' },
- },
- records: [],
+ /**
+ * @override
+ */
+ generateData() {
+ const data = this._super(...arguments);
+ Object.assign(data, {
+ 'website.visitor': {
+ fields: {
+ country_id: { string: "Country", type: 'many2one', relation: 'res.country' },
+ display_name: { string: "Display name", type: 'string' },
+ // Represent the browsing history of the visitor as a string.
+ // To ease testing this allows tests to set it directly instead
+ // of implementing the computation made on server.
+ // This should normally not be a field.
+ history: { string: "History", type: 'string'},
+ is_connected: { string: "Is connected", type: 'boolean' },
+ lang_name: { string: "Language name", type: 'string'},
+ partner_id: {string: "partner", type: "many2one", relation: 'res.partner'},
+ website_name: { string: "Website name", type: 'string' },
},
- });
- Object.assign(data['mail.channel'].fields, {
- livechat_visitor_id: { string: "Visitor", type: 'many2one', relation: 'website.visitor' },
- });
- return data;
- }
-
- }
-);
+ records: [],
+ },
+ });
+ Object.assign(data['mail.channel'].fields, {
+ livechat_visitor_id: { string: "Visitor", type: 'many2one', relation: 'website.visitor' },
+ });
+ return data;
+ },
+
+});
});
diff --git a/addons/website_slides/static/src/components/activity/activity.js b/addons/website_slides/static/src/components/activity/activity.js
index 4bd79cddc17..6dc8c58744d 100644
--- a/addons/website_slides/static/src/components/activity/activity.js
+++ b/addons/website_slides/static/src/components/activity/activity.js
@@ -6,7 +6,7 @@ const components = {
};
const { patch } = require('web.utils');
-patch(components.Activity, 'website_slides/static/src/components/activity/activity.js', {
+patch(components.Activity.prototype, 'website_slides/static/src/components/activity/activity.js', {
//--------------------------------------------------------------------------
// Handlers
diff --git a/doc/reference/javascript_reference.rst b/doc/reference/javascript_reference.rst
index 8ad49f0c507..e6d0ad9b56f 100644
--- a/doc/reference/javascript_reference.rst
+++ b/doc/reference/javascript_reference.rst
@@ -607,6 +607,14 @@ to use inheritance, mixins.
For these reasons, Odoo decided to use its own class system, inspired by John
Resig. The base Class is located in *web.Class*, in the file *class.js*.
+
+.. note ::
+
+ Note that the custom class system should be avoided for creating new code. It
+ will be deprecated at some point, and then removed. New classes should use
+ the standard ES6 class system.
+
+
Creating a subclass
-------------------
@@ -2650,3 +2658,158 @@ For more information, look into the `control_panel_renderer.js