Commit 13024982 authored by Phil Hughes's avatar Phil Hughes

Merge branch 'tz-treeshake-bs-tooltips' into 'master'

Remove bootstrap tooltip and popovers

See merge request gitlab-org/gitlab!51321
parents 6e923530 da006492
...@@ -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