650 lines
16 KiB
JavaScript
650 lines
16 KiB
JavaScript
|
import Tooltip from '../../src/index.js';
|
||
|
import '@popperjs/test-utils';
|
||
|
import then from '@popperjs/test-utils/utils/then.js';
|
||
|
import '@popperjs/test-utils/utils/customEventPolyfill.js';
|
||
|
|
||
|
const jasmineWrapper = document.getElementById('jasmineWrapper');
|
||
|
const isIE10 = navigator.appVersion.indexOf('MSIE 10') !== -1;
|
||
|
|
||
|
let reference;
|
||
|
let instance;
|
||
|
function createReference() {
|
||
|
reference = document.createElement('div');
|
||
|
reference.style.width = '100px';
|
||
|
reference.style.height = '100px';
|
||
|
reference.style.margin = '100px';
|
||
|
reference.textContent = 'reference';
|
||
|
jasmineWrapper.appendChild(reference);
|
||
|
}
|
||
|
|
||
|
describe('[tooltip.js]', () => {
|
||
|
describe('manual', () => {
|
||
|
beforeEach(() => {
|
||
|
createReference();
|
||
|
});
|
||
|
|
||
|
it('should show tooltip', done => {
|
||
|
instance = new Tooltip(reference, {
|
||
|
title: 'foobar',
|
||
|
});
|
||
|
|
||
|
instance.show();
|
||
|
|
||
|
then(() => {
|
||
|
expect(document.querySelector('.tooltip')).not.toBeNull();
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('should hide tooltip', done => {
|
||
|
instance = new Tooltip(reference, {
|
||
|
title: 'foobar',
|
||
|
});
|
||
|
|
||
|
instance.show();
|
||
|
then(() => instance.hide());
|
||
|
|
||
|
then(() => {
|
||
|
expect(document.querySelector('.tooltip').style.visibility).toBe('hidden');
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('should toggle (show) tooltip', done => {
|
||
|
instance = new Tooltip(reference, {
|
||
|
title: 'foobar',
|
||
|
});
|
||
|
|
||
|
instance.toggle();
|
||
|
|
||
|
then(() => {
|
||
|
expect(document.querySelector('.tooltip')).not.toBeNull();
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('should toggle (hide) tooltip', done => {
|
||
|
instance = new Tooltip(reference, {
|
||
|
title: 'foobar',
|
||
|
});
|
||
|
|
||
|
instance.show();
|
||
|
then(() => instance.toggle());
|
||
|
|
||
|
then(() => {
|
||
|
expect(document.querySelector('.tooltip').style.visibility).toBe('hidden');
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('should show, hide and show again a tooltip', done => {
|
||
|
instance = new Tooltip(reference, {
|
||
|
title: 'foobar',
|
||
|
});
|
||
|
|
||
|
instance.show();
|
||
|
then(() => instance.hide());
|
||
|
then(() => instance.show());
|
||
|
|
||
|
then(() => {
|
||
|
expect(document.querySelector('.tooltip')).not.toBeNull();
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('should dispose tooltip', done => {
|
||
|
instance = new Tooltip(reference, {
|
||
|
title: 'foobar',
|
||
|
});
|
||
|
|
||
|
instance.show();
|
||
|
then(() => instance.dispose());
|
||
|
|
||
|
then(() => {
|
||
|
expect(document.querySelector('.tooltip')).toBeNull();
|
||
|
});
|
||
|
|
||
|
then(() => {
|
||
|
reference.click();
|
||
|
});
|
||
|
|
||
|
then(() => {
|
||
|
expect(document.querySelector('.tooltip')).toBeNull();
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('should dispose tooltip despite never being shown', done => {
|
||
|
instance = new Tooltip(reference, {
|
||
|
title: 'foobar',
|
||
|
});
|
||
|
|
||
|
instance.dispose();
|
||
|
|
||
|
then(() => {
|
||
|
expect(document.querySelector('.tooltip')).toBeNull();
|
||
|
});
|
||
|
|
||
|
then(() => {
|
||
|
reference.dispatchEvent(new CustomEvent('mouseenter'));
|
||
|
});
|
||
|
|
||
|
then(() => {
|
||
|
expect(document.querySelector('.tooltip')).toBeNull();
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('container', () => {
|
||
|
beforeEach(() => {
|
||
|
createReference();
|
||
|
});
|
||
|
|
||
|
it('should show tooltip as child of body', done => {
|
||
|
instance = new Tooltip(reference, {
|
||
|
title: 'foobar',
|
||
|
container: 'body',
|
||
|
});
|
||
|
|
||
|
instance.show();
|
||
|
|
||
|
then(() => {
|
||
|
expect(document.querySelector('.tooltip').parentNode).toBe(
|
||
|
document.body
|
||
|
);
|
||
|
instance.dispose();
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('content', () => {
|
||
|
beforeEach(() => {
|
||
|
createReference();
|
||
|
});
|
||
|
|
||
|
it('should show text tooltip', done => {
|
||
|
instance = new Tooltip(reference, {
|
||
|
title: 'foobar',
|
||
|
});
|
||
|
|
||
|
instance.show();
|
||
|
|
||
|
then(() => {
|
||
|
expect(
|
||
|
document.querySelector('.tooltip .tooltip-inner').textContent
|
||
|
).toBe('foobar');
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('should show HTML tooltip', done => {
|
||
|
instance = new Tooltip(reference, {
|
||
|
title: '<strong>foobar</strong>',
|
||
|
html: true,
|
||
|
});
|
||
|
|
||
|
instance.show();
|
||
|
|
||
|
then(() => {
|
||
|
expect(
|
||
|
document.querySelector('.tooltip .tooltip-inner').innerHTML
|
||
|
).toBe('<strong>foobar</strong>');
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('should show stripped out HTML tooltip', done => {
|
||
|
instance = new Tooltip(reference, {
|
||
|
title: '<strong>foobar</strong>',
|
||
|
html: true,
|
||
|
});
|
||
|
|
||
|
instance.show();
|
||
|
|
||
|
then(() => {
|
||
|
expect(
|
||
|
document.querySelector('.tooltip .tooltip-inner').textContent
|
||
|
).toBe('foobar');
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('should use a DOM node as tooltip content', done => {
|
||
|
const content = document.createElement('div');
|
||
|
content.textContent = 'foobar';
|
||
|
instance = new Tooltip(reference, {
|
||
|
title: content,
|
||
|
html: true,
|
||
|
});
|
||
|
|
||
|
instance.show();
|
||
|
|
||
|
then(() => {
|
||
|
expect(
|
||
|
document.querySelector('.tooltip .tooltip-inner').innerHTML
|
||
|
).toBe('<div>foobar</div>');
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('should use a document fragment as tooltip content', done => {
|
||
|
const content = document.createDocumentFragment();
|
||
|
const inner = document.createElement('div');
|
||
|
inner.textContent = 'test';
|
||
|
content.appendChild(inner);
|
||
|
instance = new Tooltip(reference, {
|
||
|
title: content,
|
||
|
html: true,
|
||
|
});
|
||
|
|
||
|
instance.show();
|
||
|
|
||
|
then(() => {
|
||
|
expect(
|
||
|
document.querySelector('.tooltip .tooltip-inner').innerHTML
|
||
|
).toBe('<div>test</div>');
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('should use a string returned by a function as tooltip content', done => {
|
||
|
instance = new Tooltip(reference, {
|
||
|
title: () => 'foobar',
|
||
|
});
|
||
|
|
||
|
instance.show();
|
||
|
|
||
|
then(() => {
|
||
|
expect(
|
||
|
document.querySelector('.tooltip .tooltip-inner').textContent
|
||
|
).toBe('foobar');
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('should use a DOM node returned by a function as tooltip content', done => {
|
||
|
const content = document.createElement('div');
|
||
|
content.textContent = 'foobar';
|
||
|
instance = new Tooltip(reference, {
|
||
|
title: () => content,
|
||
|
html: true,
|
||
|
});
|
||
|
|
||
|
instance.show();
|
||
|
|
||
|
then(() => {
|
||
|
expect(
|
||
|
document.querySelector('.tooltip .tooltip-inner').innerHTML
|
||
|
).toBe('<div>foobar</div>');
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('should use a document fragment returned by a function as tooltip content', done => {
|
||
|
const content = document.createDocumentFragment();
|
||
|
const inner = document.createElement('div');
|
||
|
inner.textContent = 'test';
|
||
|
content.appendChild(inner);
|
||
|
instance = new Tooltip(reference, {
|
||
|
title: () => content,
|
||
|
html: true,
|
||
|
});
|
||
|
|
||
|
instance.show();
|
||
|
|
||
|
then(() => {
|
||
|
expect(
|
||
|
document.querySelector('.tooltip .tooltip-inner').innerHTML
|
||
|
).toBe('<div>test</div>');
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('should not use dom node returned by function as tooltip content if html option disabled', done => {
|
||
|
const content = document.createElement('div');
|
||
|
content.textContent = 'test';
|
||
|
instance = new Tooltip(reference, {
|
||
|
title: () => content,
|
||
|
html: false,
|
||
|
});
|
||
|
|
||
|
instance.show();
|
||
|
|
||
|
then(() => {
|
||
|
expect(
|
||
|
document.querySelector('.tooltip .tooltip-inner').innerHTML
|
||
|
).toBe('');
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('should use a custom template with empty leading&trailing spaces', done => {
|
||
|
instance = new Tooltip(reference, {
|
||
|
title: 'foobar',
|
||
|
template: `
|
||
|
<div class="tooltip" role="tooltip">
|
||
|
<div class="tooltip-arrow"></div>
|
||
|
<div class="foobar"><div class="tooltip-inner"></div></div>
|
||
|
</div>
|
||
|
`,
|
||
|
});
|
||
|
|
||
|
instance.show();
|
||
|
|
||
|
then(() => {
|
||
|
expect(document.querySelector('.tooltip .foobar').textContent).toBe(
|
||
|
'foobar'
|
||
|
);
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('should update title content with String', done => {
|
||
|
const updatedContent = 'Updated string';
|
||
|
instance = new Tooltip(reference, {
|
||
|
title: 'Constructor message',
|
||
|
});
|
||
|
|
||
|
instance.show();
|
||
|
|
||
|
instance.updateTitleContent(updatedContent);
|
||
|
|
||
|
then(() => {
|
||
|
expect(
|
||
|
document.querySelector('.tooltip .tooltip-inner').textContent
|
||
|
).toBe(updatedContent);
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('should update title content with HTMLElement', done => {
|
||
|
const updatedContent = 'Updated with div element';
|
||
|
const el = document.createElement('div');
|
||
|
el.textContent = updatedContent;
|
||
|
instance = new Tooltip(reference, {
|
||
|
title: 'Constructor message',
|
||
|
html: true,
|
||
|
});
|
||
|
|
||
|
instance.show();
|
||
|
|
||
|
instance.updateTitleContent(el);
|
||
|
|
||
|
then(() => {
|
||
|
expect(document.querySelector('.tooltip-inner').innerHTML).toBe(
|
||
|
el.outerHTML
|
||
|
);
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('should update the tooltip position when changing the title', done => {
|
||
|
// Unreliable on IE...
|
||
|
if (isIE10) {
|
||
|
pending();
|
||
|
}
|
||
|
const updatedContent = 'Updated string with a different length';
|
||
|
instance = new Tooltip(reference, {
|
||
|
title: 'Constructor message',
|
||
|
});
|
||
|
|
||
|
instance.show();
|
||
|
|
||
|
const oldPosition = document
|
||
|
.querySelector('.tooltip .tooltip-inner')
|
||
|
.getBoundingClientRect().left;
|
||
|
|
||
|
instance.updateTitleContent(updatedContent);
|
||
|
|
||
|
then(() => {
|
||
|
expect(
|
||
|
document
|
||
|
.querySelector('.tooltip .tooltip-inner')
|
||
|
.getBoundingClientRect().left
|
||
|
).not.toBe(oldPosition);
|
||
|
done();
|
||
|
}, 500);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('events', () => {
|
||
|
beforeEach(() => {
|
||
|
createReference();
|
||
|
});
|
||
|
|
||
|
afterEach(() => {
|
||
|
instance.dispose();
|
||
|
jasmineWrapper.innerHTML = '';
|
||
|
});
|
||
|
|
||
|
it('should show a tooltip when hovered', done => {
|
||
|
instance = new Tooltip(reference, {
|
||
|
title: 'foobar',
|
||
|
trigger: 'hover',
|
||
|
});
|
||
|
|
||
|
expect(document.querySelector('.tooltip')).toBeNull();
|
||
|
|
||
|
reference.dispatchEvent(new CustomEvent('mouseenter'));
|
||
|
|
||
|
then(() => {
|
||
|
expect(document.querySelector('.tooltip')).not.toBeNull();
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('should hide a tooltip on reference mouseleave', done => {
|
||
|
instance = new Tooltip(reference, {
|
||
|
title: 'foobar',
|
||
|
trigger: 'hover',
|
||
|
});
|
||
|
|
||
|
expect(document.querySelector('.tooltip')).toBeNull();
|
||
|
|
||
|
reference.dispatchEvent(new CustomEvent('mouseenter'));
|
||
|
then(() => reference.dispatchEvent(new CustomEvent('mouseleave')), 200);
|
||
|
then(() => {
|
||
|
expect(document.querySelector('.tooltip').style.visibility).toBe('hidden');
|
||
|
done();
|
||
|
}, 200);
|
||
|
});
|
||
|
|
||
|
it('should hide a tooltip on tooltip mouseleave', done => {
|
||
|
instance = new Tooltip(reference, {
|
||
|
title: 'foobar',
|
||
|
trigger: 'hover',
|
||
|
});
|
||
|
|
||
|
expect(document.querySelector('.tooltip')).toBeNull();
|
||
|
|
||
|
reference.dispatchEvent(new CustomEvent('mouseenter'));
|
||
|
then(() => reference.dispatchEvent(new CustomEvent('mouseleave')));
|
||
|
then(() =>
|
||
|
document
|
||
|
.querySelector('.tooltip')
|
||
|
.dispatchEvent(new CustomEvent('mouseenter'))
|
||
|
);
|
||
|
then(() =>
|
||
|
document
|
||
|
.querySelector('.tooltip')
|
||
|
.dispatchEvent(new CustomEvent('mouseleave'))
|
||
|
);
|
||
|
then(() => {
|
||
|
expect(document.querySelector('.tooltip').style.visibility).toBe('hidden');
|
||
|
done();
|
||
|
}, 200);
|
||
|
});
|
||
|
|
||
|
it('should not hide a tooltip if user mouseenter tooltip and then mouseenter reference element again', done => {
|
||
|
instance = new Tooltip(reference, {
|
||
|
title: 'foobar',
|
||
|
trigger: 'hover',
|
||
|
});
|
||
|
|
||
|
expect(document.querySelector('.tooltip')).toBeNull();
|
||
|
|
||
|
reference.dispatchEvent(new CustomEvent('mouseenter'));
|
||
|
then(() => reference.dispatchEvent(new CustomEvent('mouseleave')));
|
||
|
then(() =>
|
||
|
document
|
||
|
.querySelector('.tooltip')
|
||
|
.dispatchEvent(new CustomEvent('mouseenter'))
|
||
|
);
|
||
|
then(() =>
|
||
|
document
|
||
|
.querySelector('.tooltip')
|
||
|
.dispatchEvent(new CustomEvent('mouseleave'))
|
||
|
);
|
||
|
then(() => reference.dispatchEvent(new CustomEvent('mouseenter')));
|
||
|
then(() => {
|
||
|
expect(document.querySelector('.tooltip').style.visibility).toBe('visible');
|
||
|
done();
|
||
|
}, 200);
|
||
|
});
|
||
|
|
||
|
it('should show a tooltip on click', done => {
|
||
|
instance = new Tooltip(reference, {
|
||
|
title: 'foobar',
|
||
|
trigger: 'click',
|
||
|
});
|
||
|
|
||
|
expect(document.querySelector('.tooltip')).toBeNull();
|
||
|
|
||
|
reference.dispatchEvent(new CustomEvent('click'));
|
||
|
|
||
|
then(() => {
|
||
|
expect(document.querySelector('.tooltip')).not.toBeNull();
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('should not show tooltip if mouse leaves reference before tooltip is shown', done => {
|
||
|
instance = new Tooltip(reference, {
|
||
|
title: 'foobar',
|
||
|
trigger: 'hover',
|
||
|
delay: { show: 1000, hide: 0 },
|
||
|
});
|
||
|
|
||
|
expect(document.querySelector('.tooltip')).toBeNull();
|
||
|
|
||
|
reference.dispatchEvent(new CustomEvent('mouseenter'));
|
||
|
reference.dispatchEvent(new CustomEvent('mouseleave'));
|
||
|
|
||
|
then(() => {
|
||
|
expect(document.querySelector('.tooltip')).toBeNull();
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('should not flash the tooltip if mouse leaves reference before tooltip is shown', done => {
|
||
|
const delay = 500;
|
||
|
const hoverTime = 200;
|
||
|
|
||
|
instance = new Tooltip(reference, {
|
||
|
title: 'foobar',
|
||
|
trigger: 'hover',
|
||
|
delay,
|
||
|
});
|
||
|
|
||
|
spyOn(instance, '_show');
|
||
|
|
||
|
expect(document.querySelector('.tooltip')).toBeNull();
|
||
|
|
||
|
then(() => reference.dispatchEvent(new CustomEvent('mouseenter')), hoverTime);
|
||
|
then(() => reference.dispatchEvent(new CustomEvent('mouseleave')), delay * 2);
|
||
|
|
||
|
then(() => {
|
||
|
expect(instance._show).not.toHaveBeenCalled();
|
||
|
done()
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('should hide a tooltip on click while open', done => {
|
||
|
instance = new Tooltip(reference, {
|
||
|
title: 'foobar',
|
||
|
trigger: 'click',
|
||
|
});
|
||
|
|
||
|
expect(document.querySelector('.tooltip')).toBeNull();
|
||
|
|
||
|
reference.dispatchEvent(new CustomEvent('click'));
|
||
|
then(() => reference.dispatchEvent(new CustomEvent('click')));
|
||
|
then(() => {
|
||
|
expect(document.querySelector('.tooltip').style.visibility).toBe('hidden');
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('options', () => {
|
||
|
beforeEach(() => {
|
||
|
createReference();
|
||
|
});
|
||
|
|
||
|
it('should proxy the `options.offset` value to the Popper.js instance', done => {
|
||
|
instance = new Tooltip(reference, {
|
||
|
title: 'test',
|
||
|
offset: 10,
|
||
|
}).show();
|
||
|
expect(instance._popperOptions.modifiers.offset.offset).toBe(10);
|
||
|
done();
|
||
|
});
|
||
|
|
||
|
it('should proxy a `offset` modifier to the Popper.js instance', done => {
|
||
|
instance = new Tooltip(reference, {
|
||
|
title: 'test',
|
||
|
popperOptions: {
|
||
|
modifiers: { offset: { offset: 10 } },
|
||
|
},
|
||
|
}).show();
|
||
|
expect(instance._popperOptions.modifiers.offset.offset).toBe(10);
|
||
|
done();
|
||
|
});
|
||
|
|
||
|
it('should hide on click outside with `options.closeOnClickOutside`', done => {
|
||
|
instance = new Tooltip(reference, {
|
||
|
title: 'foobar',
|
||
|
trigger: 'click',
|
||
|
closeOnClickOutside: true,
|
||
|
});
|
||
|
reference.dispatchEvent(new CustomEvent('click'));
|
||
|
then(() => expect(document.querySelector('.tooltip')).not.toBeNull());
|
||
|
then(() => document.body.dispatchEvent(new CustomEvent('mousedown')));
|
||
|
then(() => expect(document.querySelector('.tooltip').style.visibility).toBe('hidden'));
|
||
|
then(done);
|
||
|
});
|
||
|
|
||
|
it('should proxy the arrow modifier to the Popper.js instance', done => {
|
||
|
instance = new Tooltip(reference, {
|
||
|
title: 'test',
|
||
|
popperOptions: {
|
||
|
modifiers: {
|
||
|
arrow: {
|
||
|
enabled: false,
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
}).show();
|
||
|
expect(instance._popperOptions.modifiers.arrow.enabled).toBe(false);
|
||
|
done();
|
||
|
});
|
||
|
|
||
|
it('should proxy the offset modifier to the Popper.js instance', done => {
|
||
|
instance = new Tooltip(reference, {
|
||
|
title: 'test',
|
||
|
popperOptions: {
|
||
|
modifiers: {
|
||
|
offset: {
|
||
|
enabled: false,
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
}).show();
|
||
|
expect(instance._popperOptions.modifiers.offset.enabled).toBe(false);
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
});
|