diff --git a/addons/bus/static/src/js/services/bus_service.js b/addons/bus/static/src/js/services/bus_service.js index 7dc932d4575..39b61da51f1 100644 --- a/addons/bus/static/src/js/services/bus_service.js +++ b/addons/bus/static/src/js/services/bus_service.js @@ -4,6 +4,7 @@ odoo.define('bus.BusService', function (require) { var CrossTab = require('bus.CrossTab'); var core = require('web.core'); var ServicesMixin = require('web.ServicesMixin'); +const session = require('web.session'); var BusService = CrossTab.extend(ServicesMixin, { dependencies : ['local_storage'], @@ -11,6 +12,38 @@ var BusService = CrossTab.extend(ServicesMixin, { // properties _audio: null, + /** + * As the BusService doesn't extend AbstractService, we have to replicate + * here what is done in AbstractService + * + * @param {Object} env + */ + init: function (env) { + this.env = env; + this._super(); + }, + + /** + * Replicate the behavior of AbstractService: + * + * Directly calls the requested service, instead of triggering a + * 'call_service' event up, which wouldn't work as services have no parent. + * + * @param {OdooEvent} ev + */ + _trigger_up: function (ev) { + if (ev.name === 'call_service') { + const payload = ev.data; + let args = payload.args || []; + if (payload.service === 'ajax' && payload.method === 'rpc') { + // ajax service uses an extra 'target' argument for rpc + args = args.concat(ev.target); + } + const service = this.env.services[payload.service]; + const result = service[payload.method].apply(service, args); + payload.callback(result); + } + }, /** * This method is necessary in order for this Class to be used to instantiate services * @@ -77,7 +110,6 @@ var BusService = CrossTab.extend(ServicesMixin, { if (!this._audio) { this._audio = new Audio(); var ext = this._audio.canPlayType("audio/ogg; codecs=vorbis") ? ".ogg" : ".mp3"; - var session = this.getSession(); this._audio.src = session.url("/mail/static/src/audio/ting" + ext); } Promise.resolve(this._audio.play()).catch(_.noop); diff --git a/addons/bus/static/tests/bus_tests.js b/addons/bus/static/tests/bus_tests.js index 551a145bda9..b13694a7afb 100644 --- a/addons/bus/static/tests/bus_tests.js +++ b/addons/bus/static/tests/bus_tests.js @@ -27,7 +27,7 @@ QUnit.module('Bus', { var pollPromise = testUtils.makeTestPromise(); var parent = new Widget(); - testUtils.mock.addMockEnvironment(parent, { + await testUtils.mock.addMockEnvironment(parent, { data: {}, services: { bus_service: BusService, @@ -101,7 +101,7 @@ QUnit.module('Bus', { var pollPromise = testUtils.makeTestPromise(); var parent = new Widget(); - testUtils.mock.addMockEnvironment(parent, { + await testUtils.mock.addMockEnvironment(parent, { data: {}, services: { bus_service: BusService, @@ -142,7 +142,7 @@ QUnit.module('Bus', { var pollPromiseMaster = testUtils.makeTestPromise(); var parentMaster = new Widget(); - testUtils.mock.addMockEnvironment(parentMaster, { + await testUtils.mock.addMockEnvironment(parentMaster, { data: {}, services: { bus_service: BusService, @@ -173,7 +173,7 @@ QUnit.module('Bus', { // slave await testUtils.nextTick(); var parentSlave = new Widget(); - testUtils.mock.addMockEnvironment(parentSlave, { + await testUtils.mock.addMockEnvironment(parentSlave, { data: {}, services: { bus_service: BusService, @@ -220,7 +220,7 @@ QUnit.module('Bus', { var pollPromiseMaster = testUtils.makeTestPromise(); var parentMaster = new Widget(); - testUtils.mock.addMockEnvironment(parentMaster, { + await testUtils.mock.addMockEnvironment(parentMaster, { data: {}, services: { bus_service: BusService, @@ -252,7 +252,7 @@ QUnit.module('Bus', { await testUtils.nextTick(); var parentSlave = new Widget(); var pollPromiseSlave = testUtils.makeTestPromise(); - testUtils.mock.addMockEnvironment(parentSlave, { + await testUtils.mock.addMockEnvironment(parentSlave, { data: {}, services: { bus_service: BusService, diff --git a/addons/calendar/static/tests/systray_activity_menu_tests.js b/addons/calendar/static/tests/systray_activity_menu_tests.js index 5a25792ff34..02937bae0ed 100644 --- a/addons/calendar/static/tests/systray_activity_menu_tests.js +++ b/addons/calendar/static/tests/systray_activity_menu_tests.js @@ -41,7 +41,7 @@ QUnit.test('activity menu widget:today meetings', async function (assert) { assert.expect(6); var self = this; var activityMenu = new ActivityMenu(); - testUtils.mock.addMockEnvironment(activityMenu, { + await testUtils.mock.addMockEnvironment(activityMenu, { services: this.services, mockRPC: function (route, args) { if (args.method === 'systray_get_activities') { diff --git a/addons/hr_attendance/static/tests/hr_attendance_tests.js b/addons/hr_attendance/static/tests/hr_attendance_tests.js index 22de18817ad..1fc06e7474e 100644 --- a/addons/hr_attendance/static/tests/hr_attendance_tests.js +++ b/addons/hr_attendance/static/tests/hr_attendance_tests.js @@ -59,7 +59,7 @@ QUnit.module('HR Attendance', { var $target = $('#qunit-fixture'); var clientAction = new MyAttendances(null, {}); - testUtils.mock.addMockEnvironment(clientAction, { + await testUtils.mock.addMockEnvironment(clientAction, { data: this.data, session: { uid: 1, @@ -80,7 +80,7 @@ QUnit.module('HR Attendance', { var self = this; var rpcCount = 0; var clientAction = new KioskMode(null, {}); - testUtils.mock.addMockEnvironment(clientAction, { + await testUtils.mock.addMockEnvironment(clientAction, { data: this.data, session: { uid: 1, @@ -124,7 +124,7 @@ QUnit.module('HR Attendance', { barcode: barcode, }; var clientAction = new GreetingMessage(null, action); - testUtils.mock.addMockEnvironment(clientAction, { + await testUtils.mock.addMockEnvironment(clientAction, { data: self.data, session: { uid: 1, diff --git a/addons/im_livechat/static/tests/website_livechat_window_tests.js b/addons/im_livechat/static/tests/website_livechat_window_tests.js index e8a296fa095..8907d790cb1 100644 --- a/addons/im_livechat/static/tests/website_livechat_window_tests.js +++ b/addons/im_livechat/static/tests/website_livechat_window_tests.js @@ -45,7 +45,7 @@ QUnit.test('basic rendering', async function (assert) { livechat.setMessages(messages); var chatWindow = new ChatWindow(parent, livechat); - testUtils.mock.addMockEnvironment(parent, {}); + await testUtils.mock.addMockEnvironment(parent, {}); await chatWindow.appendTo($('#qunit-fixture')); chatWindow.render(); @@ -95,7 +95,7 @@ QUnit.test('define several livechat colors', async function (assert) { headerBackgroundColor: 'yellow', titleColor: 'green', }); - testUtils.mock.addMockEnvironment(parent, {}); + await testUtils.mock.addMockEnvironment(parent, {}); await chatWindow.appendTo($('#qunit-fixture')); chatWindow.render(); assert.strictEqual( diff --git a/addons/mail/static/src/js/services/mail_manager.js b/addons/mail/static/src/js/services/mail_manager.js index 04cc696d401..f52af585f94 100644 --- a/addons/mail/static/src/js/services/mail_manager.js +++ b/addons/mail/static/src/js/services/mail_manager.js @@ -1004,7 +1004,7 @@ var MailManager = AbstractService.extend({ _t("%d Messages"), this._outOfFocusUnreadMessageCounter ); - this.trigger_up('set_title_part', { + this.env.bus.trigger('set_title_part', { part: '_chat', title: tabTitle }); @@ -1048,12 +1048,14 @@ var MailManager = AbstractService.extend({ * @param {string} viewID */ _redirectToDocument: function (resModel, resID, viewID) { - this.do_action({ - type: 'ir.actions.act_window', - view_mode: 'form', - res_model: resModel, - views: [[viewID || false, 'form']], - res_id: resID, + this.env.bus.trigger('do-action', { + action: { + type: 'ir.actions.act_window', + view_mode: 'form', + res_model: resModel, + views: [[viewID || false, 'form']], + res_id: resID, + }, }); }, /** @@ -1345,7 +1347,7 @@ var MailManager = AbstractService.extend({ */ _onWindowFocus: function () { this._resetOutOfFocusUnreadMessageCounter(); - this.trigger_up('set_title_part', { part: '_chat' }); + this.env.bus.trigger('set_title_part', { part: '_chat' }); }, }); diff --git a/addons/mail/static/tests/document_thread_window_tests.js b/addons/mail/static/tests/document_thread_window_tests.js index f01ab0925f2..c4a84040433 100644 --- a/addons/mail/static/tests/document_thread_window_tests.js +++ b/addons/mail/static/tests/document_thread_window_tests.js @@ -1,6 +1,8 @@ odoo.define('mail.documentThreadWindowTests', function (require) { "use strict"; +const Bus = require('web.Bus'); +const LocalStorageService = require('web.LocalStorageService'); var mailTestUtils = require('mail.testUtils'); var MessagingMenu = require('mail.systray.MessagingMenu'); @@ -128,20 +130,23 @@ QUnit.test('open a document thread in a thread window', async function (assert) QUnit.test('expand a document thread window', async function (assert) { assert.expect(4); + const bus = new Bus(); + bus.on('do-action', null, function (payload) { + assert.deepEqual(payload.action, { + res_id: 1, + res_model: 'some.res.model', + type: 'ir.actions.act_window', + views: [[false, 'form']], + }, "should open the document in form view"); + }); + var messagingMenu = new MessagingMenu(); await testUtils.mock.addMockEnvironment(messagingMenu, { services: this.services, data: this.data, session: this.session, - intercepts: { - do_action: function (ev) { - assert.deepEqual(ev.data.action, { - res_id: 1, - res_model: 'some.res.model', - type: 'ir.actions.act_window', - views: [[false, 'form']], - }, "should open the document in form view"); - }, + env: { + bus }, }); await messagingMenu.appendTo($('#qunit-fixture')); @@ -177,8 +182,18 @@ QUnit.test('post messages in a document thread window', async function (assert) res_id: 1, }; var messagingMenu = new MessagingMenu(); + const MockedLocalStorageService = LocalStorageService.extend({ + setItem(key, value) { + if (key === 'mail.document_threads_last_message') { + assert.deepEqual(value.messageData, newMessage, + "should write sent message in local storage, to share info with other tabs"); + } + }, + }); await testUtils.mock.addMockEnvironment(messagingMenu, { - services: this.services, + services: Object.assign(this.services, { + local_storage: MockedLocalStorageService, + }), data: this.data, session: this.session, mockRPC: function (route, args) { @@ -198,13 +213,7 @@ QUnit.test('post messages in a document thread window', async function (assert) return this._super.apply(this, arguments); }, }); - testUtils.mock.intercept(messagingMenu, 'call_service', function (ev) { - if (ev.data.service === 'local_storage' && ev.data.method === 'setItem' && - ev.data.args[0] === 'mail.document_threads_last_message') { - assert.deepEqual(ev.data.args[1].messageData, newMessage, - "should write sent message in local storage, to share info with other tabs"); - } - }, true); + await messagingMenu.appendTo($('#qunit-fixture')); // toggle the messaging menu and open the documentThread @@ -229,24 +238,25 @@ QUnit.test('post messages in a document thread window', async function (assert) QUnit.test('open, fold, unfold and close a document thread window', async function (assert) { assert.expect(20); var messagingMenu = new MessagingMenu(); - await testUtils.mock.addMockEnvironment(messagingMenu, { - services: this.services, - data: this.data, - session: this.session, - }); - await testUtils.nextTick(); - testUtils.mock.intercept(messagingMenu, 'call_service', function (ev) { - if (ev.data.service === 'local_storage' && ev.data.method === 'setItem') { - const key = ev.data.args[0]; + const MockedLocalStorageService = LocalStorageService.extend({ + setItem(key, value) { if (key === 'mail.document_threads_state/some.res.model_1') { - const state = ev.data.args[1].state; + const state = value.state; assert.ok(state); assert.ok(state.name); assert.ok(state.windowState); assert.step(`${key}: { name: "${state.name}", windowState: '${state.windowState}' }`); } } - }, true); + }); + await testUtils.mock.addMockEnvironment(messagingMenu, { + services: Object.assign(this.services, { + local_storage: MockedLocalStorageService, + }), + data: this.data, + session: this.session, + }); + await testUtils.nextTick(); await messagingMenu.appendTo($('#qunit-fixture')); // toggle the messaging menu and open the documentThread diff --git a/addons/mail/static/tests/document_viewer_tests.js b/addons/mail/static/tests/document_viewer_tests.js index aa50483b48a..ca6b433d519 100644 --- a/addons/mail/static/tests/document_viewer_tests.js +++ b/addons/mail/static/tests/document_viewer_tests.js @@ -14,7 +14,7 @@ var Widget = require('web.Widget'); * @param {boolean} [params.debug] * @returns {DocumentViewer} */ -var createViewer = function (params) { +var createViewer = async function (params) { var parent = new Widget(); var viewer = new DocumentViewer(parent, params.attachments, params.attachmentID); @@ -32,7 +32,7 @@ var createViewer = function (params) { return Promise.resolve(); } }; - testUtils.mock.addMockEnvironment(parent, { + await testUtils.mock.addMockEnvironment(parent, { mockRPC: function () { if (params.mockRPC) { var _super = this._super; diff --git a/addons/mail/static/tests/mail_status_manager_tests.js b/addons/mail/static/tests/mail_status_manager_tests.js index f21cefdec9e..891cf3aaa68 100644 --- a/addons/mail/static/tests/mail_status_manager_tests.js +++ b/addons/mail/static/tests/mail_status_manager_tests.js @@ -17,9 +17,9 @@ QUnit.module('Status manager', { this.timeoutMock = mailTestUtils.patchMailTimeouts(); }, }); -QUnit.test('simple set im_status', function (assert) { +QUnit.test('simple set im_status', async function (assert) { assert.expect(1); - var parent = testUtils.createParent({ + var parent = await testUtils.createParent({ services: this.services, mockRPC: function (route, args) { if (route === '/mail/init_messaging') { @@ -41,7 +41,7 @@ QUnit.test('simple set im_status', function (assert) { QUnit.test('multi get_im_status', async function (assert) { assert.expect(8); var readCount = 0; - var parent = testUtils.createParent({ + var parent = await testUtils.createParent({ //data: this.data, services: this.services, mockRPC: function (route, args) { @@ -86,7 +86,7 @@ QUnit.test('multi get_im_status', async function (assert) { QUnit.test('update loop', async function (assert) { assert.expect(10); var readCount = 0; - var parent = testUtils.createParent({ + var parent = await testUtils.createParent({ services: this.services, mockRPC: function (route, args) { if (route === '/mail/init_messaging') { @@ -162,7 +162,7 @@ QUnit.test('update status', async function (assert) { } }); var statusWidget = new StatusWidget(); - testUtils.mock.addMockEnvironment(statusWidget, { + await testUtils.mock.addMockEnvironment(statusWidget, { services: this.services, mockRPC: function (route, args) { if (route === '/mail/init_messaging') { diff --git a/addons/mail/static/tests/models/cc_throttle_function_tests.js b/addons/mail/static/tests/models/cc_throttle_function_tests.js index 5ab00ef6048..f760675b1f3 100644 --- a/addons/mail/static/tests/models/cc_throttle_function_tests.js +++ b/addons/mail/static/tests/models/cc_throttle_function_tests.js @@ -31,7 +31,7 @@ QUnit.module('mail', { QUnit.module('model', {}, function () { QUnit.module('CC Throttle Function'); -QUnit.test('cancel()', function (assert) { +QUnit.test('cancel()', async function (assert) { var done = assert.async(); assert.expect(3); @@ -39,7 +39,7 @@ QUnit.test('cancel()', function (assert) { var def = testUtils.makeTestPromise(); var step = 1; - var widget = testUtils.createParent({ + var widget = await testUtils.createParent({ mockRPC: function (route, args) { if (args.method === '__rpc__') { assert.step(args.method + args.args[0]); @@ -87,7 +87,7 @@ QUnit.test('cancel()', function (assert) { }); }); -QUnit.test('clear()', function (assert) { +QUnit.test('clear()', async function (assert) { var done = assert.async(); assert.expect(5); @@ -96,7 +96,7 @@ QUnit.test('clear()', function (assert) { var def2 = testUtils.makeTestPromise(); var step = 1; - var widget = testUtils.createParent({ + var widget = await testUtils.createParent({ mockRPC: function (route, args) { if (args.method === '__rpc__') { assert.step(args.method + args.args[0]); diff --git a/addons/mail/static/tests/systray/systray_activity_menu_tests.js b/addons/mail/static/tests/systray/systray_activity_menu_tests.js index 1cedca2b9c1..23f3500001b 100644 --- a/addons/mail/static/tests/systray/systray_activity_menu_tests.js +++ b/addons/mail/static/tests/systray/systray_activity_menu_tests.js @@ -83,7 +83,7 @@ QUnit.test('activity menu widget: menu with no records', async function (assert) assert.expect(1); var activityMenu = new ActivityMenu(); - testUtils.mock.addMockEnvironment(activityMenu, { + await testUtils.mock.addMockEnvironment(activityMenu, { services: this.services, mockRPC: function (route, args) { if (args.method === 'systray_get_activities') { @@ -102,7 +102,7 @@ QUnit.test('activity menu widget: activity menu with 3 records', async function assert.expect(10); var self = this; var activityMenu = new ActivityMenu(); - testUtils.mock.addMockEnvironment(activityMenu, { + await testUtils.mock.addMockEnvironment(activityMenu, { services: this.services, mockRPC: function (route, args) { if (args.method === 'systray_get_activities') { @@ -163,7 +163,7 @@ QUnit.test('activity menu widget: activity view icon', async function (assert) { assert.expect(12); var self = this; var activityMenu = new ActivityMenu(); - testUtils.mock.addMockEnvironment(activityMenu, { + await testUtils.mock.addMockEnvironment(activityMenu, { services: this.services, session: this.session, mockRPC: function (route, args) { diff --git a/addons/mail/static/tests/thread_window/basic_thread_window_tests.js b/addons/mail/static/tests/thread_window/basic_thread_window_tests.js index 87e3c536998..61414d2aeaa 100644 --- a/addons/mail/static/tests/thread_window/basic_thread_window_tests.js +++ b/addons/mail/static/tests/thread_window/basic_thread_window_tests.js @@ -56,7 +56,7 @@ QUnit.module('Basic', { this.services = mailTestUtils.getMailServices(); this.ORIGINAL_THREAD_WINDOW_APPENDTO = this.services.mail_service.prototype.THREAD_WINDOW_APPENDTO; - this.createParent = function (params) { + this.createParent = async function (params) { var widget = new Widget(); // in non-debug mode, append thread windows in qunit-fixture @@ -68,7 +68,7 @@ QUnit.module('Basic', { self.services.mail_service.prototype.THREAD_WINDOW_APPENDTO = '#qunit-fixture'; } - testUtils.mock.addMockEnvironment(widget, params); + await testUtils.mock.addMockEnvironment(widget, params); return widget; }; }, @@ -81,7 +81,7 @@ QUnit.module('Basic', { QUnit.test('basic rendering thread window', async function (assert) { assert.expect(10); - var parent = this.createParent({ + var parent = await this.createParent({ data: this.data, services: this.services, }); @@ -128,7 +128,7 @@ QUnit.test('basic rendering thread window', async function (assert) { QUnit.test('close thread window using ESCAPE key', async function (assert) { assert.expect(5); - var parent = this.createParent({ + var parent = await this.createParent({ data: this.data, services: this.services, mockRPC: function (route, args) { @@ -169,7 +169,7 @@ QUnit.test('close thread window using ESCAPE key', async function (assert) { QUnit.test('thread window\'s input can still be focused when the UI is blocked', async function (assert) { assert.expect(2); - var parent = this.createParent({ + var parent = await this.createParent({ data: this.data, services: this.services, }); @@ -205,7 +205,7 @@ QUnit.test('thread window\'s input can still be focused when the UI is blocked', QUnit.test('emoji popover should open correctly in thread windows', async function (assert) { assert.expect(1); - var parent = this.createParent({ + var parent = await this.createParent({ data: this.data, services: this.services, }); @@ -227,7 +227,7 @@ QUnit.test('emoji popover should open correctly in thread windows', async functi QUnit.test('do not increase unread counter when receiving message with myself as author', async function (assert) { assert.expect(4); - var parent = this.createParent({ + var parent = await this.createParent({ data: this.data, services: this.services, session: { partner_id: 3 } @@ -274,7 +274,7 @@ QUnit.test('do not increment unread counter with focus on thread window', async // order to set the focus on it. assert.expect(2); - var parent = this.createParent({ + var parent = await this.createParent({ data: this.data, services: this.services, session: { partner_id: 3 } @@ -322,7 +322,7 @@ QUnit.test('do not mark as read the newly open thread window from received messa message_unread_counter: 0, }]; - var parent = this.createParent({ + var parent = await this.createParent({ data: this.data, services: this.services, session: { partner_id: 3 }, @@ -393,7 +393,7 @@ QUnit.test('show document link of message linked to a document', async function channel_type: "public", }); - var parent = this.createParent({ + var parent = await this.createParent({ data: this.data, services: this.services, session: { partner_id: 3 }, @@ -510,7 +510,7 @@ QUnit.test('do not auto-focus chat window on receiving new message from new DM', state: 'open', }]; - var parent = this.createParent({ + var parent = await this.createParent({ data: this.data, services: this.services, session: { partner_id: 3 }, @@ -600,7 +600,7 @@ QUnit.test('no out-of-office status in thread window', async function (assert) { }], }, }; - var parent = this.createParent({ + var parent = await this.createParent({ data: this.data, services: this.services, }); @@ -650,7 +650,7 @@ QUnit.test('receive 2 new DM messages in quick succession (no chat window initia }], }; - const parent = this.createParent({ + const parent = await this.createParent({ data: this.data, mockRPC(route, args) { if (args.method === 'channel_minimize') { @@ -740,7 +740,7 @@ QUnit.test('non-deletable message attachments', async function (assert) { record_name: "general", res_id: 1, }]; - const parent = this.createParent({ + const parent = await this.createParent({ data: this.data, services: this.services, }); @@ -781,7 +781,7 @@ QUnit.test('join channel from click channel mention', async function (assert) { channel_ids: [1], }); - const parent = this.createParent({ + const parent = await this.createParent({ data: this.data, services: this.services, session: { partner_id: 3 }, diff --git a/addons/mail/static/tests/thread_window/blank_thread_window_tests.js b/addons/mail/static/tests/thread_window/blank_thread_window_tests.js index 4a24a80d4c3..2d295144b62 100644 --- a/addons/mail/static/tests/thread_window/blank_thread_window_tests.js +++ b/addons/mail/static/tests/thread_window/blank_thread_window_tests.js @@ -32,7 +32,7 @@ QUnit.module('Blank', { this.services = mailTestUtils.getMailServices(); this.ORIGINAL_THREAD_WINDOW_APPENDTO = this.services.mail_service.prototype.THREAD_WINDOW_APPENDTO; - this.createParent = function (params) { + this.createParent = async function (params) { var widget = new Widget(); // in non-debug mode, append thread windows in qunit-fixture @@ -44,7 +44,7 @@ QUnit.module('Blank', { self.services.mail_service.prototype.THREAD_WINDOW_APPENDTO = '#qunit-fixture'; } - testUtils.mock.addMockEnvironment(widget, params); + await testUtils.mock.addMockEnvironment(widget, params); return widget; }; @@ -90,7 +90,7 @@ QUnit.module('Blank', { QUnit.test('basic rendering blank thread window', async function (assert) { assert.expect(5); - var parent = this.createParent({ + var parent = await this.createParent({ data: this.data, services: this.services, }); @@ -117,7 +117,7 @@ QUnit.test('basic rendering blank thread window', async function (assert) { QUnit.test('close blank thread window', async function (assert) { assert.expect(1); - var parent = this.createParent({ + var parent = await this.createParent({ data: this.data, services: this.services, }); @@ -139,7 +139,7 @@ QUnit.test('fold blank thread window', async function (assert) { // (e.g. 400px) assert.expect(3); - var parent = this.createParent({ + var parent = await this.createParent({ data: this.data, services: this.services, }); @@ -191,7 +191,7 @@ QUnit.test('open new DM chat from blank thread window', async function (assert) records: [], }; - var parent = this.createParent({ + var parent = await this.createParent({ data: this.data, services: this.services, mockRPC: function (route, args) { @@ -274,7 +274,7 @@ QUnit.test('open already detached DM chat from blank thread window', async funct name: 'DemoUser1', }]; - var parent = this.createParent({ + var parent = await this.createParent({ data: this.data, services: this.services, mockRPC: function (route, args) { diff --git a/addons/mail/static/tests/thread_window/hidden_thread_window_tests.js b/addons/mail/static/tests/thread_window/hidden_thread_window_tests.js index 078230e276a..36da266128c 100644 --- a/addons/mail/static/tests/thread_window/hidden_thread_window_tests.js +++ b/addons/mail/static/tests/thread_window/hidden_thread_window_tests.js @@ -31,7 +31,7 @@ QUnit.module('Hidden', { this.services = mailTestUtils.getMailServices(); this.ORIGINAL_THREAD_WINDOW_APPENDTO = this.services.mail_service.prototype.THREAD_WINDOW_APPENDTO; - this.createParent = function (params) { + this.createParent = async function (params) { var widget = new Widget(); // in non-debug mode, append thread windows in qunit-fixture @@ -43,7 +43,7 @@ QUnit.module('Hidden', { self.services.mail_service.prototype.THREAD_WINDOW_APPENDTO = '#qunit-fixture'; } - testUtils.mock.addMockEnvironment(widget, params); + await testUtils.mock.addMockEnvironment(widget, params); return widget; }; }, @@ -86,7 +86,7 @@ QUnit.test('hidden thread windows dropdown when not enough horizontal space (ver }, }; - var parent = this.createParent({ + var parent = await this.createParent({ data: this.data, services: this.services, }); @@ -172,7 +172,7 @@ QUnit.test('hidden thread windows dropdown when not enough horizontal space (ver }, }; - var parent = this.createParent({ + var parent = await this.createParent({ data: this.data, services: this.services, }); @@ -266,7 +266,7 @@ QUnit.test('receive message from hidden thread window', async function (assert) }, }; - var parent = this.createParent({ + var parent = await this.createParent({ data: this.data, services: this.services, }); diff --git a/addons/mail/static/tests/thread_window/moderation_thread_window_tests.js b/addons/mail/static/tests/thread_window/moderation_thread_window_tests.js index 732a5fe7938..db2561fd048 100644 --- a/addons/mail/static/tests/thread_window/moderation_thread_window_tests.js +++ b/addons/mail/static/tests/thread_window/moderation_thread_window_tests.js @@ -62,7 +62,7 @@ QUnit.module('Moderation', { this.services = mailTestUtils.getMailServices(); this.ORIGINAL_THREAD_WINDOW_APPENDTO = this.services.mail_service.prototype.THREAD_WINDOW_APPENDTO; - this.createParent = function (params) { + this.createParent = async function (params) { var widget = new Widget(); // in non-debug mode, append thread windows in qunit-fixture @@ -74,7 +74,7 @@ QUnit.module('Moderation', { self.services.mail_service.prototype.THREAD_WINDOW_APPENDTO = '#qunit-fixture'; } - testUtils.mock.addMockEnvironment(widget, params); + await testUtils.mock.addMockEnvironment(widget, params); return widget; }; }, @@ -110,7 +110,7 @@ QUnit.test('moderator: moderated channel with pending moderation message', async res_id: 1, }]; - var parent = this.createParent({ + var parent = await this.createParent({ data: this.data, services: this.services, }); diff --git a/addons/mail/static/tests/thread_window/typing_thread_window_tests.js b/addons/mail/static/tests/thread_window/typing_thread_window_tests.js index be2bd4beec8..53960dc283d 100644 --- a/addons/mail/static/tests/thread_window/typing_thread_window_tests.js +++ b/addons/mail/static/tests/thread_window/typing_thread_window_tests.js @@ -51,7 +51,7 @@ QUnit.module('Typing', { await testUtils.nextMicrotaskTick(); }; - this.createParent = function (params) { + this.createParent = async function (params) { var widget = new Widget(); // in non-debug mode, append thread windows in qunit-fixture @@ -63,7 +63,7 @@ QUnit.module('Typing', { self.services.mail_service.prototype.THREAD_WINDOW_APPENDTO = '#qunit-fixture'; } - testUtils.mock.addMockEnvironment(widget, params); + await testUtils.mock.addMockEnvironment(widget, params); return widget; }; }, @@ -76,7 +76,7 @@ QUnit.module('Typing', { QUnit.test('receive typing notification', async function (assert) { assert.expect(4); - var parent = this.createParent({ + var parent = await this.createParent({ data: this.data, services: this.services, mockRPC: function (route, args) { diff --git a/addons/mail/static/tests/tools/debug_manager_tests.js b/addons/mail/static/tests/tools/debug_manager_tests.js index c21d36572b9..151b89024b6 100644 --- a/addons/mail/static/tests/tools/debug_manager_tests.js +++ b/addons/mail/static/tests/tools/debug_manager_tests.js @@ -10,7 +10,7 @@ QUnit.module('Mail DebugManager', {}, function () { QUnit.test("Manage Messages", async function (assert) { assert.expect(3); - var debugManager = createDebugManager({ + var debugManager = await createDebugManager({ intercepts: { do_action: function (event) { assert.deepEqual(event.data.action, { diff --git a/addons/note/static/tests/systray_activity_menu_tests.js b/addons/note/static/tests/systray_activity_menu_tests.js index 768412df2eb..85ca835e674 100644 --- a/addons/note/static/tests/systray_activity_menu_tests.js +++ b/addons/note/static/tests/systray_activity_menu_tests.js @@ -38,7 +38,7 @@ QUnit.test('note activity menu widget: create note from activity menu', async fu assert.expect(15); var self = this; var activityMenu = new ActivityMenu(); - testUtils.mock.addMockEnvironment(activityMenu, { + await testUtils.mock.addMockEnvironment(activityMenu, { services: this.services, mockRPC: function (route, args) { if (args.method === 'systray_get_activities') { 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 feba8f0f9e5..20a9d809d70 100644 --- a/addons/stock/static/tests/stock_traceability_report_backend_tests.js +++ b/addons/stock/static/tests/stock_traceability_report_backend_tests.js @@ -13,7 +13,7 @@ odoo.define('stock.stock_traceability_report_backend_tests', function (require) * @returns {Promise} */ async function createStockReportAction(params) { - const parent = testUtils.createParent(params); + const parent = await testUtils.createParent(params); const report = new StockReportGeneric(parent, params.action); const target = testUtils.prepareTarget(params.debug); diff --git a/addons/web/static/src/js/chrome/abstract_web_client.js b/addons/web/static/src/js/chrome/abstract_web_client.js index 0cabba1d23d..95d9868b89a 100644 --- a/addons/web/static/src/js/chrome/abstract_web_client.js +++ b/addons/web/static/src/js/chrome/abstract_web_client.js @@ -20,7 +20,6 @@ var data_manager = require('web.data_manager'); var dom = require('web.dom'); var KeyboardNavigationMixin = require('web.KeyboardNavigationMixin'); var Loading = require('web.Loading'); -var local_storage = require('web.local_storage'); var RainbowMan = require('web.RainbowMan'); var session = require('web.session'); var utils = require('web.utils'); @@ -103,7 +102,7 @@ var AbstractWebClient = Widget.extend(KeyboardNavigationMixin, { this.action_mutex = new concurrency.Mutex(); this.set('title_part', {"zopenerp": "Odoo"}); this.env = env; - core.bus.on('legacy_webclient_request', this, this._onLegacyWebclientRequest); + this.env.bus.on('set_title_part', this, this._onSetTitlePart); }, /** * @override @@ -206,6 +205,11 @@ var AbstractWebClient = Widget.extend(KeyboardNavigationMixin, { set_action_manager: function () { var self = this; this.action_manager = new ActionManager(this, session.user_context); + this.env.bus.on('do-action', this, payload => { + this.action_manager.doAction(payload.action, payload.options) + .then(payload.on_success || (() => {})) + .guardedCatch(payload.on_fail || (() => {})); + }); var fragment = document.createDocumentFragment(); return this.action_manager.appendTo(fragment).then(function () { dom.append(self.$el, fragment, { @@ -439,20 +443,6 @@ var AbstractWebClient = Widget.extend(KeyboardNavigationMixin, { _onGetScrollPosition: function (ev) { ev.data.callback(this.getScrollPosition()); }, - /** - * Services used to trigger_up some events (e.g. do_action, get_session, - * set_title_part) when the webclient was still instantiating them. Now that - * services have been moved to the env, the webclient is no longer their - * parent and they can thus no longer communicate with the webclient by - * trigger_up. Instead, they trigger those events on the bus, and we end up - * in this handler. Eventually, services should stop triggering events up. - * - * @private - * @param {CustomEvent} ev - */ - _onLegacyWebclientRequest: function (ev) { - this._trigger_up(ev); - }, /** * Loads an action from the database given its ID. * @@ -502,13 +492,13 @@ var AbstractWebClient = Widget.extend(KeyboardNavigationMixin, { }, /** * @private - * @param {OdooEvent} ev - * @param {string} ev.data.part - * @param {string} [ev.data.title] + * @param {Object} payload + * @param {string} payload.part + * @param {string} [payload.title] */ - _onSetTitlePart: function (ev) { - var part = ev.data.part; - var title = ev.data.title; + _onSetTitlePart: function (payload) { + var part = payload.part; + var title = payload.title; this.set_title_part(part, title); }, /** diff --git a/addons/web/static/src/js/common_env.js b/addons/web/static/src/js/common_env.js new file mode 100644 index 00000000000..0e31b8e3ceb --- /dev/null +++ b/addons/web/static/src/js/common_env.js @@ -0,0 +1,119 @@ +odoo.define("web.commonEnv", function (require) { + "use strict"; + + /** + * This file defines the common environment, which contains everything that + * is needed in the env for both the backend and the frontend (Odoo + * terminology). This module shouldn't be used as is. It should only be + * imported by the module defining the final env to use (in the frontend or + * in the backend). For instance, module 'web.env' imports it, adds stuff to + * it, and exports the final env that is used by the whole webclient + * application. + * + * There should be as much dependencies as possible in the env object. This + * will allow an easier testing of components. See [1] for more information + * on environments. + * + * [1] https://github.com/odoo/owl/blob/master/doc/reference/environment.md#content-of-an-environment + */ + + const { jsonRpc } = require("web.ajax"); + const { device, isDebug } = require("web.config"); + const { bus, serviceRegistry } = require("web.core"); + const rpc = require("web.rpc"); + const session = require("web.session"); + const { _t } = require("web.translation"); + const utils = require("web.utils"); + + + // Build the basic env + const env = { + _t, + bus, + device, + isDebug, + qweb: new owl.QWeb({ translateFn: _t }), + services: { + ajaxJsonRPC() { + return jsonRpc(...arguments); + }, + getCookie() { + return utils.get_cookie(...arguments); + }, + httpRequest(route, params = {}, readMethod = 'json') { + const info = { + method: params.method || 'POST', + }; + if (params.method !== 'GET') { + const formData = new FormData(); + for (const key in params) { + if (key === 'method') { + continue; + } + const value = params[key]; + if (Array.isArray(value) && value.length) { + for (const val of value) { + formData.append(key, val); + } + } else { + formData.append(key, value); + } + } + info.body = formData; + } + return fetch(route, info).then(response => response[readMethod]()); + }, + navigate(url, params) { + window.location = $.param.querystring(url, params); + }, + reloadPage() { + window.location.reload(); + }, + rpc(params, options) { + const query = rpc.buildQuery(params); + return session.rpc(query.route, query.params, options); + }, + setCookie() { + utils.set_cookie(...arguments); + }, + }, + session, + }; + + + // Deploy services in the env (specializations of AbstractService registered + // into the serviceRegistry) + const UndeployedServices = Object.assign({}, serviceRegistry.map); + function _deployServices() { + let done = false; + while (!done) { + // find a service with no missing dependency + const serviceName = Object.keys(UndeployedServices).find(serviceName => { + const Service = UndeployedServices[serviceName]; + return Service.prototype.dependencies.every(depName => { + return env.services[depName]; + }); + }); + if (serviceName) { + const Service = UndeployedServices[serviceName]; + const service = new Service(env); + env.services[serviceName] = service; + delete UndeployedServices[serviceName]; + service.start(); + } else { + done = true; + } + } + } + serviceRegistry.onAdd((serviceName, Service) => { + if (serviceName in env.services || serviceName in UndeployedServices) { + throw new Error(`Service ${serviceName} is already loaded.`); + } + UndeployedServices[serviceName] = Service; + _deployServices(); + }); + _deployServices(); + + + return env; +}); diff --git a/addons/web/static/src/js/core/abstract_service.js b/addons/web/static/src/js/core/abstract_service.js index c907b28f7c8..f9ad859fef2 100644 --- a/addons/web/static/src/js/core/abstract_service.js +++ b/addons/web/static/src/js/core/abstract_service.js @@ -7,14 +7,39 @@ var ServicesMixin = require('web.ServicesMixin'); var AbstractService = Class.extend(Mixins.EventDispatcherMixin, ServicesMixin, { dependencies: [], - init: function (parent) { + init: function (env) { Mixins.EventDispatcherMixin.init.call(this, arguments); - this.setParent(parent); + this.env = env; }, /** * @abstract */ start: function () {}, + /** + * Directly calls the requested service, instead of triggering a + * 'call_service' event up, which wouldn't work as services have no parent + * + * @param {OdooEvent} ev + */ + _trigger_up: function (ev) { + Mixins.EventDispatcherMixin._trigger_up.apply(this, arguments); + if (ev.is_stopped()) { + return; + } + const payload = ev.data; + if (ev.name === 'call_service') { + let args = payload.args || []; + if (payload.service === 'ajax' && payload.method === 'rpc') { + // ajax service uses an extra 'target' argument for rpc + args = args.concat(ev.target); + } + const service = this.env.services[payload.service]; + const result = service[payload.method].apply(service, args); + payload.callback(result); + } else if (ev.name === 'do_action') { + this.env.bus.trigger('do-action', payload); + } + }, }); return AbstractService; diff --git a/addons/web/static/src/js/core/service_mixins.js b/addons/web/static/src/js/core/service_mixins.js index f191dba5c08..198d00993ed 100644 --- a/addons/web/static/src/js/core/service_mixins.js +++ b/addons/web/static/src/js/core/service_mixins.js @@ -3,6 +3,8 @@ odoo.define('web.ServiceProviderMixin', function (require) { var core = require('web.core'); +// ServiceProviderMixin is deprecated. It is only used by the ProjectTimesheet +// app. As soon as it no longer uses it, we can remove it. var ServiceProviderMixin = { services: {}, // dict containing deployed service instances UndeployedServices: {}, // dict containing classes of undeployed services diff --git a/addons/web/static/src/js/env.js b/addons/web/static/src/js/env.js index c95cbd3045a..7fd7e1defab 100644 --- a/addons/web/static/src/js/env.js +++ b/addons/web/static/src/js/env.js @@ -1,157 +1,16 @@ odoo.define("web.env", function (require) { "use strict"; - const { jsonRpc } = require('web.ajax'); - const { device, isDebug } = require("web.config"); - const { bus, serviceRegistry } = require("web.core"); + /** + * This file defines the env to use in the webclient. + */ + + const commonEnv = require('web.commonEnv'); const dataManager = require('web.data_manager'); const { blockUI, unblockUI } = require("web.framework"); - const rpc = require("web.rpc"); - const session = require("web.session"); - const { _t } = require('web.translation'); - const utils = require("web.utils"); - const qweb = new owl.QWeb({ translateFn: _t }); + const env = Object.assign(commonEnv, { dataManager }); + env.services = Object.assign(env.services, { blockUI, unblockUI }); - function ajaxJsonRPC() { - return jsonRpc(...arguments); - } - - function getCookie() { - return utils.get_cookie(...arguments); - } - - function httpRequest(route, params = {}, readMethod = 'json') { - const info = { - method: params.method || 'POST', - }; - if (params.method !== 'GET') { - const formData = new FormData(); - for (const key in params) { - if (key === 'method') { - continue; - } - const value = params[key]; - if (Array.isArray(value) && value.length) { - for (const val of value) { - formData.append(key, val); - } - } else { - formData.append(key, value); - } - } - info.body = formData; - } - - return fetch(route, info).then(response => response[readMethod]()); - } - - function navigate(url, params) { - window.location = $.param.querystring(url, params); - } - - function performRPC(params, options) { - const query = rpc.buildQuery(params); - return session.rpc(query.route, query.params, options); - } - - function reloadPage() { - window.location.reload(); - } - - function setCookie() { - utils.set_cookie(...arguments); - } - - // ServiceProvider - const services = {}; // dict containing deployed service instances - const UndeployedServices = {}; // dict containing classes of undeployed services - function _deployServices() { - let done = false; - while (!done) { - const serviceName = _.findKey(UndeployedServices, Service => { - // no missing dependency - return !_.some(Service.prototype.dependencies, depName => { - return !services[depName]; - }); - }); - if (serviceName) { - const Service = UndeployedServices[serviceName]; - // we created a patched version of the Service in which the 'trigger_up' - // function directly calls the requested service, instead of triggering - // a 'call_service' event up, which wouldn't work as services have - // no parent - const PatchedService = Service.extend({ - _trigger_up: function (ev) { - this._super(...arguments); - if (!ev.is_stopped() && ev.name === 'call_service') { - const payload = ev.data; - let args = payload.args || []; - if (payload.service === 'ajax' && payload.method === 'rpc') { - // ajax service uses an extra 'target' argument for rpc - args = args.concat(ev.target); - } - const service = services[payload.service]; - const result = service[payload.method].apply(service, args); - payload.callback(result); - } else { - // historically, some services could reach the webclient - // by triggering events up, as the webclient was their - // parent. Since services have been moved to the env, - // this is no longer the case, so we re-trigger those - // events on the bus for now. Eventually, services - // should stop triggering events up (they can still - // communicate with other services through the env). - bus.trigger('legacy_webclient_request', ev); - } - }, - }); - const service = new PatchedService(); - services[serviceName] = service; - delete UndeployedServices[serviceName]; - service.start(); - } else { - done = true; - } - } - } - _.each(serviceRegistry.map, (Service, serviceName) => { - if (serviceName in UndeployedServices) { - throw new Error(`Service ${serviceName} is already loaded.`); - } - UndeployedServices[serviceName] = Service; - }); - serviceRegistry.onAdd((serviceName, Service) => { - if (serviceName in services || serviceName in UndeployedServices) { - throw new Error(`Service ${serviceName} is already loaded.`); - } - UndeployedServices[serviceName] = Service; - _deployServices(); - }); - _deployServices(); - - // There should be as much dependencies as possible in the env object. - // This will allow an easier testing of components. - // See https://github.com/odoo/owl/blob/master/doc/reference/environment.md#content-of-an-environment - // for more information on environments. - return { - _t, - bus, - dataManager, - device, - isDebug, - qweb, - services: Object.assign(services, { - ajaxJsonRPC, - blockUI, - getCookie, - httpRequest, - navigate, - reloadPage, - rpc: performRPC, - setCookie, - unblockUI, - }), - session, - }; + return env; }); diff --git a/addons/web/static/src/js/public/public_env.js b/addons/web/static/src/js/public/public_env.js index 373c7b512b8..581cccd430b 100644 --- a/addons/web/static/src/js/public/public_env.js +++ b/addons/web/static/src/js/public/public_env.js @@ -1,24 +1,11 @@ odoo.define("web.public_env", function (require) { "use strict"; - const { _t } = require("web.core"); - const rpc = require("web.rpc"); + /** + * This file defines the env to use in the public side. + */ - const qweb = new owl.QWeb({ translateFn: _t }); + const commonEnv = require("web.commonEnv"); - function performRPC(params, options) { - const query = rpc.buildQuery(params); - return session.rpc(query.route, query.params, options); - } - - // There should be as much dependencies as possible in the env object. - // This will allow an easier testing of components. - // See https://github.com/odoo/owl/blob/master/doc/reference/environment.md#content-of-an-environment - // for more information on environments. - return { - qweb, - services: { - rpc: performRPC, - }, - }; + return commonEnv; }); diff --git a/addons/web/static/src/js/public/public_root.js b/addons/web/static/src/js/public/public_root.js index 33d442e1d76..f684df2b629 100644 --- a/addons/web/static/src/js/public/public_root.js +++ b/addons/web/static/src/js/public/public_root.js @@ -3,7 +3,7 @@ odoo.define('web.public.root', function (require) { var ajax = require('web.ajax'); var dom = require('web.dom'); -var ServiceProviderMixin = require('web.ServiceProviderMixin'); +const env = require('web.public_env'); var session = require('web.session'); var utils = require('web.utils'); var publicWidget = require('web.public.widget'); @@ -25,16 +25,17 @@ var localeDef = ajax.loadJS('/web/webclient/locale/' + lang.replace('-', '_')); * this Class instance. Its main role will be to retrieve RPC demands from its * children and handle them. */ -var PublicRoot = publicWidget.RootWidget.extend(ServiceProviderMixin, { +var PublicRoot = publicWidget.RootWidget.extend({ events: _.extend({}, publicWidget.RootWidget.prototype.events || {}, { 'submit .js_website_submit_form': '_onWebsiteFormSubmit', 'click .js_disable_on_click': '_onDisableOnClick', }), custom_events: _.extend({}, publicWidget.RootWidget.prototype.custom_events || {}, { - 'context_get': '_onContextGet', - 'main_object_request': '_onMainObjectRequest', - 'widgets_start_request': '_onWidgetsStartRequest', - 'widgets_stop_request': '_onWidgetsStopRequest', + call_service: '_onCallService', + context_get: '_onContextGet', + main_object_request: '_onMainObjectRequest', + widgets_start_request: '_onWidgetsStartRequest', + widgets_stop_request: '_onWidgetsStopRequest', }), /** @@ -42,7 +43,7 @@ var PublicRoot = publicWidget.RootWidget.extend(ServiceProviderMixin, { */ init: function () { this._super.apply(this, arguments); - ServiceProviderMixin.init.call(this); + this.env = env; this.publicWidgets = []; }, /** @@ -90,37 +91,6 @@ var PublicRoot = publicWidget.RootWidget.extend(ServiceProviderMixin, { // Private //-------------------------------------------------------------------------- - /** - * Automatically adds the global context to RPCs. - * - * @override - */ - _call_service: function (ev) { - if (ev.data.service === 'ajax' && ev.data.method === 'rpc') { - var route = ev.data.args[0]; - if (_.str.startsWith(route, '/web/dataset/call_kw/')) { - var params = ev.data.args[1]; - var options = ev.data.args[2]; - var noContextKeys = undefined; - if (options) { - noContextKeys = options.noContextKeys; - ev.data.args[2] = _.omit(options, 'noContextKeys'); - } - params.kwargs.context = _computeContext.call(this, params.kwargs.context, noContextKeys); - } - } else if (ev.data.service === 'ajax' && ev.data.method === 'loadLibs') { - ev.data.args[1] = _computeContext.call(this, ev.data.args[1]); - } - return ServiceProviderMixin._call_service.apply(this, arguments); - - function _computeContext(context, noContextKeys) { - context = _.extend({}, this._getContext(), context); - if (noContextKeys) { - context = _.omit(context, noContextKeys); - } - return JSON.parse(JSON.stringify(context)); - } - }, /** * Retrieves the global context of the public environment. This is the * context which is automatically added to each RPC. @@ -234,6 +204,47 @@ var PublicRoot = publicWidget.RootWidget.extend(ServiceProviderMixin, { // Handlers //-------------------------------------------------------------------------- + /** + * Calls the requested service from the env. Automatically adds the global + * context to RPCs. + * + * @private + * @param {OdooEvent} event + */ + _onCallService: function (ev) { + function _computeContext(context, noContextKeys) { + context = _.extend({}, this._getContext(), context); + if (noContextKeys) { + context = _.omit(context, noContextKeys); + } + return JSON.parse(JSON.stringify(context)); + } + + const payload = ev.data; + let args = payload.args || []; + if (payload.service === 'ajax' && payload.method === 'rpc') { + // ajax service uses an extra 'target' argument for rpc + args = args.concat(ev.target); + + var route = args[0]; + if (_.str.startsWith(route, '/web/dataset/call_kw/')) { + var params = args[1]; + var options = args[2]; + var noContextKeys; + if (options) { + noContextKeys = options.noContextKeys; + args[2] = _.omit(options, 'noContextKeys'); + } + params.kwargs.context = _computeContext.call(this, params.kwargs.context, noContextKeys); + } + } else if (payload.service === 'ajax' && payload.method === 'loadLibs') { + args[1] = _computeContext.call(this, args[1]); + } + + const service = this.env.services[payload.service]; + const result = service[payload.method].apply(service, args); + payload.callback(result); + }, /** * Called when someone asked for the global public context. * diff --git a/addons/web/static/src/js/public/public_root_instance.js b/addons/web/static/src/js/public/public_root_instance.js index 988d939bbe1..e368d421fbe 100644 --- a/addons/web/static/src/js/public/public_root_instance.js +++ b/addons/web/static/src/js/public/public_root_instance.js @@ -1,9 +1,16 @@ odoo.define('root.widget', function (require) { 'use strict'; +const env = require('web.public_env'); var lazyloader = require('web.public.lazyloader'); var rootData = require('web.public.root'); +/** + * Configure Owl with the public env + */ +owl.config.mode = env.isDebug() ? "dev" : "prod"; +owl.Component.env = env; + /** * This widget is important, because the tour manager needs a root widget in * order to work. The root widget must be a service provider with the ajax diff --git a/addons/web/static/tests/chrome/keyboard_navigation_mixin_tests.js b/addons/web/static/tests/chrome/keyboard_navigation_mixin_tests.js index dcf1c23ce39..b0c638dd802 100644 --- a/addons/web/static/tests/chrome/keyboard_navigation_mixin_tests.js +++ b/addons/web/static/tests/chrome/keyboard_navigation_mixin_tests.js @@ -26,7 +26,7 @@ QUnit.module('KeyboardNavigationMixin', function () { return this._super(...arguments); }, }); - var parent = testUtils.createParent({}); + var parent = await testUtils.createParent({}); var w = new KeyboardWidget(parent); await w.appendTo($target); @@ -67,7 +67,7 @@ QUnit.module('KeyboardNavigationMixin', function () { return this._super(...arguments); }, }); - var parent = testUtils.createParent({}); + var parent = await testUtils.createParent({}); var w = new KeyboardWidget(parent); await w.appendTo($target); diff --git a/addons/web/static/tests/chrome/menu_tests.js b/addons/web/static/tests/chrome/menu_tests.js index c7ce40b16c2..a50e2189906 100644 --- a/addons/web/static/tests/chrome/menu_tests.js +++ b/addons/web/static/tests/chrome/menu_tests.js @@ -13,7 +13,7 @@ odoo.define('web.menu_tests', function (require) { QUnit.test('Systray on_attach_callback is called', async function (assert) { assert.expect(4); - const parent = testUtils.createParent({}); + const parent = await testUtils.createParent({}); // Add some widgets to the systray const Widget1 = Widget.extend({ @@ -22,7 +22,7 @@ odoo.define('web.menu_tests', function (require) { const Widget2 = Widget.extend({ on_attach_callback: () => assert.step('on_attach_callback widget2') }); - SystrayMenu.Items = [Widget1, Widget2] + SystrayMenu.Items = [Widget1, Widget2]; testUtils.mock.patch(SystrayMenu, { on_attach_callback: function () { diff --git a/addons/web/static/tests/chrome/systray_tests.js b/addons/web/static/tests/chrome/systray_tests.js index c5238ceff04..941e275f19b 100644 --- a/addons/web/static/tests/chrome/systray_tests.js +++ b/addons/web/static/tests/chrome/systray_tests.js @@ -7,8 +7,7 @@ odoo.define('web.systray_tests', function (require) { QUnit.test('Adding async components to the registry respects the sequence', async function (assert) { assert.expect(2); - var parent = testUtils.createParent({ - }); + var parent = await testUtils.createParent({}); var prom = testUtils.makeTestPromise(); var synchronousFirstWidget = Widget.extend({ diff --git a/addons/web/static/tests/chrome/user_menu_tests.js b/addons/web/static/tests/chrome/user_menu_tests.js index 73c74b0753c..752697a5228 100644 --- a/addons/web/static/tests/chrome/user_menu_tests.js +++ b/addons/web/static/tests/chrome/user_menu_tests.js @@ -13,7 +13,7 @@ QUnit.module('chrome', {}, function () { var parent = new Widget(); - testUtils.mock.addMockEnvironment(parent, {}); + await testUtils.mock.addMockEnvironment(parent, {}); var userMenu = new UserMenu(parent); await userMenu.appendTo($('body')); diff --git a/addons/web/static/tests/control_panel/custom_filter_item_tests.js b/addons/web/static/tests/control_panel/custom_filter_item_tests.js index 52a5aa738a6..7602f56aae0 100644 --- a/addons/web/static/tests/control_panel/custom_filter_item_tests.js +++ b/addons/web/static/tests/control_panel/custom_filter_item_tests.js @@ -6,21 +6,10 @@ odoo.define('web.filter_menu_generator_tests', function (require) { const { Model } = require('web.model'); const pyUtils = require('web.py_utils'); const testUtils = require('web.test_utils'); - const session = require('web.session'); const cpHelpers = testUtils.controlPanel; const { createComponent } = testUtils; - function patchSession(newSession) { - // We have to patch the "legacy" session because field_utils is using it. - // TODO: remove it when the field_utils are re-written. - const initialSession = session; - Object.assign(session, newSession); - return function () { - Object.assign(session, initialSession); - }; - } - QUnit.module('Components', { beforeEach: function () { this.fields = { @@ -203,11 +192,6 @@ odoo.define('web.filter_menu_generator_tests', function (require) { QUnit.test('custom filter datetime with equal operator', async function (assert) { assert.expect(4); - patchSession({ - getTZOffset: function () { - return -240; - }, - }); class MockedControlPanelModel extends Model { createNewFilters(preFilters) { const preFilter = preFilters[0]; @@ -224,6 +208,11 @@ odoo.define('web.filter_menu_generator_tests', function (require) { props: { fields: this.fields, }, + session: { + getTZOffset() { + return -240; + }, + }, env: { controlPanelModel }, }); @@ -243,11 +232,6 @@ odoo.define('web.filter_menu_generator_tests', function (require) { QUnit.test('custom filter datetime between operator', async function (assert) { assert.expect(4); - patchSession({ - getTZOffset: function () { - return -240; - }, - }); class MockedControlPanelModel extends Model { createNewFilters(preFilters) { const preFilter = preFilters[0]; @@ -265,6 +249,11 @@ odoo.define('web.filter_menu_generator_tests', function (require) { props: { fields: this.fields, }, + session: { + getTZOffset() { + return -240; + }, + }, env: { controlPanelModel }, }); diff --git a/addons/web/static/tests/core/dialog_tests.js b/addons/web/static/tests/core/dialog_tests.js index ea3a672322f..c51239a01bb 100644 --- a/addons/web/static/tests/core/dialog_tests.js +++ b/addons/web/static/tests/core/dialog_tests.js @@ -7,10 +7,10 @@ var Widget = require('web.Widget'); var ESCAPE_KEY = $.Event("keyup", { which: 27 }); -function createEmptyParent(debug) { +async function createEmptyParent(debug) { var widget = new Widget(); - testUtils.mock.addMockEnvironment(widget, { + await testUtils.mock.addMockEnvironment(widget, { debug: debug || false, }); return widget; @@ -24,7 +24,7 @@ QUnit.module('core', {}, function () { assert.expect(3); var testPromise = testUtils.makeTestPromiseWithAssert(assert, 'custom callback'); - var parent = createEmptyParent(); + var parent = await createEmptyParent(); new Dialog(parent, { buttons: [ { @@ -54,7 +54,7 @@ QUnit.module('core', {}, function () { assert.expect(3); var testPromise = testUtils.makeTestPromiseWithAssert(assert, 'custom callback'); - var parent = createEmptyParent(); + var parent = await createEmptyParent(); new Dialog(parent, { buttons: [ { @@ -84,7 +84,7 @@ QUnit.module('core', {}, function () { assert.expect(3); var testPromise = testUtils.makeTestPromiseWithAssert(assert, 'confirm callback'); - var parent = createEmptyParent(); + var parent = await createEmptyParent(); var options = { confirm_callback: testPromise.reject, cancel_callback: testPromise.resolve, @@ -107,7 +107,7 @@ QUnit.module('core', {}, function () { assert.expect(3); var testPromise = testUtils.makeTestPromiseWithAssert(assert, 'alert callback'); - var parent = createEmptyParent(); + var parent = await createEmptyParent(); var options = { confirm_callback: testPromise.resolve, }; diff --git a/addons/web/static/tests/core/widget_tests.js b/addons/web/static/tests/core/widget_tests.js index f8283f91c35..1c1c59f1d52 100644 --- a/addons/web/static/tests/core/widget_tests.js +++ b/addons/web/static/tests/core/widget_tests.js @@ -402,7 +402,7 @@ QUnit.module('core', {}, function () { var def; var parent = new Widget(); - testUtils.mock.addMockEnvironment(parent, { + await testUtils.mock.addMockEnvironment(parent, { session: { rpc: function () { def = testUtils.makeTestPromise(); diff --git a/addons/web/static/tests/fields/relational_fields/field_many2one_tests.js b/addons/web/static/tests/fields/relational_fields/field_many2one_tests.js index 856019092fe..98ff6a47925 100644 --- a/addons/web/static/tests/fields/relational_fields/field_many2one_tests.js +++ b/addons/web/static/tests/fields/relational_fields/field_many2one_tests.js @@ -1060,7 +1060,7 @@ QUnit.module('fields', {}, function () { var fixture = $('#qunit-fixture'); var self = this; - var model = testUtils.createModel({ + var model = await testUtils.createModel({ Model: BasicModel, data: this.data, }); @@ -1083,7 +1083,7 @@ QUnit.module('fields', {}, function () { }); var parent = new StandaloneWidget(model); model.setParent(parent); - testUtils.mock.addMockEnvironment(parent, { + await testUtils.mock.addMockEnvironment(parent, { data: self.data, mockRPC: function (route, args) { assert.step(args.method); diff --git a/addons/web/static/tests/helpers/test_env.js b/addons/web/static/tests/helpers/test_env.js index 5b197451517..5a3256e98fd 100644 --- a/addons/web/static/tests/helpers/test_env.js +++ b/addons/web/static/tests/helpers/test_env.js @@ -1,10 +1,8 @@ odoo.define('web.test_env', async function (require) { "use strict"; - const AbstractStorageService = require('web.AbstractStorageService'); - const Bus = require("web.Bus"); - const RamStorage = require('web.RamStorage'); - const { buildQuery } = require("web.rpc"); + const Bus = require('web.Bus'); + const { buildQuery } = require('web.rpc'); const session = require('web.session'); /** @@ -17,9 +15,6 @@ odoo.define('web.test_env', async function (require) { * @returns {Proxy} */ function makeTestEnvironment(env = {}, providedRPC = null) { - const RamStorageService = AbstractStorageService.extend({ - storage: new RamStorage(), - }); const database = { parameters: { code: "en_US", @@ -31,33 +26,26 @@ odoo.define('web.test_env', async function (require) { time_format: '%H:%M:%S', }, }; - let testEnv = {}; const defaultEnv = { _t: env._t || Object.assign((s => s), { database }), - bus: new Bus(), + bus: env.bus || new Bus(), device: Object.assign({ isMobile: false }, env.device), isDebug: env.isDebug || (() => false), qweb: new owl.QWeb({ templates: session.owlTemplates }), services: Object.assign({ - ajax: { // for legacy subwidgets + ajax: { rpc() { - const prom = testEnv.session.rpc(...arguments); - prom.abort = function () { - throw new Error("Can't abort this request"); - }; - return prom; - }, + return env.session.rpc(...arguments); // Compatibility Legacy Widgets + } }, - getCookie() { }, - httpRequest() { + getCookie() {}, + httpRequest(/* route, params = {}, readMethod = 'json' */) { return Promise.resolve(''); }, rpc(params, options) { const query = buildQuery(params); - return testEnv.session.rpc(query.route, query.params, options); + return env.session.rpc(query.route, query.params, options); }, - local_storage: new RamStorageService(), - session_storage: new RamStorageService(), notification: { notify() { } }, }, env.services), session: Object.assign({ @@ -67,10 +55,10 @@ odoo.define('web.test_env', async function (require) { } throw new Error(`No method to perform RPC`); }, + url: session.url, }, env.session), }; - testEnv = Object.assign(env, defaultEnv); - return testEnv; + return Object.assign(env, defaultEnv); } /** diff --git a/addons/web/static/tests/helpers/test_utils.js b/addons/web/static/tests/helpers/test_utils.js index f6114b84fcd..1a1f38f7f09 100644 --- a/addons/web/static/tests/helpers/test_utils.js +++ b/addons/web/static/tests/helpers/test_utils.js @@ -121,7 +121,7 @@ odoo.define('web.test_utils', async function (require) { return { mock: { addMockEnvironment: testUtilsMock.addMockEnvironment, - getMockedOwlEnv: testUtilsMock.getMockedOwlEnv, + addMockEnvironmentOwl: testUtilsMock.addMockEnvironmentOwl, intercept: testUtilsMock.intercept, patch: testUtilsMock.patch, patchDate: testUtilsMock.patchDate, diff --git a/addons/web/static/tests/helpers/test_utils_create.js b/addons/web/static/tests/helpers/test_utils_create.js index 494a0aa2436..801120bf64b 100644 --- a/addons/web/static/tests/helpers/test_utils_create.js +++ b/addons/web/static/tests/helpers/test_utils_create.js @@ -67,7 +67,6 @@ odoo.define('web.test_utils_create', function (require) { } params.server = mockServer; - Component.env = testUtilsMock.getMockedOwlEnv(params); const userContext = params.context && params.context.user_context || {}; const actionManager = new ActionManager(widget, userContext); @@ -142,7 +141,7 @@ odoo.define('web.test_utils_create', function (require) { if (!(constructor.prototype instanceof Component)) { throw new Error(`Argument "constructor" must be an Owl Component.`); } - const env = Object.assign(testUtilsMock.getMockedOwlEnv(params), params.env); + const cleanUp = await testUtilsMock.addMockEnvironmentOwl(Component, params); class Parent extends Component { constructor() { super(...arguments); @@ -154,7 +153,6 @@ odoo.define('web.test_utils_create', function (require) { } } } - Parent.env = env; Parent.template = xml``; const parent = new Parent(); await parent.mount(prepareTarget(params.debug), { position: 'first-child' }); @@ -162,6 +160,7 @@ odoo.define('web.test_utils_create', function (require) { const originalDestroy = child.destroy; child.destroy = function () { child.destroy = originalDestroy; + cleanUp(); parent.destroy(); }; return child; @@ -241,9 +240,9 @@ odoo.define('web.test_utils_create', function (require) { * mock method, assuming that the user has access rights, and is an admin. * * @param {Object} [params={}] - * @returns {DebugManager} + * @returns {Promise} */ - function createDebugManager(params = {}) { + async function createDebugManager(params = {}) { const mockRPC = params.mockRPC; Object.assign(params, { async mockRPC(route, args) { @@ -268,7 +267,7 @@ odoo.define('web.test_utils_create', function (require) { }, }); const debugManager = new DebugManager(); - testUtilsMock.addMockEnvironment(debugManager, params); + await testUtilsMock.addMockEnvironment(debugManager, params); return debugManager; } @@ -280,12 +279,12 @@ odoo.define('web.test_utils_create', function (require) { * @param {Class} params.Model the model class to use * @returns {Model} */ - function createModel(params) { + async function createModel(params) { const widget = new Widget(); const model = new params.Model(widget); - testUtilsMock.addMockEnvironment(widget, params); + await testUtilsMock.addMockEnvironment(widget, params); // override the model's 'destroy' so that it calls 'destroy' on the widget // instead, as the widget is the parent of the model and the mockServer. @@ -304,11 +303,11 @@ odoo.define('web.test_utils_create', function (require) { * * @param {Object} params This object will be given to addMockEnvironment, so * any parameters from that method applies - * @returns {Widget} + * @returns {Promise} */ - function createParent(params) { + async function createParent(params) { const widget = new Widget(); - testUtilsMock.addMockEnvironment(widget, params); + await testUtilsMock.addMockEnvironment(widget, params); return widget; } @@ -358,8 +357,6 @@ odoo.define('web.test_utils_create', function (require) { const viewInfo = testUtilsMock.fieldsViewGet(mockServer, params); params.server = mockServer; - const env = Object.assign(testUtilsMock.getMockedOwlEnv(params)); - Component.env = env; // create the view const View = params.View; diff --git a/addons/web/static/tests/helpers/test_utils_mock.js b/addons/web/static/tests/helpers/test_utils_mock.js index 414a9c0df64..6df49cf59f3 100644 --- a/addons/web/static/tests/helpers/test_utils_mock.js +++ b/addons/web/static/tests/helpers/test_utils_mock.js @@ -10,12 +10,16 @@ odoo.define('web.test_utils_mock', function (require) { * testUtils file. */ +const AbstractStorageService = require('web.AbstractStorageService'); +const AjaxService = require('web.AjaxService'); const basic_fields = require('web.basic_fields'); +const Bus = require('web.Bus'); const config = require('web.config'); const core = require('web.core'); const dom = require('web.dom'); const makeTestEnvironment = require('web.test_env'); const MockServer = require('web.MockServer'); +const RamStorage = require('web.RamStorage'); const session = require('web.session'); const DebouncedField = basic_fields.DebouncedField; @@ -25,6 +29,199 @@ const DebouncedField = basic_fields.DebouncedField; // Private functions //------------------------------------------------------------------------------ +/** + * Returns a mocked environment to be used by OWL components in tests, with + * requested services (+ ajax, local_storage and session_storage) deployed. + * + * @private + * @param {Object} params + * @param {Bus} [params.bus] + * @param {boolean} [params.debug] + * @param {Object} [params.env] + * @param {Bus} [params.env.bus] + * @param {Object} [params.env.dataManager] + * @param {Object} [params.env.services] + * @param {Object[]} [params.favoriteFilters] + * @param {Object} [params.services] + * @param {Object} [params.session] + * @param {MockServer} [mockServer] + * @returns {Promise} env + */ +async function _getMockedOwlEnv(params, mockServer) { + params.env = params.env || {}; + + // build the env + const favoriteFilters = params.favoriteFilters; + const debug = params.debug; + const services = {}; + const env = Object.assign({}, params.env, { + bus: params.bus || params.env.bus || new Bus(), + dataManager: Object.assign({ + load_action: (actionID, context) => { + return mockServer.performRpc('/web/action/load', { + action_id: actionID, + additional_context: context, + }); + }, + load_views: (params, options) => { + return mockServer.performRpc('/web/dataset/call_kw/' + params.model, { + args: [], + kwargs: { + context: params.context, + options: options, + views: params.views_descr, + }, + method: 'load_views', + model: params.model, + }).then(function (views) { + views = _.mapObject(views, viewParams => { + return fieldsViewGet(mockServer, viewParams); + }); + if (favoriteFilters && 'search' in views) { + views.search.favoriteFilters = favoriteFilters; + } + return views; + }); + }, + load_filters: params => { + if (debug) { + console.log('[mock] load_filters', params); + } + return Promise.resolve([]); + }, + }, params.env.dataManager), + services: Object.assign(services, params.env.services), + session: params.session || {}, + }); + + // deploy services into the env + // determine services to instantiate (classes), and already register function services + const servicesToDeploy = {}; + for (const name in params.services || {}) { + const Service = params.services[name]; + if (Service.constructor.name === 'Class') { + servicesToDeploy[name] = Service; + } else { + services[name] = Service; + } + } + // always deploy ajax, local storage and session storage + if (!servicesToDeploy.ajax) { + const MockedAjaxService = AjaxService.extend({ + rpc: mockServer.performRpc.bind(mockServer), + }); + services.ajax = new MockedAjaxService(env); + } + const RamStorageService = AbstractStorageService.extend({ + storage: new RamStorage(), + }); + if (!servicesToDeploy.local_storage) { + services.local_storage = new RamStorageService(env); + } + if (!servicesToDeploy.session_storage) { + services.session_storage = new RamStorageService(env); + } + // deploy other requested services + let done = false; + while (!done) { + const serviceName = Object.keys(servicesToDeploy).find(serviceName => { + const Service = servicesToDeploy[serviceName]; + return Service.prototype.dependencies.every(depName => { + return env.services[depName]; + }); + }); + if (serviceName) { + const Service = servicesToDeploy[serviceName]; + services[serviceName] = new Service(env); + delete servicesToDeploy[serviceName]; + services[serviceName].start(); + } else { + const serviceNames = _.keys(servicesToDeploy); + if (serviceNames.length) { + console.warn("Non loaded services:", serviceNames); + } + done = true; + } + } + // wait for asynchronous services to properly start + await new Promise(setTimeout); + + return env; +} +/** + * This function is used to mock global objects (session, config...) in tests. + * It is necessary for legacy widgets. It returns a cleanUp function to call at + * the end of the test. + * + * The function could be removed as soon as we do not support legacy widgets + * anymore. + * + * @private + * @param {Object} params + * @param {Object} [params.config] if given, it is used to extend the global + * config, + * @param {Object} [params.session] if given, it is used to extend the current, + * real session. + * @param {Object} [params.translateParameters] if given, it will be used to + * extend the core._t.database.parameters object. + * @returns {function} a cleanUp function to restore everything, to call at the + * end of the test + */ +function _mockGlobalObjects(params) { + // patch session + let initialSession = Object.assign({}, session); + session.getTZOffset = function () { + return 0; // by default, but may be overridden in specific tests + }; + if ('session' in params) { + Object.assign(session, params.session); + } + + // patch config + let initialConfig; + if ('config' in params) { + initialConfig = Object.assign({}, config); + initialConfig.device = Object.assign({}, config.device); + if ('device' in params.config) { + Object.assign(config.device, params.config.device); + } + if ('debug' in params.config) { + odoo.debug = params.config.debug; + } + } + + // patch translate params + let initialParameters; + if ('translateParameters' in params) { + initialParameters = Object.assign({}, core._t.database.parameters); + Object.assign(core._t.database.parameters, params.translateParameters); + } + + // build the cleanUp function to restore everything at the end of the test + function cleanUp() { + let key; + if ('session' in params) { + for (key in session) { + delete session[key]; + } + } + Object.assign(session, initialSession); + if ('config' in params) { + for (key in config) { + delete config[key]; + } + _.extend(config, initialConfig); + } + if ('translateParameters' in params) { + for (key in core._t.database.parameters) { + delete core._t.database.parameters[key]; + } + _.extend(core._t.database.parameters, initialParameters); + } + } + + return cleanUp; +} /** * logs all event going through the target widget. * @@ -42,16 +239,58 @@ function _observe(widget) { // Public functions //------------------------------------------------------------------------------ +/** + * performs a fields_view_get, and mocks the postprocessing done by the + * data_manager to return an equivalent structure. + * + * @param {MockServer} server + * @param {Object} params + * @param {string} params.model + * @returns {Object} an object with 3 keys: arch, fields and viewFields + */ +function fieldsViewGet(server, params) { + var fieldsView = server.fieldsViewGet(params); + // mock the structure produced by the DataManager + fieldsView.viewFields = fieldsView.fields; + fieldsView.fields = server.fieldsGet(params.model); + return fieldsView; +} + +/** + * intercepts an event bubbling up the widget hierarchy. The event intercepted + * must be a "custom event", i.e. an event generated by the method 'trigger_up'. + * + * Note that this method really intercepts the event if @propagate is not set. + * It will not be propagated further, and even the handlers on the target will + * not fire. + * + * @param {Widget} widget the target widget (any Odoo widget) + * @param {string} eventName description of the event + * @param {function} fn callback executed when the even is intercepted + * @param {boolean} [propagate=false] + */ +function intercept(widget, eventName, fn, propagate) { + var _trigger_up = widget._trigger_up.bind(widget); + widget._trigger_up = function (event) { + if (event.name === eventName) { + fn(event); + if (!propagate) { return; } + } + _trigger_up(event); + }; +} + /** * Removes the src attribute on images and iframes to prevent not found errors, * and optionally triggers an rpc with the src url as route on a widget. * This method is critical and must be fastest (=> no jQuery, no underscore) * * @param {HTMLElement} el - * @param {[Widget]} widget the widget on which the rpc should be performed + * @param {[function]} rpc */ -function removeSrcAttribute(el, widget) { +function removeSrcAttribute(el, rpc) { var nodes; + el = el.nodeType === 8 ? el.nextSibling : el; if (el.nodeName === 'IMG' || el.nodeName === 'IFRAME') { nodes = [el]; } else { @@ -68,14 +307,135 @@ function removeSrcAttribute(el, widget) { } else { node.setAttribute('src', 'about:blank'); } - if (widget) { - widget._rpc({ route: src }); + if (rpc) { + rpc(src, []); } $(node).trigger('load'); } } } +/** + * Add a mock environment to test Owl Components. This function generates a test + * env and sets it on the given Component. It also has several side effects, + * like patching the global session or config objects. It returns a cleanup + * function to call at the end of the test. + * + * @param {Component} Component + * @param {Object} [params] + * @param {Object} [params.actions] + * @param {Object} [params.archs] + * @param {string} [params.currentDate] + * @param {Object} [params.data] + * @param {boolean} [params.debug] + * @param {function} [params.mockRPC] + * @param {number} [params.fieldDebounce=0] the value of the DEBOUNCE attribute + * of fields + * @param {boolean} [params.debounce=true] if false, patch _.debounce to remove + * its behavior + * @param {boolean} [params.throttle=false] by default, _.throttle is patched to + * remove its behavior, except if this params is set to true + * @param {boolean} [params.mockSRC=false] if true, redirect src GET requests to + * the mockServer + * @param {MockServer} [mockServer] + * @returns {Promise} the cleanup function + */ +async function addMockEnvironmentOwl(Component, params, mockServer) { + params = params || {}; + + // instantiate a mockServer if not provided + if (!mockServer) { + let Server = MockServer; + if (params.mockRPC) { + Server = MockServer.extend({ _performRpc: params.mockRPC }); + } + mockServer = new Server(params.data, { + actions: params.actions, + archs: params.archs, + currentDate: params.currentDate, + debug: params.debug, + }); + } + + // make sure the debounce value for input fields is set to 0 + const initialDebounceValue = DebouncedField.prototype.DEBOUNCE; + DebouncedField.prototype.DEBOUNCE = params.fieldDebounce || 0; + const initialDOMDebounceValue = dom.DEBOUNCE; + dom.DEBOUNCE = 0; + + // patch underscore debounce/throttle functions + const initialDebounce = _.debounce; + if (params.debounce === false) { + _.debounce = function (func) { + return func; + }; + } + // fixme: throttle is inactive by default, should we make it explicit ? + const initialThrottle = _.throttle; + if (!('throttle' in params) || !params.throttle) { + _.throttle = function (func) { + return func; + }; + } + + // make sure images do not trigger a GET on the server + $('body').on('DOMNodeInserted.removeSRC', function (ev) { + let rpc; + if (params.mockSRC) { + rpc = mockServer.performRpc.bind(mockServer); + } + removeSrcAttribute(ev.target, rpc); + }); + + // mock global objects for legacy widgets (session, config...) + const restoreMockedGlobalObjects = _mockGlobalObjects(params); + + // set the test env on owl Component + const env = await _getMockedOwlEnv(params, mockServer); + const originalEnv = Component.env; + Component.env = makeTestEnvironment(env, mockServer.performRpc.bind(mockServer)); + + // while we have a mix between Owl and legacy stuff, some of them triggering + // events on the env.bus (a new Bus instance especially created for the current + // test), the others using core.bus, we have to ensure that events triggered + // on env.bus are also triggered on core.bus (note that outside the testing + // environment, both are the exact same instance of Bus) + const envBusTrigger = env.bus.trigger; + env.bus.trigger = function () { + core.bus.trigger(...arguments); + envBusTrigger.call(env.bus, ...arguments); + }; + + // build the clean up function to call at the end of the test + function cleanUp() { + env.bus.destroy(); + Object.keys(env.services).forEach(function (s) { + var service = env.services[s]; + if (service.destroy && !service.isDestroyed()) { + service.destroy(); + } + }); + + DebouncedField.prototype.DEBOUNCE = initialDebounceValue; + dom.DEBOUNCE = initialDOMDebounceValue; + _.debounce = initialDebounce; + _.throttle = initialThrottle; + + // clear the caches (e.g. data_manager, ModelFieldSelector) at the end + // of each test to avoid collisions + core.bus.trigger('clear_cache'); + + $('body').off('DOMNodeInserted.removeSRC'); + $('.blockUI').remove(); // fixme: move to qunit_config in OdooAfterTestHook? + + restoreMockedGlobalObjects(); + + Component.env = originalEnv; + } + + return cleanUp; +} + /** * Add a mock environment to a widget. This helper function can simulate * various kind of side effects, such as mocking RPCs, changing the session, @@ -100,6 +460,7 @@ function removeSrcAttribute(el, widget) { * used to generate mock answers for every kind of routes supported by odoo * @param {number} [params.debug] if set to true, logs RPCs and uncaught Odoo * events. + * @param {Object} [params.bus] the instance of Bus that will be used (in the env) * @param {function} [params.mockRPC] a function that will be used to override * the _performRpc method from the mock server. It is really useful to add * some custom rpc mocks, or to check some assertions. @@ -130,11 +491,7 @@ function removeSrcAttribute(el, widget) { * other methods on it. */ async function addMockEnvironment(widget, params) { - var Server = MockServer; - params.services = params.services || {}; - if (params.mockRPC) { - Server = MockServer.extend({ _performRpc: params.mockRPC }); - } + // log events triggered up if debug flag is true if (params.debug) { _observe(widget); var separator = window.location.href.indexOf('?') !== -1 ? "&" : "?"; @@ -142,6 +499,11 @@ async function addMockEnvironment(widget, params) { console.log('%c[debug] debug mode activated', 'color: blue; font-weight: bold;', url); } + // instantiate mock server + var Server = MockServer; + if (params.mockRPC) { + Server = MockServer.extend({ _performRpc: params.mockRPC }); + } var mockServer = new Server(params.data, { actions: params.actions, archs: params.archs, @@ -150,310 +512,60 @@ async function addMockEnvironment(widget, params) { widget: widget, }); - // make sure images do not trigger a GET on the server - $('body').on('DOMNodeInserted.removeSRC', function (event) { - removeSrcAttribute(event.target, widget); - }); + // build and set the Owl env on Component + if (!('mockSRC' in params)) { // redirect src rpcs to the mock server + params.mockSRC = true; + } + const cleanUp = await addMockEnvironmentOwl(owl.Component, params, mockServer); + const env = owl.Component.env; - // make sure the debounce value for input fields is set to 0 - var initialDebounceValue = DebouncedField.prototype.DEBOUNCE; - DebouncedField.prototype.DEBOUNCE = params.fieldDebounce || 0; - var initialDOMDebounceValue = dom.DEBOUNCE; - dom.DEBOUNCE = 0; - var initialSession, initialConfig, initialParameters, initialDebounce, initialThrottle; - initialSession = _.extend({}, session); - session.getTZOffset = function () { - return 0; // by default, but may be overridden in specific tests - }; - if ('session' in params) { - _.extend(session, params.session); - } - if ('config' in params) { - initialConfig = _.clone(config); - initialConfig.device = _.clone(config.device); - if ('device' in params.config) { - _.extend(config.device, params.config.device); - } - if ('debug' in params.config) { - odoo.debug = params.config.debug; - } - } - if ('translateParameters' in params) { - initialParameters = _.extend({}, core._t.database.parameters); - _.extend(core._t.database.parameters, params.translateParameters); - } - if (params.debounce === false) { - initialDebounce = _.debounce; - _.debounce = function (func) { - return func; - }; - } - if (!('throttle' in params) || !params.throttle) { - initialThrottle = _.throttle; - _.throttle = function (func) { - return func; - }; - } - - var widgetDestroy = widget.destroy; + // ensure to clean up everything when the widget will be destroyed + const destroy = widget.destroy; widget.destroy = function () { - // clear the caches (e.g. data_manager, ModelFieldSelector) when the - // widget is destroyed, at the end of each test to avoid collisions - core.bus.trigger('clear_cache'); - - Object.keys(services).forEach(function(s) { - var service = services[s]; - if (service && !service.isDestroyed()) - service.destroy(); - }); - - DebouncedField.prototype.DEBOUNCE = initialDebounceValue; - dom.DEBOUNCE = initialDOMDebounceValue; - if (params.debounce === false) { - _.debounce = initialDebounce; - } - if (!('throttle' in params) || !params.throttle) { - _.throttle = initialThrottle; - } - - var key; - if ('session' in params) { - for (key in session) { - delete session[key]; - } - } - _.extend(session, initialSession); - if ('config' in params) { - for (key in config) { - delete config[key]; - } - _.extend(config, initialConfig); - } - if ('translateParameters' in params) { - for (key in core._t.database.parameters) { - delete core._t.database.parameters[key]; - } - _.extend(core._t.database.parameters, initialParameters); - } - - $('body').off('DOMNodeInserted.removeSRC'); - $('body').removeClass('debug'); - $('.blockUI').remove(); - - widgetDestroy.call(this); + cleanUp(); + destroy.call(this, ...arguments); }; - // Dispatch service calls - // Note: some services could call other services at init, - // Which is why we have to init services after that - var services = {}; + // intercept service/data manager calls and redirect them to the env intercept(widget, 'call_service', function (ev) { - var args, result; - if (services[ev.data.service]) { - var service = services[ev.data.service]; - args = (ev.data.args || []); - result = service[ev.data.method].apply(service, args); - } else if (ev.data.service === 'ajax' && ev.data.method === 'rpc') { - // use ajax service that is mocked by the server - var route = ev.data.args[0]; - args = ev.data.args[1]; - result = mockServer.performRpc(route, args); + if (env.services[ev.data.service]) { + var service = env.services[ev.data.service]; + const result = service[ev.data.method].apply(service, ev.data.args || []); + ev.data.callback(result); } - ev.data.callback(result); }); - - intercept(widget, 'load_action', function (event) { - mockServer.performRpc('/web/action/load', { - action_id: event.data.actionID, - additional_context: event.data.context, - }).then(function (action) { - event.data.on_success(action); - }); + intercept(widget, 'load_action', async ev => { + const action = await env.dataManager.load_action(ev.data.actionID, ev.data.context); + ev.data.on_success(action); }); - - intercept(widget, "load_views", function (event) { - mockServer.performRpc('/web/dataset/call_kw/' + event.data.modelName, { - args: [], - kwargs: { - context: event.data.context, - options: event.data.options, - views: event.data.views, - }, - method: 'load_views', - model: event.data.modelName, - }).then(function (views) { - views = _.mapObject(views, function (viewParams) { - return fieldsViewGet(mockServer, viewParams); - }); - if ('search' in views && params.favoriteFilters) { - views.search.favoriteFilters = params.favoriteFilters; - } - event.data.on_success(views); - }); - }); - - intercept(widget, "get_session", function (event) { - event.data.callback(session); - }); - - intercept(widget, "load_filters", function (event) { - if (params.debug) { - console.log('[mock] load_filters', event.data); + intercept(widget, "load_views", async ev => { + const params = { + model: ev.data.modelName, + context: ev.data.context, + views_descr: ev.data.views, + }; + const views = await env.dataManager.load_views(params, ev.data.options); + if ('search' in views && params.favoriteFilters) { + views.search.favoriteFilters = params.favoriteFilters; } - event.data.on_success([]); + ev.data.on_success(views); + }); + intercept(widget, "get_session", ev => { + ev.data.callback(session); + }); + intercept(widget, "load_filters", async ev => { + const filters = await env.dataManager.load_filters(ev.data); + ev.data.on_success(filters); }); - // make sure all Odoo events bubbling up are intercepted - if ('intercepts' in params) { - _.each(params.intercepts, function (cb, name) { - intercept(widget, name, cb); - }); - } - - // Deploy services - var done = false; - var servicesToDeploy = _.clone(params.services); - if (!servicesToDeploy.ajax) { - services.ajax = null; // use mocked ajax from mocked server - } - while (!done) { - var serviceName = _.findKey(servicesToDeploy, function (Service) { - return !_.some(Service.prototype.dependencies, function (depName) { - return !_.has(services, depName); - }); - }); - if (serviceName) { - var Service = servicesToDeploy[serviceName]; - var service = services[serviceName] = new Service(widget); - delete servicesToDeploy[serviceName]; - - intercept(service, "get_session", function (event) { - event.data.callback(session); - }); - service.start(); - } else { - var serviceNames = _.keys(servicesToDeploy); - if (serviceNames.length) { - console.warn("Non loaded services:", serviceNames); - } - done = true; - } - } - - // Wait for asynchronous services to properly start - await new Promise(setTimeout); + // make sure all other Odoo events bubbling up are intercepted + Object.keys(params.intercepts || {}).forEach(function (name) { + intercept(widget, name, params.intercepts[name]); + }); return mockServer; } -/** - * performs a fields_view_get, and mocks the postprocessing done by the - * data_manager to return an equivalent structure. - * - * @param {MockServer} server - * @param {Object} params - * @param {string} params.model - * @returns {Object} an object with 3 keys: arch, fields and viewFields - */ -function fieldsViewGet(server, params) { - var fieldsView = server.fieldsViewGet(params); - // mock the structure produced by the DataManager - fieldsView.viewFields = fieldsView.fields; - fieldsView.fields = server.fieldsGet(params.model); - return fieldsView; -} - -/** - * Returns a mocked environment to be used by OWL components in tests. - * - * @param {Object} [params] - * @param {Object} [params.actions] the actions given to the mock server - * @param {Object} [params.archs] this archs given to the mock server - * @param {Object} [params.data] the business data given to the mock server - * @param {boolean} [params.debug] - * @param {Object} [params.env] - * @param {function} [params.mockRPC] - * @param {function} [params.server] an already instantiated server to avoid - * creating another one. - * @returns {Object} - */ -function getMockedOwlEnv(params = {}) { - let server; - if (params.server) { - server = params.server; - } else { - let Server = MockServer; - if (params.mockRPC) { - Server = MockServer.extend({ _performRpc: params.mockRPC }); - } - server = new Server(params.data, { - actions: params.actions, - archs: params.archs, - debug: params.debug, - }); - } - const env = Object.assign({ - dataManager: { - load_action: (actionID, context) => { - return server.performRpc('/web/action/load', { - kwargs: { - action_id: actionID, - additional_context: context, - }, - }); - }, - load_views: (params, options) => { - return server.performRpc('/web/dataset/call_kw/' + params.model, { - args: [], - kwargs: { - context: params.context, - options: options, - views: params.views_descr, - }, - method: 'load_views', - model: params.model, - }).then(function (views) { - return _.mapObject(views, viewParams => { - return fieldsViewGet(server, viewParams); - }); - }); - }, - load_filters: params => { - if (params.debug) { - console.log('[mock] load_filters', params); - } - return Promise.resolve([]); - }, - }, - session: params.session || {}, - }, params.env); - return makeTestEnvironment(env, server.performRpc.bind(server)); -} - -/** - * intercepts an event bubbling up the widget hierarchy. The event intercepted - * must be a "custom event", i.e. an event generated by the method 'trigger_up'. - * - * Note that this method really intercepts the event if @propagate is not set. - * It will not be propagated further, and even the handlers on the target will - * not fire. - * - * @param {Widget} widget the target widget (any Odoo widget) - * @param {string} eventName description of the event - * @param {function} fn callback executed when the even is intercepted - * @param {boolean} [propagate=false] - */ -function intercept(widget, eventName, fn, propagate) { - var _trigger_up = widget._trigger_up.bind(widget); - widget._trigger_up = function (event) { - if (event.name === eventName) { - fn(event); - if (!propagate) { return; } - } - _trigger_up(event); - }; -} - /** * Patch window.Date so that the time starts its flow from the provided Date. * @@ -644,7 +756,7 @@ function patchSetTimeout() { return { addMockEnvironment: addMockEnvironment, fieldsViewGet: fieldsViewGet, - getMockedOwlEnv: getMockedOwlEnv, + addMockEnvironmentOwl: addMockEnvironmentOwl, intercept: intercept, patchDate: patchDate, patch: patch, diff --git a/addons/web/static/tests/owl_compatibility_tests.js b/addons/web/static/tests/owl_compatibility_tests.js index f49fee5f78a..1cc463d0eeb 100644 --- a/addons/web/static/tests/owl_compatibility_tests.js +++ b/addons/web/static/tests/owl_compatibility_tests.js @@ -5,9 +5,9 @@ odoo.define('web.OwlCompatibilityTests', function (require) { const testUtils = require('web.test_utils'); const Widget = require('web.Widget'); - const getMockedOwlEnv = testUtils.mock.getMockedOwlEnv; const makeTestPromise = testUtils.makeTestPromise; const nextTick = testUtils.nextTick; + const addMockEnvironmentOwl = testUtils.mock.addMockEnvironmentOwl; const { Component, tags, useState } = owl; const { xml } = tags; @@ -448,17 +448,17 @@ odoo.define('web.OwlCompatibilityTests', function (require) { this.MyWidget = MyWidget; } } - Parent.env = getMockedOwlEnv({ - mockRPC: function (route, args) { - assert.step(`${route} ${args.val}`); - return Promise.resolve(); - }, - }); Parent.template = xml`
`; Parent.components = { ComponentAdapter }; + const cleanUp = await addMockEnvironmentOwl(Parent, { + mockRPC: function (route, args) { + assert.step(`${route} ${args.val}`); + return Promise.resolve(); + }, + }); const target = testUtils.prepareTarget(); const parent = new Parent(); @@ -468,6 +468,7 @@ odoo.define('web.OwlCompatibilityTests', function (require) { assert.verifySteps(['some/route 2']); parent.destroy(); + cleanUp(); }); QUnit.test("sub widget that calls a service", async function (assert) { @@ -493,16 +494,14 @@ odoo.define('web.OwlCompatibilityTests', function (require) { this.MyWidget = MyWidget; } } - const env = getMockedOwlEnv(); - env.services.math = { - sqrt: v => Math.sqrt(v), - }; - Parent.env = env; Parent.template = xml`
`; Parent.components = { ComponentAdapter }; + Parent.env.services.math = { + sqrt: v => Math.sqrt(v), + }; const target = testUtils.prepareTarget(); const parent = new Parent(); @@ -525,20 +524,21 @@ odoo.define('web.OwlCompatibilityTests', function (require) { this.MyWidget = MyWidget; } } - Parent.env = getMockedOwlEnv({ - session: { key: 'value' }, - }); Parent.template = xml`
`; Parent.components = { ComponentAdapter }; + const cleanUp = await addMockEnvironmentOwl(Parent, { + session: { key: 'value' }, + }); const target = testUtils.prepareTarget(); const parent = new Parent(); await parent.mount(target); parent.destroy(); + cleanUp(); }); QUnit.test("sub widget that calls load_views", async function (assert) { @@ -555,7 +555,12 @@ odoo.define('web.OwlCompatibilityTests', function (require) { this.MyWidget = MyWidget; } } - Parent.env = getMockedOwlEnv({ + Parent.template = xml` +
+ +
`; + Parent.components = { ComponentAdapter }; + const cleanUp = await addMockEnvironmentOwl(Parent, { mockRPC: function (route, args) { assert.strictEqual(route, '/web/dataset/call_kw/some_model'); assert.deepEqual(args.kwargs.context, { x: 2 }); @@ -563,11 +568,6 @@ odoo.define('web.OwlCompatibilityTests', function (require) { return Promise.resolve(); }, }); - Parent.template = xml` -
- -
`; - Parent.components = { ComponentAdapter }; const target = testUtils.prepareTarget(); const parent = new Parent(); @@ -576,6 +576,7 @@ odoo.define('web.OwlCompatibilityTests', function (require) { assert.strictEqual(parent.el.innerHTML, '
'); parent.destroy(); + cleanUp(); }); QUnit.test("sub widgets in a t-if/t-else", async function (assert) { diff --git a/addons/web/static/tests/tools/debug_manager_tests.js b/addons/web/static/tests/tools/debug_manager_tests.js index 0f850835c66..77970a1922e 100644 --- a/addons/web/static/tests/tools/debug_manager_tests.js +++ b/addons/web/static/tests/tools/debug_manager_tests.js @@ -11,7 +11,7 @@ QUnit.module('DebugManager', {}, function () { QUnit.test("list: edit view menu item", async function (assert) { assert.expect(3); - var debugManager = createDebugManager(); + var debugManager = await createDebugManager(); await debugManager.appendTo($('#qunit-fixture')); @@ -43,7 +43,7 @@ QUnit.module('DebugManager', {}, function () { QUnit.test("form: Manage Attachments option", async function (assert) { assert.expect(3); - var debugManager = createDebugManager({ + var debugManager = await createDebugManager({ intercepts: { do_action: function (event) { assert.deepEqual(event.data.action, { @@ -126,7 +126,7 @@ QUnit.module('DebugManager', {}, function () { }); // Now the real tested component - var debugManager = createDebugManager({ + var debugManager = await createDebugManager({ data: data, mockRPC: function (route, args) { if (route == "/web/dataset/call_kw/ir.default/set") { diff --git a/addons/web/static/tests/views/basic_model_tests.js b/addons/web/static/tests/views/basic_model_tests.js index 326c8550bb5..3d28a32cf2e 100644 --- a/addons/web/static/tests/views/basic_model_tests.js +++ b/addons/web/static/tests/views/basic_model_tests.js @@ -136,7 +136,7 @@ odoo.define('web.basic_model_tests', function (require) { this.params.fieldNames = ['foo']; this.params.context = { active_field: 2 }; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, mockRPC: function (route, args) { @@ -171,7 +171,7 @@ odoo.define('web.basic_model_tests', function (require) { this.params.res_id = 99; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, }); @@ -190,7 +190,7 @@ odoo.define('web.basic_model_tests', function (require) { this.params.fieldNames = ['foo', 'qux']; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, }); @@ -211,7 +211,7 @@ odoo.define('web.basic_model_tests', function (require) { this.data.partner.records[1].qux = 1; this.params.fieldNames = ['qux']; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, }); @@ -238,7 +238,7 @@ odoo.define('web.basic_model_tests', function (require) { var rpcCount = 0; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, mockRPC: function (route, args) { @@ -277,7 +277,7 @@ odoo.define('web.basic_model_tests', function (require) { this.params.fieldNames = ['foo', 'bar']; this.params.context = { hello: 'world' }; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, mockRPC: function (route, args) { @@ -317,7 +317,7 @@ odoo.define('web.basic_model_tests', function (require) { var rpcCount = 0; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, mockRPC: function (route, args) { @@ -354,7 +354,7 @@ odoo.define('web.basic_model_tests', function (require) { this.params.fieldNames = ['foo']; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, }); @@ -381,7 +381,7 @@ odoo.define('web.basic_model_tests', function (require) { this.data.partner.records[1].product_ids = [37]; this.params.fieldNames = ['product_ids']; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, mockRPC: function (route, args) { @@ -461,7 +461,7 @@ odoo.define('web.basic_model_tests', function (require) { this.params.fieldNames = ['product_id']; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, mockRPC: function (route, args) { @@ -495,7 +495,7 @@ odoo.define('web.basic_model_tests', function (require) { this.params.fieldNames = ['foo', 'product_id']; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, mockRPC: function (route, args) { @@ -524,7 +524,7 @@ odoo.define('web.basic_model_tests', function (require) { this.params.fieldNames = ['foo', 'product_id']; this.params.res_id = 1; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, }); @@ -553,7 +553,7 @@ odoo.define('web.basic_model_tests', function (require) { }, }; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, }); @@ -583,7 +583,7 @@ odoo.define('web.basic_model_tests', function (require) { }, }; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, }); @@ -615,7 +615,7 @@ odoo.define('web.basic_model_tests', function (require) { }, }; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, }); @@ -641,7 +641,7 @@ odoo.define('web.basic_model_tests', function (require) { this.params.res_id = undefined; this.params.context = { active_field: 2 }; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, mockRPC: function (route, args) { @@ -671,7 +671,7 @@ odoo.define('web.basic_model_tests', function (require) { this.params.groupedBy = []; this.params.res_id = undefined; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, mockRPC: function (route, args) { @@ -698,7 +698,7 @@ odoo.define('web.basic_model_tests', function (require) { this.params.res_id = undefined; this.params.type = 'record'; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, mockRPC: function (route, args) { @@ -732,7 +732,7 @@ odoo.define('web.basic_model_tests', function (require) { this.params.res_id = undefined; this.params.type = 'record'; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, mockRPC: function (route, args) { @@ -784,7 +784,7 @@ odoo.define('web.basic_model_tests', function (require) { }; this.params.viewType = 'form'; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, mockRPC: function (route, args) { @@ -822,7 +822,7 @@ odoo.define('web.basic_model_tests', function (require) { this.params.res_id = undefined; this.params.type = 'record'; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, mockRPC: function (route, args) { @@ -848,7 +848,7 @@ odoo.define('web.basic_model_tests', function (require) { fields.product_id.default = 37; fields.other_product_id.default = 41; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, mockRPC: function (route, args) { @@ -880,7 +880,7 @@ odoo.define('web.basic_model_tests', function (require) { this.params.fieldNames = ['foo']; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, }); @@ -902,7 +902,7 @@ odoo.define('web.basic_model_tests', function (require) { this.params.fieldNames = ['category']; this.params.res_id = 1; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, }); @@ -927,7 +927,7 @@ odoo.define('web.basic_model_tests', function (require) { this.params.fieldNames = ['foo']; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, }); @@ -951,7 +951,7 @@ odoo.define('web.basic_model_tests', function (require) { this.params.fieldNames = ['product_id']; var self = this; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, }); @@ -981,7 +981,7 @@ odoo.define('web.basic_model_tests', function (require) { this.params.res_id = undefined; var filterEnabled = false; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, mockRPC: function (route, args) { @@ -1046,7 +1046,7 @@ odoo.define('web.basic_model_tests', function (require) { this.params.groupedBy = ['date:month']; this.params.res_id = undefined; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, mockRPC: function (route, args) { @@ -1072,7 +1072,7 @@ odoo.define('web.basic_model_tests', function (require) { }; this.data.partner.records[0].selection = 'a'; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, }); @@ -1108,7 +1108,7 @@ odoo.define('web.basic_model_tests', function (require) { this.params.context = { active_field: 2 }; var id; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, mockRPC: function (route, args) { @@ -1146,7 +1146,7 @@ odoo.define('web.basic_model_tests', function (require) { this.params.fieldNames = ['product_ids']; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, mockRPC: function (route, args) { @@ -1183,7 +1183,7 @@ odoo.define('web.basic_model_tests', function (require) { QUnit.test('create commands on a one2many', async function (assert) { assert.expect(3); - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, mockRPC: function (route, args) { @@ -1260,7 +1260,7 @@ odoo.define('web.basic_model_tests', function (require) { type: 'record', }; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, mockRPC: function (route, args) { @@ -1297,7 +1297,7 @@ odoo.define('web.basic_model_tests', function (require) { QUnit.test('dates are properly loaded and parsed (record)', async function (assert) { assert.expect(2); - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, }); @@ -1328,7 +1328,7 @@ odoo.define('web.basic_model_tests', function (require) { QUnit.test('dates are properly loaded and parsed (list)', async function (assert) { assert.expect(2); - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, }); @@ -1355,7 +1355,7 @@ odoo.define('web.basic_model_tests', function (require) { QUnit.test('dates are properly loaded and parsed (default_get)', async function (assert) { assert.expect(1); - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, }); @@ -1379,7 +1379,7 @@ odoo.define('web.basic_model_tests', function (require) { this.data.partner.fields.category.default = [12, 14]; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, }); @@ -1412,7 +1412,7 @@ odoo.define('web.basic_model_tests', function (require) { }; this.data.partner.fields.category.type = 'many2one'; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, mockRPC: function (route, args) { @@ -1464,7 +1464,7 @@ odoo.define('web.basic_model_tests', function (require) { string: "O2M", type: 'one2many', relation: 'partner', default: [[6, 0, [1]]], }; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, }); @@ -1510,7 +1510,7 @@ odoo.define('web.basic_model_tests', function (require) { this.data.partner.fields.product_id.context = "{'hello': 'world', 'test': foo}"; this.data.partner.fields.product_id.domain = "[['hello', 'like', 'world'], ['test', 'like', foo]]"; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, }); @@ -1538,7 +1538,7 @@ odoo.define('web.basic_model_tests', function (require) { model.destroy(); // Try again with xml override of field domain and context - model = createModel({ + model = await createModel({ Model: BasicModel, data: this.data, }); @@ -1585,7 +1585,7 @@ odoo.define('web.basic_model_tests', function (require) { } }; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, mockRPC: function (route, args) { @@ -1650,7 +1650,7 @@ odoo.define('web.basic_model_tests', function (require) { } }; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, mockRPC: function (route, args) { @@ -1699,7 +1699,7 @@ odoo.define('web.basic_model_tests', function (require) { QUnit.test('default_get with one2many values', async function (assert) { assert.expect(1); - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, mockRPC: function (route, args) { @@ -1738,7 +1738,7 @@ odoo.define('web.basic_model_tests', function (require) { assert.expect(3); var rpcCount = 0; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, mockRPC: function (route, args) { @@ -1773,7 +1773,7 @@ odoo.define('web.basic_model_tests', function (require) { assert.expect(5); var rpcCount = 0; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, mockRPC: function (route, args) { @@ -1812,7 +1812,7 @@ odoo.define('web.basic_model_tests', function (require) { assert.expect(5); var rpcCount = 0; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, mockRPC: function (route, args) { @@ -1857,7 +1857,7 @@ odoo.define('web.basic_model_tests', function (require) { assert.expect(4); var rpcCount = 0; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, mockRPC: function (route, args) { @@ -1888,7 +1888,7 @@ odoo.define('web.basic_model_tests', function (require) { assert.expect(2); let rpcCount = 0; - const model = createModel({ + const model = await createModel({ Model: BasicModel, data: this.data, mockRPC: function (route, args) { @@ -1916,7 +1916,7 @@ odoo.define('web.basic_model_tests', function (require) { this.data.partner.fields.product_id.context = "{'id': id, 'active_id': active_id, 'active_ids': active_ids, 'active_model': active_model}"; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, }); @@ -1949,7 +1949,7 @@ odoo.define('web.basic_model_tests', function (require) { this.params.fieldNames = ['category']; this.params.res_id = 1; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, mockRPC: function (route, args) { @@ -1991,7 +1991,7 @@ odoo.define('web.basic_model_tests', function (require) { viewType: 'form', }; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, }); @@ -2011,7 +2011,7 @@ odoo.define('web.basic_model_tests', function (require) { assert.expect(2); this.data.partner.fields.foo.default = 'default'; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, }); @@ -2042,7 +2042,7 @@ odoo.define('web.basic_model_tests', function (require) { this.params.fieldNames = Object.keys(this.data.partner.fields); this.params.res_id = 1; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, }); @@ -2095,7 +2095,7 @@ odoo.define('web.basic_model_tests', function (require) { this.params.viewType = 'form'; this.params.res_id = 1; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, }); @@ -2133,7 +2133,7 @@ odoo.define('web.basic_model_tests', function (require) { this.params.groupedBy = []; this.params.res_id = undefined; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, mockRPC: function (route, args) { @@ -2164,7 +2164,7 @@ odoo.define('web.basic_model_tests', function (require) { this.params.res_id = undefined; this.params.type = 'record'; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, mockRPC: function (route, args) { @@ -2196,7 +2196,7 @@ odoo.define('web.basic_model_tests', function (require) { this.params.res_id = undefined; this.params.type = 'record'; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, }); @@ -2215,7 +2215,7 @@ odoo.define('web.basic_model_tests', function (require) { this.params.res_id = undefined; this.params.orderedBy = [{ name: 'display_name', asc: true }]; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, mockRPC: function (route) { @@ -2269,7 +2269,7 @@ odoo.define('web.basic_model_tests', function (require) { this.params.res_id = 1; this.params.fieldNames = ['foobool', 'foobool2']; this.params.fields = this.data.partner.fields; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, }); @@ -2291,7 +2291,7 @@ odoo.define('web.basic_model_tests', function (require) { this.data.partner.records[1].product_ids = [37, 38]; this.params.fieldNames = ['product_ids']; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, }); @@ -2341,7 +2341,7 @@ odoo.define('web.basic_model_tests', function (require) { this.data.partner.records[1].product_ids = [37, 38]; this.params.fieldNames = ['product_ids']; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, }); @@ -2401,7 +2401,7 @@ odoo.define('web.basic_model_tests', function (require) { }, }; - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, }); @@ -2448,7 +2448,7 @@ odoo.define('web.basic_model_tests', function (require) { QUnit.test('identify correct active field', async function(assert) { assert.expect(4); - var model = createModel({ + var model = await createModel({ Model: BasicModel, data: this.data, }); diff --git a/addons/web/static/tests/views/calendar_tests.js b/addons/web/static/tests/views/calendar_tests.js index 7b69e8ce167..ba8c1f35dfd 100644 --- a/addons/web/static/tests/views/calendar_tests.js +++ b/addons/web/static/tests/views/calendar_tests.js @@ -2541,7 +2541,7 @@ QUnit.module('Views', { }); QUnit.test('check if the view destroys all widgets and instances', async function (assert) { - assert.expect(1); + assert.expect(2); var instanceNumber = 0; testUtils.mock.patch(mixins.ParentedMixin, { @@ -2579,21 +2579,10 @@ QUnit.module('Views', { }; var calendar = await createCalendarView(params); - calendar.destroy(); - - var initialInstanceNumber = instanceNumber; - instanceNumber = 0; - - calendar = await createCalendarView(params); - - // call destroy function of controller to ensure that it correctly destroys everything - calendar.__destroy(); - - // + 1 (parent) - assert.strictEqual(instanceNumber, initialInstanceNumber + 1, - "every widget must be destroyed exept the parent"); + assert.ok(instanceNumber > 0); calendar.destroy(); + assert.strictEqual(instanceNumber, 0); testUtils.mock.unpatch(mixins.ParentedMixin); }); diff --git a/addons/web/static/tests/views/form_tests.js b/addons/web/static/tests/views/form_tests.js index 33bc3de7347..56fba6515bc 100644 --- a/addons/web/static/tests/views/form_tests.js +++ b/addons/web/static/tests/views/form_tests.js @@ -7706,7 +7706,7 @@ QUnit.module('Views', { }); QUnit.test('check if the view destroys all widgets and instances', async function (assert) { - assert.expect(1); + assert.expect(2); var instanceNumber = 0; await testUtils.mock.patch(mixins.ParentedMixin, { @@ -7766,21 +7766,10 @@ QUnit.module('Views', { }; var form = await createView(params); - form.destroy(); - - var initialInstanceNumber = instanceNumber; - instanceNumber = 0; - - form = await createView(params); - - // call destroy function of controller to ensure that it correctly destroys everything - form.__destroy(); - - // + 4 (parent) - assert.strictEqual(instanceNumber, initialInstanceNumber + 1, - "every widget must be destroyed exept the parent"); + assert.ok(instanceNumber > 0); form.destroy(); + assert.strictEqual(instanceNumber, 0); await testUtils.mock.unpatch(mixins.ParentedMixin); }); diff --git a/addons/web/static/tests/views/kanban_model_tests.js b/addons/web/static/tests/views/kanban_model_tests.js index 793a5749b46..2598f2bd676 100644 --- a/addons/web/static/tests/views/kanban_model_tests.js +++ b/addons/web/static/tests/views/kanban_model_tests.js @@ -67,7 +67,7 @@ QUnit.module('Views', { assert.expect(22); var calledRoutes = {}; - var model = createModel({ + var model = await createModel({ Model: KanbanModel, data: this.data, mockRPC: function (route) { @@ -132,7 +132,7 @@ QUnit.module('Views', { var done = assert.async(); assert.expect(4); - var model = createModel({ + var model = await createModel({ Model: KanbanModel, data: this.data, mockRPC: function (route, args) { @@ -173,7 +173,7 @@ QUnit.module('Views', { var done = assert.async(); assert.expect(2); - var model = createModel({ + var model = await createModel({ Model: KanbanModel, data: this.data, mockRPC: function (route, args) { @@ -210,7 +210,7 @@ QUnit.module('Views', { this.data.partner.records.push({id: 3, foo: 'aaa', product_id: 37}); var nbReseq = 0; - var model = createModel({ + var model = await createModel({ Model: KanbanModel, data: this.data, mockRPC: function (route, args) { @@ -270,7 +270,7 @@ QUnit.module('Views', { assert.expect(8); var self = this; - var model = createModel({ + var model = await createModel({ Model: KanbanModel, data: this.data, }); diff --git a/addons/web/static/tests/views/kanban_tests.js b/addons/web/static/tests/views/kanban_tests.js index 22c29168585..225826c8515 100644 --- a/addons/web/static/tests/views/kanban_tests.js +++ b/addons/web/static/tests/views/kanban_tests.js @@ -5420,7 +5420,7 @@ QUnit.module('Views', { }); QUnit.test('check if the view destroys all widgets and instances', async function (assert) { - assert.expect(1); + assert.expect(2); var instanceNumber = 0; testUtils.mock.patch(mixins.ParentedMixin, { @@ -5457,21 +5457,10 @@ QUnit.module('Views', { }; var kanban = await createView(params); - kanban.destroy(); - - var initialInstanceNumber = instanceNumber; - instanceNumber = 0; - - kanban = await createView(params); - - // call destroy function of controller to ensure that it correctly destroys everything - kanban.__destroy(); - - // + 1 (parent) - assert.strictEqual(instanceNumber, initialInstanceNumber + 1, - "every widget must be destroyed exept the parent"); + assert.ok(instanceNumber > 0); kanban.destroy(); + assert.strictEqual(instanceNumber, 0); testUtils.mock.unpatch(mixins.ParentedMixin); }); diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js index d69aef904c1..67c1a7962a0 100644 --- a/addons/web/static/tests/views/list_tests.js +++ b/addons/web/static/tests/views/list_tests.js @@ -7409,7 +7409,7 @@ QUnit.module('Views', { }); QUnit.test('check if the view destroys all widgets and instances', async function (assert) { - assert.expect(1); + assert.expect(2); var instanceNumber = 0; testUtils.mock.patch(mixins.ParentedMixin, { @@ -7446,21 +7446,10 @@ QUnit.module('Views', { }; var list = await createView(params); - list.destroy(); - - var initialInstanceNumber = instanceNumber; - instanceNumber = 0; - - list = await createView(params); - - // call destroy function of controller to ensure that it correctly destroys everything - list.__destroy(); - - // + 1 (parent) - assert.strictEqual(instanceNumber, initialInstanceNumber + 1, - "every widget must be destroyed exept the parent"); + assert.ok(instanceNumber > 0); list.destroy(); + assert.strictEqual(instanceNumber, 0); testUtils.mock.unpatch(mixins.ParentedMixin); }); diff --git a/addons/web/static/tests/views/view_dialogs_tests.js b/addons/web/static/tests/views/view_dialogs_tests.js index 26fc002605a..7d1554dc78d 100644 --- a/addons/web/static/tests/views/view_dialogs_tests.js +++ b/addons/web/static/tests/views/view_dialogs_tests.js @@ -12,8 +12,7 @@ var createView = testUtils.createView; async function createParent(params) { var widget = new Widget(); - params.server = await testUtils.mock.addMockEnvironment(widget, params); - owl.Component.env = testUtils.mock.getMockedOwlEnv(params); + params.server = await testUtils.mock.addMockEnvironment(widget, params); return widget; } diff --git a/addons/web/static/tests/widgets/company_switcher_tests.js b/addons/web/static/tests/widgets/company_switcher_tests.js index 6b3ead230ca..fe0fec5e6c2 100644 --- a/addons/web/static/tests/widgets/company_switcher_tests.js +++ b/addons/web/static/tests/widgets/company_switcher_tests.js @@ -9,7 +9,7 @@ async function createSwitchCompanyMenu(params) { params = params || {}; var target = params.debug ? document.body : $('#qunit-fixture'); var menu = new SwitchCompanyMenu(); - testUtils.mock.addMockEnvironment(menu, params); + await testUtils.mock.addMockEnvironment(menu, params); await menu.appendTo(target) return menu } diff --git a/addons/web/static/tests/widgets/domain_selector_tests.js b/addons/web/static/tests/widgets/domain_selector_tests.js index 8c246fd8399..a587117f95a 100644 --- a/addons/web/static/tests/widgets/domain_selector_tests.js +++ b/addons/web/static/tests/widgets/domain_selector_tests.js @@ -60,7 +60,7 @@ QUnit.module('DomainSelector', { readonly: false, debugMode: true, }); - testUtils.mock.addMockEnvironment(domainSelector, {data: this.data}); + await testUtils.mock.addMockEnvironment(domainSelector, {data: this.data}); await domainSelector.appendTo($target); // As we gave an empty domain, there should be a visible button to add @@ -161,7 +161,7 @@ QUnit.module('DomainSelector', { var domainSelector = new DomainSelector(null, "partner", [["nice_datetime", "=", "2017-03-27 15:42:00"]], { readonly: false, }); - testUtils.mock.addMockEnvironment(domainSelector, {data: this.data}); + await testUtils.mock.addMockEnvironment(domainSelector, {data: this.data}); await domainSelector.appendTo($target); // Check that there is a datepicker to choose the date @@ -189,7 +189,7 @@ QUnit.module('DomainSelector', { debugMode: true, readonly: false, }); - testUtils.mock.addMockEnvironment(domainSelector, {data: this.data}); + await testUtils.mock.addMockEnvironment(domainSelector, {data: this.data}); await domainSelector.appendTo($target); await testUtils.fields.editAndTrigger(domainSelector.$('.o_domain_leaf_value_input'), @@ -210,7 +210,7 @@ QUnit.module('DomainSelector', { debugMode: true, readonly: false, }); - testUtils.mock.addMockEnvironment(domainSelector, {data: this.data}); + await testUtils.mock.addMockEnvironment(domainSelector, {data: this.data}); await domainSelector.appendTo($target); assert.strictEqual(domainSelector.$el.text(), "This domain is not supported.", @@ -230,7 +230,7 @@ QUnit.module('DomainSelector', { debugMode: true, default: [["foo","=","kikou"]], }); - testUtils.mock.addMockEnvironment(domainSelector, {data: this.data}); + await testUtils.mock.addMockEnvironment(domainSelector, {data: this.data}); await domainSelector.appendTo($target); // Clicking on the button should add a visible field selector in the diff --git a/addons/web/static/tests/widgets/model_field_selector_tests.js b/addons/web/static/tests/widgets/model_field_selector_tests.js index 75580e09df3..69bb1eb9314 100644 --- a/addons/web/static/tests/widgets/model_field_selector_tests.js +++ b/addons/web/static/tests/widgets/model_field_selector_tests.js @@ -59,7 +59,7 @@ QUnit.module('ModelFieldSelector', { readonly: false, debugMode: true, }); - testUtils.mock.addMockEnvironment(fieldSelector, {data: this.data}); + await testUtils.mock.addMockEnvironment(fieldSelector, {data: this.data}); await fieldSelector.appendTo($target); var $value = fieldSelector.$("> .o_field_selector_value"); @@ -182,7 +182,7 @@ QUnit.module('ModelFieldSelector', { readonly: false, debugMode: true, }); - testUtils.addMockEnvironment(fieldSelector, {data: this.data}); + await testUtils.addMockEnvironment(fieldSelector, {data: this.data}); await fieldSelector.appendTo($target); // Focusing the field selector input should open a field selector popover @@ -215,7 +215,7 @@ QUnit.module('ModelFieldSelector', { return field.type === 'many2one'; }, }); - testUtils.mock.addMockEnvironment(fieldSelector, {data: this.data}); + await testUtils.mock.addMockEnvironment(fieldSelector, {data: this.data}); await fieldSelector.appendTo($target); fieldSelector.$el.trigger('focusin'); @@ -237,7 +237,7 @@ QUnit.module('ModelFieldSelector', { var fieldSelector = new ModelFieldSelector(null, "partner", [], { readonly: false, }); - testUtils.mock.addMockEnvironment(fieldSelector, {data: this.data}); + await testUtils.mock.addMockEnvironment(fieldSelector, {data: this.data}); await fieldSelector.appendTo($target); fieldSelector.$el.trigger('focusin'); @@ -269,7 +269,7 @@ QUnit.module('ModelFieldSelector', { readonly: false, showSearchInput: false, }); - testUtils.mock.addMockEnvironment(fieldSelector, { data: this.data }); + await testUtils.mock.addMockEnvironment(fieldSelector, { data: this.data }); await fieldSelector.appendTo($target); fieldSelector.$el.trigger('focusin'); @@ -291,7 +291,7 @@ QUnit.module('ModelFieldSelector', { readonly: false, showSearchInput: false, }); - testUtils.mock.addMockEnvironment(fieldSelector, {data: this.data}); + await testUtils.mock.addMockEnvironment(fieldSelector, {data: this.data}); await fieldSelector.appendTo($target); var $fieldName = fieldSelector.$('.o_field_selector_chain_part'); @@ -311,7 +311,7 @@ QUnit.module('ModelFieldSelector', { readonly: false, showSearchInput: false, }); - testUtils.mock.addMockEnvironment(fieldSelector, {data: this.data}); + await testUtils.mock.addMockEnvironment(fieldSelector, {data: this.data}); await fieldSelector.appendTo($target); var $fieldName = fieldSelector.$('.o_field_selector_chain_part'); diff --git a/addons/web/views/webclient_templates.xml b/addons/web/views/webclient_templates.xml index bebc3255dc2..3cb081e7e37 100644 --- a/addons/web/views/webclient_templates.xml +++ b/addons/web/views/webclient_templates.xml @@ -167,6 +167,8 @@ + + diff --git a/addons/web_editor/static/tests/test_utils.js b/addons/web_editor/static/tests/test_utils.js index 8b28e292438..9cd3249f953 100644 --- a/addons/web_editor/static/tests/test_utils.js +++ b/addons/web_editor/static/tests/test_utils.js @@ -293,12 +293,12 @@ function wysiwygData(data) { * * @param {object} params */ -function createWysiwyg(params) { +async function createWysiwyg(params) { patch(); params.data = wysiwygData(params.data); var parent = new Widget(); - testUtils.mock.addMockEnvironment(parent, params); + await testUtils.mock.addMockEnvironment(parent, params); var wysiwygOptions = _.extend({}, params.wysiwygOptions, { recordInfo: { diff --git a/addons/web_tour/static/tests/tour_manager_tests.js b/addons/web_tour/static/tests/tour_manager_tests.js index 79ce4c0ceb5..14802f9086f 100644 --- a/addons/web_tour/static/tests/tour_manager_tests.js +++ b/addons/web_tour/static/tests/tour_manager_tests.js @@ -20,7 +20,7 @@ odoo.define('web_tour.tour_manager_tests', async function (require) { * @param {Object[]} params.tours { {string} name, {Object} option, {Object[]} steps } */ async function createTourManager({ consumed_tours, debug, template, tours }) { - const parent = testUtils.createParent({ debug }); + const parent = await testUtils.createParent({ debug }); const tourManager = new TourManager(parent, consumed_tours); tourManager.running_step_delay = 0; for (const { name, options, steps } of tours) {