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 3492af748f5..612d04c577a 100644 --- a/addons/web/static/src/js/chrome/abstract_web_client.js +++ b/addons/web/static/src/js/chrome/abstract_web_client.js @@ -21,7 +21,6 @@ var Dialog = require('web.Dialog'); var dom = require('web.dom'); var KeyboardNavigationMixin = require('web.KeyboardNavigationMixin'); var Loading = require('web.Loading'); -var NotificationManager = require('web.NotificationManager'); var RainbowMan = require('web.RainbowMan'); var ServiceProviderMixin = require('web.ServiceProviderMixin'); var session = require('web.session'); @@ -31,6 +30,7 @@ var _t = core._t; var qweb = core.qweb; var AbstractWebClient = Widget.extend(ServiceProviderMixin, KeyboardNavigationMixin, { + dependencies: ['notification'], events: _.extend(KeyboardNavigationMixin.events, {}), custom_events: { clear_uncommitted_changes: function (e) { @@ -46,13 +46,6 @@ var AbstractWebClient = Widget.extend(ServiceProviderMixin, KeyboardNavigationMi // the next events are dedicated to generic services required by // downstream widgets. Mainly side effects, such as rpcs, notifications // or cache. - - // notifications, warnings and effects - notification: function (e) { - if (this.notification_manager) { - this.notification_manager.notify(e.data.title, e.data.message, e.data.sticky); - } - }, warning: '_onDisplayWarning', load_action: '_onLoadAction', load_views: function (event) { @@ -120,7 +113,6 @@ var AbstractWebClient = Widget.extend(ServiceProviderMixin, KeyboardNavigationMi self.bind_events(); return $.when( self.set_action_manager(), - self.set_notification_manager(), self.set_loading() ); }).then(function () { @@ -174,8 +166,8 @@ var AbstractWebClient = Widget.extend(ServiceProviderMixin, KeyboardNavigationMi } this._hideAccessKeyOverlay(); }); - core.bus.on('connection_lost', this, this.on_connection_lost); - core.bus.on('connection_restored', this, this.on_connection_restored); + core.bus.on('connection_lost', this, this._onConnectionLost); + core.bus.on('connection_restored', this, this._onConnectionRestored); // crash manager integration session.on('error', crash_manager, crash_manager.rpc_error); @@ -222,10 +214,6 @@ var AbstractWebClient = Widget.extend(ServiceProviderMixin, KeyboardNavigationMi }); }); }, - set_notification_manager: function () { - this.notification_manager = new NotificationManager(this); - return this.notification_manager.appendTo(this.$el); - }, set_loading: function () { this.loading = new Loading(this); return this.loading.appendTo(this.$el); @@ -309,24 +297,6 @@ var AbstractWebClient = Widget.extend(ServiceProviderMixin, KeyboardNavigationMi // -------------------------------------------------------------- // Connection notifications // -------------------------------------------------------------- - on_connection_lost: function () { - this.connection_notification = this.notification_manager.notify( - _t('Connection lost'), - _t('Trying to reconnect...'), - true - ); - }, - on_connection_restored: function () { - if (this.connection_notification) { - this.connection_notification.destroy(); - this.notification_manager.notify( - _t('Connection restored'), - _t('You are back online'), - false - ); - this.connection_notification = false; - } - }, /** * Handler to be overridden, called each time the UI is updated by the * ActionManager. @@ -347,6 +317,34 @@ var AbstractWebClient = Widget.extend(ServiceProviderMixin, KeyboardNavigationMi // Handlers //-------------------------------------------------------------------------- + /** + * Whenever the connection is lost, we need to notify the user. + * + * @private + */ + _onConnectionLost: function () { + this.connectionNotificationID = this.call('notification', 'notify', { + title: _t('Connection lost'), + message: _t('Trying to reconnect...'), + sticky: true + }); + }, + /** + * Whenever the connection is restored, we need to notify the user. + * + * @private + */ + _onConnectionRestored: function () { + if (this.connectionNotificationID) { + this.call('notification', 'close', this.connectionNotificationID); + this.call('notification', 'notify', { + title: _t('Connection restored'), + message: _t('You are back online'), + sticky: false + }); + this.connectionNotificationID = false; + } + }, /** * @private * @param {OdooEvent} e @@ -360,7 +358,7 @@ var AbstractWebClient = Widget.extend(ServiceProviderMixin, KeyboardNavigationMi .then(e.data.on_success); }, /** - * Displays a warning in a dialog of with the NotificationManager + * Displays a warning in a dialog or with the notification service * * @private * @param {OdooEvent} e @@ -368,7 +366,7 @@ var AbstractWebClient = Widget.extend(ServiceProviderMixin, KeyboardNavigationMi * @param {string} e.data.title the warning's title * @param {string} [e.data.type] 'dialog' to display in a dialog * @param {boolean} [e.data.sticky] whether or not the warning should be - * sticky (if displayed with the NotificationManager) + * sticky (if displayed with the Notification) */ _onDisplayWarning: function (e) { var data = e.data; @@ -378,8 +376,8 @@ var AbstractWebClient = Widget.extend(ServiceProviderMixin, KeyboardNavigationMi title: data.title, $content: qweb.render("CrashManager.warning", data), }).open(); - } else if (this.notification_manager) { - this.notification_manager.warn(e.data.title, e.data.message, e.data.sticky); + } else { + this.call('notification', 'notify', e.data); } }, /** diff --git a/addons/web/static/src/js/core/service_mixins.js b/addons/web/static/src/js/core/service_mixins.js index 4ab0a361988..463b0aef4b1 100644 --- a/addons/web/static/src/js/core/service_mixins.js +++ b/addons/web/static/src/js/core/service_mixins.js @@ -185,10 +185,10 @@ var ServicesMixin = { return def; }, do_notify: function (title, message, sticky, className) { - this.trigger_up('notification', {title: title, message: message, sticky: sticky, className: className}); + return this.call('notification', 'notify', {title: title, message: message, sticky: sticky, className: className}); }, do_warn: function (title, message, sticky, className) { - this.trigger_up('warning', {title: title, message: message, sticky: sticky, className: className}); + return this.call('notification', 'notify', {type: 'warning', title: title, message: message, sticky: sticky, className: className}); }, }; diff --git a/addons/web/static/src/js/services/notification_manager.js b/addons/web/static/src/js/services/notification_manager.js deleted file mode 100644 index b9b128be78d..00000000000 --- a/addons/web/static/src/js/services/notification_manager.js +++ /dev/null @@ -1,90 +0,0 @@ -odoo.define('web.NotificationManager', function (require) { -"use strict"; - -/** - * NotificationManager - * - * The NotificationManager is simply a widget that will be instantiated by the - * web client to display notifications in the top/right part of the screen. - * - * If you want to display such a notification, you probably do not want to do it - * by using this file. The proper way is to use the do_warn or do_notify - * methods on the Widget class. - * - * @todo This class should be converted into a proper service. Need the right - * service API to do it. - */ - -var Notification = require('web.Notification'); -var Widget = require('web.Widget'); - -var NotificationManager = Widget.extend({ - className: 'o_notification_manager', - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - /** - * Display a notification at the appropriate location, and returns the - * reference to the same widget (useful to get back a reference). - * - * Note that this method does not wait for the appendTo method to complete. - * - * @param {Notification} notification - * @returns {Notification} - */ - display: function (notification) { - notification.appendTo(this.$el); - return notification; - }, - /** - * Will display a notification of type 'notification' - * - * @param {string} title - * @param {string} text - * @param {boolean} sticky - * @returns {Notification} - */ - notify: function (title, text, sticky) { - return this._display(title, text, sticky, 'notification'); - }, - /** - * Will display a notification of type 'warning' - * - * @param {string} title - * @param {string} text - * @param {boolean} sticky - * @returns {Notification} - */ - warn: function (title, text, sticky) { - return this._display(title, text, sticky, 'warning'); - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * Display a notification - * - * @private - * @param {string} title - * @param {string} text - * @param {boolean} sticky - * @param {string} type either 'notification' or 'warning' - * @returns {Notification} - */ - _display: function (title, text, sticky, type) { - return this.display(new Notification(this, { - title: title, - text: text, - sticky: sticky, - type: type, - })); - } -}); - -return NotificationManager; - -}); diff --git a/addons/web/static/src/js/services/notification_service.js b/addons/web/static/src/js/services/notification_service.js new file mode 100644 index 00000000000..7d6ad473fe4 --- /dev/null +++ b/addons/web/static/src/js/services/notification_service.js @@ -0,0 +1,108 @@ +odoo.define('web.NotificationService', function (require) { +"use strict"; + +var AbstractService = require('web.AbstractService'); +var Notification = require('web.Notification'); +var core = require('web.core'); +var id = 0; + +/** + * Notification Service + * + * The Notification Service is simply a service used to display notifications in + * the top/right part of the screen. + * + * If you want to display such a notification, you probably do not want to do it + * by using this file. The proper way is to use the do_warn or do_notify + * methods on the Widget class. + */ + +var NotificationService = AbstractService.extend({ + name: 'notification', + + custom_events: { + close: '_onCloseNotification', + }, + + init: function () { + this._super.apply(this, arguments); + this.notifications = {}; + }, + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * It may sometimes be useful to close programmatically a notification. For + * example, when there is a sticky notification that warns the user about + * some condition (connection lost), but the condition do not apply anymore. + * + * @param {number} notificationId + * @param {boolean} [silent=false] if true, the notification does not call + * onClose callback + */ + close: function (notificationId, silent) { + var notification = this.notifications[notificationId]; + if (!notification) { + return; + } + notification.close(silent); + }, + /** + * Display a notification at the appropriate location, and returns the + * reference id to the same widget. + * + * Note that this method does not wait for the appendTo method to complete. + * + * @param {Object} params + * @param {string} params.title notification title + * @param {string} params.message notification main message + * @param {string} params.type 'notification' or 'warning' + * @param {boolean} [params.sticky=false] if true, the notification will stay + * visible until the user clicks on it. + * @param {string} [params.className] className to add on the dom + * @param {function} [params.onClose] callback when the user click on the x + * or when the notification is auto close (no sticky) + * @param {Object[]} params.buttons + * @param {function} params.buttons[0].click callback on click + * @param {Boolean} [params.buttons[0].primary] display the button as primary + * @param {string} [params.buttons[0].text] button label + * @param {string} [params.buttons[0].icon] font-awsome className or image src + * @returns {Number} notification id + */ + notify: function (options) { + if (!this.$el) { + this.$el = $('
'); + this.$el.prependTo('body'); + } + var notification = this.notifications[++id] = new Notification(this, options); + notification.appendTo(this.$el); + return id; + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + * @param {OdooEvent} ev + */ + _onCloseNotification: function (ev) { + ev.stopPropagation(); + for (var notificationId in this.notifications) { + if (this.notifications[notificationId] === ev.target) { + delete this.notifications[notificationId]; + break; + } + } + }, +}); + + +core.serviceRegistry.add('notification', NotificationService); + + +return NotificationService; +}); diff --git a/addons/web/static/src/js/widgets/notification.js b/addons/web/static/src/js/widgets/notification.js index d05a0be380c..e9425144085 100644 --- a/addons/web/static/src/js/widgets/notification.js +++ b/addons/web/static/src/js/widgets/notification.js @@ -17,26 +17,45 @@ var Widget = require('web.Widget'); var Notification = Widget.extend({ template: 'Notification', events: { - 'click .o_close': '_onClose', + 'click > .o_close': '_onClose', + 'click .o_buttons button': '_onClickButton' }, + _autoCloseDelay: 2500, + _animationDelay: 400, /** * @override * @param {Widget} parent * @param {Object} params * @param {string} params.title notification title - * @param {string} params.text notification main text + * @param {string} params.message notification main message * @param {string} params.type 'notification' or 'warning' * @param {boolean} [params.sticky=false] if true, the notification will stay * visible until the user clicks on it. + * @param {string} [params.className] className to add on the dom + * @param {function} [params.onClose] callback when the user click on the x + * or when the notification is auto close (no sticky) + * @param {Object[]} params.buttons + * @param {function} params.buttons[0].click callback on click + * @param {boolean} [params.buttons[0].primary] display the button as primary + * @param {string} [params.buttons[0].text] button label + * @param {string} [params.buttons[0].icon] font-awsome className or image src */ init: function (parent, params) { this._super.apply(this, arguments); this.title = params.title; - this.text = params.text; - this.sticky = !!params.sticky; - this.type = params.type; + this.message = params.message; + this.buttons = params.buttons || []; + this.sticky = !!this.buttons.length || !!params.sticky; + this.type = params.type || 'notification'; + this.className = params.className || ''; + this._closeCallback = params.onClose; + this.icon = 'fa-lightbulb-o'; + if (this.buttons && this.buttons.length) { + this.icon = 'fa-question-circle-o'; + } if (this.type === 'warning') { - this.template = 'Warning'; + this.icon = 'fa-exclamation'; + this.className += ' o_error'; } }, /** @@ -44,46 +63,68 @@ var Notification = Widget.extend({ */ start: function () { var self = this; - if (this.type === 'warning') { - this.$el.addClass('o_error'); - } return this._super.apply(this, arguments).then(function () { - self.$el.animate({opacity: 1.0}, 400, "swing", function () { + self.$el.animate({opacity: 1.0}, self._animationDelay, "swing", function () { if(!self.sticky) { - setTimeout(self._destroy.bind(self), 2500); + setTimeout(function () { + self.close(); + }, self._autoCloseDelay); } }); }); }, //-------------------------------------------------------------------------- - // Private + // Public //-------------------------------------------------------------------------- /** - * This method is used to destroy the widget with a nice animation. We + * This method is used to destroy the widget with a nice animation. We * first perform an animation, then call the real destroy method. * * @private + * @param {boolean} [silent=false] if true, the notification does not call + * _closeCallback method */ - _destroy: function () { + close: function (silent) { var self = this; - this.$el.animate({opacity: 0.0}, 400, "swing", function() { - self.$el.animate({height: 0}, 400, "swing", self.destroy.bind(self)); - }); + this.trigger_up('close'); + if (!silent && !this._buttonClicked) { + if (this._closeCallback) { + this._closeCallback(); + } + } + this.$el.animate({opacity: 0.0, height: 0}, this._animationDelay, "swing", self.destroy.bind(self)); }, //-------------------------------------------------------------------------- // Handlers //-------------------------------------------------------------------------- + /** + * @private + * @param {MouseEvent} ev + */ + _onClickButton: function (ev) { + ev.preventDefault(); + if (this._buttonClicked) { + return; + } + this._buttonClicked = true; + var index = $(ev.currentTarget).index(); + var button = this.buttons[index]; + if (button.click) { + button.click(); + } + this.close(true); + }, /** * @private * @param {MouseEvent} ev */ _onClose: function (ev) { ev.preventDefault(); - this._destroy(); + this.close(); }, }); diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 592c41a518c..20fee925a0f 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -115,20 +115,24 @@ -
+
- +
-
+
+
+ +
- - - - -
diff --git a/addons/web/static/tests/chrome/action_manager_tests.js b/addons/web/static/tests/chrome/action_manager_tests.js index 350bf20a06a..656bc3b8e42 100644 --- a/addons/web/static/tests/chrome/action_manager_tests.js +++ b/addons/web/static/tests/chrome/action_manager_tests.js @@ -2,7 +2,7 @@ odoo.define('web.action_manager_tests', function (require) { "use strict"; var ReportClientAction = require('report.client_action'); - +var NotificationService = require('web.NotificationService'); var AbstractAction = require('web.AbstractAction'); var AbstractStorageService = require('web.AbstractStorageService'); var BasicFields = require('web.basic_fields'); @@ -1770,6 +1770,7 @@ QUnit.module('ActionManager', { assert.step(params.url); params.success(); params.complete(); + return true; }, }, }); @@ -1797,7 +1798,11 @@ QUnit.module('ActionManager', { actions: this.actions, archs: this.archs, data: this.data, - services: [ReportService], + services: [ReportService, NotificationService.extend({ + notify: function (params) { + assert.step(params.type || 'notification'); + } + })], mockRPC: function (route, args) { assert.step(args.method || route); if (route === '/report/check_wkhtmltopdf') { @@ -1810,11 +1815,7 @@ QUnit.module('ActionManager', { assert.step(params.url); params.success(); params.complete(); - }, - }, - intercepts: { - notification: function () { - assert.step('notification'); + return true; }, }, }); @@ -1851,7 +1852,11 @@ QUnit.module('ActionManager', { actions: this.actions, archs: this.archs, data: this.data, - services: [ReportService], + services: [ReportService, NotificationService.extend({ + notify: function (params) { + assert.step(params.type || 'notification'); + } + })], mockRPC: function (route, args) { assert.step(args.method || route); if (route === '/report/check_wkhtmltopdf') { @@ -1865,11 +1870,7 @@ QUnit.module('ActionManager', { session: { get_file: function (params) { assert.step(params.url); // should not be called - }, - }, - intercepts: { - notification: function () { - assert.step('notification'); + return true; }, }, }); diff --git a/addons/web/static/tests/services/notification_service_tests.js b/addons/web/static/tests/services/notification_service_tests.js new file mode 100644 index 00000000000..68227093cea --- /dev/null +++ b/addons/web/static/tests/services/notification_service_tests.js @@ -0,0 +1,247 @@ +odoo.define('web.notification_tests', function (require) { +"use strict"; + +var AbstractView = require('web.AbstractView'); +var Notification = require('web.Notification'); +var NotificationService = require('web.NotificationService'); + +var testUtils = require('web.test_utils'); +var createView = testUtils.createView; + + +QUnit.module('Services', { + beforeEach: function () { + testUtils.patch(Notification, { + _autoCloseDelay: 0, + _animationDelay: 0, + }); + this.viewParams = { + View: AbstractView, + arch: '', + data: { + fake_model: { + fields: {}, + record: [], + }, + }, + model: 'fake_model', + services: [NotificationService], + }; + }, + afterEach: function () { + testUtils.unpatch(Notification); + } +}, function () { + QUnit.module('Notification'); + + QUnit.test('Display a simple notification', function (assert) { + var done = assert.async(); + assert.expect(4); + + var view = createView(this.viewParams); + view.call('notification', 'notify', { + title: 'a', + message: 'b', + }); + var $notification = $('body .o_notification_manager .o_notification'); + assert.strictEqual(_.str.trim($notification.html().replace(/\s+/g, ' ')), + "
a
b
", + "should display notification"); + assert.strictEqual($notification.find('.o_close').length, 0, "should not display the close button in "); + setTimeout(function () { + assert.strictEqual($notification.is(':hidden'), true, "should hide the notification"); + assert.strictEqual($('body .o_notification_manager .o_notification').length, 0, "should destroy the notification"); + view.destroy(); + done(); + }); + }); + + QUnit.test('Display a warning', function (assert) { + var done = assert.async(); + assert.expect(1); + + var view = createView(this.viewParams); + view.call('notification', 'notify', { + title: 'a', + message: 'b', + type: 'warning' + }); + var $notification = $('body .o_notification_manager .o_notification'); + assert.strictEqual(_.str.trim($notification.html().replace(/\s+/g, ' ')), + "
a
b
", + "should display notification"); + view.destroy(); + setTimeout(done); + }); + + QUnit.test('Display a sticky notification', function (assert) { + var done = assert.async(); + assert.expect(3); + + var view = createView(this.viewParams); + view.call('notification', 'notify', { + title: 'a', + message: 'b', + sticky: true, + }); + var $notification = $('body .o_notification_manager .o_notification'); + assert.strictEqual($notification.find('.o_close').length, 1, "should display the close button in notification"); + + setTimeout(function () { + assert.strictEqual($notification.is(':hidden'), false, "should not hide the notification automatically"); + $notification.find('.o_close').click(); + setTimeout(function () { + assert.strictEqual($('body .o_notification_manager .o_notification').length, 0, "should destroy the notification"); + view.destroy(); + done(); + }); + }); + }); + + QUnit.test('Display a simple notification with onClose callback when automatically close', function (assert) { + var done = assert.async(); + assert.expect(2); + + var close = 0; + var view = createView(this.viewParams); + view.call('notification', 'notify', { + title: 'a', + message: 'b', + onClose: function () { + close++; + } + }); + view.destroy(); + assert.strictEqual(close, 0, "should wait to call onClose method once"); + setTimeout(function () { + assert.strictEqual(close, 1, "should call onClose method once"); + done(); + }); + }); + + QUnit.test('Display a sticky notification with onClose callback', function (assert) { + var done = assert.async(); + assert.expect(2); + + testUtils.unpatch(Notification); + testUtils.patch(Notification, { + _autoCloseDelay: 2500, + _animationDelay: 0, + }); + var view = createView(this.viewParams); + + var close = 0; + view.call('notification', 'notify', { + title: 'a', + message: 'b', + sticky: true, + onClose: function () { + close++; + } + }); + assert.strictEqual(close, 0, "should wait to call onClose method once"); + $('body .o_notification_manager .o_notification .o_close').click(); + setTimeout(function () { + assert.strictEqual(close, 1, "should call onClose method once"); + view.destroy(); + done(); + }); + }); + + QUnit.test('Display a question', function (assert) { + var done = assert.async(); + assert.expect(8); + + var view = createView(this.viewParams); + function notification (inc) { + return { + title: 'a' + inc, + message: 'b' + inc, + buttons: [ + { + text: 'accept' + inc, + primary: true, + click: function () { + assert.step('accept' + inc); + }, + }, + { + text: 'refuse' + inc, + click: function () { + assert.step('refuse' + inc); + }, + } + ], + onClose: function () { + assert.step('close' + inc); + } + }; + }; + view.call('notification', 'notify', notification(0)); + view.call('notification', 'notify', notification(1)); + view.call('notification', 'notify', notification(2)); + + var $notification = $('body .o_notification_manager .o_notification'); + assert.strictEqual($notification.eq(0).find('.o_close').length, 1, "should display the close button in notification"); + assert.strictEqual(_.str.trim($notification.eq(0).html().replace(/\s+/g, ' ')), + "
a0
b0
", + "should display notification"); + + $notification.find('.o_buttons button:contains(accept0)').click(); + $notification.find('.o_buttons button:contains(accept0)').click(); + $notification.find('.o_buttons button:contains(refuse0)').click(); + $notification.eq(0).find('.o_close').click(); + + $notification.find('.o_buttons button:contains(refuse1)').click(); + + $notification.eq(2).find('.o_close').click(); + + setTimeout(function () { + assert.strictEqual($notification.is(':hidden'), true, "should hide the notification"); + assert.strictEqual($('body .o_notification_manager .o_notification').length, 0, "should destroy the notification"); + assert.verifySteps(['accept0', 'refuse1', 'close2']); + view.destroy(); + done(); + }); + }); + + QUnit.test('call close notification service', function (assert) { + var done = assert.async(); + assert.expect(2); + + testUtils.unpatch(Notification); + testUtils.patch(Notification, { + _autoCloseDelay: 2500, + _animationDelay: 0, + }); + var view = createView(this.viewParams); + + var close = 0; + var notificationId0 = view.call('notification', 'notify', { + title: 'a', + message: 'b', + onClose: function () { + close++; + } + }); + var notificationId1 = view.call('notification', 'notify', { + title: 'a', + message: 'b', + sticky: true, + onClose: function () { + close++; + } + }); + + view.call('notification', 'close', notificationId0); + view.call('notification', 'close', notificationId1); + + setTimeout(function () { + assert.strictEqual($('body .o_notification_manager .o_notification').length, 0, "should destroy the notifications"); + assert.strictEqual(close, 2, "should call onClose method twice"); + view.destroy(); + done(); + }); + }); + +});}); diff --git a/addons/web/static/tests/views/form_tests.js b/addons/web/static/tests/views/form_tests.js index 534fa4fbea9..76c97b10d70 100644 --- a/addons/web/static/tests/views/form_tests.js +++ b/addons/web/static/tests/views/form_tests.js @@ -8,6 +8,7 @@ var core = require('web.core'); var fieldRegistry = require('web.field_registry'); var FormView = require('web.FormView'); var mixins = require('web.mixins'); +var NotificationService = require('web.NotificationService'); var pyeval = require('web.pyeval'); var testUtils = require('web.test_utils'); var widgetRegistry = require('web.widget_registry'); @@ -2669,13 +2670,17 @@ QUnit.module('Views', { arch: '
' + '' + '
', - }); - - testUtils.intercept(form, 'warning', function (event) { - assert.strictEqual(event.data.title, 'The following fields are invalid:', - "should have a warning with correct title"); - assert.strictEqual(event.data.message, '
  • Foo
', - "should have a warning with correct message"); + services: [NotificationService.extend({ + notify: function (params) { + if (params.type !== 'warning') { + return; + } + assert.strictEqual(params.title, 'The following fields are invalid:', + "should have a warning with correct title"); + assert.strictEqual(params.message, '
  • Foo
', + "should have a warning with correct message"); + } + })], }); form.$buttons.find('.o_form_button_save').click(); @@ -4713,11 +4718,11 @@ QUnit.module('Views', { } return result; }, - intercepts: { - warning: function () { - assert.step('warning'); - }, - }, + services: [NotificationService.extend({ + notify: function (params) { + assert.step(params.type); + } + })], }); form.$buttons.find('.o_form_button_edit').click(); diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js index c42fbfc4725..fd2f77f068d 100644 --- a/addons/web/static/tests/views/list_tests.js +++ b/addons/web/static/tests/views/list_tests.js @@ -8,6 +8,7 @@ var basicFields = require('web.basic_fields'); var FormView = require('web.FormView'); var ListView = require('web.ListView'); var mixins = require('web.mixins'); +var NotificationService = require('web.NotificationService'); var testUtils = require('web.test_utils'); var widgetRegistry = require('web.widget_registry'); var Widget = require('web.Widget'); @@ -2095,11 +2096,13 @@ QUnit.module('Views', { '' + '' + '', - intercepts: { - warning: function () { - warnings++; - }, - }, + services: [NotificationService.extend({ + notify: function (params) { + if (params.type === 'warning') { + warnings++; + } + } + })], }); // Start first line edition diff --git a/addons/web/views/webclient_templates.xml b/addons/web/views/webclient_templates.xml index ae3b882e764..d4d0a1e8c37 100644 --- a/addons/web/views/webclient_templates.xml +++ b/addons/web/views/webclient_templates.xml @@ -192,7 +192,7 @@ - + @@ -499,6 +499,8 @@ + + diff --git a/addons/web_settings_dashboard/static/tests/dashboard_tests.js b/addons/web_settings_dashboard/static/tests/dashboard_tests.js index 9ff65c32783..c96087934d1 100644 --- a/addons/web_settings_dashboard/static/tests/dashboard_tests.js +++ b/addons/web_settings_dashboard/static/tests/dashboard_tests.js @@ -2,7 +2,7 @@ odoo.define('web_settings_dashboard.settings_dashboard_tests', function (require "use strict"; var webSettingsDashboard = require('web_settings_dashboard'); - +var NotificationService = require('web.NotificationService'); var testUtils = require('web.test_utils'); var Widget = require('web.Widget'); @@ -88,9 +88,11 @@ QUnit.module('settings_dashboard', function () { } return this._super.apply(this, arguments); }, - intercepts: { - warning: assert.step.bind(assert, 'warning'), - }, + services: [NotificationService.extend({ + notify: function (params) { + assert.step(params.type); + } + })], }); // enter an invalid email address to invite @@ -165,9 +167,11 @@ QUnit.module('settings_dashboard', function () { } return this._super.apply(this, arguments); }, - intercepts: { - warning: assert.step.bind(assert, 'warning'), - }, + services: [NotificationService.extend({ + notify: function (params) { + assert.step(params.type); + } + })], }); // simulate a copy paste of multiple email addresses