Commit e5e4531c authored by Kushal Pandya's avatar Kushal Pandya

Merge branch '229044-tooltips-hide-and-disable' into 'master'

Implement tooltip API for the following actions

Closes #229044

See merge request gitlab-org/gitlab!41941
parents 8019bd27 0faf72b0
...@@ -39,7 +39,7 @@ export default { ...@@ -39,7 +39,7 @@ export default {
created() { created() {
this.observer = new MutationObserver(mutations => { this.observer = new MutationObserver(mutations => {
mutations.forEach(mutation => { mutations.forEach(mutation => {
this.dispose(mutation.removedNodes); mutation.removedNodes.forEach(this.dispose);
}); });
}); });
}, },
...@@ -61,22 +61,36 @@ export default { ...@@ -61,22 +61,36 @@ export default {
childList: true, childList: true,
}); });
}, },
dispose(elements) { dispose(target) {
if (!elements) { if (!target) {
this.tooltips = []; this.tooltips = [];
return; } else {
} const index = this.tooltips.indexOf(this.findTooltipByTarget(target));
elements.forEach(element => {
const index = this.tooltips.findIndex(tooltip => tooltip.target === element);
if (index > -1) { if (index > -1) {
this.tooltips.splice(index, 1); this.tooltips.splice(index, 1);
} }
}); }
},
fixTitle(target) {
const tooltip = this.findTooltipByTarget(target);
if (tooltip) {
tooltip.title = target.getAttribute('title');
}
},
triggerEvent(target, event) {
const tooltip = this.findTooltipByTarget(target);
if (tooltip) {
this.$refs[tooltip.id][0].$emit(event);
}
}, },
tooltipExists(element) { tooltipExists(element) {
return this.tooltips.some(tooltip => tooltip.target === element); return Boolean(this.findTooltipByTarget(element));
},
findTooltipByTarget(element) {
return this.tooltips.find(tooltip => tooltip.target === element);
}, },
}, },
}; };
...@@ -86,6 +100,7 @@ export default { ...@@ -86,6 +100,7 @@ export default {
<gl-tooltip <gl-tooltip
v-for="(tooltip, index) in tooltips" v-for="(tooltip, index) in tooltips"
:id="tooltip.id" :id="tooltip.id"
:ref="tooltip.id"
:key="index" :key="index"
:target="tooltip.target" :target="tooltip.target"
:triggers="tooltip.triggers" :triggers="tooltip.triggers"
......
import Vue from 'vue'; import Vue from 'vue';
import { toArray } from 'lodash';
import Tooltips from './components/tooltips.vue'; import Tooltips from './components/tooltips.vue';
let app; let app;
...@@ -31,13 +32,13 @@ const tooltipsApp = () => { ...@@ -31,13 +32,13 @@ const tooltipsApp = () => {
}).$mount(container); }).$mount(container);
} }
return app; return app.$refs.tooltips;
}; };
const isTooltip = (node, selector) => node.matches && node.matches(selector); const isTooltip = (node, selector) => node.matches && node.matches(selector);
const addTooltips = (elements, config) => { const addTooltips = (elements, config) => {
tooltipsApp().$refs.tooltips.addTooltips(Array.from(elements), config); tooltipsApp().addTooltips(toArray(elements), config);
}; };
const handleTooltipEvent = (rootTarget, e, selector, config = {}) => { const handleTooltipEvent = (rootTarget, e, selector, config = {}) => {
...@@ -63,9 +64,14 @@ export const initTooltips = (selector, config = {}) => { ...@@ -63,9 +64,14 @@ export const initTooltips = (selector, config = {}) => {
return tooltipsApp(); return tooltipsApp();
}; };
export const dispose = elements => { const elementsIterator = handler => elements => toArray(elements).forEach(handler);
return tooltipsApp().$refs.tooltips.dispose(elements);
}; export const dispose = elementsIterator(element => tooltipsApp().dispose(element));
export const fixTitle = elementsIterator(element => tooltipsApp().fixTitle(element));
export const enable = elementsIterator(element => tooltipsApp().triggerEvent(element, 'enable'));
export const disable = elementsIterator(element => tooltipsApp().triggerEvent(element, 'disable'));
export const hide = elementsIterator(element => tooltipsApp().triggerEvent(element, 'close'));
export const show = elementsIterator(element => tooltipsApp().triggerEvent(element, 'open'));
export const destroy = () => { export const destroy = () => {
tooltipsApp().$destroy(); tooltipsApp().$destroy();
......
...@@ -120,7 +120,7 @@ describe('tooltips/components/tooltips.vue', () => { ...@@ -120,7 +120,7 @@ describe('tooltips/components/tooltips.vue', () => {
wrapper.vm.addTooltips([target, createTooltipTarget()]); wrapper.vm.addTooltips([target, createTooltipTarget()]);
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
wrapper.vm.dispose([target]); wrapper.vm.dispose(target);
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
expect(allTooltips()).toHaveLength(1); expect(allTooltips()).toHaveLength(1);
...@@ -148,6 +148,48 @@ describe('tooltips/components/tooltips.vue', () => { ...@@ -148,6 +148,48 @@ describe('tooltips/components/tooltips.vue', () => {
}); });
}); });
describe('triggerEvent', () => {
it('triggers a bootstrap-vue tooltip global event for the tooltip specified', async () => {
const target = createTooltipTarget();
const event = 'hide';
buildWrapper();
wrapper.vm.addTooltips([target]);
await wrapper.vm.$nextTick();
wrapper.vm.triggerEvent(target, event);
expect(wrapper.find(GlTooltip).emitted(event)).toHaveLength(1);
});
});
describe('fixTitle', () => {
it('updates tooltip content with the latest value the target title property', async () => {
const target = createTooltipTarget();
const currentTitle = 'title';
const newTitle = 'new title';
target.setAttribute('title', currentTitle);
buildWrapper();
wrapper.vm.addTooltips([target]);
await wrapper.vm.$nextTick();
expect(wrapper.find(GlTooltip).text()).toBe(currentTitle);
target.setAttribute('title', newTitle);
wrapper.vm.fixTitle(target);
await wrapper.vm.$nextTick();
expect(wrapper.find(GlTooltip).text()).toBe(newTitle);
});
});
it('disconnects mutation observer on beforeDestroy', () => { it('disconnects mutation observer on beforeDestroy', () => {
buildWrapper(); buildWrapper();
wrapper.vm.addTooltips([createTooltipTarget()]); wrapper.vm.addTooltips([createTooltipTarget()]);
......
import { initTooltips, dispose, destroy } from '~/tooltips'; import { initTooltips, dispose, destroy, hide, show, enable, disable, fixTitle } from '~/tooltips';
describe('tooltips/index.js', () => { describe('tooltips/index.js', () => {
let tooltipsApp; let tooltipsApp;
...@@ -80,4 +80,41 @@ describe('tooltips/index.js', () => { ...@@ -80,4 +80,41 @@ describe('tooltips/index.js', () => {
expect(document.querySelector('.gl-tooltip')).toBe(null); expect(document.querySelector('.gl-tooltip')).toBe(null);
}); });
}); });
it.each`
methodName | method | event
${'enable'} | ${enable} | ${'enable'}
${'disable'} | ${disable} | ${'disable'}
${'hide'} | ${hide} | ${'close'}
${'show'} | ${show} | ${'open'}
`(
'$methodName calls triggerEvent in tooltip app with $event event',
async ({ method, event }) => {
const target = createTooltipTarget();
buildTooltipsApp();
await tooltipsApp.$nextTick();
jest.spyOn(tooltipsApp, 'triggerEvent');
method([target]);
expect(tooltipsApp.triggerEvent).toHaveBeenCalledWith(target, event);
},
);
it('fixTitle calls fixTitle in tooltip app with the target specified', async () => {
const target = createTooltipTarget();
buildTooltipsApp();
await tooltipsApp.$nextTick();
jest.spyOn(tooltipsApp, 'fixTitle');
fixTitle([target]);
expect(tooltipsApp.fixTitle).toHaveBeenCalledWith(target);
});
}); });
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