Commit 6698021a authored by Andrew Fontaine's avatar Andrew Fontaine

Merge branch '229044-bootstrap-tooltip-api-facade' into 'master'

Bootstrap tooltip API facade

Closes #229044

See merge request gitlab-org/gitlab!42399
parents 5e3c8ce1 a6e28b5c
import Vue from 'vue'; import Vue from 'vue';
import { toArray } from 'lodash'; import jQuery from 'jquery';
import { toArray, isFunction } from 'lodash';
import Tooltips from './components/tooltips.vue'; import Tooltips from './components/tooltips.vue';
let app; let app;
...@@ -53,26 +54,66 @@ const handleTooltipEvent = (rootTarget, e, selector, config = {}) => { ...@@ -53,26 +54,66 @@ const handleTooltipEvent = (rootTarget, e, selector, config = {}) => {
} }
}; };
export const initTooltips = (selector, config = {}) => { const applyToElements = (elements, handler) => toArray(elements).forEach(handler);
const triggers = config?.triggers || DEFAULT_TRIGGER;
const events = triggers.split(' ').map(trigger => EVENTS_MAP[trigger]);
events.forEach(event => { const invokeBootstrapApi = (elements, method) => {
document.addEventListener(event, e => handleTooltipEvent(document, e, selector, config), true); if (isFunction(elements.tooltip)) {
}); jQuery(elements).tooltip(method);
}
};
return tooltipsApp(); const isGlTooltipsEnabled = () => Boolean(window.gon.glTooltipsEnabled);
const tooltipApiInvoker = ({ glHandler, bsHandler }) => (elements, ...params) => {
if (isGlTooltipsEnabled()) {
applyToElements(elements, glHandler);
} else {
bsHandler(elements, ...params);
}
}; };
const elementsIterator = handler => elements => toArray(elements).forEach(handler); export const initTooltips = (config = {}) => {
if (isGlTooltipsEnabled()) {
const triggers = config?.triggers || DEFAULT_TRIGGER;
const events = triggers.split(' ').map(trigger => EVENTS_MAP[trigger]);
export const dispose = elementsIterator(element => tooltipsApp().dispose(element)); events.forEach(event => {
export const fixTitle = elementsIterator(element => tooltipsApp().fixTitle(element)); document.addEventListener(
export const enable = elementsIterator(element => tooltipsApp().triggerEvent(element, 'enable')); event,
export const disable = elementsIterator(element => tooltipsApp().triggerEvent(element, 'disable')); e => handleTooltipEvent(document, e, config.selector, config),
export const hide = elementsIterator(element => tooltipsApp().triggerEvent(element, 'close')); true,
export const show = elementsIterator(element => tooltipsApp().triggerEvent(element, 'open')); );
});
return tooltipsApp();
}
return invokeBootstrapApi(document.body, config);
};
export const dispose = tooltipApiInvoker({
glHandler: element => tooltipsApp().dispose(element),
bsHandler: elements => invokeBootstrapApi(elements, 'dispose'),
});
export const fixTitle = tooltipApiInvoker({
glHandler: element => tooltipsApp().fixTitle(element),
bsHandler: elements => invokeBootstrapApi(elements, '_fixTitle'),
});
export const enable = tooltipApiInvoker({
glHandler: element => tooltipsApp().triggerEvent(element, 'enable'),
bsHandler: elements => invokeBootstrapApi(elements, 'enable'),
});
export const disable = tooltipApiInvoker({
glHandler: element => tooltipsApp().triggerEvent(element, 'disable'),
bsHandler: elements => invokeBootstrapApi(elements, 'disable'),
});
export const hide = tooltipApiInvoker({
glHandler: element => tooltipsApp().triggerEvent(element, 'close'),
bsHandler: elements => invokeBootstrapApi(elements, 'hide'),
});
export const show = tooltipApiInvoker({
glHandler: element => tooltipsApp().triggerEvent(element, 'open'),
bsHandler: elements => invokeBootstrapApi(elements, 'show'),
});
export const destroy = () => { export const destroy = () => {
tooltipsApp().$destroy(); tooltipsApp().$destroy();
app = null; app = null;
......
import jQuery from 'jquery';
import { initTooltips, dispose, destroy, hide, show, enable, disable, fixTitle } from '~/tooltips'; import { initTooltips, dispose, destroy, hide, show, enable, disable, fixTitle } from '~/tooltips';
describe('tooltips/index.js', () => { describe('tooltips/index.js', () => {
...@@ -21,7 +22,7 @@ describe('tooltips/index.js', () => { ...@@ -21,7 +22,7 @@ describe('tooltips/index.js', () => {
}; };
const buildTooltipsApp = () => { const buildTooltipsApp = () => {
tooltipsApp = initTooltips('.has-tooltip'); tooltipsApp = initTooltips({ selector: '.has-tooltip' });
}; };
const triggerEvent = (target, eventName = 'mouseenter') => { const triggerEvent = (target, eventName = 'mouseenter') => {
...@@ -30,6 +31,10 @@ describe('tooltips/index.js', () => { ...@@ -30,6 +31,10 @@ describe('tooltips/index.js', () => {
target.dispatchEvent(event); target.dispatchEvent(event);
}; };
beforeEach(() => {
window.gon.glTooltipsEnabled = true;
});
afterEach(() => { afterEach(() => {
document.body.childNodes.forEach(node => node.remove()); document.body.childNodes.forEach(node => node.remove());
destroy(); destroy();
...@@ -117,4 +122,28 @@ describe('tooltips/index.js', () => { ...@@ -117,4 +122,28 @@ describe('tooltips/index.js', () => {
expect(tooltipsApp.fixTitle).toHaveBeenCalledWith(target); expect(tooltipsApp.fixTitle).toHaveBeenCalledWith(target);
}); });
describe('when glTooltipsEnabled feature flag is disabled', () => {
beforeEach(() => {
window.gon.glTooltipsEnabled = false;
});
it.each`
method | methodName | bootstrapParams
${dispose} | ${'dispose'} | ${'dispose'}
${fixTitle} | ${'fixTitle'} | ${'_fixTitle'}
${enable} | ${'enable'} | ${'enable'}
${disable} | ${'disable'} | ${'disable'}
${hide} | ${'hide'} | ${'hide'}
${show} | ${'show'} | ${'show'}
`('delegates $methodName to bootstrap tooltip API', ({ method, bootstrapParams }) => {
const elements = jQuery(createTooltipTarget());
jest.spyOn(jQuery.fn, 'tooltip');
method(elements);
expect(elements.tooltip).toHaveBeenCalledWith(bootstrapParams);
});
});
}); });
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