[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
This commit is contained in:
Romeo Fragomeli
2022-07-07 13:30:17 +02:00
parent 19a0ee516f
commit 23facbfb76
12 changed files with 105 additions and 64 deletions
@@ -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");
+11 -1
View File
@@ -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
@@ -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;
}
}
@@ -6,7 +6,7 @@
t-att-class="props.popoverClass"
t-ref="popper"
>
<div class="arrow"/>
<div class="popover-arrow"/>
<t t-slot="default" />
</div>
</t>
@@ -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);
},
+19 -26
View File
@@ -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;
}
}
}
@@ -21,7 +21,7 @@
<t t-name="CalendarView.event.popover.placeholder">
<div t-attf-class="o_cw_popover popover card shadow #{typeof color === 'number' ? _.str.sprintf('o_calendar_color_%s', color) : ''}" role="tooltip">
<div class="arrow"/>
<div class="popover-arrow"/>
<div class="card-header d-flex justify-content-between py-2 pr-2">
<h4 class="p-0 pt-1">
<span class="popover-header border-0"/><span t-if="displayLock" class="ml-1"><i class="fa fa-lock"/></span>
@@ -170,7 +170,7 @@
<t t-name="CalendarView.yearEvent.popover.placeholder">
<div class="popover o_cw_popover" style="position: relative">
<div class="arrow"></div>
<div class="popover-arrow"></div>
<div class="popover-header"></div>
<div style="position: absolute; top: 0; right: 0.5rem;">
<span class="o_cw_popover_close"><i class="fa fa-close small"></i></span>
@@ -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");
@@ -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();
});
@@ -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();
});
@@ -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);
@@ -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) {