]>
git.ipfire.org Git - thirdparty/bootstrap.git/blob - js/tests/unit/tooltip.spec.js
1 import Tooltip
from '../../src/tooltip'
2 import EventHandler
from '../../src/dom/event-handler'
3 import { noop
} from '../../src/util/index'
4 import { clearFixture
, createEvent
, getFixture
, jQueryMock
} from '../helpers/fixture'
6 describe('Tooltip', () => {
10 fixtureEl
= getFixture()
16 for (const tooltipEl
of document
.querySelectorAll('.tooltip')) {
21 describe('VERSION', () => {
22 it('should return plugin version', () => {
23 expect(Tooltip
.VERSION
).toEqual(jasmine
.any(String
))
27 describe('Default', () => {
28 it('should return plugin default config', () => {
29 expect(Tooltip
.Default
).toEqual(jasmine
.any(Object
))
33 describe('NAME', () => {
34 it('should return plugin name', () => {
35 expect(Tooltip
.NAME
).toEqual(jasmine
.any(String
))
39 describe('DATA_KEY', () => {
40 it('should return plugin data key', () => {
41 expect(Tooltip
.DATA_KEY
).toEqual('bs.tooltip')
45 describe('Event', () => {
46 it('should return plugin events', () => {
47 expect(Tooltip
.Event
).toEqual(jasmine
.any(Object
))
51 describe('EVENT_KEY', () => {
52 it('should return plugin event key', () => {
53 expect(Tooltip
.EVENT_KEY
).toEqual('.bs.tooltip')
57 describe('DefaultType', () => {
58 it('should return plugin default type', () => {
59 expect(Tooltip
.DefaultType
).toEqual(jasmine
.any(Object
))
63 describe('constructor', () => {
64 it('should take care of element either passed as a CSS selector or DOM element', () => {
65 fixtureEl
.innerHTML
= '<a href="#" id="tooltipEl" rel="tooltip" title="Nice and short title">'
67 const tooltipEl
= fixtureEl
.querySelector('#tooltipEl')
68 const tooltipBySelector
= new Tooltip('#tooltipEl')
69 const tooltipByElement
= new Tooltip(tooltipEl
)
71 expect(tooltipBySelector
._element
).toEqual(tooltipEl
)
72 expect(tooltipByElement
._element
).toEqual(tooltipEl
)
75 it('should not take care of disallowed data attributes', () => {
76 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" data-bs-sanitize="false" title="Another tooltip">'
78 const tooltipEl
= fixtureEl
.querySelector('a')
79 const tooltip
= new Tooltip(tooltipEl
)
81 expect(tooltip
._config
.sanitize
).toBeTrue()
84 it('should convert title and content to string if numbers', () => {
85 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip">'
87 const tooltipEl
= fixtureEl
.querySelector('a')
88 const tooltip
= new Tooltip(tooltipEl
, {
93 expect(tooltip
._config
.title
).toEqual('1')
94 expect(tooltip
._config
.content
).toEqual('7')
97 it('should enable selector delegation', () => {
98 return new Promise(resolve
=> {
99 fixtureEl
.innerHTML
= '<div></div>'
101 const containerEl
= fixtureEl
.querySelector('div')
102 const tooltipContainer
= new Tooltip(containerEl
, {
103 selector
: 'a[rel="tooltip"]',
107 containerEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">'
109 const tooltipInContainerEl
= containerEl
.querySelector('a')
111 tooltipInContainerEl
.addEventListener('shown.bs.tooltip', () => {
112 expect(document
.querySelector('.tooltip')).not
.toBeNull()
113 tooltipContainer
.dispose()
117 tooltipInContainerEl
.click()
121 it('should create offset modifier when offset is passed as a function', () => {
122 return new Promise(resolve
=> {
123 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Offset from function">'
125 const getOffset
= jasmine
.createSpy('getOffset').and
.returnValue([10, 20])
126 const tooltipEl
= fixtureEl
.querySelector('a')
127 const tooltip
= new Tooltip(tooltipEl
, {
130 onFirstUpdate(state
) {
131 expect(getOffset
).toHaveBeenCalledWith({
132 popper
: state
.rects
.popper
,
133 reference
: state
.rects
.reference
,
134 placement
: state
.placement
141 const offset
= tooltip
._getOffset()
143 expect(offset
).toEqual(jasmine
.any(Function
))
149 it('should create offset modifier when offset option is passed in data attribute', () => {
150 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" data-bs-offset="10,20" title="Another tooltip">'
152 const tooltipEl
= fixtureEl
.querySelector('a')
153 const tooltip
= new Tooltip(tooltipEl
)
155 expect(tooltip
._getOffset()).toEqual([10, 20])
158 it('should allow to pass config to Popper with `popperConfig`', () => {
159 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip">'
161 const tooltipEl
= fixtureEl
.querySelector('a')
162 const tooltip
= new Tooltip(tooltipEl
, {
168 const popperConfig
= tooltip
._getPopperConfig('top')
170 expect(popperConfig
.placement
).toEqual('left')
173 it('should allow to pass config to Popper with `popperConfig` as a function', () => {
174 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip">'
176 const tooltipEl
= fixtureEl
.querySelector('a')
177 const getPopperConfig
= jasmine
.createSpy('getPopperConfig').and
.returnValue({ placement
: 'left' })
178 const tooltip
= new Tooltip(tooltipEl
, {
179 popperConfig
: getPopperConfig
182 const popperConfig
= tooltip
._getPopperConfig('top')
184 expect(getPopperConfig
).toHaveBeenCalled()
185 expect(popperConfig
.placement
).toEqual('left')
188 it('should use original title, if not "data-bs-title" is given', () => {
189 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip"></a>'
191 const tooltipEl
= fixtureEl
.querySelector('a')
192 const tooltip
= new Tooltip(tooltipEl
)
194 expect(tooltip
._config
.title
).toEqual('Another tooltip')
198 describe('enable', () => {
199 it('should enable a tooltip', () => {
200 return new Promise(resolve
=> {
201 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">'
203 const tooltipEl
= fixtureEl
.querySelector('a')
204 const tooltip
= new Tooltip(tooltipEl
)
208 tooltipEl
.addEventListener('shown.bs.tooltip', () => {
209 expect(document
.querySelector('.tooltip')).not
.toBeNull()
218 describe('disable', () => {
219 it('should disable tooltip', () => {
220 return new Promise(resolve
=> {
221 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">'
223 const tooltipEl
= fixtureEl
.querySelector('a')
224 const tooltip
= new Tooltip(tooltipEl
)
228 tooltipEl
.addEventListener('show.bs.tooltip', () => {
229 throw new Error('should not show a disabled tooltip')
242 describe('toggleEnabled', () => {
243 it('should toggle enabled', () => {
244 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">'
246 const tooltipEl
= fixtureEl
.querySelector('a')
247 const tooltip
= new Tooltip(tooltipEl
)
249 expect(tooltip
._isEnabled
).toBeTrue()
251 tooltip
.toggleEnabled()
253 expect(tooltip
._isEnabled
).toBeFalse()
257 describe('toggle', () => {
258 it('should do nothing if disabled', () => {
259 return new Promise(resolve
=> {
260 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">'
262 const tooltipEl
= fixtureEl
.querySelector('a')
263 const tooltip
= new Tooltip(tooltipEl
)
267 tooltipEl
.addEventListener('show.bs.tooltip', () => {
268 throw new Error('should not show a disabled tooltip')
280 it('should show a tooltip', () => {
281 return new Promise(resolve
=> {
282 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">'
284 const tooltipEl
= fixtureEl
.querySelector('a')
285 const tooltip
= new Tooltip(tooltipEl
)
287 tooltipEl
.addEventListener('shown.bs.tooltip', () => {
288 expect(document
.querySelector('.tooltip')).not
.toBeNull()
296 it('should call toggle and show the tooltip when trigger is "click"', () => {
297 return new Promise(resolve
=> {
298 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">'
300 const tooltipEl
= fixtureEl
.querySelector('a')
301 const tooltip
= new Tooltip(tooltipEl
, {
305 spyOn(tooltip
, 'toggle').and
.callThrough()
307 tooltipEl
.addEventListener('shown.bs.tooltip', () => {
308 expect(tooltip
.toggle
).toHaveBeenCalled()
316 it('should hide a tooltip', () => {
317 return new Promise(resolve
=> {
318 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">'
320 const tooltipEl
= fixtureEl
.querySelector('a')
321 const tooltip
= new Tooltip(tooltipEl
)
323 tooltipEl
.addEventListener('shown.bs.tooltip', () => {
327 tooltipEl
.addEventListener('hidden.bs.tooltip', () => {
328 expect(document
.querySelector('.tooltip')).toBeNull()
336 it('should call toggle and hide the tooltip when trigger is "click"', () => {
337 return new Promise(resolve
=> {
338 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">'
340 const tooltipEl
= fixtureEl
.querySelector('a')
341 const tooltip
= new Tooltip(tooltipEl
, {
345 spyOn(tooltip
, 'toggle').and
.callThrough()
347 tooltipEl
.addEventListener('shown.bs.tooltip', () => {
351 tooltipEl
.addEventListener('hidden.bs.tooltip', () => {
352 expect(tooltip
.toggle
).toHaveBeenCalled()
361 describe('dispose', () => {
362 it('should destroy a tooltip', () => {
363 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">'
365 const tooltipEl
= fixtureEl
.querySelector('a')
366 const addEventSpy
= spyOn(tooltipEl
, 'addEventListener').and
.callThrough()
367 const removeEventSpy
= spyOn(tooltipEl
, 'removeEventListener').and
.callThrough()
369 const tooltip
= new Tooltip(tooltipEl
)
371 expect(Tooltip
.getInstance(tooltipEl
)).toEqual(tooltip
)
373 const expectedArgs
= [
374 ['mouseover', jasmine
.any(Function
), jasmine
.any(Boolean
)],
375 ['mouseout', jasmine
.any(Function
), jasmine
.any(Boolean
)],
376 ['focusin', jasmine
.any(Function
), jasmine
.any(Boolean
)],
377 ['focusout', jasmine
.any(Function
), jasmine
.any(Boolean
)]
380 expect(addEventSpy
.calls
.allArgs()).toEqual(expectedArgs
)
384 expect(Tooltip
.getInstance(tooltipEl
)).toBeNull()
385 expect(removeEventSpy
.calls
.allArgs()).toEqual(expectedArgs
)
388 it('should destroy a tooltip after it is shown and hidden', () => {
389 return new Promise(resolve
=> {
390 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">'
392 const tooltipEl
= fixtureEl
.querySelector('a')
393 const tooltip
= new Tooltip(tooltipEl
)
395 tooltipEl
.addEventListener('shown.bs.tooltip', () => {
398 tooltipEl
.addEventListener('hidden.bs.tooltip', () => {
400 expect(tooltip
.tip
).toBeNull()
401 expect(Tooltip
.getInstance(tooltipEl
)).toBeNull()
409 it('should destroy a tooltip and remove it from the dom', () => {
410 return new Promise(resolve
=> {
411 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">'
413 const tooltipEl
= fixtureEl
.querySelector('a')
414 const tooltip
= new Tooltip(tooltipEl
)
416 tooltipEl
.addEventListener('shown.bs.tooltip', () => {
417 expect(document
.querySelector('.tooltip')).not
.toBeNull()
421 expect(document
.querySelector('.tooltip')).toBeNull()
430 describe('show', () => {
431 it('should show a tooltip', () => {
432 return new Promise(resolve
=> {
433 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">'
435 const tooltipEl
= fixtureEl
.querySelector('a')
436 const tooltip
= new Tooltip(tooltipEl
)
438 tooltipEl
.addEventListener('shown.bs.tooltip', () => {
439 const tooltipShown
= document
.querySelector('.tooltip')
441 expect(tooltipShown
).not
.toBeNull()
442 expect(tooltipEl
.getAttribute('aria-describedby')).toEqual(tooltipShown
.getAttribute('id'))
443 expect(tooltipShown
.getAttribute('id')).toContain('tooltip')
451 it('should show a tooltip when hovering a child element', () => {
452 return new Promise(resolve
=> {
453 fixtureEl
.innerHTML
= [
454 '<a href="#" rel="tooltip" title="Another tooltip">',
455 ' <svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 100 100">',
456 ' <rect width="100%" fill="#563d7c"/>',
457 ' <circle cx="50" cy="50" r="30" fill="#fff"/>',
462 const tooltipEl
= fixtureEl
.querySelector('a')
463 const tooltip
= new Tooltip(tooltipEl
)
465 spyOn(tooltip
, 'show')
467 tooltipEl
.querySelector('rect').dispatchEvent(createEvent('mouseover', { bubbles
: true }))
470 expect(tooltip
.show
).toHaveBeenCalled()
476 it('should show a tooltip on mobile', () => {
477 return new Promise(resolve
=> {
478 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">'
480 const tooltipEl
= fixtureEl
.querySelector('a')
481 const tooltip
= new Tooltip(tooltipEl
)
482 document
.documentElement
.ontouchstart
= noop
484 spyOn(EventHandler
, 'on').and
.callThrough()
486 tooltipEl
.addEventListener('shown.bs.tooltip', () => {
487 expect(document
.querySelector('.tooltip')).not
.toBeNull()
488 expect(EventHandler
.on
).toHaveBeenCalledWith(jasmine
.any(Object
), 'mouseover', noop
)
489 document
.documentElement
.ontouchstart
= undefined
497 it('should show a tooltip relative to placement option', () => {
498 return new Promise(resolve
=> {
499 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">'
501 const tooltipEl
= fixtureEl
.querySelector('a')
502 const tooltip
= new Tooltip(tooltipEl
, {
506 tooltipEl
.addEventListener('inserted.bs.tooltip', () => {
507 expect(tooltip
._getTipElement()).toHaveClass('bs-tooltip-auto')
510 tooltipEl
.addEventListener('shown.bs.tooltip', () => {
511 expect(tooltip
._getTipElement()).toHaveClass('bs-tooltip-auto')
512 expect(tooltip
._getTipElement().getAttribute('data-popper-placement')).toEqual('bottom')
520 it('should not error when trying to show a tooltip that has been removed from the dom', () => {
521 return new Promise(resolve
=> {
522 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">'
524 const tooltipEl
= fixtureEl
.querySelector('a')
525 const tooltip
= new Tooltip(tooltipEl
)
527 const firstCallback
= () => {
528 tooltipEl
.removeEventListener('shown.bs.tooltip', firstCallback
)
529 let tooltipShown
= document
.querySelector('.tooltip')
531 tooltipShown
.remove()
533 tooltipEl
.addEventListener('shown.bs.tooltip', () => {
534 tooltipShown
= document
.querySelector('.tooltip')
536 expect(tooltipShown
).not
.toBeNull()
543 tooltipEl
.addEventListener('shown.bs.tooltip', firstCallback
)
549 it('should show a tooltip with a dom element container', () => {
550 return new Promise(resolve
=> {
551 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">'
553 const tooltipEl
= fixtureEl
.querySelector('a')
554 const tooltip
= new Tooltip(tooltipEl
, {
558 tooltipEl
.addEventListener('shown.bs.tooltip', () => {
559 expect(fixtureEl
.querySelector('.tooltip')).not
.toBeNull()
567 it('should show a tooltip with a jquery element container', () => {
568 return new Promise(resolve
=> {
569 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">'
571 const tooltipEl
= fixtureEl
.querySelector('a')
572 const tooltip
= new Tooltip(tooltipEl
, {
579 tooltipEl
.addEventListener('shown.bs.tooltip', () => {
580 expect(fixtureEl
.querySelector('.tooltip')).not
.toBeNull()
588 it('should show a tooltip with a selector in container', () => {
589 return new Promise(resolve
=> {
590 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">'
592 const tooltipEl
= fixtureEl
.querySelector('a')
593 const tooltip
= new Tooltip(tooltipEl
, {
594 container
: '#fixture'
597 tooltipEl
.addEventListener('shown.bs.tooltip', () => {
598 expect(fixtureEl
.querySelector('.tooltip')).not
.toBeNull()
606 it('should show a tooltip with placement as a function', () => {
607 return new Promise(resolve
=> {
608 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">'
610 const spy
= jasmine
.createSpy('placement').and
.returnValue('top')
611 const tooltipEl
= fixtureEl
.querySelector('a')
612 const tooltip
= new Tooltip(tooltipEl
, {
616 tooltipEl
.addEventListener('shown.bs.tooltip', () => {
617 expect(document
.querySelector('.tooltip')).not
.toBeNull()
618 expect(spy
).toHaveBeenCalled()
626 it('should show a tooltip without the animation', () => {
627 return new Promise(resolve
=> {
628 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">'
630 const tooltipEl
= fixtureEl
.querySelector('a')
631 const tooltip
= new Tooltip(tooltipEl
, {
635 tooltipEl
.addEventListener('shown.bs.tooltip', () => {
636 const tip
= document
.querySelector('.tooltip')
638 expect(tip
).not
.toBeNull()
639 expect(tip
).not
.toHaveClass('fade')
647 it('should throw an error the element is not visible', () => {
648 fixtureEl
.innerHTML
= '<a href="#" style="display: none" rel="tooltip" title="Another tooltip">'
650 const tooltipEl
= fixtureEl
.querySelector('a')
651 const tooltip
= new Tooltip(tooltipEl
)
656 expect(error
.message
).toEqual('Please use show on visible elements')
660 it('should not show a tooltip if show.bs.tooltip is prevented', () => {
661 return new Promise(resolve
=> {
662 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">'
664 const tooltipEl
= fixtureEl
.querySelector('a')
665 const tooltip
= new Tooltip(tooltipEl
)
667 const expectedDone
= () => {
669 expect(document
.querySelector('.tooltip')).toBeNull()
674 tooltipEl
.addEventListener('show.bs.tooltip', ev
=> {
679 tooltipEl
.addEventListener('shown.bs.tooltip', () => {
680 throw new Error('Tooltip should not be shown')
687 it('should show tooltip if leave event hasn\'t occurred before delay expires', () => {
688 return new Promise(resolve
=> {
689 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">'
691 const tooltipEl
= fixtureEl
.querySelector('a')
692 const tooltip
= new Tooltip(tooltipEl
, {
696 spyOn(tooltip
, 'show')
699 expect(tooltip
.show
).not
.toHaveBeenCalled()
703 expect(tooltip
.show
).toHaveBeenCalled()
707 tooltipEl
.dispatchEvent(createEvent('mouseover'))
711 it('should not show tooltip if leave event occurs before delay expires', () => {
712 return new Promise(resolve
=> {
713 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">'
715 const tooltipEl
= fixtureEl
.querySelector('a')
716 const tooltip
= new Tooltip(tooltipEl
, {
720 spyOn(tooltip
, 'show')
723 expect(tooltip
.show
).not
.toHaveBeenCalled()
724 tooltipEl
.dispatchEvent(createEvent('mouseover'))
728 expect(tooltip
.show
).toHaveBeenCalled()
729 expect(document
.querySelectorAll('.tooltip')).toHaveSize(0)
733 tooltipEl
.dispatchEvent(createEvent('mouseover'))
737 it('should not hide tooltip if leave event occurs and enter event occurs within the hide delay', () => {
738 return new Promise(resolve
=> {
739 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">'
741 const tooltipEl
= fixtureEl
.querySelector('a')
742 const tooltip
= new Tooltip(tooltipEl
, {
750 expect(tooltip
._getTipElement()).toHaveClass('show')
751 tooltipEl
.dispatchEvent(createEvent('mouseout'))
754 expect(tooltip
._getTipElement()).toHaveClass('show')
755 tooltipEl
.dispatchEvent(createEvent('mouseover'))
759 expect(tooltip
._getTipElement()).toHaveClass('show')
760 expect(document
.querySelectorAll('.tooltip')).toHaveSize(1)
765 tooltipEl
.dispatchEvent(createEvent('mouseover'))
769 it('should not hide tooltip if leave event occurs and interaction remains inside trigger', () => {
770 return new Promise(resolve
=> {
771 fixtureEl
.innerHTML
= [
772 '<a href="#" rel="tooltip" title="Another tooltip">',
778 const tooltipEl
= fixtureEl
.querySelector('a')
779 const tooltip
= new Tooltip(tooltipEl
)
780 const triggerChild
= tooltipEl
.querySelector('b')
782 spyOn(tooltip
, 'hide').and
.callThrough()
784 tooltipEl
.addEventListener('mouseover', () => {
785 const moveMouseToChildEvent
= createEvent('mouseout')
786 Object
.defineProperty(moveMouseToChildEvent
, 'relatedTarget', {
790 tooltipEl
.dispatchEvent(moveMouseToChildEvent
)
793 tooltipEl
.addEventListener('mouseout', () => {
794 expect(tooltip
.hide
).not
.toHaveBeenCalled()
798 tooltipEl
.dispatchEvent(createEvent('mouseover'))
802 it('should properly maintain tooltip state if leave event occurs and enter event occurs during hide transition', () => {
803 return new Promise(resolve
=> {
804 // Style this tooltip to give it plenty of room for popper to do what it wants
805 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip" data-bs-placement="top" style="position:fixed;left:50%;top:50%;">Trigger</a>'
807 const tooltipEl
= fixtureEl
.querySelector('a')
808 const tooltip
= new Tooltip(tooltipEl
)
810 spyOn(window
, 'getComputedStyle').and
.returnValue({
811 transitionDuration
: '0.15s',
812 transitionDelay
: '0s'
816 expect(tooltip
._popper
).not
.toBeNull()
817 expect(tooltip
._getTipElement().getAttribute('data-popper-placement')).toEqual('top')
818 tooltipEl
.dispatchEvent(createEvent('mouseout'))
821 expect(tooltip
._getTipElement()).not
.toHaveClass('show')
822 tooltipEl
.dispatchEvent(createEvent('mouseover'))
826 expect(tooltip
._popper
).not
.toBeNull()
827 expect(tooltip
._getTipElement().getAttribute('data-popper-placement')).toEqual('top')
832 tooltipEl
.dispatchEvent(createEvent('mouseover'))
836 it('should only trigger inserted event if a new tooltip element was created', () => {
837 return new Promise(resolve
=> {
838 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">'
840 const tooltipEl
= fixtureEl
.querySelector('a')
841 const tooltip
= new Tooltip(tooltipEl
)
843 spyOn(window
, 'getComputedStyle').and
.returnValue({
844 transitionDuration
: '0.15s',
845 transitionDelay
: '0s'
848 const insertedFunc
= jasmine
.createSpy()
849 tooltipEl
.addEventListener('inserted.bs.tooltip', insertedFunc
)
852 expect(insertedFunc
).toHaveBeenCalledTimes(1)
860 expect(insertedFunc
).toHaveBeenCalledTimes(1)
869 it('should show a tooltip with custom class provided in data attributes', () => {
870 return new Promise(resolve
=> {
871 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip" data-bs-custom-class="custom-class">'
873 const tooltipEl
= fixtureEl
.querySelector('a')
874 const tooltip
= new Tooltip(tooltipEl
)
876 tooltipEl
.addEventListener('shown.bs.tooltip', () => {
877 const tip
= document
.querySelector('.tooltip')
878 expect(tip
).not
.toBeNull()
879 expect(tip
).toHaveClass('custom-class')
887 it('should show a tooltip with custom class provided as a string in config', () => {
888 return new Promise(resolve
=> {
889 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">'
891 const tooltipEl
= fixtureEl
.querySelector('a')
892 const tooltip
= new Tooltip(tooltipEl
, {
893 customClass
: 'custom-class custom-class-2'
896 tooltipEl
.addEventListener('shown.bs.tooltip', () => {
897 const tip
= document
.querySelector('.tooltip')
898 expect(tip
).not
.toBeNull()
899 expect(tip
).toHaveClass('custom-class')
900 expect(tip
).toHaveClass('custom-class-2')
908 it('should show a tooltip with custom class provided as a function in config', () => {
909 return new Promise(resolve
=> {
910 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">'
912 const spy
= jasmine
.createSpy('customClass').and
.returnValue('custom-class')
913 const tooltipEl
= fixtureEl
.querySelector('a')
914 const tooltip
= new Tooltip(tooltipEl
, {
918 tooltipEl
.addEventListener('shown.bs.tooltip', () => {
919 const tip
= document
.querySelector('.tooltip')
920 expect(tip
).not
.toBeNull()
921 expect(spy
).toHaveBeenCalled()
922 expect(tip
).toHaveClass('custom-class')
930 it('should remove `title` attribute if exists', () => {
931 return new Promise(resolve
=> {
932 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip"></a>'
934 const tooltipEl
= fixtureEl
.querySelector('a')
935 const tooltip
= new Tooltip(tooltipEl
)
937 tooltipEl
.addEventListener('shown.bs.tooltip', () => {
938 expect(tooltipEl
.getAttribute('title')).toBeNull()
946 describe('hide', () => {
947 it('should hide a tooltip', () => {
948 return new Promise(resolve
=> {
949 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">'
951 const tooltipEl
= fixtureEl
.querySelector('a')
952 const tooltip
= new Tooltip(tooltipEl
)
954 tooltipEl
.addEventListener('shown.bs.tooltip', () => tooltip
.hide())
955 tooltipEl
.addEventListener('hidden.bs.tooltip', () => {
956 expect(document
.querySelector('.tooltip')).toBeNull()
957 expect(tooltipEl
.getAttribute('aria-describedby')).toBeNull()
965 it('should hide a tooltip on mobile', () => {
966 return new Promise(resolve
=> {
967 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">'
969 const tooltipEl
= fixtureEl
.querySelector('a')
970 const tooltip
= new Tooltip(tooltipEl
)
972 tooltipEl
.addEventListener('shown.bs.tooltip', () => {
973 document
.documentElement
.ontouchstart
= noop
974 spyOn(EventHandler
, 'off')
978 tooltipEl
.addEventListener('hidden.bs.tooltip', () => {
979 expect(document
.querySelector('.tooltip')).toBeNull()
980 expect(EventHandler
.off
).toHaveBeenCalledWith(jasmine
.any(Object
), 'mouseover', noop
)
981 document
.documentElement
.ontouchstart
= undefined
989 it('should hide a tooltip without animation', () => {
990 return new Promise(resolve
=> {
991 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">'
993 const tooltipEl
= fixtureEl
.querySelector('a')
994 const tooltip
= new Tooltip(tooltipEl
, {
998 tooltipEl
.addEventListener('shown.bs.tooltip', () => tooltip
.hide())
999 tooltipEl
.addEventListener('hidden.bs.tooltip', () => {
1000 expect(document
.querySelector('.tooltip')).toBeNull()
1001 expect(tooltipEl
.getAttribute('aria-describedby')).toBeNull()
1009 it('should not hide a tooltip if hide event is prevented', () => {
1010 return new Promise(resolve
=> {
1011 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">'
1013 const assertDone
= () => {
1015 expect(document
.querySelector('.tooltip')).not
.toBeNull()
1020 const tooltipEl
= fixtureEl
.querySelector('a')
1021 const tooltip
= new Tooltip(tooltipEl
, {
1025 tooltipEl
.addEventListener('shown.bs.tooltip', () => tooltip
.hide())
1026 tooltipEl
.addEventListener('hide.bs.tooltip', event
=> {
1027 event
.preventDefault()
1030 tooltipEl
.addEventListener('hidden.bs.tooltip', () => {
1031 throw new Error('should not trigger hidden event')
1038 it('should not throw error running hide if popper hasn\'t been shown', () => {
1039 fixtureEl
.innerHTML
= '<div></div>'
1041 const div
= fixtureEl
.querySelector('div')
1042 const tooltip
= new Tooltip(div
)
1048 throw new Error('should not throw error')
1053 describe('update', () => {
1054 it('should call popper update', () => {
1055 return new Promise(resolve
=> {
1056 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">'
1058 const tooltipEl
= fixtureEl
.querySelector('a')
1059 const tooltip
= new Tooltip(tooltipEl
)
1061 tooltipEl
.addEventListener('shown.bs.tooltip', () => {
1062 spyOn(tooltip
._popper
, 'update')
1066 expect(tooltip
._popper
.update
).toHaveBeenCalled()
1074 it('should do nothing if the tooltip is not shown', () => {
1075 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">'
1077 const tooltipEl
= fixtureEl
.querySelector('a')
1078 const tooltip
= new Tooltip(tooltipEl
)
1085 describe('_isWithContent', () => {
1086 it('should return true if there is content', () => {
1087 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">'
1089 const tooltipEl
= fixtureEl
.querySelector('a')
1090 const tooltip
= new Tooltip(tooltipEl
)
1092 expect(tooltip
._isWithContent()).toBeTrue()
1095 it('should return false if there is no content', () => {
1096 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="">'
1098 const tooltipEl
= fixtureEl
.querySelector('a')
1099 const tooltip
= new Tooltip(tooltipEl
)
1101 expect(tooltip
._isWithContent()).toBeFalse()
1105 describe('_getTipElement', () => {
1106 it('should create the tip element and return it', () => {
1107 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">'
1109 const tooltipEl
= fixtureEl
.querySelector('a')
1110 const tooltip
= new Tooltip(tooltipEl
)
1112 spyOn(document
, 'createElement').and
.callThrough()
1114 expect(tooltip
._getTipElement()).toBeDefined()
1115 expect(document
.createElement
).toHaveBeenCalled()
1118 it('should return the created tip element', () => {
1119 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">'
1121 const tooltipEl
= fixtureEl
.querySelector('a')
1122 const tooltip
= new Tooltip(tooltipEl
)
1124 const spy
= spyOn(document
, 'createElement').and
.callThrough()
1126 expect(tooltip
._getTipElement()).toBeDefined()
1127 expect(spy
).toHaveBeenCalled()
1131 expect(tooltip
._getTipElement()).toBeDefined()
1132 expect(spy
).not
.toHaveBeenCalled()
1136 describe('setContent', () => {
1137 it('should set tip content', () => {
1138 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">'
1140 const tooltipEl
= fixtureEl
.querySelector('a')
1141 const tooltip
= new Tooltip(tooltipEl
, { animation
: false })
1143 const tip
= tooltip
._getTipElement()
1145 tooltip
.setContent(tip
)
1147 expect(tip
).not
.toHaveClass('show')
1148 expect(tip
).not
.toHaveClass('fade')
1149 expect(tip
.querySelector('.tooltip-inner').textContent
).toEqual('Another tooltip')
1152 it('should re-show tip if it was already shown', () => {
1153 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" data-bs-title="Another tooltip">'
1155 const tooltipEl
= fixtureEl
.querySelector('a')
1156 const tooltip
= new Tooltip(tooltipEl
)
1158 const tip
= () => tooltip
._getTipElement()
1160 expect(tip()).toHaveClass('show')
1161 tooltip
.setContent({ '.tooltip-inner': 'foo' })
1163 expect(tip()).toHaveClass('show')
1164 expect(tip().querySelector('.tooltip-inner').textContent
).toEqual('foo')
1167 it('should keep tip hidden, if it was already hidden before', () => {
1168 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" data-bs-title="Another tooltip">'
1170 const tooltipEl
= fixtureEl
.querySelector('a')
1171 const tooltip
= new Tooltip(tooltipEl
)
1172 const tip
= () => tooltip
._getTipElement()
1174 expect(tip()).not
.toHaveClass('show')
1175 tooltip
.setContent({ '.tooltip-inner': 'foo' })
1177 expect(tip()).not
.toHaveClass('show')
1178 expect(tip().querySelector('.tooltip-inner').textContent
).toEqual('foo')
1181 it('"setContent" should keep the initial template', () => {
1182 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">'
1184 const tooltipEl
= fixtureEl
.querySelector('a')
1185 const tooltip
= new Tooltip(tooltipEl
)
1187 tooltip
.setContent({ '.tooltip-inner': 'foo' })
1188 const tip
= tooltip
._getTipElement()
1190 expect(tip
).toHaveClass('tooltip')
1191 expect(tip
).toHaveClass('bs-tooltip-auto')
1192 expect(tip
.querySelector('.tooltip-arrow')).not
.toBeNull()
1193 expect(tip
.querySelector('.tooltip-inner')).not
.toBeNull()
1197 describe('setContent', () => {
1198 it('should do nothing if the element is null', () => {
1199 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">'
1201 const tooltipEl
= fixtureEl
.querySelector('a')
1202 const tooltip
= new Tooltip(tooltipEl
)
1204 tooltip
.setContent({ '.tooltip': null })
1208 it('should do nothing if the content is a child of the element', () => {
1209 fixtureEl
.innerHTML
= [
1210 '<a href="#" rel="tooltip" title="Another tooltip">',
1211 ' <div id="childContent"></div>',
1215 const tooltipEl
= fixtureEl
.querySelector('a')
1216 const childContent
= fixtureEl
.querySelector('div')
1217 const tooltip
= new Tooltip(tooltipEl
, {
1221 tooltip
._getTipElement().append(childContent
)
1222 tooltip
.setContent({ '.tooltip': childContent
})
1227 it('should add the content as a child of the element for jQuery elements', () => {
1228 fixtureEl
.innerHTML
= [
1229 '<a href="#" rel="tooltip" title="Another tooltip">',
1230 ' <div id="childContent"></div>',
1234 const tooltipEl
= fixtureEl
.querySelector('a')
1235 const childContent
= fixtureEl
.querySelector('div')
1236 const tooltip
= new Tooltip(tooltipEl
, {
1240 tooltip
.setContent({ '.tooltip': { 0: childContent
, jquery
: 'jQuery' } })
1242 expect(childContent
.parentNode
).toEqual(tooltip
._getTipElement())
1245 it('should add the child text content in the element', () => {
1246 fixtureEl
.innerHTML
= [
1247 '<a href="#" rel="tooltip" title="Another tooltip">',
1248 ' <div id="childContent">Tooltip</div>',
1252 const tooltipEl
= fixtureEl
.querySelector('a')
1253 const childContent
= fixtureEl
.querySelector('div')
1254 const tooltip
= new Tooltip(tooltipEl
)
1256 tooltip
.setContent({ '.tooltip': childContent
})
1258 expect(childContent
.textContent
).toEqual(tooltip
._getTipElement().textContent
)
1261 it('should add html without sanitize it', () => {
1262 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip"></a>'
1264 const tooltipEl
= fixtureEl
.querySelector('a')
1265 const tooltip
= new Tooltip(tooltipEl
, {
1270 tooltip
.setContent({ '.tooltip': '<div id="childContent">Tooltip</div>' })
1272 expect(tooltip
._getTipElement().querySelector('div').id
).toEqual('childContent')
1275 it('should add html sanitized', () => {
1276 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip"></a>'
1278 const tooltipEl
= fixtureEl
.querySelector('a')
1279 const tooltip
= new Tooltip(tooltipEl
, {
1284 '<div id="childContent">',
1285 ' <button type="button">test btn</button>',
1289 tooltip
.setContent({ '.tooltip': content
})
1290 expect(tooltip
._getTipElement().querySelector('div').id
).toEqual('childContent')
1291 expect(tooltip
._getTipElement().querySelector('button')).toBeNull()
1294 it('should add text content', () => {
1295 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip"></a>'
1297 const tooltipEl
= fixtureEl
.querySelector('a')
1298 const tooltip
= new Tooltip(tooltipEl
)
1300 tooltip
.setContent({ '.tooltip': 'test' })
1302 expect(tooltip
._getTipElement().textContent
).toEqual('test')
1306 describe('_getTitle', () => {
1307 it('should return the title', () => {
1308 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip"></a>'
1310 const tooltipEl
= fixtureEl
.querySelector('a')
1311 const tooltip
= new Tooltip(tooltipEl
)
1313 expect(tooltip
._getTitle()).toEqual('Another tooltip')
1316 it('should call title function', () => {
1317 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip"></a>'
1319 const tooltipEl
= fixtureEl
.querySelector('a')
1320 const tooltip
= new Tooltip(tooltipEl
, {
1324 expect(tooltip
._getTitle()).toEqual('test')
1328 describe('getInstance', () => {
1329 it('should return tooltip instance', () => {
1330 fixtureEl
.innerHTML
= '<div></div>'
1332 const div
= fixtureEl
.querySelector('div')
1333 const alert
= new Tooltip(div
)
1335 expect(Tooltip
.getInstance(div
)).toEqual(alert
)
1336 expect(Tooltip
.getInstance(div
)).toBeInstanceOf(Tooltip
)
1339 it('should return null when there is no tooltip instance', () => {
1340 fixtureEl
.innerHTML
= '<div></div>'
1342 const div
= fixtureEl
.querySelector('div')
1344 expect(Tooltip
.getInstance(div
)).toBeNull()
1348 describe('aria-label', () => {
1349 it('should add the aria-label attribute for referencing original title', () => {
1350 return new Promise(resolve
=> {
1351 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip"></a>'
1353 const tooltipEl
= fixtureEl
.querySelector('a')
1354 const tooltip
= new Tooltip(tooltipEl
)
1356 tooltipEl
.addEventListener('shown.bs.tooltip', () => {
1357 const tooltipShown
= document
.querySelector('.tooltip')
1359 expect(tooltipShown
).not
.toBeNull()
1360 expect(tooltipEl
.getAttribute('aria-label')).toEqual('Another tooltip')
1368 it('should not add the aria-label attribute if the attribute already exists', () => {
1369 return new Promise(resolve
=> {
1370 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" aria-label="Different label" title="Another tooltip"></a>'
1372 const tooltipEl
= fixtureEl
.querySelector('a')
1373 const tooltip
= new Tooltip(tooltipEl
)
1375 tooltipEl
.addEventListener('shown.bs.tooltip', () => {
1376 const tooltipShown
= document
.querySelector('.tooltip')
1378 expect(tooltipShown
).not
.toBeNull()
1379 expect(tooltipEl
.getAttribute('aria-label')).toEqual('Different label')
1387 it('should not add the aria-label attribute if the element has text content', () => {
1388 return new Promise(resolve
=> {
1389 fixtureEl
.innerHTML
= '<a href="#" rel="tooltip" title="Another tooltip">text content</a>'
1391 const tooltipEl
= fixtureEl
.querySelector('a')
1392 const tooltip
= new Tooltip(tooltipEl
)
1394 tooltipEl
.addEventListener('shown.bs.tooltip', () => {
1395 const tooltipShown
= document
.querySelector('.tooltip')
1397 expect(tooltipShown
).not
.toBeNull()
1398 expect(tooltipEl
.getAttribute('aria-label')).toBeNull()
1407 describe('getOrCreateInstance', () => {
1408 it('should return tooltip instance', () => {
1409 fixtureEl
.innerHTML
= '<div></div>'
1411 const div
= fixtureEl
.querySelector('div')
1412 const tooltip
= new Tooltip(div
)
1414 expect(Tooltip
.getOrCreateInstance(div
)).toEqual(tooltip
)
1415 expect(Tooltip
.getInstance(div
)).toEqual(Tooltip
.getOrCreateInstance(div
, {}))
1416 expect(Tooltip
.getOrCreateInstance(div
)).toBeInstanceOf(Tooltip
)
1419 it('should return new instance when there is no tooltip instance', () => {
1420 fixtureEl
.innerHTML
= '<div></div>'
1422 const div
= fixtureEl
.querySelector('div')
1424 expect(Tooltip
.getInstance(div
)).toBeNull()
1425 expect(Tooltip
.getOrCreateInstance(div
)).toBeInstanceOf(Tooltip
)
1428 it('should return new instance when there is no tooltip instance with given configuration', () => {
1429 fixtureEl
.innerHTML
= '<div></div>'
1431 const div
= fixtureEl
.querySelector('div')
1433 expect(Tooltip
.getInstance(div
)).toBeNull()
1434 const tooltip
= Tooltip
.getOrCreateInstance(div
, {
1437 expect(tooltip
).toBeInstanceOf(Tooltip
)
1439 expect(tooltip
._getTitle()).toEqual('test')
1442 it('should return the instance when exists without given configuration', () => {
1443 fixtureEl
.innerHTML
= '<div></div>'
1445 const div
= fixtureEl
.querySelector('div')
1446 const tooltip
= new Tooltip(div
, {
1447 title
: () => 'nothing'
1449 expect(Tooltip
.getInstance(div
)).toEqual(tooltip
)
1451 const tooltip2
= Tooltip
.getOrCreateInstance(div
, {
1454 expect(tooltip
).toBeInstanceOf(Tooltip
)
1455 expect(tooltip2
).toEqual(tooltip
)
1457 expect(tooltip2
._getTitle()).toEqual('nothing')
1461 describe('jQueryInterface', () => {
1462 it('should create a tooltip', () => {
1463 fixtureEl
.innerHTML
= '<div></div>'
1465 const div
= fixtureEl
.querySelector('div')
1467 jQueryMock
.fn
.tooltip
= Tooltip
.jQueryInterface
1468 jQueryMock
.elements
= [div
]
1470 jQueryMock
.fn
.tooltip
.call(jQueryMock
)
1472 expect(Tooltip
.getInstance(div
)).not
.toBeNull()
1475 it('should not re create a tooltip', () => {
1476 fixtureEl
.innerHTML
= '<div></div>'
1478 const div
= fixtureEl
.querySelector('div')
1479 const tooltip
= new Tooltip(div
)
1481 jQueryMock
.fn
.tooltip
= Tooltip
.jQueryInterface
1482 jQueryMock
.elements
= [div
]
1484 jQueryMock
.fn
.tooltip
.call(jQueryMock
)
1486 expect(Tooltip
.getInstance(div
)).toEqual(tooltip
)
1489 it('should call a tooltip method', () => {
1490 fixtureEl
.innerHTML
= '<div></div>'
1492 const div
= fixtureEl
.querySelector('div')
1493 const tooltip
= new Tooltip(div
)
1495 spyOn(tooltip
, 'show')
1497 jQueryMock
.fn
.tooltip
= Tooltip
.jQueryInterface
1498 jQueryMock
.elements
= [div
]
1500 jQueryMock
.fn
.tooltip
.call(jQueryMock
, 'show')
1502 expect(Tooltip
.getInstance(div
)).toEqual(tooltip
)
1503 expect(tooltip
.show
).toHaveBeenCalled()
1506 it('should throw error on undefined method', () => {
1507 fixtureEl
.innerHTML
= '<div></div>'
1509 const div
= fixtureEl
.querySelector('div')
1510 const action
= 'undefinedMethod'
1512 jQueryMock
.fn
.tooltip
= Tooltip
.jQueryInterface
1513 jQueryMock
.elements
= [div
]
1516 jQueryMock
.fn
.tooltip
.call(jQueryMock
, action
)
1517 }).toThrowError(TypeError
, `No method named "${action}"`)