From 23facbfb76c6e8b23137cddda5f5113273555498 Mon Sep 17 00:00:00 2001 From: Romeo Fragomeli Date: Wed, 19 Jan 2022 11:52:31 +0000 Subject: [PATCH] [REF] *: BS5: migrate popovers/Tooltip * data('bs.popover') is not a jQuery data anymore -> fetch data from the instance instead. * use the new class name 'popover-arrow' instead of 'arrow' * website_forum: offset is not a number anymore -> Convert to string instead. * BS5 event listener: 'focus' -> 'focusin' * use `mouseover` event to show and `mouseout` to hide. * disable at some point the animation to avoid remaining listener in the DOM. Ref: https://getbootstrap.com/docs/5.1/migration/#popovers Task ID: 2766483 Part-of: odoo/odoo#95450 --- .../static/tests/popover_widget_tests.js | 2 +- addons/web/static/src/core/popover/popover.js | 12 ++++- .../web/static/src/core/popover/popover.scss | 24 ++++++++-- .../web/static/src/core/popover/popover.xml | 2 +- .../js/views/calendar/calendar_renderer.js | 2 +- .../web/static/src/legacy/scss/tooltip.scss | 45 ++++++++----------- .../static/src/legacy/xml/web_calendar.xml | 4 +- .../legacy/fields/relational_fields_tests.js | 4 +- .../static/tests/legacy/views/form_tests.js | 38 +++++++++------- .../static/tests/legacy/views/list_tests.js | 15 +++++-- .../tests/webclient/actions/legacy_tests.js | 16 ++++--- .../static/src/js/website_forum.js | 5 ++- 12 files changed, 105 insertions(+), 64 deletions(-) diff --git a/addons/stock/static/tests/popover_widget_tests.js b/addons/stock/static/tests/popover_widget_tests.js index dff13d60489..0e2ea8b32e2 100644 --- a/addons/stock/static/tests/popover_widget_tests.js +++ b/addons/stock/static/tests/popover_widget_tests.js @@ -39,7 +39,7 @@ QUnit.module('ModelFieldSelector', { var $popoverButton = form.$('a.fa.fa-info-circle.text-danger'); assert.strictEqual($popoverButton.length, 1, "Should have a popover icon/button in red"); assert.strictEqual($popoverButton.prop('special_click'), true, "Special click properpy should be activated"); - await testUtils.dom.triggerEvents($popoverButton, ['focus']); + await testUtils.dom.triggerEvent($popoverButton, 'focusin'); $popover = $('div.popover'); assert.strictEqual($popover.length, 1, "Should have a popover container in DOM"); assert.strictEqual($popover.html().includes("var that = self // why not?"), true, "The message should be in DOM"); diff --git a/addons/web/static/src/core/popover/popover.js b/addons/web/static/src/core/popover/popover.js index 9eb7960a52a..d0bb630752e 100644 --- a/addons/web/static/src/core/popover/popover.js +++ b/addons/web/static/src/core/popover/popover.js @@ -13,7 +13,17 @@ export class Popover extends Component { } onPositioned(el, { direction, variant }) { const position = `${direction[0]}${variant[0]}`; - el.classList.add(`bs-popover-${direction}`, `o-popover--${position}`); + const directionMap = { + top: "top", + bottom: "bottom", + left: "start", + right: "end", + }; + el.classList.add( + `bs-popover-${directionMap[direction]}`, + `o-popover-${direction}`, + `o-popover--${position}` + ); const arrowEl = el.firstElementChild; switch (position) { case "tm": // top-middle diff --git a/addons/web/static/src/core/popover/popover.scss b/addons/web/static/src/core/popover/popover.scss index f7714dc33d7..0973c758d7b 100644 --- a/addons/web/static/src/core/popover/popover.scss +++ b/addons/web/static/src/core/popover/popover.scss @@ -35,19 +35,35 @@ } } - &.o-popover--ts .arrow, &.o-popover--bs .arrow { + &.o-popover-top, &.o-popover-auto[x-placement^="top"] { + margin-bottom: 0.5rem; + } + + &.o-popover-right, &.o-popover-auto[x-placement^="right"] { + margin-left: 0.5rem; + } + + &.o-popover-bottom, &.o-popover-auto[x-placement^="bottom"] { + margin-top: 0.5rem; + } + + &.o-popover-left, &.o-popover-auto[x-placement^="left"] { + margin-right: 0.5rem; + } + + &.o-popover--ts .popover-arrow, &.o-popover--bs .popover-arrow { left: $popover-border-radius; } - &.o-popover--te .arrow, &.o-popover--be .arrow { + &.o-popover--te .popover-arrow, &.o-popover--be .popover-arrow { right: $popover-border-radius; } - &.o-popover--ls .arrow, &.o-popover--rs .arrow { + &.o-popover--ls .popover-arrow, &.o-popover--rs .popover-arrow { top: $popover-border-radius; } - &.o-popover--le .arrow, &.o-popover--re .arrow { + &.o-popover--le .popover-arrow, &.o-popover--re .popover-arrow { bottom: $popover-border-radius; } } diff --git a/addons/web/static/src/core/popover/popover.xml b/addons/web/static/src/core/popover/popover.xml index 2e057bef5bd..c5c659b10e9 100644 --- a/addons/web/static/src/core/popover/popover.xml +++ b/addons/web/static/src/core/popover/popover.xml @@ -6,7 +6,7 @@ t-att-class="props.popoverClass" t-ref="popper" > -
+
diff --git a/addons/web/static/src/legacy/js/views/calendar/calendar_renderer.js b/addons/web/static/src/legacy/js/views/calendar/calendar_renderer.js index 4975cdd1177..ee51cbce9d1 100644 --- a/addons/web/static/src/legacy/js/views/calendar/calendar_renderer.js +++ b/addons/web/static/src/legacy/js/views/calendar/calendar_renderer.js @@ -664,7 +664,7 @@ return AbstractRenderer.extend({ * @private */ _onPopoverShown: function ($popoverElement, calendarPopover) { - var $popover = $($popoverElement.data('bs.popover').tip); + const $popover = $(Popover.getInstance($popoverElement[0]).tip); $popover.find('.o_cw_popover_close').on('click', this._unselectEvent.bind(this)); $popover.find('.o_cw_body').replaceWith(calendarPopover.$el); }, diff --git a/addons/web/static/src/legacy/scss/tooltip.scss b/addons/web/static/src/legacy/scss/tooltip.scss index fdf10fea1c1..eaae68362f1 100644 --- a/addons/web/static/src/legacy/scss/tooltip.scss +++ b/addons/web/static/src/legacy/scss/tooltip.scss @@ -43,7 +43,10 @@ } } - .arrow { + .tooltip-arrow { + &::before, &::after { + border: $arrow-size solid transparent; + } &::after { content: ''; position: absolute; @@ -51,64 +54,54 @@ } &.bs-tooltip-bottom { - margin-top: $tooltip-arrow-height; - .arrow { + .tooltip-arrow { top: $tooltip-arrow-height; &::before { - border: $arrow-size solid transparent; border-bottom-color: $o-tooltip-color; + bottom: 0; } &::after { - bottom: -1px; - border: $arrow-size solid transparent; border-bottom-color: $o-tooltip-title-background-color; + bottom: -1px; } } } &.bs-tooltip-top { - margin-bottom: $tooltip-arrow-height; - .arrow { + .tooltip-arrow { bottom: $tooltip-arrow-height; &::before { - border: $arrow-size solid transparent; border-top-color: $o-tooltip-color; + top: 0; } &::after { - border: $arrow-size solid transparent; border-top-color: $o-tooltip-arrow-color; - margin-top: -1px; + top: -1px; } } } - &.bs-tooltip-left { - margin-right: $tooltip-arrow-height; - .arrow { + &.bs-tooltip-start { + .tooltip-arrow { right: $tooltip-arrow-height; &::before { - border: $arrow-size solid transparent; border-left-color: $o-tooltip-color; + left: 0; } &::after { - border: $arrow-size solid transparent; border-left-color: $o-tooltip-arrow-color; - margin-left: -1px; + left: -1px; } } } - &.bs-tooltip-right { - margin-left: $tooltip-arrow-height; - .arrow { + &.bs-tooltip-end { + .tooltip-arrow { left: $tooltip-arrow-height; - &::before, - &::after { - border: $arrow-size solid transparent; + &::before { border-right-color: $o-tooltip-color; + right: 0; } &::after { - border: $arrow-size solid transparent; border-right-color: $o-tooltip-arrow-color; - margin-right: -1px; - right: 0px; + right: -1px; } } } diff --git a/addons/web/static/src/legacy/xml/web_calendar.xml b/addons/web/static/src/legacy/xml/web_calendar.xml index d74c9670dcb..e30a82609ff 100644 --- a/addons/web/static/src/legacy/xml/web_calendar.xml +++ b/addons/web/static/src/legacy/xml/web_calendar.xml @@ -21,7 +21,7 @@
-
+

@@ -170,7 +170,7 @@
-
+
diff --git a/addons/web/static/tests/legacy/fields/relational_fields_tests.js b/addons/web/static/tests/legacy/fields/relational_fields_tests.js index 0a1f76cca4c..f3b6be7bada 100644 --- a/addons/web/static/tests/legacy/fields/relational_fields_tests.js +++ b/addons/web/static/tests/legacy/fields/relational_fields_tests.js @@ -2135,7 +2135,7 @@ QUnit.module('Legacy relational_fields', { assert.strictEqual(list.$('.o_data_row:eq(3) .o_field_many2manytags .o_m2m_avatar_empty').text().trim(), "+9", "should have +9 in o_m2m_avatar_empty"); - list.$('.o_data_row:eq(1) .o_field_many2manytags .o_m2m_avatar_empty').trigger($.Event('mouseenter')); + list.$('.o_data_row:eq(1) .o_field_many2manytags .o_m2m_avatar_empty')[0].dispatchEvent(new Event('mouseover')); await testUtils.nextTick(); assert.containsOnce(list, '.popover', "should open a popover hover on o_m2m_avatar_empty"); @@ -2241,7 +2241,7 @@ QUnit.module('Legacy relational_fields', { assert.strictEqual(kanban.$('.o_kanban_record:eq(3) .o_field_many2manytags .o_m2m_avatar_empty').text().trim(), "9+", "should have 9+ in o_m2m_avatar_empty"); - kanban.$('.o_kanban_record:eq(2) .o_field_many2manytags .o_m2m_avatar_empty').trigger($.Event('mouseenter')); + kanban.$('.o_kanban_record:eq(2) .o_field_many2manytags .o_m2m_avatar_empty')[0].dispatchEvent(new Event('mouseover')); await testUtils.nextTick(); assert.containsOnce(kanban, '.popover', "should open a popover hover on o_m2m_avatar_empty"); diff --git a/addons/web/static/tests/legacy/views/form_tests.js b/addons/web/static/tests/legacy/views/form_tests.js index 058c7b18a2a..8bb1025f471 100644 --- a/addons/web/static/tests/legacy/views/form_tests.js +++ b/addons/web/static/tests/legacy/views/form_tests.js @@ -41,6 +41,9 @@ let serverData; let target; QUnit.module('LegacyViews', { beforeEach: function () { + // Avoid animation to not have to wait until the tooltip is removed + this.initialTooltipDefaultAnimation = Tooltip.Default.animation; + Tooltip.Default.animation = false; target = getFixture(); registry.category("services").add("scroller", scrollerService); @@ -188,6 +191,9 @@ QUnit.module('LegacyViews', { models: this.data, }; }, + afterEach: async function () { + Tooltip.Default.animation = this.initialTooltipDefaultAnimation; + }, }, function () { QUnit.module('FormView (legacy)'); @@ -1597,27 +1603,27 @@ QUnit.module('LegacyViews', { const $fooLabel1 = form.$('.o_form_label:nth(0)'); $fooLabel1.tooltip('show', false); - $fooLabel1.trigger($.Event('mouseenter')); + $fooLabel1[0].dispatchEvent(new Event('mouseover')); assert.strictEqual($('.tooltip .oe_tooltip_help').text().trim(), "foo tooltip"); - $fooLabel1.trigger($.Event('mouseleave')); + $fooLabel1[0].dispatchEvent(new Event('mouseout')); const $fooLabel2 = form.$('.o_form_label:nth(2)'); $fooLabel2.tooltip('show', false); - $fooLabel2.trigger($.Event('mouseenter')); + $fooLabel2[0].dispatchEvent(new Event('mouseover')); assert.strictEqual($('.tooltip .oe_tooltip_help').text().trim(), "foo tooltip"); - $fooLabel2.trigger($.Event('mouseleave')); + $fooLabel2[0].dispatchEvent(new Event('mouseout')); const $barLabel1 = form.$('.o_form_label:nth(1)'); $barLabel1.tooltip('show', false); - $barLabel1.trigger($.Event('mouseenter')); + $barLabel1[0].dispatchEvent(new Event('mouseover')); assert.strictEqual($('.tooltip .oe_tooltip_help').text().trim(), "bar tooltip"); - $barLabel1.trigger($.Event('mouseleave')); + $barLabel1[0].dispatchEvent(new Event('mouseout')); const $barLabel2 = form.$('.o_form_label:nth(3)'); $barLabel2.tooltip('show', false); - $barLabel2.trigger($.Event('mouseenter')); + $barLabel2[0].dispatchEvent(new Event('mouseover')); assert.strictEqual($('.tooltip .oe_tooltip_help').text().trim(), "bar tooltip"); - $barLabel2.trigger($.Event('mouseleave')); + $barLabel2[0].dispatchEvent(new Event('mouseout')); odoo.debug = initialDebugMode; form.destroy(); @@ -8209,6 +8215,7 @@ QUnit.module('LegacyViews', { assert.strictEqual($('.tooltip .oe_tooltip_string').length, 1, "should have rendered a tooltip"); $secondButton.trigger($.Event('mouseleave')); + $secondButton.tooltip('hide'); odoo.debug = initialDebugMode; form.destroy(); @@ -8599,6 +8606,7 @@ QUnit.module('LegacyViews', { assert.strictEqual($('.oe_tooltip_technical>li[data-item="widget"]')[0].lastChild.wholeText.trim(), 'Many2one (many2one)', "widget description should be correct"); + $field.tooltip('hide'); odoo.debug = initialDebugMode; form.destroy(); }); @@ -9874,7 +9882,7 @@ QUnit.module('LegacyViews', { form.$buttons.find('.o_form_buttons_edit').tooltip('show',false); assert.strictEqual($('.tooltip .oe_tooltip_string').length, 1, "should have rendered a tooltip"); - await testUtils.nextTick(); + form.$buttons.find('.o_form_buttons_edit').tooltip('hide'); form.destroy(); }); QUnit.test('if the focus is on the save button, hitting ENTER should save', async function (assert) { @@ -10160,17 +10168,17 @@ QUnit.module('LegacyViews', { const $productLabel = form.$('.o_form_label:eq(1)'); $productLabel.tooltip('show', false); - await testUtils.dom.triggerMouseEvent($productLabel, 'mouseenter'); + await testUtils.dom.triggerMouseEvent($productLabel, 'mouseover'); assert.strictEqual($('.tooltip .oe_tooltip_help').text().trim(), "this is a tooltip\n\nValues set here are company-specific."); - await testUtils.dom.triggerMouseEvent($productLabel, 'mouseleave'); + await testUtils.dom.triggerMouseEvent($productLabel, 'mouseout'); const $fooLabel = form.$('.o_form_label:first'); $fooLabel.tooltip('show', false); - await testUtils.dom.triggerMouseEvent($fooLabel, 'mouseenter'); + await testUtils.dom.triggerMouseEvent($fooLabel, 'mouseover'); assert.strictEqual($('.tooltip .oe_tooltip_help').text().trim(), "Values set here are company-specific."); - await testUtils.dom.triggerMouseEvent($fooLabel, 'mouseleave'); + await testUtils.dom.triggerMouseEvent($fooLabel, 'mouseout'); form.destroy(); }); @@ -10199,9 +10207,9 @@ QUnit.module('LegacyViews', { const $productLabel = form.$('.o_form_label'); $productLabel.tooltip('show', false); - await testUtils.dom.triggerMouseEvent($productLabel, 'mouseenter'); + await testUtils.dom.triggerMouseEvent($productLabel, 'mouseover'); assert.strictEqual($('.tooltip .oe_tooltip_help').text().trim(), "this is a tooltip"); - await testUtils.dom.triggerMouseEvent($productLabel, 'mouseleave'); + await testUtils.dom.triggerMouseEvent($productLabel, 'mouseout'); form.destroy(); }); diff --git a/addons/web/static/tests/legacy/views/list_tests.js b/addons/web/static/tests/legacy/views/list_tests.js index 99ac97efef5..833afa59f25 100644 --- a/addons/web/static/tests/legacy/views/list_tests.js +++ b/addons/web/static/tests/legacy/views/list_tests.js @@ -40,6 +40,9 @@ let serverData; let target; QUnit.module('LegacyViews', { beforeEach: function () { + // Avoid animation to not have to wait until the tooltip is removed + this.initialTooltipDefaultAnimation = Tooltip.Default.animation; + Tooltip.Default.animation = false; registry.category("views").remove("list"); // remove new list from registry registry.category("views").remove("kanban"); // remove new kanban from registry registry.category("views").remove("form"); // remove new form from registry @@ -149,7 +152,10 @@ QUnit.module('LegacyViews', { serverData = { models: this.data }; target = getFixture(); - } + }, + afterEach: async function () { + Tooltip.Default.animation = this.initialTooltipDefaultAnimation; + }, }, function () { QUnit.module('ListView (legacy)'); @@ -4430,24 +4436,25 @@ QUnit.module('LegacyViews', { // 1000 ms. not totally academic, but a short test suite is easier to sell :( list.$('th[data-name=foo]').tooltip('show', false); - list.$('th[data-name=foo]').trigger($.Event('mouseenter')); + list.$('th[data-name=foo]')[0].dispatchEvent(new Event('mouseover')); assert.strictEqual($('.tooltip .oe_tooltip_string').length, 0, "should not have rendered a tooltip"); odoo.debug = true; // it is necessary to rerender the list so tooltips can be properly created await list.reload(); list.$('th[data-name=foo]').tooltip('show', false); - list.$('th[data-name=foo]').trigger($.Event('mouseenter')); + list.$('th[data-name=foo]')[0].dispatchEvent(new Event('mouseover')); assert.strictEqual($('.tooltip .oe_tooltip_string').length, 1, "should have rendered a tooltip"); await list.reload(); list.$('th[data-name=bar]').tooltip('show', false); - list.$('th[data-name=bar]').trigger($.Event('mouseenter')); + list.$('th[data-name=bar]')[0].dispatchEvent(new Event('mouseover')); assert.containsOnce($, '.oe_tooltip_technical>li[data-item="widget"]', 'widget should be present for this field'); assert.strictEqual($('.oe_tooltip_technical>li[data-item="widget"]')[0].lastChild.wholeText.trim(), 'Button (toggle_button)', "widget description should be correct"); + list.$('th[data-name=bar]')[0].dispatchEvent(new Event('mouseout')); odoo.debug = initialDebugMode; list.destroy(); }); diff --git a/addons/web/static/tests/webclient/actions/legacy_tests.js b/addons/web/static/tests/webclient/actions/legacy_tests.js index e60a65e6db3..805ab321c2f 100644 --- a/addons/web/static/tests/webclient/actions/legacy_tests.js +++ b/addons/web/static/tests/webclient/actions/legacy_tests.js @@ -685,12 +685,18 @@ QUnit.module("ActionManager", (hooks) => { assert.containsN(target, ".o_form_buttons_view button:not([disabled])", 2); const actionButton = target.querySelector("button[name=object]"); const tooltipProm = new Promise((resolve) => { - $(document.body).one("shown.bs.tooltip", () => { - $(actionButton).mouseleave(); - resolve(); - }); + document.body.addEventListener( + "shown.bs.tooltip", + () => { + actionButton.dispatchEvent(new Event("mouseout")); + resolve(); + }, + { + once: true, + } + ); }); - $(actionButton).mouseenter(); + actionButton.dispatchEvent(new Event("mouseover")); await tooltipProm; assert.containsOnce(document.body, ".tooltip"); await click(actionButton); diff --git a/addons/website_forum/static/src/js/website_forum.js b/addons/website_forum/static/src/js/website_forum.js index a241d586c2c..33c1da54df5 100644 --- a/addons/website_forum/static/src/js/website_forum.js +++ b/addons/website_forum/static/src/js/website_forum.js @@ -155,10 +155,11 @@ publicWidget.registry.websiteForum = publicWidget.Widget.extend({ _.each(this.$('.o_wforum_bio_popover'), authorBox => { $(authorBox).popover({ trigger: 'hover', - offset: 10, + offset: '10', animation: false, html: true, - }).popover('hide').data('bs.popover').tip.classList.add('o_wforum_bio_popover_container'); + customClass: 'o_wforum_bio_popover_container', + }); }); this.$('#post_reply').on('shown.bs.collapse', function (e) {