diff --git a/addons/web/static/src/legacy/frontend/notification/notification.js b/addons/web/static/src/legacy/frontend/notification/notification.js deleted file mode 100644 index b79abba1356..00000000000 --- a/addons/web/static/src/legacy/frontend/notification/notification.js +++ /dev/null @@ -1,180 +0,0 @@ -odoo.define('web.Notification', function (require) { -'use strict'; - -var Widget = require('web.Widget'); - -/** - * Widget which is used to display a warning/information message on the top - * right of the screen. - * - * If you want to display such a notification, you probably do not want to do it - * by importing this file. The proper way is to use the displayNotification - * method on the Widget class. - */ -var Notification = Widget.extend({ - xmlDependencies: ['/web/static/src/legacy/frontend/notification/notification.xml'], - template: 'Notification', - events: { - 'hidden.bs.toast': '_onClose', - 'click .o_notification_buttons button': '_onClickButton', - 'mouseenter': '_onMouseEnter', - 'mouseleave': '_onMouseLeave', - }, - _autoCloseDelay: 4000, - _animation: true, - - /** - * @override - * @param {Widget} parent - * @param {Object} params - * @param {string} [params.title] - * @param {string} [params.subtitle] - * @param {string} [params.message] - * @param {DOMElement|jQuery} [params.contentEl] element to insert inside the - * notification, useful to keep a reference to the notification content - * @param {string} [params.type='warning'] 'info', 'success', 'warning', 'danger' or '' - * @param {boolean} [params.sticky=false] if true, the notification will - * stay visible until the user clicks on it. - * @param {string} [params.className] - * @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 - * @param {boolean} [params.messageIsHtml=false] allows passing an html - * message. Strongly discouraged: other options should be considered before - * enabling this option. The responsibility is on the caller to properly - * escape the message if this option is enabled. - */ - init: function (parent, params) { - this._super.apply(this, arguments); - this.title = params.title; - this.subtitle = params.subtitle; - this.contentEl = params.contentEl; - this.message = params.message || this.contentEl && true; - this.buttons = params.buttons || []; - this.messageIsHtml = !!params.messageIsHtml; - this.sticky = !!this.buttons.length || !!params.sticky; - this.type = params.type === undefined ? 'warning' : params.type; - this.className = params.className || ''; - this._closeCallback = params.onClose; - - if (this.type === 'danger') { - this.className += ' bg-danger'; - } else if (this.type === 'warning') { - this.className += ' bg-warning'; - } else if (this.type === 'success') { - this.className += ' bg-success'; - } else if (this.type === 'info') { - this.className += ' bg-info'; - } - }, - /** - * @override - */ - start: function () { - if (this.contentEl) { - this.$('.o_notification_content').empty().append(this.contentEl); - } - this.$el.toast({ - animation: this._animation, - autohide: false, - }); - void this.$el[0].offsetWidth; // Force a paint refresh before showing the toast - if (!this.sticky) { - this.autohide = _.cancellableThrottleRemoveMeSoon(this.close, this._autoCloseDelay, {leading: false}); - this.$el.on('shown.bs.toast', () => { - this.autohide(); - }); - } - this.$el.toast('show'); - return this._super.apply(this, arguments); - }, - /** - * @override - */ - destroy: function () { - this.$el.toast('dispose'); - this._super.apply(this, arguments); - }, - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - /** - * Destroys the widget with a nice animation. - * - * @private - * @param {boolean} [silent=false] if true, the notification does not call - * _closeCallback method - */ - close: function (silent) { - this.silent = silent; - this.$el.toast('hide'); - - // Make 'close' work if the notification is not shown yet but will be. - // Should not be needed but the calendar notification system is an - // example of feature that does not work without this yet. - var self = this; - this.$el.one('shown.bs.toast', function () { - self.$el.toast('hide'); - }); - }, - - //-------------------------------------------------------------------------- - // 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 {Event} ev - */ - _onClose: function (ev) { - this.trigger_up('close'); - if (!this.silent && !this._buttonClicked) { - if (this._closeCallback) { - this._closeCallback(); - } - } - this.destroy(); - }, - /** - * @private - */ - _onMouseEnter: function () { - if (!this.sticky) { - this.autohide.cancel(); - } - }, - /** - * @private - */ - _onMouseLeave: function () { - if (!this.sticky) { - this.autohide(); - } - }, -}); - -return Notification; -}); diff --git a/addons/web/static/src/legacy/frontend/notification/notification.scss b/addons/web/static/src/legacy/frontend/notification/notification.scss deleted file mode 100644 index 83007fb653b..00000000000 --- a/addons/web/static/src/legacy/frontend/notification/notification.scss +++ /dev/null @@ -1,25 +0,0 @@ - -.o_notification_manager { - @include o-position-absolute(3.5rem, 0); - position: fixed; - z-index: ($zindex-modal + $zindex-popover) / 2; - width: $toast-max-width; - margin-right: 8px; - max-width: 100%; - - .toast:not(:last-child) { - margin-bottom: 5px; - } - .o_notification { - width: 100%; - - .toast-body { - padding: 0.75rem; - - .o_notification_close { - height: 20px; - margin-left: 1rem; - } - } - } -} diff --git a/addons/web/static/src/legacy/frontend/notification/notification.xml b/addons/web/static/src/legacy/frontend/notification/notification.xml deleted file mode 100644 index af44d65cef1..00000000000 --- a/addons/web/static/src/legacy/frontend/notification/notification.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - -
-
- -
- -
-
- -
-
-
- - - diff --git a/addons/web/static/src/legacy/frontend/notification/notification_service.js b/addons/web/static/src/legacy/frontend/notification/notification_service.js deleted file mode 100644 index c84678e2a72..00000000000 --- a/addons/web/static/src/legacy/frontend/notification/notification_service.js +++ /dev/null @@ -1,122 +0,0 @@ -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 displayNotification - * method on the Widget class. - */ -var NotificationService = AbstractService.extend({ - custom_events: { - close: '_onCloseNotification', - }, - - /** - * @override - */ - start: 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 warning the user about some - * condition (connection lost), but the condition does not apply anymore. - * - * @param {number} notificationId - * @param {boolean} [silent=false] if true, the notification does not call - * onClose callback - * @param {integer} [wait=0] seconds to wait before closing the notification - */ - close: function (notificationId, silent, wait = 0) { - var notification = this.notifications[notificationId]; - if (!notification) { - return; - } - if (wait) { - setTimeout(() => { - notification.close(silent); - }, wait); - } else { - 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 {function} [params.Notification] javascript class of a notification - * to instantiate by default use 'web.Notification' - * @param {string} params.title notification title - * @param {string} params.subtitle notification subtitle - * @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 - * @param {boolean} [params.messageIsHtml=false] allows passing an html - * message. Strongly discouraged: other options should be considered before - * enabling this option. The responsibility is on the caller to properly - * escape the message if this option is enabled. - * @returns {Number} notification id - */ - notify: function (params) { - if (!this.$el) { - this.$el = $('
'); - this.$el.prependTo('body'); - } - var NotificationWidget = params.Notification || Notification; - var notification = this.notifications[++id] = new NotificationWidget(this, params); - 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/tests/legacy/frontend/notification_service_tests.js b/addons/web/static/tests/legacy/frontend/notification_service_tests.js deleted file mode 100644 index 6cadb7f82bb..00000000000 --- a/addons/web/static/tests/legacy/frontend/notification_service_tests.js +++ /dev/null @@ -1,289 +0,0 @@ -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; - -var waitCloseNotification = function () { - return new Promise(function (resolve) { - setTimeout(resolve, 1); - }); -} - -QUnit.module('Services', { - beforeEach: function () { - // We need to use a delay above 0 ms because otherwise the notification will close right after it opens - // before we can perform any test. - testUtils.mock.patch(Notification, { - _autoCloseDelay: 1, - _animation: false, - }); - this.viewParams = { - View: AbstractView, - arch: '', - data: { - fake_model: { - fields: {}, - record: [], - }, - }, - model: 'fake_model', - services: { - notification: NotificationService, - }, - }; - }, - afterEach: function () { - // The Notification Service has a side effect: it adds a div inside - // document.body. We could implement a cleanup mechanism for services, - // but this seems a little overkill since services are not supposed to - // be destroyed anyway. - $('.o_notification_manager').remove(); - testUtils.mock.unpatch(Notification); - } -}, function () { - QUnit.module('Notification'); - - QUnit.test('Display a warning notification', async function (assert) { - assert.expect(4); - - var view = await createView(this.viewParams); - view.call('notification', 'notify', { - title: 'a', - message: 'b', - }); - await testUtils.nextMicrotaskTick(); - var $notification = $('body .o_notification_manager .o_notification'); - assert.strictEqual($notification.html().trim().replace(/\s+/g, ' '), - "
a
b
", - "should display notification"); - assert.containsOnce($notification, '.o_notification_close'); - await waitCloseNotification(); - 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(); - }); - - QUnit.test('Display a danger notification', async function (assert) { - assert.expect(1); - - var view = await createView(this.viewParams); - view.call('notification', 'notify', { - title: 'a', - message: 'b', - type: 'danger' - }); - await testUtils.nextMicrotaskTick(); - var $notification = $('body .o_notification_manager .o_notification'); - assert.strictEqual($notification.html().trim().replace(/\s+/g, ' '), - "
a
b
", - "should display notification"); - view.destroy(); - }); - - QUnit.test('Display a sticky notification', async function (assert) { - assert.expect(3); - - var view = await createView(this.viewParams); - view.call('notification', 'notify', { - title: 'a', - message: 'b', - sticky: true, - }); - await testUtils.nextTick(); - var $notification = $('body .o_notification_manager .o_notification'); - assert.containsOnce($notification, '.o_notification_close', "should display the close button in notification"); - - assert.strictEqual($notification.is(':hidden'), false, "should not hide the notification automatically"); - await testUtils.dom.click($notification.find('.o_notification_close')); - assert.strictEqual($('body .o_notification_manager .o_notification').length, - 0, "should destroy the notification"); - view.destroy(); - }); - - QUnit.test('Display a notification without title', async function (assert) { - assert.expect(3); - - const view = await createView(this.viewParams); - view.call('notification', 'notify', { - title: false, - message: 'b', - sticky: true, - }); - await testUtils.nextTick(); - const $notification = $('body .o_notification_manager .o_notification'); - assert.containsNone($notification, '.toast-header .o_notification_title'); - assert.containsOnce($notification, '.o_notification_content'); - assert.containsOnce($notification, '.toast-body .o_notification_close'); - - view.destroy(); - }); - - // FIXME skip because the feature is unused and do not understand why the test even worked before - QUnit.skip('Display a simple notification with onClose callback when automatically close', async function (assert) { - assert.expect(2); - - var close = 0; - var view = await createView(this.viewParams); - view.call('notification', 'notify', { - title: 'a', - message: 'b', - onClose: function () { - close++; - } - }); - await testUtils.nextMicrotaskTick(); - view.destroy(); - assert.strictEqual(close, 0, "should wait to call onClose method once"); - await testUtils.nextTick(); - assert.strictEqual(close, 1, "should call onClose method once"); - }); - - QUnit.test('Display a sticky notification with onClose callback', async function (assert) { - assert.expect(2); - - testUtils.mock.unpatch(Notification); - testUtils.mock.patch(Notification, { - _autoCloseDelay: 2500, - _animation: false, - }); - var view = await createView(this.viewParams); - - var close = 0; - view.call('notification', 'notify', { - title: 'a', - message: 'b', - sticky: true, - onClose: function () { - close++; - } - }); - await testUtils.nextMicrotaskTick(); - assert.strictEqual(close, 0, "should wait to call onClose method once"); - testUtils.dom.click($('body .o_notification_manager .o_notification .o_notification_close')); - assert.strictEqual(close, 1, "should call onClose method once"); - view.destroy(); - }); - - QUnit.test('Display a question', async function (assert) { - assert.expect(8); - - var view = await 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)); - await testUtils.nextTick(); - - var $notification = $('body .o_notification_manager .o_notification'); - assert.containsOnce($notification.eq(0), '.o_notification_close', - "should display the close button in notification"); - assert.strictEqual($notification.html().trim().replace(/\s+/g, ' '), - "
a0
b0
", - "should display notification"); - - testUtils.dom.click($notification.find('.o_notification_buttons button:contains(accept0)')); - testUtils.dom.click($notification.find('.o_notification_buttons button:contains(refuse1)')); - testUtils.dom.click($notification.eq(2).find('.o_notification_close')); - - 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(); - }); - - QUnit.test('call close notification service', async function (assert) { - assert.expect(2); - - testUtils.mock.unpatch(Notification); - testUtils.mock.patch(Notification, { - _autoCloseDelay: 2500, - _animation: false, - }); - var view = await 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++; - } - }); - await testUtils.nextTick(); - - view.call('notification', 'close', notificationId0); - view.call('notification', 'close', notificationId1); - await testUtils.nextTick(); - - 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(); - }); - - QUnit.test('Display a custom notification', async function (assert) { - assert.expect(3); - - var Custom = Notification.extend({ - init: function (parent, params) { - this._super.apply(this, arguments); - assert.ok(params.customParams, 'instantiate custom notification'); - }, - start: function () { - var self = this; - return this._super().then(function () { - self.$el.html('Custom'); - }); - }, - }); - - var view = await createView(this.viewParams); - view.call('notification', 'notify', { - Notification: Custom, - customParams: true, - }); - await testUtils.nextMicrotaskTick(); - assert.containsOnce($('body'), '.o_notification_manager .o_notification:contains(Custom)', - "should display the notification"); - view.destroy(); - assert.containsNone($('body'), '.o_notification_manager .o_notification', - "should destroy the notification"); - }); - -});}); diff --git a/addons/web/static/tests/legacy/helpers/test_utils.js b/addons/web/static/tests/legacy/helpers/test_utils.js index 06837d007ef..e8b5d23250c 100644 --- a/addons/web/static/tests/legacy/helpers/test_utils.js +++ b/addons/web/static/tests/legacy/helpers/test_utils.js @@ -126,7 +126,6 @@ odoo.define('web.test_utils', async function (require) { ajax.loadXML('/web/static/src/legacy/frontend/debug_manager/debug.xml', core.qweb), ajax.loadXML('/web/static/src/legacy/xml/dialog.xml', core.qweb), ajax.loadXML('/web/static/src/legacy/xml/translation_dialog.xml', core.qweb), - ajax.loadXML('/web/static/src/legacy/frontend/notification/notification.xml', core.qweb), ]); setTimeout(function () { // jquery autocomplete refines the search in a setTimeout() parameterized