Commit da006492 authored by Tim Zallmann's avatar Tim Zallmann Committed by Phil Hughes

Remove unused popover directive

This removes the unused popover directive and related source code.
Thanks to th work done as part of the epics:

- https://gitlab.com/groups/gitlab-org/-/epics/4409
- https://gitlab.com/groups/gitlab-org/-/epics/4383

we are not reliant on Bootstrap's tooltip and popover implementations
anymore and they can be safely removed.
parent 08a19d0a
...@@ -6,8 +6,6 @@ import 'bootstrap/js/dist/button'; ...@@ -6,8 +6,6 @@ import 'bootstrap/js/dist/button';
import 'bootstrap/js/dist/collapse'; import 'bootstrap/js/dist/collapse';
import 'bootstrap/js/dist/modal'; import 'bootstrap/js/dist/modal';
import 'bootstrap/js/dist/dropdown'; import 'bootstrap/js/dist/dropdown';
import 'bootstrap/js/dist/popover';
import 'bootstrap/js/dist/tooltip';
import 'bootstrap/js/dist/tab'; import 'bootstrap/js/dist/tab';
// custom jQuery functions // custom jQuery functions
...@@ -19,68 +17,3 @@ $.fn.extend({ ...@@ -19,68 +17,3 @@ $.fn.extend({
return $(this).prop('disabled', false).removeClass('disabled'); return $(this).prop('disabled', false).removeClass('disabled');
}, },
}); });
/*
Starting with bootstrap 4.3.1, bootstrap sanitizes html used for tooltips / popovers.
This extends the default whitelists with more elements / attributes:
https://getbootstrap.com/docs/4.3/getting-started/javascript/#sanitizer
*/
const whitelist = $.fn.tooltip.Constructor.Default.whiteList;
const inputAttributes = ['value', 'type'];
const dataAttributes = [
'data-toggle',
'data-placement',
'data-container',
'data-title',
'data-class',
'data-clipboard-text',
'data-placement',
];
// Whitelisting data attributes
whitelist['*'] = [
...whitelist['*'],
...dataAttributes,
'title',
'width height',
'abbr',
'datetime',
'name',
'width',
'height',
];
// Whitelist missing elements:
whitelist.label = ['for'];
whitelist.button = [...inputAttributes];
whitelist.input = [...inputAttributes];
whitelist.tt = [];
whitelist.samp = [];
whitelist.kbd = [];
whitelist.var = [];
whitelist.dfn = [];
whitelist.cite = [];
whitelist.big = [];
whitelist.address = [];
whitelist.dl = [];
whitelist.dt = [];
whitelist.dd = [];
whitelist.abbr = [];
whitelist.acronym = [];
whitelist.blockquote = [];
whitelist.del = [];
whitelist.ins = [];
whitelist['gl-emoji'] = [
'data-name',
'data-unicode-version',
'data-fallback-src',
'data-fallback-sprite-class',
];
// Whitelisting SVG tags and attributes
whitelist.svg = ['viewBox'];
whitelist.use = ['xlink:href'];
whitelist.path = ['d'];
import $ from 'jquery';
import { debounce } from 'lodash';
export function togglePopover(show) {
const isAlreadyShown = this.hasClass('js-popover-show');
if ((show && isAlreadyShown) || (!show && !isAlreadyShown)) {
return false;
}
this.popover(show ? 'show' : 'hide');
this.toggleClass('disable-animation js-popover-show', show);
return true;
}
export function mouseleave() {
if (!$('.popover:hover').length > 0) {
const $popover = $(this);
togglePopover.call($popover, false);
}
}
export function mouseenter() {
const $popover = $(this);
const showedPopover = togglePopover.call($popover, true);
if (showedPopover) {
$('.popover').on('mouseleave', mouseleave.bind($popover));
}
}
export function debouncedMouseleave(debounceTimeout = 300) {
return debounce(mouseleave, debounceTimeout);
}
...@@ -170,12 +170,6 @@ table { ...@@ -170,12 +170,6 @@ table {
display: none; display: none;
} }
h3.popover-header {
// Default bootstrap popovers use <h3>
// which we default to having a top margin
margin-top: 0;
}
// Add to .label so that old system notes that are saved to the db // Add to .label so that old system notes that are saved to the db
// will still receive the correct styling // will still receive the correct styling
.badge:not(.gl-badge), .badge:not(.gl-badge),
......
...@@ -188,8 +188,6 @@ describe('AwardsHandler', () => { ...@@ -188,8 +188,6 @@ describe('AwardsHandler', () => {
expect($thumbsUpEmoji.hasClass('active')).toBe(true); expect($thumbsUpEmoji.hasClass('active')).toBe(true);
expect($thumbsDownEmoji.hasClass('active')).toBe(false); expect($thumbsDownEmoji.hasClass('active')).toBe(false);
$thumbsUpEmoji.tooltip();
$thumbsDownEmoji.tooltip();
awardsHandler.addAward($votesBlock, awardUrl, 'thumbsdown', true); awardsHandler.addAward($votesBlock, awardUrl, 'thumbsdown', true);
expect($thumbsUpEmoji.hasClass('active')).toBe(false); expect($thumbsUpEmoji.hasClass('active')).toBe(false);
...@@ -217,9 +215,8 @@ describe('AwardsHandler', () => { ...@@ -217,9 +215,8 @@ describe('AwardsHandler', () => {
const $thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent(); const $thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent();
$thumbsUpEmoji.attr('data-title', 'sam, jerry, max, and andy'); $thumbsUpEmoji.attr('data-title', 'sam, jerry, max, and andy');
awardsHandler.addAward($votesBlock, awardUrl, 'thumbsup', false); awardsHandler.addAward($votesBlock, awardUrl, 'thumbsup', false);
$thumbsUpEmoji.tooltip();
expect($thumbsUpEmoji.data('originalTitle')).toBe('You, sam, jerry, max, and andy'); expect($thumbsUpEmoji.attr('title')).toBe('You, sam, jerry, max, and andy');
}); });
it('handles the special case where "You" is not cleanly comma separated', () => { it('handles the special case where "You" is not cleanly comma separated', () => {
...@@ -228,9 +225,8 @@ describe('AwardsHandler', () => { ...@@ -228,9 +225,8 @@ describe('AwardsHandler', () => {
const $thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent(); const $thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent();
$thumbsUpEmoji.attr('data-title', 'sam'); $thumbsUpEmoji.attr('data-title', 'sam');
awardsHandler.addAward($votesBlock, awardUrl, 'thumbsup', false); awardsHandler.addAward($votesBlock, awardUrl, 'thumbsup', false);
$thumbsUpEmoji.tooltip();
expect($thumbsUpEmoji.data('originalTitle')).toBe('You and sam'); expect($thumbsUpEmoji.attr('title')).toBe('You and sam');
}); });
}); });
...@@ -242,9 +238,8 @@ describe('AwardsHandler', () => { ...@@ -242,9 +238,8 @@ describe('AwardsHandler', () => {
$thumbsUpEmoji.attr('data-title', 'You, sam, jerry, max, and andy'); $thumbsUpEmoji.attr('data-title', 'You, sam, jerry, max, and andy');
$thumbsUpEmoji.addClass('active'); $thumbsUpEmoji.addClass('active');
awardsHandler.addAward($votesBlock, awardUrl, 'thumbsup', false); awardsHandler.addAward($votesBlock, awardUrl, 'thumbsup', false);
$thumbsUpEmoji.tooltip();
expect($thumbsUpEmoji.data('originalTitle')).toBe('sam, jerry, max, and andy'); expect($thumbsUpEmoji.attr('title')).toBe('sam, jerry, max, and andy');
}); });
it('handles the special case where "You" is not cleanly comma separated', () => { it('handles the special case where "You" is not cleanly comma separated', () => {
...@@ -254,9 +249,8 @@ describe('AwardsHandler', () => { ...@@ -254,9 +249,8 @@ describe('AwardsHandler', () => {
$thumbsUpEmoji.attr('data-title', 'You and sam'); $thumbsUpEmoji.attr('data-title', 'You and sam');
$thumbsUpEmoji.addClass('active'); $thumbsUpEmoji.addClass('active');
awardsHandler.addAward($votesBlock, awardUrl, 'thumbsup', false); awardsHandler.addAward($votesBlock, awardUrl, 'thumbsup', false);
$thumbsUpEmoji.tooltip();
expect($thumbsUpEmoji.data('originalTitle')).toBe('sam'); expect($thumbsUpEmoji.attr('title')).toBe('sam');
}); });
}); });
......
import $ from 'jquery';
import { togglePopover, mouseleave, mouseenter } from '~/shared/popover';
describe('popover', () => {
describe('togglePopover', () => {
describe('togglePopover(true)', () => {
it('returns true when popover is shown', () => {
const context = {
hasClass: () => false,
popover: () => {},
toggleClass: () => {},
};
expect(togglePopover.call(context, true)).toEqual(true);
});
it('returns false when popover is already shown', () => {
const context = {
hasClass: () => true,
};
expect(togglePopover.call(context, true)).toEqual(false);
});
it('shows popover', (done) => {
const context = {
hasClass: () => false,
popover: () => {},
toggleClass: () => {},
};
jest.spyOn(context, 'popover').mockImplementation((method) => {
expect(method).toEqual('show');
done();
});
togglePopover.call(context, true);
});
it('adds disable-animation and js-popover-show class', (done) => {
const context = {
hasClass: () => false,
popover: () => {},
toggleClass: () => {},
};
jest.spyOn(context, 'toggleClass').mockImplementation((classNames, show) => {
expect(classNames).toEqual('disable-animation js-popover-show');
expect(show).toEqual(true);
done();
});
togglePopover.call(context, true);
});
});
describe('togglePopover(false)', () => {
it('returns true when popover is hidden', () => {
const context = {
hasClass: () => true,
popover: () => {},
toggleClass: () => {},
};
expect(togglePopover.call(context, false)).toEqual(true);
});
it('returns false when popover is already hidden', () => {
const context = {
hasClass: () => false,
};
expect(togglePopover.call(context, false)).toEqual(false);
});
it('hides popover', (done) => {
const context = {
hasClass: () => true,
popover: () => {},
toggleClass: () => {},
};
jest.spyOn(context, 'popover').mockImplementation((method) => {
expect(method).toEqual('hide');
done();
});
togglePopover.call(context, false);
});
it('removes disable-animation and js-popover-show class', (done) => {
const context = {
hasClass: () => true,
popover: () => {},
toggleClass: () => {},
};
jest.spyOn(context, 'toggleClass').mockImplementation((classNames, show) => {
expect(classNames).toEqual('disable-animation js-popover-show');
expect(show).toEqual(false);
done();
});
togglePopover.call(context, false);
});
});
});
describe('mouseleave', () => {
it('calls hide popover if .popover:hover is false', () => {
const fakeJquery = {
length: 0,
};
jest
.spyOn($.fn, 'init')
.mockImplementation((selector) => (selector === '.popover:hover' ? fakeJquery : $.fn));
jest.spyOn(togglePopover, 'call').mockImplementation(() => {});
mouseleave();
expect(togglePopover.call).toHaveBeenCalledWith(expect.any(Object), false);
});
it('does not call hide popover if .popover:hover is true', () => {
const fakeJquery = {
length: 1,
};
jest
.spyOn($.fn, 'init')
.mockImplementation((selector) => (selector === '.popover:hover' ? fakeJquery : $.fn));
jest.spyOn(togglePopover, 'call').mockImplementation(() => {});
mouseleave();
expect(togglePopover.call).not.toHaveBeenCalledWith(false);
});
});
describe('mouseenter', () => {
const context = {};
it('shows popover', () => {
jest.spyOn(togglePopover, 'call').mockReturnValue(false);
mouseenter.call(context);
expect(togglePopover.call).toHaveBeenCalledWith(expect.any(Object), true);
});
it('registers mouseleave event if popover is showed', (done) => {
jest.spyOn(togglePopover, 'call').mockReturnValue(true);
jest.spyOn($.fn, 'on').mockImplementation((eventName) => {
expect(eventName).toEqual('mouseleave');
done();
});
mouseenter.call(context);
});
it('does not register mouseleave event if popover is not showed', () => {
jest.spyOn(togglePopover, 'call').mockReturnValue(false);
const spy = jest.spyOn($.fn, 'on').mockImplementation(() => {});
mouseenter.call(context);
expect(spy).not.toHaveBeenCalled();
});
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment