import { mount } from '@vue/test-utils'; import $ from 'jquery'; import { escape } from 'lodash'; import tooltip from '~/vue_shared/directives/tooltip'; const DEFAULT_TOOLTIP_TEMPLATE = '
'; const HTML_TOOLTIP_TEMPLATE = '
'; describe('Tooltip directive', () => { let wrapper; function createTooltipContainer({ template = DEFAULT_TOOLTIP_TEMPLATE, text = 'some text', } = {}) { wrapper = mount( { directives: { tooltip }, data: () => ({ tooltip: text }), template, }, { attachTo: document.body }, ); } async function showTooltip() { $(wrapper.vm.$el).tooltip('show'); jest.runOnlyPendingTimers(); await wrapper.vm.$nextTick(); } function findTooltipInnerHtml() { return document.querySelector('.tooltip-inner').innerHTML; } function findTooltipHtml() { return document.querySelector('.tooltip').innerHTML; } afterEach(() => { wrapper.destroy(); wrapper = null; }); describe('with a single tooltip', () => { it('should have tooltip plugin applied', () => { createTooltipContainer(); expect($(wrapper.vm.$el).data('bs.tooltip')).toBeDefined(); }); it('displays the title as tooltip', () => { createTooltipContainer(); $(wrapper.vm.$el).tooltip('show'); jest.runOnlyPendingTimers(); const tooltipElement = document.querySelector('.tooltip-inner'); expect(tooltipElement.textContent).toContain('some text'); }); it.each` condition | template | sanitize ${'does not contain any html'} | ${DEFAULT_TOOLTIP_TEMPLATE} | ${false} ${'contains html'} | ${HTML_TOOLTIP_TEMPLATE} | ${true} `('passes sanitize=$sanitize if the tooltip $condition', ({ template, sanitize }) => { createTooltipContainer({ template }); expect($(wrapper.vm.$el).data('bs.tooltip').config.sanitize).toEqual(sanitize); }); it('updates a visible tooltip', async () => { createTooltipContainer(); $(wrapper.vm.$el).tooltip('show'); jest.runOnlyPendingTimers(); const tooltipElement = document.querySelector('.tooltip-inner'); wrapper.vm.tooltip = 'other text'; jest.runOnlyPendingTimers(); await wrapper.vm.$nextTick(); expect(tooltipElement.textContent).toContain('other text'); }); describe('tooltip sanitization', () => { it('reads tooltip content as text if data-html is not passed', async () => { createTooltipContainer({ text: 'sample text' }); await showTooltip(); const result = findTooltipInnerHtml(); expect(result).toEqual('sample text<script>alert("XSS!!")</script>'); }); it('sanitizes tooltip if data-html is passed', async () => { createTooltipContainer({ template: HTML_TOOLTIP_TEMPLATE, text: 'sample text', }); await showTooltip(); const result = findTooltipInnerHtml(); expect(result).toEqual('sample text'); expect(result).not.toContain('XSS!!'); }); it('sanitizes tooltip if data-template is passed', async () => { const tooltipTemplate = escape( '', ); createTooltipContainer({ template: `
`, }); await showTooltip(); const result = findTooltipHtml(); expect(result).toEqual( // objectionable element is removed '
some text
', ); expect(result).not.toContain('XSS!!'); }); }); }); describe('with multiple tooltips', () => { beforeEach(() => { createTooltipContainer({ template: `
`, }); }); it('should have tooltip plugin applied to all instances', () => { expect($(wrapper.vm.$el).find('.js-look-for-tooltip').data('bs.tooltip')).toBeDefined(); }); }); });