From 0de5c1f076d7e7d0e5361e08c810c727bd41d9e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20K=C3=BChn?= Date: Mon, 12 Feb 2018 00:03:22 +0100 Subject: [PATCH 1/3] [FIX] web{_site,_editor}: transparent root instance 'root widget' With this commit, any component that needs an instance of the root widget can simply require the module 'root.widget'. Based on the context (backend, frontend, iframe), the real root.widget will resolve to either the webclient, the website root instance, or the iframe root instance. This is desirable in order for the tour_manager to become a child of the root widget, as tours are enabled in any of the above contexts. This commit is a requirement in order to solve an issue with JS services, in which there should be only one service provider at any given time. --- addons/web/static/src/js/chrome/root_widget.js | 7 +++++++ addons/web/views/webclient_templates.xml | 1 + addons/web_editor/static/src/js/iframe.js | 2 +- addons/web_tour/static/src/js/tour_service.js | 15 +++------------ .../website/static/src/js/content/website_root.js | 2 +- addons/website_slides/static/src/js/slides.js | 2 +- 6 files changed, 14 insertions(+), 15 deletions(-) create mode 100644 addons/web/static/src/js/chrome/root_widget.js diff --git a/addons/web/static/src/js/chrome/root_widget.js b/addons/web/static/src/js/chrome/root_widget.js new file mode 100644 index 00000000000..9cf0515b134 --- /dev/null +++ b/addons/web/static/src/js/chrome/root_widget.js @@ -0,0 +1,7 @@ +odoo.define('root.widget', function (require) { +"use strict"; + +var webClient = require('web.web_client'); + +return webClient; +}); diff --git a/addons/web/views/webclient_templates.xml b/addons/web/views/webclient_templates.xml index 2742871c790..f54755e1266 100644 --- a/addons/web/views/webclient_templates.xml +++ b/addons/web/views/webclient_templates.xml @@ -155,6 +155,7 @@ + diff --git a/addons/web_editor/static/src/js/iframe.js b/addons/web_editor/static/src/js/iframe.js index 949a141c333..f2c5e3be661 100644 --- a/addons/web_editor/static/src/js/iframe.js +++ b/addons/web_editor/static/src/js/iframe.js @@ -82,7 +82,7 @@ translator.Class.include({ //============================================================================== -odoo.define('web_editor.IframeRoot.instance', function (require) { +odoo.define('root.widget', function (require) { 'use strict'; require('web.dom_ready'); diff --git a/addons/web_tour/static/src/js/tour_service.js b/addons/web_tour/static/src/js/tour_service.js index fdfac78f6a9..eb25bc4a3aa 100644 --- a/addons/web_tour/static/src/js/tour_service.js +++ b/addons/web_tour/static/src/js/tour_service.js @@ -1,13 +1,11 @@ -odoo.define('web_tour.tour', function(require) { +odoo.define('web_tour.tour', function (require) { "use strict"; var ajax = require('web.ajax'); -var Class = require('web.Class'); var config = require('web.config'); var core = require('web.core'); -var mixins = require('web.mixins'); +var rootWidget = require('root.widget'); var rpc = require('web.rpc'); -var ServiceProviderMixin = require('web.ServiceProviderMixin'); var session = require('web.session'); var TourManager = require('web_tour.TourManager'); @@ -17,13 +15,6 @@ if (config.device.isMobile) { return $.Deferred().reject(); } -var CallService = Class.extend(mixins.EventDispatcherMixin, ServiceProviderMixin, { - init: function () { - mixins.EventDispatcherMixin.init.call(this); - ServiceProviderMixin.init.call(this); - }, -}); - /** * @namespace * @property {Object} active_tooltips @@ -48,7 +39,7 @@ return session.is_bound.then(function () { } return $.when.apply($, defs).then(function (consumed_tours) { consumed_tours = session.is_frontend ? consumed_tours : session.web_tours; - var tour_manager = new TourManager(new CallService(), consumed_tours); + var tour_manager = new TourManager(rootWidget, consumed_tours); // Use a MutationObserver to detect DOM changes var untracked_classnames = ["o_tooltip", "o_tooltip_content", "o_tooltip_overlay"]; diff --git a/addons/website/static/src/js/content/website_root.js b/addons/website/static/src/js/content/website_root.js index fbe839365ae..336ea822902 100644 --- a/addons/website/static/src/js/content/website_root.js +++ b/addons/website/static/src/js/content/website_root.js @@ -1,4 +1,4 @@ -odoo.define('website.WebsiteRoot.instance', function (require) { +odoo.define('root.widget', function (require) { 'use strict'; require('web.dom_ready'); diff --git a/addons/website_slides/static/src/js/slides.js b/addons/website_slides/static/src/js/slides.js index 279c256c5ac..7d945e9eb28 100644 --- a/addons/website_slides/static/src/js/slides.js +++ b/addons/website_slides/static/src/js/slides.js @@ -7,7 +7,7 @@ var core = require('web.core'); var time = require('web.time'); var Widget = require('web.Widget'); var local_storage = require('web.local_storage'); -var websiteRootInstance = require('website.WebsiteRoot.instance'); +require('root.widget'); var _t = core._t; var page_widgets = {}; From 066eaaeafad845a7b616a535e8827a5b3ae0164a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20K=C3=BChn?= Date: Mon, 12 Feb 2018 10:08:39 +0100 Subject: [PATCH 2/3] [FIX] mail,web: JS mail and services tracebacks Fix over this commit: https://github.com/odoo/odoo/commit/02ec09cb1c3e2d7bc7968f40c18f2208d7f3f498 In mobile, when discuss is installed, there is a traceback. This is a consequence on an issue with JS services, where some of them are not registered in the service provider. For instance, the webclient (the service provider) was instantiated before chat_manager (a service), so it was not aware of this service. To solve this issue, service providers now listen on newly registered services. Also improving deployment of JS services by not relying on topological sort. --- .../bus/static/src/js/services/bus_service.js | 3 + .../static/src/js/services/chat_manager.js | 2 + .../src/js/services/chat_window_manager.js | 2 + .../static/src/js/core/abstract_service.js | 9 -- addons/web/static/src/js/core/registry.js | 14 +++ .../web/static/src/js/core/service_mixins.js | 98 ++++++++++++------- addons/web/static/src/js/core/utils.js | 31 ------ .../static/src/js/services/ajax_service.js | 3 + addons/web/static/src/js/services/core.js | 1 + .../src/js/services/local_storage_service.js | 3 + .../static/src/js/services/report_service.js | 3 + addons/web/static/tests/helpers/test_utils.js | 45 +++------ 12 files changed, 109 insertions(+), 105 deletions(-) diff --git a/addons/bus/static/src/js/services/bus_service.js b/addons/bus/static/src/js/services/bus_service.js index 78be7a9740a..80247843dad 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 bus = require('bus.bus').bus; var AbstractService = require('web.AbstractService'); +var core = require('web.core'); var BusService = AbstractService.extend({ name: 'bus_service', @@ -91,6 +92,8 @@ var BusService = AbstractService.extend({ }); +core.serviceRegistry.add('bus_service', BusService); + return BusService; }); diff --git a/addons/mail/static/src/js/services/chat_manager.js b/addons/mail/static/src/js/services/chat_manager.js index b7c46f2d4c1..421a9053134 100644 --- a/addons/mail/static/src/js/services/chat_manager.js +++ b/addons/mail/static/src/js/services/chat_manager.js @@ -1746,6 +1746,8 @@ var ChatManager = AbstractService.extend({ }); +core.serviceRegistry.add('chat_manager', ChatManager); + return ChatManager; }); diff --git a/addons/mail/static/src/js/services/chat_window_manager.js b/addons/mail/static/src/js/services/chat_window_manager.js index f82da369ecf..be8588f9cb1 100644 --- a/addons/mail/static/src/js/services/chat_window_manager.js +++ b/addons/mail/static/src/js/services/chat_window_manager.js @@ -527,6 +527,8 @@ var ChatWindowManager = AbstractService.extend({ }); +core.serviceRegistry.add('chat_window_manager', ChatWindowManager); + return ChatWindowManager; }); diff --git a/addons/web/static/src/js/core/abstract_service.js b/addons/web/static/src/js/core/abstract_service.js index cbd6c1322ca..14efef09b39 100644 --- a/addons/web/static/src/js/core/abstract_service.js +++ b/addons/web/static/src/js/core/abstract_service.js @@ -6,7 +6,6 @@ var Mixins = require('web.mixins'); var ServicesMixin = require('web.ServicesMixin'); var AbstractService = Class.extend(Mixins.EventDispatcherMixin, ServicesMixin, { - Services: [], dependencies: [], name: null, init: function (parent) { @@ -15,13 +14,5 @@ var AbstractService = Class.extend(Mixins.EventDispatcherMixin, ServicesMixin, { }, }); -var realExtend = AbstractService.extend; - -AbstractService.extend = function () { - var Service = realExtend.apply(this, arguments); - AbstractService.prototype.Services.push(Service); - return Service; -}; - return AbstractService; }); diff --git a/addons/web/static/src/js/core/registry.js b/addons/web/static/src/js/core/registry.js index d70c09e4ae0..f925b0ea30a 100644 --- a/addons/web/static/src/js/core/registry.js +++ b/addons/web/static/src/js/core/registry.js @@ -25,6 +25,7 @@ var Registry = Class.extend({ */ init: function (mapping) { this.map = Object.create(mapping || null); + this.listeners = []; // listening callbacks on newly added items. }, //-------------------------------------------------------------------------- @@ -34,12 +35,17 @@ var Registry = Class.extend({ /** * Add a key (and a value) to the registry. * + * Notify the listeners on newly added item in the registry. + * * @param {string} key * @param {any} value * @returns {Registry} can be used to chain add calls. */ add: function (key, value) { this.map[key] = value; + _.each(this.listeners, function (callback) { + callback(key, value); + }); return this; }, /** @@ -89,6 +95,14 @@ var Registry = Class.extend({ } return null; }, + /** + * Register a callback to execute when items are added to the registry. + * + * @param {function} callback function with parameters (key, value). + */ + onAdd: function (callback) { + this.listeners.push(callback); + }, }); return Registry; diff --git a/addons/web/static/src/js/core/service_mixins.js b/addons/web/static/src/js/core/service_mixins.js index d5abf727704..052e2a6a3d9 100644 --- a/addons/web/static/src/js/core/service_mixins.js +++ b/addons/web/static/src/js/core/service_mixins.js @@ -1,58 +1,86 @@ odoo.define('web.ServiceProviderMixin', function (require) { "use strict"; -var AbstractService = require('web.AbstractService'); -var utils = require('web.utils'); +var core = require('web.core'); var ServiceProviderMixin = { - services: {}, - init: function () { + services: {}, // dict containing deployed service instances + UndeployedServices: {}, // dict containing classes of undeployed services + /** + * @override + */ + init: function (parent) { + var self = this; // to properly instantiate services with this as parent, this mixin // assumes that it is used along the EventDispatcherMixin, and that // EventDispatchedMixin's init is called first - var self = this; - // as EventDispatcherMixin's init is already called, this handler has to - // be bound manually + // as EventDispatcherMixin's init is already called, this handler has + // to be bound manually this.on('call_service', this, this._call_service.bind(this)); - var sortedServices = this._sortServices(AbstractService.prototype.Services); - _.each(sortedServices, function (Service) { - var service = new Service(self); - self.services[service.name] = service; + // add already registered services from the service registry + _.each(core.serviceRegistry.map, function (Service, serviceName) { + self.UndeployedServices[serviceName] = Service; + }); + this._deployServices(); + + // listen on newly added services + core.serviceRegistry.onAdd(function (serviceName, Service) { + self.UndeployedServices[serviceName] = Service; + self._deployServices(); }); }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @private + */ + _deployServices: function () { + var self = this; + var done = false; + while (!done) { + var Service = _.find(this.UndeployedServices, function (Service) { + // no missing dependency + return !_.some(Service.prototype.dependencies, function (depName) { + return !self.services[depName]; + }); + }); + if (Service) { + this.services[Service.prototype.name] = new Service(this); + delete this.UndeployedServices[Service.prototype.name]; + } else { + done = true; + } + } + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * Call the 'service', using data from the 'event' that + * has triggered the service call. + * + * For the ajax service, the arguments are extended with + * the target so that it can call back the caller. + * + * @private + * @param {OdooEvent} event + */ _call_service: function (event) { - var service = this.services[event.data.service]; var args = event.data.args || []; - // ajax service uses an extra 'target' argument for rpc if (event.data.service === 'ajax' && event.data.method === 'rpc') { + // ajax service uses an extra 'target' argument for rpc args = args.concat(event.target); } + var service = this.services[event.data.service]; var result = service[event.data.method].apply(service, args); event.data.callback(result); }, - _sortServices: function (services) { - var nodes = {}; - // Create nodes (services) - _.each(services, function (Service) { - nodes[Service.prototype.name] = Service.prototype.dependencies; - }); - var sorted; - try { - sorted = utils.topologicalSort(nodes); - } catch (err) { - console.warn('topologicalSort Error:', err.message); - sorted = nodes; - } - // Sort services based on sorted - // Note: we convert sorted to an object key=>index for efficiency - var sortedObj = _.invert(_.object(_.pairs(sorted))); - sorted = _.sortBy(services, function (Service) { - return sortedObj[Service.prototype.name]; - }); - - return sorted; - }, }; return ServiceProviderMixin; diff --git a/addons/web/static/src/js/core/utils.js b/addons/web/static/src/js/core/utils.js index 0b4af722203..1bdd9e70a69 100644 --- a/addons/web/static/src/js/core/utils.js +++ b/addons/web/static/src/js/core/utils.js @@ -300,37 +300,6 @@ var utils = { } return fn(mod, Math.floor(x)); }, - /** - * Topological sort of nodes - * - * @param {Object} nodes where key is name of node, and value is list of - * names of dependent nodes. - * @param {string[]} nodes[name] list of named dependencies of node having - * 'name' as its name. - * @return {string[]} topological sort of nodes by their names. - * @throws {Error} if there is at least one circular - * dependency between nodes - */ - topologicalSort: function (nodes) { - var sorted = []; - var unresolved = nodes; - while (!_.isEmpty(unresolved)) { - // Get all nodes that have no dependency - var resolvable = _.pick(unresolved, function (dependencies, name) { - return _.isEmpty(_.difference(dependencies, sorted)); - }); - if (_.isEmpty(resolvable)) { - throw new Error("Circular dependency detected"); - } - // Append them to sorted - sorted = _.union(sorted, _.keys(resolvable)); - // Remove these nodes from unresolved - _.each(resolvable, function (dependencies, name) { - unresolved = _.omit(unresolved, name); - }); - } - return sorted; - }, /** * performs a half up rounding with a fixed amount of decimals, correcting for float loss of precision * See the corresponding float_round() in server/tools/float_utils.py for more info diff --git a/addons/web/static/src/js/services/ajax_service.js b/addons/web/static/src/js/services/ajax_service.js index 480549347d4..3446f651a59 100644 --- a/addons/web/static/src/js/services/ajax_service.js +++ b/addons/web/static/src/js/services/ajax_service.js @@ -2,6 +2,7 @@ odoo.define('web.AjaxService', function (require) { "use strict"; var AbstractService = require('web.AbstractService'); +var core = require('web.core'); var session = require('web.session'); var AjaxService = AbstractService.extend({ @@ -21,6 +22,8 @@ var AjaxService = AbstractService.extend({ }, }); +core.serviceRegistry.add('ajax', AjaxService); + return AjaxService; }); diff --git a/addons/web/static/src/js/services/core.js b/addons/web/static/src/js/services/core.js index 73071a2a65d..522d6b07302 100644 --- a/addons/web/static/src/js/services/core.js +++ b/addons/web/static/src/js/services/core.js @@ -41,6 +41,7 @@ return { crash_registry: new Registry(), search_filters_registry: new Registry(), search_widgets_registry: new Registry(), + serviceRegistry: new Registry(), /** * @type {String} */ diff --git a/addons/web/static/src/js/services/local_storage_service.js b/addons/web/static/src/js/services/local_storage_service.js index a4527026238..7a8c803a0f9 100644 --- a/addons/web/static/src/js/services/local_storage_service.js +++ b/addons/web/static/src/js/services/local_storage_service.js @@ -2,6 +2,7 @@ odoo.define('web.LocalStorageService', function (require) { 'use strict'; var AbstractService = require('web.AbstractService'); +var core = require('web.core'); var localStorage = require('web.local_storage'); var LocalStorageService = AbstractService.extend({ @@ -20,6 +21,8 @@ var LocalStorageService = AbstractService.extend({ } }); +core.serviceRegistry.add('local_storage', LocalStorageService); + return LocalStorageService; }); diff --git a/addons/web/static/src/js/services/report_service.js b/addons/web/static/src/js/services/report_service.js index a2bb8759a24..cfd62c0499f 100644 --- a/addons/web/static/src/js/services/report_service.js +++ b/addons/web/static/src/js/services/report_service.js @@ -6,6 +6,7 @@ odoo.define('web.ReportService', function (require) { */ var AbstractService = require('web.AbstractService'); +var core = require('web.core'); var ReportService = AbstractService.extend({ name: 'report', @@ -28,6 +29,8 @@ var ReportService = AbstractService.extend({ }, }); +core.serviceRegistry.add('report', ReportService); + return ReportService; }); diff --git a/addons/web/static/tests/helpers/test_utils.js b/addons/web/static/tests/helpers/test_utils.js index 6b2c09d2631..7070716ed27 100644 --- a/addons/web/static/tests/helpers/test_utils.js +++ b/addons/web/static/tests/helpers/test_utils.js @@ -19,7 +19,6 @@ var core = require('web.core'); var dom = require('web.dom'); var session = require('web.session'); var MockServer = require('web.MockServer'); -var utils = require('web.utils'); var Widget = require('web.Widget'); var DebouncedField = basic_fields.DebouncedField; @@ -405,7 +404,7 @@ function addMockEnvironment(widget, params) { // Dispatch service calls // Note: some services could call other services at init, // Which is why we have to init services after that - var services = {}; + var services = {ajax: null}; // mocked ajax service already loaded intercept(widget, 'call_service', function (ev) { var args, result; if (ev.data.service === 'ajax') { @@ -420,36 +419,22 @@ function addMockEnvironment(widget, params) { } ev.data.callback(result); }); - // Instantiate services - // Note: ensure topological sort of services based on their dependencies - var sortServices = function (services) { - // Create nodes (services), with ajax already loaded - var nodes = { ajax: [] }; - _.each(services, function (Service) { - nodes[Service.prototype.name] = Service.prototype.dependencies; + + // Deploy services + var done = false; + while (!done) { + var index = _.findIndex(params.services, function (Service) { + return !_.some(Service.prototype.dependencies, function (depName) { + return !_.has(services, depName); + }); }); - var sorted; - try { - sorted = utils.topologicalSort(nodes); - } catch (err) { - console.warn('topologicalSort Error:', err.message); - sorted = nodes; + if (index !== -1) { + var Service = params.services.splice(index, 1)[0]; + services[Service.prototype.name] = new Service(widget); + } else { + done = true; } - // Remove ajax from sorted - sorted = _.without(sorted, 'ajax'); - // Sort services based on sorted - // Note: we convert sorted to an object key=>index for efficiency - var sortedObj = _.invert(_.object(_.pairs(sorted))); - sorted = _.sortBy(services, function (Service) { - return sortedObj[Service.prototype.name]; - }); - return sorted; - }; - var sortedServices = sortServices(params.services); - _.each(sortedServices, function (Service) { - var service = new Service(widget); - services[service.name] = service; - }); + } intercept(widget, 'load_action', function (event) { mockServer.performRpc('/web/action/load', { From 7339ae978f5c3d374c43d59e9f75bc6d279d1c96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20K=C3=BChn?= Date: Mon, 12 Feb 2018 00:04:33 +0100 Subject: [PATCH 3/3] [FIX] mail,web,*: Cleaning some JS code - Whitespace trailing - Split long lines - Replace some 'self' to 'this' - Improved some JS docs - DRY in tests using services --- .../static/tests/reconciliation_tests.js | 7 ++- .../bus/static/src/js/services/bus_service.js | 2 +- .../im_livechat/static/src/js/im_livechat.js | 23 ++++---- addons/mail/static/src/js/composer.js | 6 +- addons/mail/static/src/js/discuss.js | 35 +++++------ .../static/src/js/extended_chat_window.js | 3 +- .../static/src/js/services/chat_manager.js | 58 ++++++++++--------- .../src/js/services/chat_window_manager.js | 24 ++++---- addons/mail/static/src/js/systray.js | 49 ++++++++-------- addons/mail/static/src/js/thread_field.js | 5 +- addons/mail/static/tests/chat_window_tests.js | 12 ++-- addons/mail/static/tests/chatter_tests.js | 28 ++++----- .../mail/static/tests/discuss_mobile_tests.js | 8 +-- .../mail/static/tests/helpers/test_utils.js | 14 ++--- addons/mail/static/tests/systray_tests.js | 6 +- .../src/js/chrome/abstract_web_client.js | 4 ++ .../web/static/src/js/core/service_mixins.js | 5 ++ .../tests/chrome/action_manager_tests.js | 6 +- .../tests/fields/relational_fields_tests.js | 4 +- .../static/tests/views/view_dialogs_tests.js | 1 - addons/web_tour/static/src/js/tour_service.js | 4 +- 21 files changed, 160 insertions(+), 144 deletions(-) diff --git a/addons/account/static/tests/reconciliation_tests.js b/addons/account/static/tests/reconciliation_tests.js index 1c413c422a2..5a3e6dba6c2 100644 --- a/addons/account/static/tests/reconciliation_tests.js +++ b/addons/account/static/tests/reconciliation_tests.js @@ -511,6 +511,7 @@ odoo.define('account.reconciliation_tests', function (require) { var ReconciliationClientAction = require('account.ReconciliationClientAction'); var demoData = require('account.reconciliation_tests.data'); + var testUtils = require('web.test_utils'); QUnit.module('account', { @@ -738,7 +739,7 @@ QUnit.module('account', { // We also create a line which is the open balance. testUtils.intercept(clientAction, 'call_service', function (event) { assert.deepEqual(event.data.args[1].args, - [[5],[{partner_id: 8, + [[5],[{partner_id: 8, counterpart_aml_dicts: [{ counterpart_aml_id: 109, credit: 650, @@ -746,7 +747,7 @@ QUnit.module('account', { name: 'INV/2017/0002', analytic_tag_ids: [[6, null, []]] }], - payment_aml_ids: [], + payment_aml_ids: [], new_aml_dicts: [{ account_id: 287, credit: 525, @@ -898,7 +899,7 @@ QUnit.module('account', { var widget = clientAction.widgets[0]; assert.strictEqual(widget.$('.o_input_dropdown input').val(), "Agrolait", "the partner many2one should display agrolait"); assert.strictEqual(widget.$('.match table tr').length, 2, "agrolait should have 2 propositions for reconciliation"); - + // Adding the two propositions // This is in order to try that after changing partner the propositions are emptied widget.$('.match .cell_account_code:first').trigger('click'); diff --git a/addons/bus/static/src/js/services/bus_service.js b/addons/bus/static/src/js/services/bus_service.js index 80247843dad..c6e9517acc6 100644 --- a/addons/bus/static/src/js/services/bus_service.js +++ b/addons/bus/static/src/js/services/bus_service.js @@ -24,7 +24,7 @@ var BusService = AbstractService.extend({ /** * Get the bus * - * @return {web.Bus} + * @return {web.Bus} the longpoll bus */ getBus: function () { return this.bus; diff --git a/addons/im_livechat/static/src/js/im_livechat.js b/addons/im_livechat/static/src/js/im_livechat.js index ba2433abafc..e9d23227202 100644 --- a/addons/im_livechat/static/src/js/im_livechat.js +++ b/addons/im_livechat/static/src/js/im_livechat.js @@ -64,17 +64,19 @@ var LivechatButton = Widget.extend(ServiceProviderMixin, { var cookie = utils.get_cookie('im_livechat_session'); var ready; if (!cookie) { - ready = session.rpc("/im_livechat/init", {channel_id: this.options.channel_id}).then(function (result) { - if (!result.available_for_me) { - return $.Deferred().reject(); - } - self.rule = result.rule; - }); + ready = session.rpc("/im_livechat/init", {channel_id: this.options.channel_id}) + .then(function (result) { + if (!result.available_for_me) { + return $.Deferred().reject(); + } + self.rule = result.rule; + }); } else { var channel = JSON.parse(cookie); - ready = session.rpc("/mail/chat_history", {uuid: channel.uuid, limit: 100}).then(function (history) { - self.history = history; - }); + ready = session.rpc("/mail/chat_history", {uuid: channel.uuid, limit: 100}) + .then(function (history) { + self.history = history; + }); } return ready.then(this.load_qweb_template.bind(this)); }, @@ -87,7 +89,8 @@ var LivechatButton = Widget.extend(ServiceProviderMixin, { } else if (!config.device.isMobile && this.rule.action === 'auto_popup') { var auto_popup_cookie = utils.get_cookie('im_livechat_auto_popup'); if (!auto_popup_cookie || JSON.parse(auto_popup_cookie)) { - this.auto_popup_timeout = setTimeout(this.open_chat.bind(this), this.rule.auto_popup_timer*1000); + this.auto_popup_timeout = + setTimeout(this.open_chat.bind(this), this.rule.auto_popup_timer*1000); } } this.busBus.on('notification', this, function (notifications) { diff --git a/addons/mail/static/src/js/composer.js b/addons/mail/static/src/js/composer.js index 06675ec3902..c4ad1b53306 100644 --- a/addons/mail/static/src/js/composer.js +++ b/addons/mail/static/src/js/composer.js @@ -790,11 +790,11 @@ var BasicComposer = Widget.extend({ var def = $.Deferred(); clearTimeout(this.canned_timeout); this.canned_timeout = setTimeout(function () { - var canned_responses = self.call('chat_manager', 'getCannedResponses'); - var matches = fuzzy.filter(utils.unaccent(search), _.pluck(canned_responses, 'source')); + var cannedResponses = self.call('chat_manager', 'getCannedResponses'); + var matches = fuzzy.filter(utils.unaccent(search), _.pluck(cannedResponses, 'source')); var indexes = _.pluck(matches.slice(0, self.options.mention_fetch_limit), 'index'); def.resolve(_.map(indexes, function (i) { - return canned_responses[i]; + return cannedResponses[i]; })); }, 500); return def; diff --git a/addons/mail/static/src/js/discuss.js b/addons/mail/static/src/js/discuss.js index 52bf2b75822..3b405761169 100644 --- a/addons/mail/static/src/js/discuss.js +++ b/addons/mail/static/src/js/discuss.js @@ -63,13 +63,14 @@ var PartnerInviteDialog = Dialog.extend({ return $('').text(item.text).prepend(status); }, query: function (query) { - self.call('chat_manager', 'searchPartner', query.term, 20).then(function (partners) { - query.callback({ - results: _.map(partners, function (partner) { - return _.extend(partner, { text: partner.label }); - }), + self.call('chat_manager', 'searchPartner', query.term, 20) + .then(function (partners) { + query.callback({ + results: _.map(partners, function (partner) { + return _.extend(partner, { text: partner.label }); + }), + }); }); - }); } }); return this._super.apply(this, arguments); @@ -152,15 +153,13 @@ var Discuss = Widget.extend(ControlPanelMixin, { .then(function (fields_view) { self.fields_view = fields_view; }); - var chatReady = this.call('chat_manager', 'isReady'); - return $.when(this._super(), chatReady, def); + return $.when(this._super(), this.call('chat_manager', 'isReady'), def); }, /** * @override */ start: function () { var self = this; - var defaultChannel = this.call('chat_manager', 'getChannel', this.defaultChannelID) || this.call('chat_manager', 'getChannel', 'channel_inbox'); @@ -214,8 +213,7 @@ var Discuss = Widget.extend(ControlPanelMixin, { * @override */ on_attach_callback: function () { - var chatBus = this.call('chat_manager', 'getChatBus'); - chatBus.trigger('discuss_open', true); + this.call('chat_manager', 'getChatBus').trigger('discuss_open', true); if (this.channel) { this.thread.scroll_to({offset: this.channelsScrolltop[this.channel.id]}); } @@ -224,8 +222,7 @@ var Discuss = Widget.extend(ControlPanelMixin, { * @override */ on_detach_callback: function () { - var chatBus = this.call('chat_manager', 'getChatBus'); - chatBus.trigger('discuss_open', false); + this.call('chat_manager', 'getChatBus').trigger('discuss_open', false); this.channelsScrolltop[this.channel.id] = this.thread.get_scrolltop(); }, @@ -582,7 +579,7 @@ var Discuss = Widget.extend(ControlPanelMixin, { chatBus.on('new_channel', this, this._onNewChannel); chatBus.on('anyone_listening', this, function (channel, query) { query.is_displayed = query.is_displayed || - (channel.id === this.channel.id && this.thread.is_at_bottom()); + (channel.id === this.channel.id && this.thread.is_at_bottom()); }); chatBus.on('unsubscribe_from_channel', this, this._onChannelLeft); chatBus.on('update_needaction', this, this.throttledUpdateChannels); @@ -642,7 +639,7 @@ var Discuss = Widget.extend(ControlPanelMixin, { needaction_counter: this.call('chat_manager', 'getNeedactionCounter'), starred_counter: this.call('chat_manager', 'getStarredCounter'), }); - self.$(".o_mail_chat_sidebar").html($sidebar.contents()); + this.$(".o_mail_chat_sidebar").html($sidebar.contents()); _.each(['dm', 'public', 'private'], function (type) { var $input = self.$('.o_mail_add_channel[data-type=' + type + '] input'); self._prepareAddChannelInput($input, type); @@ -791,10 +788,10 @@ var Discuss = Widget.extend(ControlPanelMixin, { domain: this.domain }).then(function (messages) { var options = self._getThreadRenderingOptions(messages); - self.thread.remove_message_and_render(message.id, messages, options).then(function () { - self._updateButtonStatus(messages.length === 0, type); - - }); + self.thread.remove_message_and_render(message.id, messages, options) + .then(function () { + self._updateButtonStatus(messages.length === 0, type); + }); }); } else if (_.contains(message.channel_ids, currentChannelID)) { this._fetchAndRenderThread(); diff --git a/addons/mail/static/src/js/extended_chat_window.js b/addons/mail/static/src/js/extended_chat_window.js index a8aa5673b81..a85740ac5e1 100644 --- a/addons/mail/static/src/js/extended_chat_window.js +++ b/addons/mail/static/src/js/extended_chat_window.js @@ -48,8 +48,7 @@ return ChatWindow.extend({ event.stopPropagation(); }, on_reverse_breadcrumb: function () { - var chatBus = this.call('chat_manager', 'getChatBus'); - chatBus.trigger('discuss_open', false); + this.call('chat_manager', 'getChatBus').trigger('discuss_open', false); }, on_click_expand: _.debounce(function (event) { event.preventDefault(); diff --git a/addons/mail/static/src/js/services/chat_manager.js b/addons/mail/static/src/js/services/chat_manager.js index 421a9053134..b90cc0263fb 100644 --- a/addons/mail/static/src/js/services/chat_manager.js +++ b/addons/mail/static/src/js/services/chat_manager.js @@ -16,11 +16,11 @@ var _lt = core._lt; var LIMIT = 30; // max number of fetched messages from the server var PREVIEW_MSG_MAX_SIZE = 350; // optimal for native english speakers -var ODOOBOT_ID = "ODOOBOT"; // default author_id for messages +var ODOOBOT_ID = "ODOOBOT"; // default author_id for messages /** * This service handles everything about chat channels and messages. - * + * * There are basically two points of entry: * * 1. Calling a public method by means of 'this.call' @@ -60,7 +60,7 @@ var ChatManager = AbstractService.extend({ this.chatBus.on('discuss_open', null, function (open) { self.discussOpen = open; }); - + this.busBus.on('notification', this, this._onNotification); this.busBus.on('window_focus', this, this._onWindowFocus); @@ -77,12 +77,12 @@ var ChatManager = AbstractService.extend({ this._isReady = this._initMessaging(); + // Add static channels this._addChannel({ id: "channel_inbox", name: _lt("Inbox"), type: "static", }, { displayNeedactions: true }); - this._addChannel({ id: "channel_starred", name: _lt("Starred"), @@ -181,7 +181,7 @@ var ChatManager = AbstractService.extend({ * Returns a channel corresponding to the given id. * * @param {string|integer} id e.g. 'channel_inbox', 'channel_starred' - * @return {Object|undefined} channel + * @return {Object|undefined} the channel, if it exists */ getChannel: function (id) { return _.findWhere(this.channels, {id: id}); @@ -189,7 +189,7 @@ var ChatManager = AbstractService.extend({ /** * Returns a list of channels * - * @return {Object[]} + * @return {Object[]} list of channels */ getChannels: function () { return _.clone(this.channels); @@ -282,12 +282,13 @@ var ChatManager = AbstractService.extend({ * Show the list of available commands next to a message (e.g. star) * * @param {Object} channel - * @return {Array} + * @return {Array} list of commands */ getCommands: function (channel) { - return _.filter(this.commands, function (command) { + var commands = _.filter(this.commands, function (command) { return !command.channel_types || _.contains(command.channel_types, channel.server_type); }); + return commands; }, /** * Returns the record id of ir.ui.menu for Discuss @@ -309,7 +310,7 @@ var ChatManager = AbstractService.extend({ /** * Returns list of emojis Objects * - * @return {Object[]} + * @return {Object[]} list of emojis * ['id', 'source', 'unicode_source', 'substitution', 'description'] */ getEmojis: function () { @@ -319,9 +320,10 @@ var ChatManager = AbstractService.extend({ * Get the last seen message for a given channel * * @param {Object} channel - * @return {Object} Message Object + * @return {Object|undefined} last seen Message Object (if any) */ getLastSeenMessage: function (channel) { + var result; if (channel.last_seen_message_id) { var messages = channel.cache['[]'].messages; var msg = _.findWhere(messages, {id: channel.last_seen_message_id}); @@ -332,9 +334,10 @@ var ChatManager = AbstractService.extend({ msg = messages[i]; i++; } - return msg; + result = msg; } } + return result; }, /** * Get all listeners of a channel. @@ -378,22 +381,22 @@ var ChatManager = AbstractService.extend({ * Gets message from its id * * @param {integer} msgID - * @return {Object|undefined} Message Object + * @return {Object|undefined} Message Object (if any) */ getMessage: function (msgID) { return _.findWhere(this.messages, {id: msgID}); }, /** * Gets messages from channel or ids or record (model and res_id): - * + * * 1. From channel if we have 'channelID' in options * 2. From ids if we have 'ids' in options * 3. From model if we have 'model' and 'res_id' in options - * + * * Rule of precedence: - * + * * 'channelID' < 'ids' < 'model' and 'res_id' - * + * * If we have none of the cases above, we return an empty list. * * @param {Object} [options] @@ -451,7 +454,7 @@ var ChatManager = AbstractService.extend({ * Returns the number of messages received from followed channels * + all messages where the current user is notified. * - * @return {integer} + * @return {integer} needaction counter */ getNeedactionCounter: function () { return this.needactionCounter; @@ -459,7 +462,7 @@ var ChatManager = AbstractService.extend({ /** * Gets the number of starred message * - * @return {integer} + * @return {integer} starred counter */ getStarredCounter: function () { return this.starredCounter; @@ -467,7 +470,7 @@ var ChatManager = AbstractService.extend({ /** * Gets the number of conversation which contains unread messages * - * @return {integer} + * @return {integer} unread conversation counter */ getUnreadConversationCounter: function () { return this.unreadConversationCounter; @@ -491,7 +494,7 @@ var ChatManager = AbstractService.extend({ /** * join an existing channel * See @createChannel to join a new channel - * + * * @param {integer} channelID * @param {Object} [options] * @return {$.Promise} resolved with channel object @@ -593,7 +596,7 @@ var ChatManager = AbstractService.extend({ }, /** * Open the channel: - * + * * 1. If discuss is opened, asks discuss to open the channel * 2. Otherwise, asks the chat_window_manager to detach the channel * @@ -604,14 +607,14 @@ var ChatManager = AbstractService.extend({ }, /** * Prepares and sends a message to the server: - * + * * 1. Either the message is posted on a channel * 2. Or the message is posted in a model's record (chatter) * * Rule of precedence: - * + * * 'channelID' < 'model' & 'res_id' - * + * * If options as none of these parameters, do nothing and return * a promise no resolved item. * @@ -856,7 +859,7 @@ var ChatManager = AbstractService.extend({ * @param {string} [data.state] e.g. 'open', 'folded' * @param {Object|integer} [options=undefined] * @param {boolean} [options.silent] - * @return {Object} the newly or already exisiting channel + * @return {Object} the newly or already existing channel */ _addChannel: function (data, options) { options = typeof options === "object" ? options : {}; @@ -909,7 +912,7 @@ var ChatManager = AbstractService.extend({ * @param {Object} data message data * @param {integer} data.id * @param {Object} [options] - * @param {Array} [options.domain] + * @param {Array} [options.domain] * @param {boolean} [options.increment_unread] whether we should increment * the unread_counter of channel. * @param {boolean} [options.silent] whether it should inform in the chatBus @@ -1134,6 +1137,7 @@ var ChatManager = AbstractService.extend({ } } }); + self.busBus.start_polling(); }); }, @@ -1466,7 +1470,7 @@ var ChatManager = AbstractService.extend({ } }, /** - * Updates channel_inbox when a message has marked as read. + * Updates channel_inbox when a message has marked as read. * * @private * @param {Object} data diff --git a/addons/mail/static/src/js/services/chat_window_manager.js b/addons/mail/static/src/js/services/chat_window_manager.js index be8588f9cb1..bab6fbf7729 100644 --- a/addons/mail/static/src/js/services/chat_window_manager.js +++ b/addons/mail/static/src/js/services/chat_window_manager.js @@ -40,8 +40,7 @@ var ChatWindowManager = AbstractService.extend({ spaceLeft: 0, windowsDropdownIsOpen: false, // used to keep dropdown open when closing chat windows }; - var chatReady = this.call('chat_manager', 'isReady'); - chatReady.then(function () { + this.call('chat_manager', 'isReady').then(function () { var channels = self.call('chat_manager', 'getChannels'); _.each(channels, function (channel) { if (channel.is_detached) { @@ -88,7 +87,7 @@ var ChatWindowManager = AbstractService.extend({ * @param {boolean} [options.passively] if set to true, open the chat window * without focusing the input and marking messages as read if it is not * open yet, and do nothing otherwise - */ + */ openChat: function (session, options) { var self = this; if (!session) { @@ -188,7 +187,7 @@ var ChatWindowManager = AbstractService.extend({ }, /** * Called when unfolding the chat window - * + * * @param {Object} channel * @param {integer} channel.id * @param {boolean} [channel.is_folded] @@ -326,12 +325,13 @@ var ChatWindowManager = AbstractService.extend({ var channel = self.call('chat_manager', 'getChannel', session.id); self.call('chat_manager', 'markChannelAsSeen', channel); } - self.call('chat_manager', 'getMessages', {channelID: session.id}).then(function (messages) { - session.window.render(messages); - if (scrollBottom && messageVisible) { - session.window.thread.scroll_to(); - } - }); + self.call('chat_manager', 'getMessages', {channelID: session.id}) + .then(function (messages) { + session.window.render(messages); + if (scrollBottom && messageVisible) { + session.window.thread.scroll_to(); + } + }); } }); }, @@ -386,7 +386,7 @@ var ChatWindowManager = AbstractService.extend({ }); this.displayState.hiddenSessions = hiddenSessions; this.displayState.hiddenUnreadCounter = hiddenUnreadCounter; - + if (this.displayState.$hidden_windows_dropdown) { this.displayState.$hidden_windows_dropdown.remove(); } @@ -397,7 +397,7 @@ var ChatWindowManager = AbstractService.extend({ .appendTo($('body')); this._repositionHiddenSessionsDropdown(); this.displayState.windowsDropdownIsOpen = false; - + $hiddenWindowsDropdown.on('click', '.o_chat_header', function (event) { var sessionID = $(event.currentTarget).data('session-id'); var session = _.findWhere(hiddenSessions, {id: sessionID}); diff --git a/addons/mail/static/src/js/systray.js b/addons/mail/static/src/js/systray.js index f2dfdc631ab..d874c7756e8 100644 --- a/addons/mail/static/src/js/systray.js +++ b/addons/mail/static/src/js/systray.js @@ -36,8 +36,7 @@ var MessagingMenu = Widget.extend({ var chatBus = this.call('chat_manager', 'getChatBus'); chatBus.on("update_needaction", this, this.update_counter); chatBus.on("update_channel_unread_counter", this, this.update_counter); - var chatReady = this.call('chat_manager', 'isReady'); - chatReady.then(this.update_counter.bind(this)); + this.call('chat_manager', 'isReady').then(this.update_counter.bind(this)); return this._super(); }, is_open: function () { @@ -58,8 +57,7 @@ var MessagingMenu = Widget.extend({ // Display spinner while waiting for channels preview this.$channels_preview.html(QWeb.render('Spinner')); - var chatReady = this.call('chat_manager', 'isReady'); - chatReady.then(function () { + this.call('chat_manager', 'isReady').then(function () { var allChannels = self.call('chat_manager', 'getChannels'); var channels = _.filter(allChannels, function (channel) { if (self.filter === 'chat') { @@ -70,23 +68,28 @@ var MessagingMenu = Widget.extend({ return channel.type !== 'static'; } }); - self.call('chat_manager', 'getMessages', {channelID: 'channel_inbox'}).then(function (result) { - var res = []; - _.each(result, function (message) { - message.unread_counter = 1; - var duplicatedMessage = _.findWhere(res, {model: message.model, 'res_id': message.res_id}); - if (message.model && message.res_id && duplicatedMessage) { - message.unread_counter = duplicatedMessage.unread_counter + 1; - res[_.findIndex(res, duplicatedMessage)] = message; - } else { - res.push(message); + self.call('chat_manager', 'getMessages', {channelID: 'channel_inbox'}) + .then(function (messages) { + var res = []; + _.each(messages, function (message) { + message.unread_counter = 1; + var duplicatedMessage = _.findWhere(res, { + model: message.model, + 'res_id': message.res_id + }); + if (message.model && message.res_id && duplicatedMessage) { + message.unread_counter = duplicatedMessage.unread_counter + 1; + res[_.findIndex(res, duplicatedMessage)] = message; + } else { + res.push(message); + } + }); + if (self.filter === 'channel_inbox' || !self.filter) { + channels = _.union(channels, res); } + self.call('chat_manager', 'getChannelsPreview', channels) + .then(self._render_channels_preview.bind(self)); }); - if (self.filter === 'channel_inbox' || !self.filter) { - channels = _.union(channels, res); - } - self.call('chat_manager', 'getChannelsPreview', channels).then(self._render_channels_preview.bind(self)); - }); }); }, _render_channels_preview: function (channels_preview) { @@ -142,7 +145,7 @@ var MessagingMenu = Widget.extend({ } else { var channel = this.call('chat_manager', 'getChannel', channelID); if (channel) { - self.call('chat_manager', 'openChannel', channel); + this.call('chat_manager', 'openChannel', channel); } } }, @@ -159,10 +162,8 @@ var ActivityMenu = Widget.extend({ }, start: function () { this.$activities_preview = this.$('.o_mail_navbar_dropdown_channels'); - var chatBus = this.call('chat_manager', 'getChatBus'); - chatBus.on("activity_updated", this, this._updateCounter); - var chatReady = this.call('chat_manager', 'isReady'); - chatReady.then(this._updateCounter.bind(this)); + this.call('chat_manager', 'getChatBus').on("activity_updated", this, this._updateCounter); + this.call('chat_manager', 'isReady').then(this._updateCounter.bind(this)); this._updateActivityPreview(); return this._super(); }, diff --git a/addons/mail/static/src/js/thread_field.js b/addons/mail/static/src/js/thread_field.js index 0a745b69a96..6d036208eb2 100644 --- a/addons/mail/static/src/js/thread_field.js +++ b/addons/mail/static/src/js/thread_field.js @@ -21,8 +21,7 @@ var ThreadField = AbstractField.extend({ this.msgIDs = this.value.res_ids; }, willStart: function () { - var chatReady = this.call('chat_manager', 'isReady'); - return this.alive(chatReady); + return this.alive(this.call('chat_manager', 'isReady')); }, start: function () { var self = this; @@ -78,7 +77,7 @@ var ThreadField = AbstractField.extend({ /** * @param {Object} message * @param {integer[]} message.partner_ids - * @return {Deferred} + * @return {$.Promise} */ postMessage: function (message) { var self = this; diff --git a/addons/mail/static/tests/chat_window_tests.js b/addons/mail/static/tests/chat_window_tests.js index a892d8e26e6..ab5a9f03538 100644 --- a/addons/mail/static/tests/chat_window_tests.js +++ b/addons/mail/static/tests/chat_window_tests.js @@ -11,7 +11,11 @@ var Widget = require('web.Widget'); var createBusService = mailTestUtils.createBusService; -QUnit.module('mail', {}, function () { +QUnit.module('mail', { + beforeEach: function () { + this.services = [ChatManager, createBusService()]; + }, +}, function () { QUnit.module('chat_window'); @@ -39,6 +43,7 @@ QUnit.module('mail', {}, function () { }]; var parent = createParent({ data: {}, + services: this.services, }); var chatWindow = new ChatWindow(parent, 1, "user", false, messages, {}); @@ -67,6 +72,7 @@ QUnit.module('mail', {}, function () { } var parent = createParent({ data: {}, + services: this.services, }); var $dom = $('#qunit-fixture'); @@ -93,8 +99,6 @@ QUnit.module('mail', {}, function () { QUnit.test('emoji popover should open correctly in chat windows', function (assert) { assert.expect(1); - var BusService = createBusService(); - function createParent(params) { var widget = new Widget(); @@ -107,7 +111,7 @@ QUnit.module('mail', {}, function () { } var parent = createParent({ data: {}, - services: [ChatManager, BusService], + services: this.services, }); var $dom = $('#qunit-fixture'); diff --git a/addons/mail/static/tests/chatter_tests.js b/addons/mail/static/tests/chatter_tests.js index f3e4b1c571c..4ef63b70615 100644 --- a/addons/mail/static/tests/chatter_tests.js +++ b/addons/mail/static/tests/chatter_tests.js @@ -21,7 +21,7 @@ QUnit.module('mail', {}, function () { QUnit.module('Chatter', { beforeEach: function () { - this.BusService = createBusService(); + this.services = [ChatManager, createBusService()]; this.data = { partner: { fields: { @@ -98,6 +98,7 @@ QUnit.test('basic rendering', function (assert) { View: FormView, model: 'partner', data: this.data, + services: this.services, arch: '
' + '' + '' + @@ -121,7 +122,6 @@ QUnit.test('basic rendering', function (assert) { } return this._super(route, args); }, - services: [ChatManager, this.BusService], res_id: 2, }); @@ -150,6 +150,7 @@ QUnit.test('chatter in create mode', function (assert) { View: FormView, model: 'partner', data: this.data, + services: this.services, arch: '' + '' + '' + @@ -165,7 +166,6 @@ QUnit.test('chatter in create mode', function (assert) { } return this._super(route, args); }, - services: [ChatManager, this.BusService], }); assert.strictEqual(form.$('.o_chatter').length, 1, @@ -213,6 +213,7 @@ QUnit.test('chatter rendering inside the sheet', function (assert) { View: FormView, model: 'partner', data: this.data, + services: this.services, arch: '' + '' + '' + @@ -232,7 +233,6 @@ QUnit.test('chatter rendering inside the sheet', function (assert) { } return this._super(route, args); }, - services: [ChatManager, this.BusService], }); assert.strictEqual(form.$('.o_chatter').length, 1, @@ -241,7 +241,7 @@ QUnit.test('chatter rendering inside the sheet', function (assert) { form.$buttons.find('.o_form_button_create').click(); assert.ok(form.$el.find('.o_form_view').hasClass('o_form_editable'), "we should be in create mode"); - + assert.strictEqual(form.$('.o_chatter').length, 1, "chatter should be displayed"); @@ -396,6 +396,7 @@ QUnit.test('chatter: post, receive and star messages', function (assert) { View: FormView, model: 'partner', data: this.data, + services: [ChatManager, BusService], arch: '' + '' + '' + @@ -455,7 +456,6 @@ QUnit.test('chatter: post, receive and star messages', function (assert) { return this._super(route, args); }, session: {}, - services: [ChatManager, BusService], }); assert.ok(form.$('.o_chatter_topbar .o_chatter_button_log_note').length, @@ -546,6 +546,7 @@ QUnit.test('chatter: post a message and switch in edit mode', function (assert) View: FormView, model: 'partner', data: this.data, + services: this.services, arch: '' + '' + '' + @@ -582,10 +583,9 @@ QUnit.test('chatter: post a message and switch in edit mode', function (assert) }); return $.when(42); } - + return this._super(route, args); }, - services: [ChatManager, this.BusService], }); assert.strictEqual(form.$('.o_thread_message').length, 0, "thread should not contain messages"); @@ -652,6 +652,7 @@ QUnit.test('chatter: Attachment viewer', function (assert) { View: FormView, model: 'partner', data: this.data, + services: this.services, arch: '' + '' + '' + @@ -675,7 +676,6 @@ QUnit.test('chatter: Attachment viewer', function (assert) { } return this._super.apply(this, arguments); }, - services: [ChatManager, this.BusService], }); assert.strictEqual(form.$('.o_thread_message .o_attachment').length, 4, "there should be three attachment on message"); @@ -720,6 +720,7 @@ QUnit.test('form activity widget: schedule next activity', function (assert) { View: FormView, model: 'partner', data: this.data, + services: this.services, arch: '' + '' + '' + @@ -746,7 +747,6 @@ QUnit.test('form activity widget: schedule next activity', function (assert) { } return this._super.apply(this, arguments); }, - services: [ChatManager, this.BusService], intercepts: { do_action: function (event) { assert.deepEqual(event.data.action, { @@ -784,6 +784,7 @@ QUnit.test('form activity widget: schedule activity does not discard changes', f View: FormView, model: 'partner', data: this.data, + services: this.services, arch: '' + '' + '' + @@ -800,7 +801,6 @@ QUnit.test('form activity widget: schedule activity does not discard changes', f } return this._super.apply(this, arguments); }, - services: [ChatManager, this.BusService], intercepts: { do_action: function (event) { event.data.options.on_close(); @@ -852,6 +852,7 @@ QUnit.test('form activity widget: mark as done and remove', function (assert) { View: FormView, model: 'partner', data: this.data, + services: this.services, arch: '' + '' + '' + @@ -903,7 +904,6 @@ QUnit.test('form activity widget: mark as done and remove', function (assert) { } return this._super.apply(this, arguments); }, - services: [ChatManager, this.BusService], }); assert.strictEqual(form.$('.o_mail_activity .o_thread_message').length, 2, @@ -951,6 +951,7 @@ QUnit.test('followers widget: follow/unfollow, edit subtypes', function (assert) View: FormView, model: 'partner', data: this.data, + services: this.services, arch: '' + '' + '' + @@ -1071,6 +1072,7 @@ QUnit.test('followers widget: do not display follower duplications', function (a View: FormView, model: 'partner', data: this.data, + services: this.services, arch: '' + '' + '
' + @@ -1129,6 +1131,7 @@ QUnit.test('does not render and crash when destroyed before chat system is ready View: FormView, model: 'partner', data: this.data, + services: this.services, arch: '' + '' + '' + @@ -1159,7 +1162,6 @@ QUnit.test('does not render and crash when destroyed before chat system is ready } return this._super(route, args); }, - services: [ChatManager, this.BusService], intercepts: { get_session: function (event) { event.stopPropagation(); diff --git a/addons/mail/static/tests/discuss_mobile_tests.js b/addons/mail/static/tests/discuss_mobile_tests.js index 431a433c19f..4e96c87f512 100644 --- a/addons/mail/static/tests/discuss_mobile_tests.js +++ b/addons/mail/static/tests/discuss_mobile_tests.js @@ -11,7 +11,7 @@ QUnit.module('mail', {}, function () { QUnit.module('Discuss client action in mobile', { beforeEach: function () { - this.BusService = createBusService(); + this.services = [ChatManager, createBusService()]; this.data = { 'mail.message': { fields: {}, @@ -33,17 +33,17 @@ QUnit.test('mobile basic rendering', function (assert) { id: 1, context: {}, params: {}, + data: this.data, + services: this.services, mockRPC: function (route, args) { if (args.method === 'message_fetch') { return $.when([]); } return this._super.apply(this, arguments); }, - services: [ChatManager, this.BusService], }); - var chatReady = discuss.call('chat_manager', 'isReady'); - chatReady.then(function () { + discuss.call('chat_manager', 'isReady').then(function () { // test basic rendering in mobile assert.strictEqual(discuss.$('.o_mail_chat_mobile_control_panel').length, 1, "should have rendered a control panel"); diff --git a/addons/mail/static/tests/helpers/test_utils.js b/addons/mail/static/tests/helpers/test_utils.js index 3d107688537..e0a8a0a13de 100644 --- a/addons/mail/static/tests/helpers/test_utils.js +++ b/addons/mail/static/tests/helpers/test_utils.js @@ -17,43 +17,41 @@ var Widget = require('web.Widget'); /** * Create a mock bus_service, using 'bus' instead of bus.bus - * + * * @param {web.bus} bus * @return {AbstractService} the mock bus_service */ function createBusService(bus) { - var BusService = AbstractService.extend({ name: 'bus_service', /** * @override */ - init: function (parent) { + init: function () { this._super.apply(this, arguments); if (!bus) { bus = new Bus(); } this.bus = new _.extend(bus, { /** - * Do nothing + * Do nothing */ start_polling: function () {}, }); }, - + //-------------------------------------------------------------------------- // Public //-------------------------------------------------------------------------- - + /** * Get the bus */ getBus: function () { return this.bus; }, - }); - + return BusService; } diff --git a/addons/mail/static/tests/systray_tests.js b/addons/mail/static/tests/systray_tests.js index 2481c4d6550..4ac60c7e123 100644 --- a/addons/mail/static/tests/systray_tests.js +++ b/addons/mail/static/tests/systray_tests.js @@ -13,7 +13,7 @@ QUnit.module('mail', {}, function () { QUnit.module('ActivityMenu', { beforeEach: function () { - this.BusService = createBusService(); + this.services = [ChatManager, createBusService()]; this.data = { 'mail.activity.menu': { fields: { @@ -58,13 +58,13 @@ QUnit.test('activity menu widget: menu with no records', function (assert) { var activityMenu = new systray.ActivityMenu(); testUtils.addMockEnvironment(activityMenu, { + services: this.services, mockRPC: function (route, args) { if (args.method === 'activity_user_count') { return $.when([]); } return this._super(route, args); }, - services: [ChatManager, this.BusService] }); activityMenu.appendTo($('#qunit-fixture')); assert.ok(activityMenu.$('.o_no_activity').hasClass('o_no_activity'), "should not have instance of widget"); @@ -76,13 +76,13 @@ QUnit.test('activity menu widget: activity menu with 3 records', function (asser var self = this; var activityMenu = new systray.ActivityMenu(); testUtils.addMockEnvironment(activityMenu, { + services: this.services, mockRPC: function (route, args) { if (args.method === 'activity_user_count') { return $.when(self.data['mail.activity.menu']['records']); } return this._super(route, args); }, - services: [ChatManager, this.BusService], }); activityMenu.appendTo($('#qunit-fixture')); assert.ok(activityMenu.$el.hasClass('o_mail_navbar_item'), 'should be the instance of widget'); 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 2dd69d7426c..b4b192c1ea9 100644 --- a/addons/web/static/src/js/chrome/abstract_web_client.js +++ b/addons/web/static/src/js/chrome/abstract_web_client.js @@ -245,6 +245,10 @@ var AbstractWebClient = Widget.extend(ServiceProviderMixin, { * identified by strings. Each time a title part is changed, all parts * are gathered, ordered by alphabetical order and displayed in the title * of the window separated by ``-``. + * + * @private + * @param {string} part + * @param {string} title */ set_title_part: function (part, title) { var tmp = _.clone(this.get("title_part")); diff --git a/addons/web/static/src/js/core/service_mixins.js b/addons/web/static/src/js/core/service_mixins.js index 052e2a6a3d9..4ab0a361988 100644 --- a/addons/web/static/src/js/core/service_mixins.js +++ b/addons/web/static/src/js/core/service_mixins.js @@ -97,6 +97,11 @@ var rpc = require('web.rpc'); * @name ServicesMixin */ var ServicesMixin = { + /** + * @param {string} service + * @param {string} method + * @return {any} result of the service called + */ call: function (service, method) { var args = Array.prototype.slice.call(arguments, 2); var result; diff --git a/addons/web/static/tests/chrome/action_manager_tests.js b/addons/web/static/tests/chrome/action_manager_tests.js index 1ee50daaf65..6c580e6b9dc 100644 --- a/addons/web/static/tests/chrome/action_manager_tests.js +++ b/addons/web/static/tests/chrome/action_manager_tests.js @@ -1333,6 +1333,7 @@ QUnit.module('ActionManager', { actions: this.actions, archs: this.archs, data: this.data, + services: [ReportService], mockRPC: function (route, args) { assert.step(args.method || route); if (route === '/report/check_wkhtmltopdf') { @@ -1340,7 +1341,6 @@ QUnit.module('ActionManager', { } return this._super.apply(this, arguments); }, - services: [ReportService], session: { get_file: function (params) { assert.step(params.url); @@ -1372,6 +1372,7 @@ QUnit.module('ActionManager', { actions: this.actions, archs: this.archs, data: this.data, + services: [ReportService], mockRPC: function (route, args) { assert.step(args.method || route); if (route === '/report/check_wkhtmltopdf') { @@ -1379,7 +1380,6 @@ QUnit.module('ActionManager', { } return this._super.apply(this, arguments); }, - services: [ReportService], session: { get_file: function (params) { assert.step(params.url); @@ -1425,6 +1425,7 @@ QUnit.module('ActionManager', { actions: this.actions, archs: this.archs, data: this.data, + services: [ReportService], mockRPC: function (route, args) { assert.step(args.method || route); if (route === '/report/check_wkhtmltopdf') { @@ -1435,7 +1436,6 @@ QUnit.module('ActionManager', { } return this._super.apply(this, arguments); }, - services: [ReportService], session: { get_file: function (params) { assert.step(params.url); // should not be called diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js index 0a1fd9128e6..ecedd4c5bc5 100644 --- a/addons/web/static/tests/fields/relational_fields_tests.js +++ b/addons/web/static/tests/fields/relational_fields_tests.js @@ -6271,7 +6271,7 @@ QUnit.module('relational_fields', { } if (args.method === 'write') { assert.deepEqual(args.args[1].timmy, [[6, false, [12]]], - 'should properly write ids') + 'should properly write ids'); } return this._super.apply(this, arguments); }, @@ -9354,7 +9354,7 @@ QUnit.module('relational_fields', { $input = form.$('.o_field_many2manytags span[data-id=13]'); // selects 'red' tag $input.click(); // opens the colorpicker dropdown assert.ok(form.$('.o_field_many2manytags span[data-id=13] .o_colorpicker .o_checkbox'), "should have a checkbox in the colorpicker dropdown menu"); - + $input.click(); var $checkBox = form.$('.o_field_many2manytags span[data-id=13] .o_colorpicker .o_checkbox input'); assert.notOk($checkBox.is(':checked'), "should have unticked checkbox in colorpicker dropdown menu"); diff --git a/addons/web/static/tests/views/view_dialogs_tests.js b/addons/web/static/tests/views/view_dialogs_tests.js index 08ddd2c91c2..014435194f3 100644 --- a/addons/web/static/tests/views/view_dialogs_tests.js +++ b/addons/web/static/tests/views/view_dialogs_tests.js @@ -21,7 +21,6 @@ QUnit.module('Views', { ], }, }; - }, }, function () { diff --git a/addons/web_tour/static/src/js/tour_service.js b/addons/web_tour/static/src/js/tour_service.js index eb25bc4a3aa..d1d46625560 100644 --- a/addons/web_tour/static/src/js/tour_service.js +++ b/addons/web_tour/static/src/js/tour_service.js @@ -74,8 +74,8 @@ return session.is_bound.then(function () { * Once the DOM is ready, we still have to wait all the modules are loaded before completing the tours * registration and starting listening for DOM mutations. */ - $.when(load_def).then(function () { - _.defer(function () { + $.when(load_def).then(function () { + _.defer(function () { tour_manager._register_all(observe); if (observe) { observer.observe(document.body, {