2021-03-11 19:13:27 +05:30
|
|
|
import { mount } from '@vue/test-utils';
|
2020-06-23 00:09:42 +05:30
|
|
|
import $ from 'jquery';
|
2021-01-03 14:25:43 +05:30
|
|
|
import { escape } from 'lodash';
|
2020-06-23 00:09:42 +05:30
|
|
|
import tooltip from '~/vue_shared/directives/tooltip';
|
|
|
|
|
2021-01-03 14:25:43 +05:30
|
|
|
const DEFAULT_TOOLTIP_TEMPLATE = '<div v-tooltip :title="tooltip"></div>';
|
|
|
|
const HTML_TOOLTIP_TEMPLATE = '<div v-tooltip data-html="true" :title="tooltip"></div>';
|
|
|
|
|
2020-06-23 00:09:42 +05:30
|
|
|
describe('Tooltip directive', () => {
|
2021-01-03 14:25:43 +05:30
|
|
|
let wrapper;
|
|
|
|
|
|
|
|
function createTooltipContainer({
|
|
|
|
template = DEFAULT_TOOLTIP_TEMPLATE,
|
|
|
|
text = 'some text',
|
|
|
|
} = {}) {
|
|
|
|
wrapper = mount(
|
|
|
|
{
|
|
|
|
directives: { tooltip },
|
|
|
|
data: () => ({ tooltip: text }),
|
|
|
|
template,
|
|
|
|
},
|
2021-03-08 18:12:59 +05:30
|
|
|
{ attachTo: document.body },
|
2021-01-03 14:25:43 +05:30
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2020-06-23 00:09:42 +05:30
|
|
|
|
|
|
|
afterEach(() => {
|
2021-01-03 14:25:43 +05:30
|
|
|
wrapper.destroy();
|
|
|
|
wrapper = null;
|
2020-06-23 00:09:42 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
describe('with a single tooltip', () => {
|
|
|
|
it('should have tooltip plugin applied', () => {
|
2021-01-03 14:25:43 +05:30
|
|
|
createTooltipContainer();
|
|
|
|
|
|
|
|
expect($(wrapper.vm.$el).data('bs.tooltip')).toBeDefined();
|
2020-06-23 00:09:42 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
it('displays the title as tooltip', () => {
|
2021-01-03 14:25:43 +05:30
|
|
|
createTooltipContainer();
|
|
|
|
|
|
|
|
$(wrapper.vm.$el).tooltip('show');
|
|
|
|
|
2020-06-23 00:09:42 +05:30
|
|
|
jest.runOnlyPendingTimers();
|
|
|
|
|
|
|
|
const tooltipElement = document.querySelector('.tooltip-inner');
|
|
|
|
|
|
|
|
expect(tooltipElement.textContent).toContain('some text');
|
|
|
|
});
|
|
|
|
|
2021-01-03 14:25:43 +05:30
|
|
|
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');
|
2020-06-23 00:09:42 +05:30
|
|
|
jest.runOnlyPendingTimers();
|
|
|
|
|
|
|
|
const tooltipElement = document.querySelector('.tooltip-inner');
|
|
|
|
|
2021-01-03 14:25:43 +05:30
|
|
|
wrapper.vm.tooltip = 'other text';
|
2020-06-23 00:09:42 +05:30
|
|
|
|
|
|
|
jest.runOnlyPendingTimers();
|
2021-01-03 14:25:43 +05:30
|
|
|
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<script>alert("XSS!!")</script>' });
|
2020-06-23 00:09:42 +05:30
|
|
|
|
2021-01-03 14:25:43 +05:30
|
|
|
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<script>alert("XSS!!")</script>',
|
|
|
|
});
|
|
|
|
|
|
|
|
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(
|
|
|
|
'<div class="tooltip" role="tooltip"><div onclick="alert(\'XSS!\')" class="arrow"></div><div class="tooltip-inner"></div></div>',
|
|
|
|
);
|
|
|
|
|
|
|
|
createTooltipContainer({
|
|
|
|
template: `<div v-tooltip :title="tooltip" data-html="false" data-template="${tooltipTemplate}"></div>`,
|
|
|
|
});
|
|
|
|
|
|
|
|
await showTooltip();
|
|
|
|
|
|
|
|
const result = findTooltipHtml();
|
|
|
|
expect(result).toEqual(
|
|
|
|
// objectionable element is removed
|
|
|
|
'<div class="arrow"></div><div class="tooltip-inner">some text</div>',
|
|
|
|
);
|
|
|
|
expect(result).not.toContain('XSS!!');
|
2020-06-23 00:09:42 +05:30
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('with multiple tooltips', () => {
|
|
|
|
beforeEach(() => {
|
2021-01-03 14:25:43 +05:30
|
|
|
createTooltipContainer({
|
|
|
|
template: `
|
|
|
|
<div>
|
|
|
|
<div
|
|
|
|
v-tooltip
|
|
|
|
class="js-look-for-tooltip"
|
|
|
|
title="foo">
|
2020-06-23 00:09:42 +05:30
|
|
|
</div>
|
2021-01-03 14:25:43 +05:30
|
|
|
<div
|
|
|
|
v-tooltip
|
|
|
|
title="bar">
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
`,
|
|
|
|
});
|
2020-06-23 00:09:42 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
it('should have tooltip plugin applied to all instances', () => {
|
2021-03-08 18:12:59 +05:30
|
|
|
expect($(wrapper.vm.$el).find('.js-look-for-tooltip').data('bs.tooltip')).toBeDefined();
|
2020-06-23 00:09:42 +05:30
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|