tooltip.spec.js 49 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585
  1. import EventHandler from '../../src/dom/event-handler.js'
  2. import Tooltip from '../../src/tooltip.js'
  3. import { noop } from '../../src/util/index.js'
  4. import {
  5. clearFixture, createEvent, getFixture, jQueryMock
  6. } from '../helpers/fixture.js'
  7. describe('Tooltip', () => {
  8. let fixtureEl
  9. beforeAll(() => {
  10. fixtureEl = getFixture()
  11. })
  12. afterEach(() => {
  13. clearFixture()
  14. for (const tooltipEl of document.querySelectorAll('.tooltip')) {
  15. tooltipEl.remove()
  16. }
  17. })
  18. describe('VERSION', () => {
  19. it('should return plugin version', () => {
  20. expect(Tooltip.VERSION).toEqual(jasmine.any(String))
  21. })
  22. })
  23. describe('Default', () => {
  24. it('should return plugin default config', () => {
  25. expect(Tooltip.Default).toEqual(jasmine.any(Object))
  26. })
  27. })
  28. describe('NAME', () => {
  29. it('should return plugin name', () => {
  30. expect(Tooltip.NAME).toEqual(jasmine.any(String))
  31. })
  32. })
  33. describe('DATA_KEY', () => {
  34. it('should return plugin data key', () => {
  35. expect(Tooltip.DATA_KEY).toEqual('bs.tooltip')
  36. })
  37. })
  38. describe('EVENT_KEY', () => {
  39. it('should return plugin event key', () => {
  40. expect(Tooltip.EVENT_KEY).toEqual('.bs.tooltip')
  41. })
  42. })
  43. describe('DefaultType', () => {
  44. it('should return plugin default type', () => {
  45. expect(Tooltip.DefaultType).toEqual(jasmine.any(Object))
  46. })
  47. })
  48. describe('constructor', () => {
  49. it('should take care of element either passed as a CSS selector or DOM element', () => {
  50. fixtureEl.innerHTML = '<a href="#" id="tooltipEl" rel="tooltip" title="Nice and short title"></a>'
  51. const tooltipEl = fixtureEl.querySelector('#tooltipEl')
  52. const tooltipBySelector = new Tooltip('#tooltipEl')
  53. const tooltipByElement = new Tooltip(tooltipEl)
  54. expect(tooltipBySelector._element).toEqual(tooltipEl)
  55. expect(tooltipByElement._element).toEqual(tooltipEl)
  56. })
  57. it('should not take care of disallowed data attributes', () => {
  58. fixtureEl.innerHTML = '<a href="#" rel="tooltip" data-bs-sanitize="false" title="Another tooltip"></a>'
  59. const tooltipEl = fixtureEl.querySelector('a')
  60. const tooltip = new Tooltip(tooltipEl)
  61. expect(tooltip._config.sanitize).toBeTrue()
  62. })
  63. it('should convert title and content to string if numbers', () => {
  64. fixtureEl.innerHTML = '<a href="#" rel="tooltip"></a>'
  65. const tooltipEl = fixtureEl.querySelector('a')
  66. const tooltip = new Tooltip(tooltipEl, {
  67. title: 1,
  68. content: 7
  69. })
  70. expect(tooltip._config.title).toEqual('1')
  71. expect(tooltip._config.content).toEqual('7')
  72. })
  73. it('should enable selector delegation', () => {
  74. return new Promise(resolve => {
  75. fixtureEl.innerHTML = '<div></div>'
  76. const containerEl = fixtureEl.querySelector('div')
  77. const tooltipContainer = new Tooltip(containerEl, {
  78. selector: 'a[rel="tooltip"]',
  79. trigger: 'click'
  80. })
  81. containerEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  82. const tooltipInContainerEl = containerEl.querySelector('a')
  83. tooltipInContainerEl.addEventListener('shown.bs.tooltip', () => {
  84. expect(document.querySelector('.tooltip')).not.toBeNull()
  85. tooltipContainer.dispose()
  86. resolve()
  87. })
  88. tooltipInContainerEl.click()
  89. })
  90. })
  91. it('should create offset modifier when offset is passed as a function', () => {
  92. return new Promise(resolve => {
  93. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Offset from function"></a>'
  94. const getOffset = jasmine.createSpy('getOffset').and.returnValue([10, 20])
  95. const tooltipEl = fixtureEl.querySelector('a')
  96. const tooltip = new Tooltip(tooltipEl, {
  97. offset: getOffset,
  98. popperConfig: {
  99. onFirstUpdate(state) {
  100. expect(getOffset).toHaveBeenCalledWith({
  101. popper: state.rects.popper,
  102. reference: state.rects.reference,
  103. placement: state.placement
  104. }, tooltipEl)
  105. resolve()
  106. }
  107. }
  108. })
  109. const offset = tooltip._getOffset()
  110. expect(offset).toEqual(jasmine.any(Function))
  111. tooltip.show()
  112. })
  113. })
  114. it('should create offset modifier when offset option is passed in data attribute', () => {
  115. fixtureEl.innerHTML = '<a href="#" rel="tooltip" data-bs-offset="10,20" title="Another tooltip"></a>'
  116. const tooltipEl = fixtureEl.querySelector('a')
  117. const tooltip = new Tooltip(tooltipEl)
  118. expect(tooltip._getOffset()).toEqual([10, 20])
  119. })
  120. it('should allow to pass config to Popper with `popperConfig`', () => {
  121. fixtureEl.innerHTML = '<a href="#" rel="tooltip"></a>'
  122. const tooltipEl = fixtureEl.querySelector('a')
  123. const tooltip = new Tooltip(tooltipEl, {
  124. popperConfig: {
  125. placement: 'left'
  126. }
  127. })
  128. const popperConfig = tooltip._getPopperConfig('top')
  129. expect(popperConfig.placement).toEqual('left')
  130. })
  131. it('should allow to pass config to Popper with `popperConfig` as a function', () => {
  132. fixtureEl.innerHTML = '<a href="#" rel="tooltip"></a>'
  133. const tooltipEl = fixtureEl.querySelector('a')
  134. const getPopperConfig = jasmine.createSpy('getPopperConfig').and.returnValue({ placement: 'left' })
  135. const tooltip = new Tooltip(tooltipEl, {
  136. popperConfig: getPopperConfig
  137. })
  138. const popperConfig = tooltip._getPopperConfig('top')
  139. // Ensure that the function was called with the default config.
  140. expect(getPopperConfig).toHaveBeenCalledWith(jasmine.objectContaining({
  141. placement: jasmine.any(String)
  142. }))
  143. expect(popperConfig.placement).toEqual('left')
  144. })
  145. it('should use original title, if not "data-bs-title" is given', () => {
  146. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  147. const tooltipEl = fixtureEl.querySelector('a')
  148. const tooltip = new Tooltip(tooltipEl)
  149. expect(tooltip._getTitle()).toEqual('Another tooltip')
  150. })
  151. })
  152. describe('enable', () => {
  153. it('should enable a tooltip', () => {
  154. return new Promise(resolve => {
  155. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  156. const tooltipEl = fixtureEl.querySelector('a')
  157. const tooltip = new Tooltip(tooltipEl)
  158. tooltip.enable()
  159. tooltipEl.addEventListener('shown.bs.tooltip', () => {
  160. expect(document.querySelector('.tooltip')).not.toBeNull()
  161. resolve()
  162. })
  163. tooltip.show()
  164. })
  165. })
  166. })
  167. describe('disable', () => {
  168. it('should disable tooltip', () => {
  169. return new Promise((resolve, reject) => {
  170. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  171. const tooltipEl = fixtureEl.querySelector('a')
  172. const tooltip = new Tooltip(tooltipEl)
  173. tooltip.disable()
  174. tooltipEl.addEventListener('show.bs.tooltip', () => {
  175. reject(new Error('should not show a disabled tooltip'))
  176. })
  177. tooltip.show()
  178. setTimeout(() => {
  179. expect().nothing()
  180. resolve()
  181. }, 10)
  182. })
  183. })
  184. })
  185. describe('toggleEnabled', () => {
  186. it('should toggle enabled', () => {
  187. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  188. const tooltipEl = fixtureEl.querySelector('a')
  189. const tooltip = new Tooltip(tooltipEl)
  190. expect(tooltip._isEnabled).toBeTrue()
  191. tooltip.toggleEnabled()
  192. expect(tooltip._isEnabled).toBeFalse()
  193. })
  194. })
  195. describe('toggle', () => {
  196. it('should do nothing if disabled', () => {
  197. return new Promise((resolve, reject) => {
  198. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  199. const tooltipEl = fixtureEl.querySelector('a')
  200. const tooltip = new Tooltip(tooltipEl)
  201. tooltip.disable()
  202. tooltipEl.addEventListener('show.bs.tooltip', () => {
  203. reject(new Error('should not show a disabled tooltip'))
  204. })
  205. tooltip.toggle()
  206. setTimeout(() => {
  207. expect().nothing()
  208. resolve()
  209. }, 10)
  210. })
  211. })
  212. it('should show a tooltip', () => {
  213. return new Promise(resolve => {
  214. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  215. const tooltipEl = fixtureEl.querySelector('a')
  216. const tooltip = new Tooltip(tooltipEl)
  217. tooltipEl.addEventListener('shown.bs.tooltip', () => {
  218. expect(document.querySelector('.tooltip')).not.toBeNull()
  219. resolve()
  220. })
  221. tooltip.toggle()
  222. })
  223. })
  224. it('should call toggle and show the tooltip when trigger is "click"', () => {
  225. return new Promise(resolve => {
  226. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  227. const tooltipEl = fixtureEl.querySelector('a')
  228. const tooltip = new Tooltip(tooltipEl, {
  229. trigger: 'click'
  230. })
  231. const spy = spyOn(tooltip, 'toggle').and.callThrough()
  232. tooltipEl.addEventListener('shown.bs.tooltip', () => {
  233. expect(spy).toHaveBeenCalled()
  234. resolve()
  235. })
  236. tooltipEl.click()
  237. })
  238. })
  239. it('should hide a tooltip', () => {
  240. return new Promise(resolve => {
  241. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  242. const tooltipEl = fixtureEl.querySelector('a')
  243. const tooltip = new Tooltip(tooltipEl)
  244. tooltipEl.addEventListener('shown.bs.tooltip', () => {
  245. tooltip.toggle()
  246. })
  247. tooltipEl.addEventListener('hidden.bs.tooltip', () => {
  248. expect(document.querySelector('.tooltip')).toBeNull()
  249. resolve()
  250. })
  251. tooltip.toggle()
  252. })
  253. })
  254. it('should call toggle and hide the tooltip when trigger is "click"', () => {
  255. return new Promise(resolve => {
  256. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  257. const tooltipEl = fixtureEl.querySelector('a')
  258. const tooltip = new Tooltip(tooltipEl, {
  259. trigger: 'click'
  260. })
  261. const spy = spyOn(tooltip, 'toggle').and.callThrough()
  262. tooltipEl.addEventListener('shown.bs.tooltip', () => {
  263. tooltipEl.click()
  264. })
  265. tooltipEl.addEventListener('hidden.bs.tooltip', () => {
  266. expect(spy).toHaveBeenCalled()
  267. resolve()
  268. })
  269. tooltipEl.click()
  270. })
  271. })
  272. })
  273. describe('dispose', () => {
  274. it('should destroy a tooltip', () => {
  275. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  276. const tooltipEl = fixtureEl.querySelector('a')
  277. const addEventSpy = spyOn(tooltipEl, 'addEventListener').and.callThrough()
  278. const removeEventSpy = spyOn(tooltipEl, 'removeEventListener').and.callThrough()
  279. const tooltip = new Tooltip(tooltipEl)
  280. expect(Tooltip.getInstance(tooltipEl)).toEqual(tooltip)
  281. const expectedArgs = [
  282. ['mouseover', jasmine.any(Function), jasmine.any(Boolean)],
  283. ['mouseout', jasmine.any(Function), jasmine.any(Boolean)],
  284. ['focusin', jasmine.any(Function), jasmine.any(Boolean)],
  285. ['focusout', jasmine.any(Function), jasmine.any(Boolean)]
  286. ]
  287. expect(addEventSpy.calls.allArgs()).toEqual(expectedArgs)
  288. tooltip.dispose()
  289. expect(Tooltip.getInstance(tooltipEl)).toBeNull()
  290. expect(removeEventSpy.calls.allArgs()).toEqual(expectedArgs)
  291. })
  292. it('should destroy a tooltip after it is shown and hidden', () => {
  293. return new Promise(resolve => {
  294. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  295. const tooltipEl = fixtureEl.querySelector('a')
  296. const tooltip = new Tooltip(tooltipEl)
  297. tooltipEl.addEventListener('shown.bs.tooltip', () => {
  298. tooltip.hide()
  299. })
  300. tooltipEl.addEventListener('hidden.bs.tooltip', () => {
  301. tooltip.dispose()
  302. expect(tooltip.tip).toBeNull()
  303. expect(Tooltip.getInstance(tooltipEl)).toBeNull()
  304. resolve()
  305. })
  306. tooltip.show()
  307. })
  308. })
  309. it('should destroy a tooltip and remove it from the dom', () => {
  310. return new Promise(resolve => {
  311. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  312. const tooltipEl = fixtureEl.querySelector('a')
  313. const tooltip = new Tooltip(tooltipEl)
  314. tooltipEl.addEventListener('shown.bs.tooltip', () => {
  315. expect(document.querySelector('.tooltip')).not.toBeNull()
  316. tooltip.dispose()
  317. expect(document.querySelector('.tooltip')).toBeNull()
  318. resolve()
  319. })
  320. tooltip.show()
  321. })
  322. })
  323. it('should destroy a tooltip and reset it\'s initial title', () => {
  324. fixtureEl.innerHTML = [
  325. '<span id="tooltipWithTitle" rel="tooltip" title="tooltipTitle"></span>',
  326. '<span id="tooltipWithoutTitle" rel="tooltip" data-bs-title="tooltipTitle"></span>'
  327. ].join('')
  328. const tooltipWithTitleEl = fixtureEl.querySelector('#tooltipWithTitle')
  329. const tooltip = new Tooltip('#tooltipWithTitle')
  330. expect(tooltipWithTitleEl.getAttribute('title')).toBeNull()
  331. tooltip.dispose()
  332. expect(tooltipWithTitleEl.getAttribute('title')).toBe('tooltipTitle')
  333. const tooltipWithoutTitleEl = fixtureEl.querySelector('#tooltipWithoutTitle')
  334. const tooltip2 = new Tooltip('#tooltipWithTitle')
  335. expect(tooltipWithoutTitleEl.getAttribute('title')).toBeNull()
  336. tooltip2.dispose()
  337. expect(tooltipWithoutTitleEl.getAttribute('title')).toBeNull()
  338. })
  339. })
  340. describe('show', () => {
  341. it('should show a tooltip', () => {
  342. return new Promise(resolve => {
  343. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  344. const tooltipEl = fixtureEl.querySelector('a')
  345. const tooltip = new Tooltip(tooltipEl)
  346. tooltipEl.addEventListener('shown.bs.tooltip', () => {
  347. const tooltipShown = document.querySelector('.tooltip')
  348. expect(tooltipShown).not.toBeNull()
  349. expect(tooltipEl.getAttribute('aria-describedby')).toEqual(tooltipShown.getAttribute('id'))
  350. expect(tooltipShown.getAttribute('id')).toContain('tooltip')
  351. resolve()
  352. })
  353. tooltip.show()
  354. })
  355. })
  356. it('should show a tooltip when hovering a child element', () => {
  357. return new Promise(resolve => {
  358. fixtureEl.innerHTML = [
  359. '<a href="#" rel="tooltip" title="Another tooltip">',
  360. ' <svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 100 100">',
  361. ' <rect width="100%" fill="#563d7c"/>',
  362. ' <circle cx="50" cy="50" r="30" fill="#fff"/>',
  363. ' </svg>',
  364. '</a>'
  365. ].join('')
  366. const tooltipEl = fixtureEl.querySelector('a')
  367. const tooltip = new Tooltip(tooltipEl)
  368. const spy = spyOn(tooltip, 'show')
  369. tooltipEl.querySelector('rect').dispatchEvent(createEvent('mouseover', { bubbles: true }))
  370. setTimeout(() => {
  371. expect(spy).toHaveBeenCalled()
  372. resolve()
  373. }, 0)
  374. })
  375. })
  376. it('should show a tooltip on mobile', () => {
  377. return new Promise(resolve => {
  378. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  379. const tooltipEl = fixtureEl.querySelector('a')
  380. const tooltip = new Tooltip(tooltipEl)
  381. document.documentElement.ontouchstart = noop
  382. const spy = spyOn(EventHandler, 'on').and.callThrough()
  383. tooltipEl.addEventListener('shown.bs.tooltip', () => {
  384. expect(document.querySelector('.tooltip')).not.toBeNull()
  385. expect(spy).toHaveBeenCalledWith(jasmine.any(Object), 'mouseover', noop)
  386. document.documentElement.ontouchstart = undefined
  387. resolve()
  388. })
  389. tooltip.show()
  390. })
  391. })
  392. it('should show a tooltip relative to placement option', () => {
  393. return new Promise(resolve => {
  394. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  395. const tooltipEl = fixtureEl.querySelector('a')
  396. const tooltip = new Tooltip(tooltipEl, {
  397. placement: 'bottom'
  398. })
  399. tooltipEl.addEventListener('inserted.bs.tooltip', () => {
  400. expect(tooltip._getTipElement()).toHaveClass('bs-tooltip-auto')
  401. })
  402. tooltipEl.addEventListener('shown.bs.tooltip', () => {
  403. expect(tooltip._getTipElement()).toHaveClass('bs-tooltip-auto')
  404. expect(tooltip._getTipElement().getAttribute('data-popper-placement')).toEqual('bottom')
  405. resolve()
  406. })
  407. tooltip.show()
  408. })
  409. })
  410. it('should not error when trying to show a tooltip that has been removed from the dom', () => {
  411. return new Promise(resolve => {
  412. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  413. const tooltipEl = fixtureEl.querySelector('a')
  414. const tooltip = new Tooltip(tooltipEl)
  415. const firstCallback = () => {
  416. tooltipEl.removeEventListener('shown.bs.tooltip', firstCallback)
  417. let tooltipShown = document.querySelector('.tooltip')
  418. tooltipShown.remove()
  419. tooltipEl.addEventListener('shown.bs.tooltip', () => {
  420. tooltipShown = document.querySelector('.tooltip')
  421. expect(tooltipShown).not.toBeNull()
  422. resolve()
  423. })
  424. tooltip.show()
  425. }
  426. tooltipEl.addEventListener('shown.bs.tooltip', firstCallback)
  427. tooltip.show()
  428. })
  429. })
  430. it('should show a tooltip with a dom element container', () => {
  431. return new Promise(resolve => {
  432. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  433. const tooltipEl = fixtureEl.querySelector('a')
  434. const tooltip = new Tooltip(tooltipEl, {
  435. container: fixtureEl
  436. })
  437. tooltipEl.addEventListener('shown.bs.tooltip', () => {
  438. expect(fixtureEl.querySelector('.tooltip')).not.toBeNull()
  439. resolve()
  440. })
  441. tooltip.show()
  442. })
  443. })
  444. it('should show a tooltip with a jquery element container', () => {
  445. return new Promise(resolve => {
  446. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  447. const tooltipEl = fixtureEl.querySelector('a')
  448. const tooltip = new Tooltip(tooltipEl, {
  449. container: {
  450. 0: fixtureEl,
  451. jquery: 'jQuery'
  452. }
  453. })
  454. tooltipEl.addEventListener('shown.bs.tooltip', () => {
  455. expect(fixtureEl.querySelector('.tooltip')).not.toBeNull()
  456. resolve()
  457. })
  458. tooltip.show()
  459. })
  460. })
  461. it('should show a tooltip with a selector in container', () => {
  462. return new Promise(resolve => {
  463. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  464. const tooltipEl = fixtureEl.querySelector('a')
  465. const tooltip = new Tooltip(tooltipEl, {
  466. container: '#fixture'
  467. })
  468. tooltipEl.addEventListener('shown.bs.tooltip', () => {
  469. expect(fixtureEl.querySelector('.tooltip')).not.toBeNull()
  470. resolve()
  471. })
  472. tooltip.show()
  473. })
  474. })
  475. it('should show a tooltip with placement as a function', () => {
  476. return new Promise(resolve => {
  477. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  478. const spy = jasmine.createSpy('placement').and.returnValue('top')
  479. const tooltipEl = fixtureEl.querySelector('a')
  480. const tooltip = new Tooltip(tooltipEl, {
  481. placement: spy
  482. })
  483. tooltipEl.addEventListener('shown.bs.tooltip', () => {
  484. expect(document.querySelector('.tooltip')).not.toBeNull()
  485. expect(spy).toHaveBeenCalled()
  486. resolve()
  487. })
  488. tooltip.show()
  489. })
  490. })
  491. it('should show a tooltip without the animation', () => {
  492. return new Promise(resolve => {
  493. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  494. const tooltipEl = fixtureEl.querySelector('a')
  495. const tooltip = new Tooltip(tooltipEl, {
  496. animation: false
  497. })
  498. tooltipEl.addEventListener('shown.bs.tooltip', () => {
  499. const tip = document.querySelector('.tooltip')
  500. expect(tip).not.toBeNull()
  501. expect(tip).not.toHaveClass('fade')
  502. resolve()
  503. })
  504. tooltip.show()
  505. })
  506. })
  507. it('should throw an error the element is not visible', () => {
  508. fixtureEl.innerHTML = '<a href="#" style="display: none" rel="tooltip" title="Another tooltip"></a>'
  509. const tooltipEl = fixtureEl.querySelector('a')
  510. const tooltip = new Tooltip(tooltipEl)
  511. try {
  512. tooltip.show()
  513. } catch (error) {
  514. expect(error.message).toEqual('Please use show on visible elements')
  515. }
  516. })
  517. it('should not show a tooltip if show.bs.tooltip is prevented', () => {
  518. return new Promise((resolve, reject) => {
  519. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  520. const tooltipEl = fixtureEl.querySelector('a')
  521. const tooltip = new Tooltip(tooltipEl)
  522. const expectedDone = () => {
  523. setTimeout(() => {
  524. expect(document.querySelector('.tooltip')).toBeNull()
  525. resolve()
  526. }, 10)
  527. }
  528. tooltipEl.addEventListener('show.bs.tooltip', ev => {
  529. ev.preventDefault()
  530. expectedDone()
  531. })
  532. tooltipEl.addEventListener('shown.bs.tooltip', () => {
  533. reject(new Error('Tooltip should not be shown'))
  534. })
  535. tooltip.show()
  536. })
  537. })
  538. it('should show tooltip if leave event hasn\'t occurred before delay expires', () => {
  539. return new Promise(resolve => {
  540. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  541. const tooltipEl = fixtureEl.querySelector('a')
  542. const tooltip = new Tooltip(tooltipEl, {
  543. delay: 150
  544. })
  545. const spy = spyOn(tooltip, 'show')
  546. setTimeout(() => {
  547. expect(spy).not.toHaveBeenCalled()
  548. }, 100)
  549. setTimeout(() => {
  550. expect(spy).toHaveBeenCalled()
  551. resolve()
  552. }, 200)
  553. tooltipEl.dispatchEvent(createEvent('mouseover'))
  554. })
  555. })
  556. it('should not show tooltip if leave event occurs before delay expires', () => {
  557. return new Promise(resolve => {
  558. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  559. const tooltipEl = fixtureEl.querySelector('a')
  560. const tooltip = new Tooltip(tooltipEl, {
  561. delay: 150
  562. })
  563. const spy = spyOn(tooltip, 'show')
  564. setTimeout(() => {
  565. expect(spy).not.toHaveBeenCalled()
  566. tooltipEl.dispatchEvent(createEvent('mouseover'))
  567. }, 100)
  568. setTimeout(() => {
  569. expect(spy).toHaveBeenCalled()
  570. expect(document.querySelectorAll('.tooltip')).toHaveSize(0)
  571. resolve()
  572. }, 200)
  573. tooltipEl.dispatchEvent(createEvent('mouseover'))
  574. })
  575. })
  576. it('should not hide tooltip if leave event occurs and enter event occurs within the hide delay', () => {
  577. return new Promise(resolve => {
  578. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip" data-bs-delay=\'{"show":0,"hide":150}\'>'
  579. const tooltipEl = fixtureEl.querySelector('a')
  580. const tooltip = new Tooltip(tooltipEl)
  581. expect(tooltip._config.delay).toEqual({ show: 0, hide: 150 })
  582. setTimeout(() => {
  583. expect(tooltip._getTipElement()).toHaveClass('show')
  584. tooltipEl.dispatchEvent(createEvent('mouseout'))
  585. setTimeout(() => {
  586. expect(tooltip._getTipElement()).toHaveClass('show')
  587. tooltipEl.dispatchEvent(createEvent('mouseover'))
  588. }, 100)
  589. setTimeout(() => {
  590. expect(tooltip._getTipElement()).toHaveClass('show')
  591. expect(document.querySelectorAll('.tooltip')).toHaveSize(1)
  592. resolve()
  593. }, 200)
  594. }, 10)
  595. tooltipEl.dispatchEvent(createEvent('mouseover'))
  596. })
  597. })
  598. it('should not hide tooltip if leave event occurs and interaction remains inside trigger', () => {
  599. return new Promise(resolve => {
  600. fixtureEl.innerHTML = [
  601. '<a href="#" rel="tooltip" title="Another tooltip">',
  602. '<b>Trigger</b>',
  603. 'the tooltip',
  604. '</a>'
  605. ].join('')
  606. const tooltipEl = fixtureEl.querySelector('a')
  607. const tooltip = new Tooltip(tooltipEl)
  608. const triggerChild = tooltipEl.querySelector('b')
  609. const spy = spyOn(tooltip, 'hide').and.callThrough()
  610. tooltipEl.addEventListener('mouseover', () => {
  611. const moveMouseToChildEvent = createEvent('mouseout')
  612. Object.defineProperty(moveMouseToChildEvent, 'relatedTarget', {
  613. value: triggerChild
  614. })
  615. tooltipEl.dispatchEvent(moveMouseToChildEvent)
  616. })
  617. tooltipEl.addEventListener('mouseout', () => {
  618. expect(spy).not.toHaveBeenCalled()
  619. resolve()
  620. })
  621. tooltipEl.dispatchEvent(createEvent('mouseover'))
  622. })
  623. })
  624. it('should properly maintain tooltip state if leave event occurs and enter event occurs during hide transition', () => {
  625. return new Promise(resolve => {
  626. // Style this tooltip to give it plenty of room for popper to do what it wants
  627. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip" data-bs-placement="top" style="position:fixed;left:50%;top:50%;">Trigger</a>'
  628. const tooltipEl = fixtureEl.querySelector('a')
  629. const tooltip = new Tooltip(tooltipEl)
  630. spyOn(window, 'getComputedStyle').and.returnValue({
  631. transitionDuration: '0.15s',
  632. transitionDelay: '0s'
  633. })
  634. setTimeout(() => {
  635. expect(tooltip._popper).not.toBeNull()
  636. expect(tooltip._getTipElement().getAttribute('data-popper-placement')).toEqual('top')
  637. tooltipEl.dispatchEvent(createEvent('mouseout'))
  638. setTimeout(() => {
  639. expect(tooltip._getTipElement()).not.toHaveClass('show')
  640. tooltipEl.dispatchEvent(createEvent('mouseover'))
  641. }, 100)
  642. setTimeout(() => {
  643. expect(tooltip._popper).not.toBeNull()
  644. expect(tooltip._getTipElement().getAttribute('data-popper-placement')).toEqual('top')
  645. resolve()
  646. }, 200)
  647. }, 10)
  648. tooltipEl.dispatchEvent(createEvent('mouseover'))
  649. })
  650. })
  651. it('should only trigger inserted event if a new tooltip element was created', () => {
  652. return new Promise(resolve => {
  653. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  654. const tooltipEl = fixtureEl.querySelector('a')
  655. const tooltip = new Tooltip(tooltipEl)
  656. spyOn(window, 'getComputedStyle').and.returnValue({
  657. transitionDuration: '0.15s',
  658. transitionDelay: '0s'
  659. })
  660. const insertedFunc = jasmine.createSpy()
  661. tooltipEl.addEventListener('inserted.bs.tooltip', insertedFunc)
  662. setTimeout(() => {
  663. expect(insertedFunc).toHaveBeenCalledTimes(1)
  664. tooltip.hide()
  665. setTimeout(() => {
  666. tooltip.show()
  667. }, 100)
  668. setTimeout(() => {
  669. expect(insertedFunc).toHaveBeenCalledTimes(2)
  670. resolve()
  671. }, 200)
  672. }, 0)
  673. tooltip.show()
  674. })
  675. })
  676. it('should show a tooltip with custom class provided in data attributes', () => {
  677. return new Promise(resolve => {
  678. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip" data-bs-custom-class="custom-class"></a>'
  679. const tooltipEl = fixtureEl.querySelector('a')
  680. const tooltip = new Tooltip(tooltipEl)
  681. tooltipEl.addEventListener('shown.bs.tooltip', () => {
  682. const tip = document.querySelector('.tooltip')
  683. expect(tip).not.toBeNull()
  684. expect(tip).toHaveClass('custom-class')
  685. resolve()
  686. })
  687. tooltip.show()
  688. })
  689. })
  690. it('should show a tooltip with custom class provided as a string in config', () => {
  691. return new Promise(resolve => {
  692. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  693. const tooltipEl = fixtureEl.querySelector('a')
  694. const tooltip = new Tooltip(tooltipEl, {
  695. customClass: 'custom-class custom-class-2'
  696. })
  697. tooltipEl.addEventListener('shown.bs.tooltip', () => {
  698. const tip = document.querySelector('.tooltip')
  699. expect(tip).not.toBeNull()
  700. expect(tip).toHaveClass('custom-class')
  701. expect(tip).toHaveClass('custom-class-2')
  702. resolve()
  703. })
  704. tooltip.show()
  705. })
  706. })
  707. it('should show a tooltip with custom class provided as a function in config', () => {
  708. return new Promise(resolve => {
  709. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip" data-class-a="custom-class-a" data-class-b="custom-class-b"></a>'
  710. const tooltipEl = fixtureEl.querySelector('a')
  711. const spy = jasmine.createSpy('customClass').and.callFake(function (el) {
  712. return `${el.dataset.classA} ${this.dataset.classB}`
  713. })
  714. const tooltip = new Tooltip(tooltipEl, {
  715. customClass: spy
  716. })
  717. tooltipEl.addEventListener('shown.bs.tooltip', () => {
  718. const tip = document.querySelector('.tooltip')
  719. expect(tip).not.toBeNull()
  720. expect(spy).toHaveBeenCalled()
  721. expect(tip).toHaveClass('custom-class-a')
  722. expect(tip).toHaveClass('custom-class-b')
  723. resolve()
  724. })
  725. tooltip.show()
  726. })
  727. })
  728. it('should remove `title` attribute if exists', () => {
  729. return new Promise(resolve => {
  730. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  731. const tooltipEl = fixtureEl.querySelector('a')
  732. const tooltip = new Tooltip(tooltipEl)
  733. tooltipEl.addEventListener('shown.bs.tooltip', () => {
  734. expect(tooltipEl.getAttribute('title')).toBeNull()
  735. resolve()
  736. })
  737. tooltip.show()
  738. })
  739. })
  740. })
  741. describe('hide', () => {
  742. it('should hide a tooltip', () => {
  743. return new Promise(resolve => {
  744. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  745. const tooltipEl = fixtureEl.querySelector('a')
  746. const tooltip = new Tooltip(tooltipEl)
  747. tooltipEl.addEventListener('shown.bs.tooltip', () => tooltip.hide())
  748. tooltipEl.addEventListener('hidden.bs.tooltip', () => {
  749. expect(document.querySelector('.tooltip')).toBeNull()
  750. expect(tooltipEl.getAttribute('aria-describedby')).toBeNull()
  751. resolve()
  752. })
  753. tooltip.show()
  754. })
  755. })
  756. it('should hide a tooltip on mobile', () => {
  757. return new Promise(resolve => {
  758. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  759. const tooltipEl = fixtureEl.querySelector('a')
  760. const tooltip = new Tooltip(tooltipEl)
  761. const spy = spyOn(EventHandler, 'off')
  762. tooltipEl.addEventListener('shown.bs.tooltip', () => {
  763. document.documentElement.ontouchstart = noop
  764. tooltip.hide()
  765. })
  766. tooltipEl.addEventListener('hidden.bs.tooltip', () => {
  767. expect(document.querySelector('.tooltip')).toBeNull()
  768. expect(spy).toHaveBeenCalledWith(jasmine.any(Object), 'mouseover', noop)
  769. document.documentElement.ontouchstart = undefined
  770. resolve()
  771. })
  772. tooltip.show()
  773. })
  774. })
  775. it('should hide a tooltip without animation', () => {
  776. return new Promise(resolve => {
  777. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  778. const tooltipEl = fixtureEl.querySelector('a')
  779. const tooltip = new Tooltip(tooltipEl, {
  780. animation: false
  781. })
  782. tooltipEl.addEventListener('shown.bs.tooltip', () => tooltip.hide())
  783. tooltipEl.addEventListener('hidden.bs.tooltip', () => {
  784. expect(document.querySelector('.tooltip')).toBeNull()
  785. expect(tooltipEl.getAttribute('aria-describedby')).toBeNull()
  786. resolve()
  787. })
  788. tooltip.show()
  789. })
  790. })
  791. it('should not hide a tooltip if hide event is prevented', () => {
  792. return new Promise((resolve, reject) => {
  793. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  794. const assertDone = () => {
  795. setTimeout(() => {
  796. expect(document.querySelector('.tooltip')).not.toBeNull()
  797. resolve()
  798. }, 20)
  799. }
  800. const tooltipEl = fixtureEl.querySelector('a')
  801. const tooltip = new Tooltip(tooltipEl, {
  802. animation: false
  803. })
  804. tooltipEl.addEventListener('shown.bs.tooltip', () => tooltip.hide())
  805. tooltipEl.addEventListener('hide.bs.tooltip', event => {
  806. event.preventDefault()
  807. assertDone()
  808. })
  809. tooltipEl.addEventListener('hidden.bs.tooltip', () => {
  810. reject(new Error('should not trigger hidden event'))
  811. })
  812. tooltip.show()
  813. })
  814. })
  815. it('should not throw error running hide if popper hasn\'t been shown', () => {
  816. fixtureEl.innerHTML = '<div></div>'
  817. const div = fixtureEl.querySelector('div')
  818. const tooltip = new Tooltip(div)
  819. try {
  820. tooltip.hide()
  821. expect().nothing()
  822. } catch {
  823. throw new Error('should not throw error')
  824. }
  825. })
  826. })
  827. describe('update', () => {
  828. it('should call popper update', () => {
  829. return new Promise(resolve => {
  830. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  831. const tooltipEl = fixtureEl.querySelector('a')
  832. const tooltip = new Tooltip(tooltipEl)
  833. tooltipEl.addEventListener('shown.bs.tooltip', () => {
  834. const spy = spyOn(tooltip._popper, 'update')
  835. tooltip.update()
  836. expect(spy).toHaveBeenCalled()
  837. resolve()
  838. })
  839. tooltip.show()
  840. })
  841. })
  842. it('should do nothing if the tooltip is not shown', () => {
  843. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  844. const tooltipEl = fixtureEl.querySelector('a')
  845. const tooltip = new Tooltip(tooltipEl)
  846. tooltip.update()
  847. expect().nothing()
  848. })
  849. })
  850. describe('_isWithContent', () => {
  851. it('should return true if there is content', () => {
  852. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  853. const tooltipEl = fixtureEl.querySelector('a')
  854. const tooltip = new Tooltip(tooltipEl)
  855. expect(tooltip._isWithContent()).toBeTrue()
  856. })
  857. it('should return false if there is no content', () => {
  858. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title=""></a>'
  859. const tooltipEl = fixtureEl.querySelector('a')
  860. const tooltip = new Tooltip(tooltipEl)
  861. expect(tooltip._isWithContent()).toBeFalse()
  862. })
  863. })
  864. describe('_getTipElement', () => {
  865. it('should create the tip element and return it', () => {
  866. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  867. const tooltipEl = fixtureEl.querySelector('a')
  868. const tooltip = new Tooltip(tooltipEl)
  869. const spy = spyOn(document, 'createElement').and.callThrough()
  870. expect(tooltip._getTipElement()).toBeDefined()
  871. expect(spy).toHaveBeenCalled()
  872. })
  873. it('should return the created tip element', () => {
  874. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  875. const tooltipEl = fixtureEl.querySelector('a')
  876. const tooltip = new Tooltip(tooltipEl)
  877. const spy = spyOn(document, 'createElement').and.callThrough()
  878. expect(tooltip._getTipElement()).toBeDefined()
  879. expect(spy).toHaveBeenCalled()
  880. spy.calls.reset()
  881. expect(tooltip._getTipElement()).toBeDefined()
  882. expect(spy).not.toHaveBeenCalled()
  883. })
  884. })
  885. describe('setContent', () => {
  886. it('should set tip content', () => {
  887. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  888. const tooltipEl = fixtureEl.querySelector('a')
  889. const tooltip = new Tooltip(tooltipEl, { animation: false })
  890. const tip = tooltip._getTipElement()
  891. tooltip.setContent(tip)
  892. expect(tip).not.toHaveClass('show')
  893. expect(tip).not.toHaveClass('fade')
  894. expect(tip.querySelector('.tooltip-inner').textContent).toEqual('Another tooltip')
  895. })
  896. it('should re-show tip if it was already shown', () => {
  897. fixtureEl.innerHTML = '<a href="#" rel="tooltip" data-bs-title="Another tooltip"></a>'
  898. const tooltipEl = fixtureEl.querySelector('a')
  899. const tooltip = new Tooltip(tooltipEl)
  900. tooltip.show()
  901. const tip = () => tooltip._getTipElement()
  902. expect(tip()).toHaveClass('show')
  903. tooltip.setContent({ '.tooltip-inner': 'foo' })
  904. expect(tip()).toHaveClass('show')
  905. expect(tip().querySelector('.tooltip-inner').textContent).toEqual('foo')
  906. })
  907. it('should keep tip hidden, if it was already hidden before', () => {
  908. fixtureEl.innerHTML = '<a href="#" rel="tooltip" data-bs-title="Another tooltip"></a>'
  909. const tooltipEl = fixtureEl.querySelector('a')
  910. const tooltip = new Tooltip(tooltipEl)
  911. const tip = () => tooltip._getTipElement()
  912. expect(tip()).not.toHaveClass('show')
  913. tooltip.setContent({ '.tooltip-inner': 'foo' })
  914. expect(tip()).not.toHaveClass('show')
  915. tooltip.show()
  916. expect(tip().querySelector('.tooltip-inner').textContent).toEqual('foo')
  917. })
  918. it('"setContent" should keep the initial template', () => {
  919. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  920. const tooltipEl = fixtureEl.querySelector('a')
  921. const tooltip = new Tooltip(tooltipEl)
  922. tooltip.setContent({ '.tooltip-inner': 'foo' })
  923. const tip = tooltip._getTipElement()
  924. expect(tip).toHaveClass('tooltip')
  925. expect(tip).toHaveClass('bs-tooltip-auto')
  926. expect(tip.querySelector('.tooltip-arrow')).not.toBeNull()
  927. expect(tip.querySelector('.tooltip-inner')).not.toBeNull()
  928. })
  929. })
  930. describe('setContent', () => {
  931. it('should do nothing if the element is null', () => {
  932. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  933. const tooltipEl = fixtureEl.querySelector('a')
  934. const tooltip = new Tooltip(tooltipEl)
  935. tooltip.setContent({ '.tooltip': null })
  936. expect().nothing()
  937. })
  938. it('should do nothing if the content is a child of the element', () => {
  939. fixtureEl.innerHTML = [
  940. '<a href="#" rel="tooltip" title="Another tooltip">',
  941. ' <div id="childContent"></div>',
  942. '</a>'
  943. ].join('')
  944. const tooltipEl = fixtureEl.querySelector('a')
  945. const childContent = fixtureEl.querySelector('div')
  946. const tooltip = new Tooltip(tooltipEl, {
  947. html: true
  948. })
  949. tooltip._getTipElement().append(childContent)
  950. tooltip.setContent({ '.tooltip': childContent })
  951. expect().nothing()
  952. })
  953. it('should add the content as a child of the element for jQuery elements', () => {
  954. fixtureEl.innerHTML = [
  955. '<a href="#" rel="tooltip" title="Another tooltip">',
  956. ' <div id="childContent"></div>',
  957. '</a>'
  958. ].join('')
  959. const tooltipEl = fixtureEl.querySelector('a')
  960. const childContent = fixtureEl.querySelector('div')
  961. const tooltip = new Tooltip(tooltipEl, {
  962. html: true
  963. })
  964. tooltip.setContent({ '.tooltip': { 0: childContent, jquery: 'jQuery' } })
  965. tooltip.show()
  966. expect(childContent.parentNode).toEqual(tooltip._getTipElement())
  967. })
  968. it('should add the child text content in the element', () => {
  969. fixtureEl.innerHTML = [
  970. '<a href="#" rel="tooltip" title="Another tooltip">',
  971. ' <div id="childContent">Tooltip</div>',
  972. '</a>'
  973. ].join('')
  974. const tooltipEl = fixtureEl.querySelector('a')
  975. const childContent = fixtureEl.querySelector('div')
  976. const tooltip = new Tooltip(tooltipEl)
  977. tooltip.setContent({ '.tooltip': childContent })
  978. expect(childContent.textContent).toEqual(tooltip._getTipElement().textContent)
  979. })
  980. it('should add html without sanitize it', () => {
  981. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  982. const tooltipEl = fixtureEl.querySelector('a')
  983. const tooltip = new Tooltip(tooltipEl, {
  984. sanitize: false,
  985. html: true
  986. })
  987. tooltip.setContent({ '.tooltip': '<div id="childContent">Tooltip</div>' })
  988. expect(tooltip._getTipElement().querySelector('div').id).toEqual('childContent')
  989. })
  990. it('should add html sanitized', () => {
  991. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  992. const tooltipEl = fixtureEl.querySelector('a')
  993. const tooltip = new Tooltip(tooltipEl, {
  994. html: true
  995. })
  996. const content = [
  997. '<div id="childContent">',
  998. ' <button type="button">test btn</button>',
  999. '</div>'
  1000. ].join('')
  1001. tooltip.setContent({ '.tooltip': content })
  1002. expect(tooltip._getTipElement().querySelector('div').id).toEqual('childContent')
  1003. expect(tooltip._getTipElement().querySelector('button')).toBeNull()
  1004. })
  1005. it('should add text content', () => {
  1006. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  1007. const tooltipEl = fixtureEl.querySelector('a')
  1008. const tooltip = new Tooltip(tooltipEl)
  1009. tooltip.setContent({ '.tooltip': 'test' })
  1010. expect(tooltip._getTipElement().textContent).toEqual('test')
  1011. })
  1012. })
  1013. describe('_getTitle', () => {
  1014. it('should return the title', () => {
  1015. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  1016. const tooltipEl = fixtureEl.querySelector('a')
  1017. const tooltip = new Tooltip(tooltipEl)
  1018. expect(tooltip._getTitle()).toEqual('Another tooltip')
  1019. })
  1020. it('should call title function', () => {
  1021. fixtureEl.innerHTML = '<a href="#" rel="tooltip"></a>'
  1022. const tooltipEl = fixtureEl.querySelector('a')
  1023. const tooltip = new Tooltip(tooltipEl, {
  1024. title: () => 'test'
  1025. })
  1026. expect(tooltip._getTitle()).toEqual('test')
  1027. })
  1028. it('should call title function with trigger element', () => {
  1029. fixtureEl.innerHTML = '<a href="#" rel="tooltip" data-foo="bar"></a>'
  1030. const tooltipEl = fixtureEl.querySelector('a')
  1031. const tooltip = new Tooltip(tooltipEl, {
  1032. title(el) {
  1033. return el.dataset.foo
  1034. }
  1035. })
  1036. expect(tooltip._getTitle()).toEqual('bar')
  1037. })
  1038. it('should call title function with correct this value', () => {
  1039. fixtureEl.innerHTML = '<a href="#" rel="tooltip" data-foo="bar"></a>'
  1040. const tooltipEl = fixtureEl.querySelector('a')
  1041. const tooltip = new Tooltip(tooltipEl, {
  1042. title() {
  1043. return this.dataset.foo
  1044. }
  1045. })
  1046. expect(tooltip._getTitle()).toEqual('bar')
  1047. })
  1048. })
  1049. describe('getInstance', () => {
  1050. it('should return tooltip instance', () => {
  1051. fixtureEl.innerHTML = '<div></div>'
  1052. const div = fixtureEl.querySelector('div')
  1053. const alert = new Tooltip(div)
  1054. expect(Tooltip.getInstance(div)).toEqual(alert)
  1055. expect(Tooltip.getInstance(div)).toBeInstanceOf(Tooltip)
  1056. })
  1057. it('should return null when there is no tooltip instance', () => {
  1058. fixtureEl.innerHTML = '<div></div>'
  1059. const div = fixtureEl.querySelector('div')
  1060. expect(Tooltip.getInstance(div)).toBeNull()
  1061. })
  1062. })
  1063. describe('aria-label', () => {
  1064. it('should add the aria-label attribute for referencing original title', () => {
  1065. return new Promise(resolve => {
  1066. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
  1067. const tooltipEl = fixtureEl.querySelector('a')
  1068. const tooltip = new Tooltip(tooltipEl)
  1069. tooltipEl.addEventListener('shown.bs.tooltip', () => {
  1070. const tooltipShown = document.querySelector('.tooltip')
  1071. expect(tooltipShown).not.toBeNull()
  1072. expect(tooltipEl.getAttribute('aria-label')).toEqual('Another tooltip')
  1073. resolve()
  1074. })
  1075. tooltip.show()
  1076. })
  1077. })
  1078. it('should add the aria-label attribute when element text content is a whitespace string', () => {
  1079. return new Promise(resolve => {
  1080. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="A tooltip"><span> </span></a>'
  1081. const tooltipEl = fixtureEl.querySelector('a')
  1082. const tooltip = new Tooltip(tooltipEl)
  1083. tooltipEl.addEventListener('shown.bs.tooltip', () => {
  1084. const tooltipShown = document.querySelector('.tooltip')
  1085. expect(tooltipShown).not.toBeNull()
  1086. expect(tooltipEl.getAttribute('aria-label')).toEqual('A tooltip')
  1087. resolve()
  1088. })
  1089. tooltip.show()
  1090. })
  1091. })
  1092. it('should not add the aria-label attribute if the attribute already exists', () => {
  1093. return new Promise(resolve => {
  1094. fixtureEl.innerHTML = '<a href="#" rel="tooltip" aria-label="Different label" title="Another tooltip"></a>'
  1095. const tooltipEl = fixtureEl.querySelector('a')
  1096. const tooltip = new Tooltip(tooltipEl)
  1097. tooltipEl.addEventListener('shown.bs.tooltip', () => {
  1098. const tooltipShown = document.querySelector('.tooltip')
  1099. expect(tooltipShown).not.toBeNull()
  1100. expect(tooltipEl.getAttribute('aria-label')).toEqual('Different label')
  1101. resolve()
  1102. })
  1103. tooltip.show()
  1104. })
  1105. })
  1106. it('should not add the aria-label attribute if the element has text content', () => {
  1107. return new Promise(resolve => {
  1108. fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">text content</a>'
  1109. const tooltipEl = fixtureEl.querySelector('a')
  1110. const tooltip = new Tooltip(tooltipEl)
  1111. tooltipEl.addEventListener('shown.bs.tooltip', () => {
  1112. const tooltipShown = document.querySelector('.tooltip')
  1113. expect(tooltipShown).not.toBeNull()
  1114. expect(tooltipEl.getAttribute('aria-label')).toBeNull()
  1115. resolve()
  1116. })
  1117. tooltip.show()
  1118. })
  1119. })
  1120. })
  1121. describe('getOrCreateInstance', () => {
  1122. it('should return tooltip instance', () => {
  1123. fixtureEl.innerHTML = '<div></div>'
  1124. const div = fixtureEl.querySelector('div')
  1125. const tooltip = new Tooltip(div)
  1126. expect(Tooltip.getOrCreateInstance(div)).toEqual(tooltip)
  1127. expect(Tooltip.getInstance(div)).toEqual(Tooltip.getOrCreateInstance(div, {}))
  1128. expect(Tooltip.getOrCreateInstance(div)).toBeInstanceOf(Tooltip)
  1129. })
  1130. it('should return new instance when there is no tooltip instance', () => {
  1131. fixtureEl.innerHTML = '<div></div>'
  1132. const div = fixtureEl.querySelector('div')
  1133. expect(Tooltip.getInstance(div)).toBeNull()
  1134. expect(Tooltip.getOrCreateInstance(div)).toBeInstanceOf(Tooltip)
  1135. })
  1136. it('should return new instance when there is no tooltip instance with given configuration', () => {
  1137. fixtureEl.innerHTML = '<div></div>'
  1138. const div = fixtureEl.querySelector('div')
  1139. expect(Tooltip.getInstance(div)).toBeNull()
  1140. const tooltip = Tooltip.getOrCreateInstance(div, {
  1141. title: () => 'test'
  1142. })
  1143. expect(tooltip).toBeInstanceOf(Tooltip)
  1144. expect(tooltip._getTitle()).toEqual('test')
  1145. })
  1146. it('should return the instance when exists without given configuration', () => {
  1147. fixtureEl.innerHTML = '<div></div>'
  1148. const div = fixtureEl.querySelector('div')
  1149. const tooltip = new Tooltip(div, {
  1150. title: () => 'nothing'
  1151. })
  1152. expect(Tooltip.getInstance(div)).toEqual(tooltip)
  1153. const tooltip2 = Tooltip.getOrCreateInstance(div, {
  1154. title: () => 'test'
  1155. })
  1156. expect(tooltip).toBeInstanceOf(Tooltip)
  1157. expect(tooltip2).toEqual(tooltip)
  1158. expect(tooltip2._getTitle()).toEqual('nothing')
  1159. })
  1160. })
  1161. describe('jQueryInterface', () => {
  1162. it('should create a tooltip', () => {
  1163. fixtureEl.innerHTML = '<div></div>'
  1164. const div = fixtureEl.querySelector('div')
  1165. jQueryMock.fn.tooltip = Tooltip.jQueryInterface
  1166. jQueryMock.elements = [div]
  1167. jQueryMock.fn.tooltip.call(jQueryMock)
  1168. expect(Tooltip.getInstance(div)).not.toBeNull()
  1169. })
  1170. it('should not re create a tooltip', () => {
  1171. fixtureEl.innerHTML = '<div></div>'
  1172. const div = fixtureEl.querySelector('div')
  1173. const tooltip = new Tooltip(div)
  1174. jQueryMock.fn.tooltip = Tooltip.jQueryInterface
  1175. jQueryMock.elements = [div]
  1176. jQueryMock.fn.tooltip.call(jQueryMock)
  1177. expect(Tooltip.getInstance(div)).toEqual(tooltip)
  1178. })
  1179. it('should call a tooltip method', () => {
  1180. fixtureEl.innerHTML = '<div></div>'
  1181. const div = fixtureEl.querySelector('div')
  1182. const tooltip = new Tooltip(div)
  1183. const spy = spyOn(tooltip, 'show')
  1184. jQueryMock.fn.tooltip = Tooltip.jQueryInterface
  1185. jQueryMock.elements = [div]
  1186. jQueryMock.fn.tooltip.call(jQueryMock, 'show')
  1187. expect(Tooltip.getInstance(div)).toEqual(tooltip)
  1188. expect(spy).toHaveBeenCalled()
  1189. })
  1190. it('should throw error on undefined method', () => {
  1191. fixtureEl.innerHTML = '<div></div>'
  1192. const div = fixtureEl.querySelector('div')
  1193. const action = 'undefinedMethod'
  1194. jQueryMock.fn.tooltip = Tooltip.jQueryInterface
  1195. jQueryMock.elements = [div]
  1196. expect(() => {
  1197. jQueryMock.fn.tooltip.call(jQueryMock, action)
  1198. }).toThrowError(TypeError, `No method named "${action}"`)
  1199. })
  1200. })
  1201. })