carousel.spec.js 50 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533
  1. import Carousel from '../../src/carousel'
  2. import EventHandler from '../../src/dom/event-handler'
  3. /** Test helpers */
  4. import { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture'
  5. import * as util from '../../src/util'
  6. describe('Carousel', () => {
  7. const { Simulator, PointerEvent } = window
  8. const originWinPointerEvent = PointerEvent
  9. const supportPointerEvent = Boolean(PointerEvent)
  10. const cssStyleCarousel = '.carousel.pointer-event { touch-action: none; }'
  11. const stylesCarousel = document.createElement('style')
  12. stylesCarousel.type = 'text/css'
  13. stylesCarousel.appendChild(document.createTextNode(cssStyleCarousel))
  14. const clearPointerEvents = () => {
  15. window.PointerEvent = null
  16. }
  17. const restorePointerEvents = () => {
  18. window.PointerEvent = originWinPointerEvent
  19. }
  20. let fixtureEl
  21. beforeAll(() => {
  22. fixtureEl = getFixture()
  23. })
  24. afterEach(() => {
  25. clearFixture()
  26. })
  27. describe('VERSION', () => {
  28. it('should return plugin version', () => {
  29. expect(Carousel.VERSION).toEqual(jasmine.any(String))
  30. })
  31. })
  32. describe('Default', () => {
  33. it('should return plugin default config', () => {
  34. expect(Carousel.Default).toEqual(jasmine.any(Object))
  35. })
  36. })
  37. describe('DATA_KEY', () => {
  38. it('should return plugin data key', () => {
  39. expect(Carousel.DATA_KEY).toEqual('bs.carousel')
  40. })
  41. })
  42. describe('constructor', () => {
  43. it('should take care of element either passed as a CSS selector or DOM element', () => {
  44. fixtureEl.innerHTML = '<div id="myCarousel" class="carousel slide"></div>'
  45. const carouselEl = fixtureEl.querySelector('#myCarousel')
  46. const carouselBySelector = new Carousel('#myCarousel')
  47. const carouselByElement = new Carousel(carouselEl)
  48. expect(carouselBySelector._element).toEqual(carouselEl)
  49. expect(carouselByElement._element).toEqual(carouselEl)
  50. })
  51. it('should go to next item if right arrow key is pressed', done => {
  52. fixtureEl.innerHTML = [
  53. '<div id="myCarousel" class="carousel slide">',
  54. ' <div class="carousel-inner">',
  55. ' <div class="carousel-item active">item 1</div>',
  56. ' <div id="item2" class="carousel-item">item 2</div>',
  57. ' <div class="carousel-item">item 3</div>',
  58. ' </div>',
  59. '</div>'
  60. ].join('')
  61. const carouselEl = fixtureEl.querySelector('#myCarousel')
  62. const carousel = new Carousel(carouselEl, {
  63. keyboard: true
  64. })
  65. spyOn(carousel, '_keydown').and.callThrough()
  66. carouselEl.addEventListener('slid.bs.carousel', () => {
  67. expect(fixtureEl.querySelector('.active')).toEqual(fixtureEl.querySelector('#item2'))
  68. expect(carousel._keydown).toHaveBeenCalled()
  69. done()
  70. })
  71. const keydown = createEvent('keydown')
  72. keydown.key = 'ArrowRight'
  73. carouselEl.dispatchEvent(keydown)
  74. })
  75. it('should go to previous item if left arrow key is pressed', done => {
  76. fixtureEl.innerHTML = [
  77. '<div id="myCarousel" class="carousel slide">',
  78. ' <div class="carousel-inner">',
  79. ' <div id="item1" class="carousel-item">item 1</div>',
  80. ' <div class="carousel-item active">item 2</div>',
  81. ' <div class="carousel-item">item 3</div>',
  82. ' </div>',
  83. '</div>'
  84. ].join('')
  85. const carouselEl = fixtureEl.querySelector('#myCarousel')
  86. const carousel = new Carousel(carouselEl, {
  87. keyboard: true
  88. })
  89. spyOn(carousel, '_keydown').and.callThrough()
  90. carouselEl.addEventListener('slid.bs.carousel', () => {
  91. expect(fixtureEl.querySelector('.active')).toEqual(fixtureEl.querySelector('#item1'))
  92. expect(carousel._keydown).toHaveBeenCalled()
  93. done()
  94. })
  95. const keydown = createEvent('keydown')
  96. keydown.key = 'ArrowLeft'
  97. carouselEl.dispatchEvent(keydown)
  98. })
  99. it('should not prevent keydown if key is not ARROW_LEFT or ARROW_RIGHT', done => {
  100. fixtureEl.innerHTML = [
  101. '<div id="myCarousel" class="carousel slide">',
  102. ' <div class="carousel-inner">',
  103. ' <div class="carousel-item active">item 1</div>',
  104. ' <div class="carousel-item">item 2</div>',
  105. ' <div class="carousel-item">item 3</div>',
  106. ' </div>',
  107. '</div>'
  108. ].join('')
  109. const carouselEl = fixtureEl.querySelector('#myCarousel')
  110. const carousel = new Carousel(carouselEl, {
  111. keyboard: true
  112. })
  113. spyOn(carousel, '_keydown').and.callThrough()
  114. carouselEl.addEventListener('keydown', event => {
  115. expect(carousel._keydown).toHaveBeenCalled()
  116. expect(event.defaultPrevented).toEqual(false)
  117. done()
  118. })
  119. const keydown = createEvent('keydown')
  120. keydown.key = 'ArrowDown'
  121. carouselEl.dispatchEvent(keydown)
  122. })
  123. it('should ignore keyboard events within <input>s and <textarea>s', () => {
  124. fixtureEl.innerHTML = [
  125. '<div id="myCarousel" class="carousel slide">',
  126. ' <div class="carousel-inner">',
  127. ' <div class="carousel-item active">',
  128. ' <input type="text">',
  129. ' <textarea></textarea>',
  130. ' </div>',
  131. ' <div class="carousel-item"></div>',
  132. ' <div class="carousel-item">item 3</div>',
  133. ' </div>',
  134. '</div>'
  135. ].join('')
  136. const carouselEl = fixtureEl.querySelector('#myCarousel')
  137. const input = fixtureEl.querySelector('input')
  138. const textarea = fixtureEl.querySelector('textarea')
  139. const carousel = new Carousel(carouselEl, {
  140. keyboard: true
  141. })
  142. const spyKeydown = spyOn(carousel, '_keydown').and.callThrough()
  143. const spySlide = spyOn(carousel, '_slide')
  144. const keydown = createEvent('keydown', { bubbles: true, cancelable: true })
  145. keydown.key = 'ArrowRight'
  146. Object.defineProperty(keydown, 'target', {
  147. value: input,
  148. writable: true,
  149. configurable: true
  150. })
  151. input.dispatchEvent(keydown)
  152. expect(spyKeydown).toHaveBeenCalled()
  153. expect(spySlide).not.toHaveBeenCalled()
  154. spyKeydown.calls.reset()
  155. spySlide.calls.reset()
  156. Object.defineProperty(keydown, 'target', {
  157. value: textarea
  158. })
  159. textarea.dispatchEvent(keydown)
  160. expect(spyKeydown).toHaveBeenCalled()
  161. expect(spySlide).not.toHaveBeenCalled()
  162. })
  163. it('should not slide if arrow key is pressed and carousel is sliding', () => {
  164. fixtureEl.innerHTML = '<div></div>'
  165. const carouselEl = fixtureEl.querySelector('div')
  166. const carousel = new Carousel(carouselEl, {})
  167. spyOn(carousel, '_triggerSlideEvent')
  168. carousel._isSliding = true;
  169. ['ArrowLeft', 'ArrowRight'].forEach(key => {
  170. const keydown = createEvent('keydown')
  171. keydown.key = key
  172. carouselEl.dispatchEvent(keydown)
  173. })
  174. expect(carousel._triggerSlideEvent).not.toHaveBeenCalled()
  175. })
  176. it('should wrap around from end to start when wrap option is true', done => {
  177. fixtureEl.innerHTML = [
  178. '<div id="myCarousel" class="carousel slide">',
  179. ' <div class="carousel-inner">',
  180. ' <div id="one" class="carousel-item active"></div>',
  181. ' <div id="two" class="carousel-item"></div>',
  182. ' <div id="three" class="carousel-item">item 3</div>',
  183. ' </div>',
  184. '</div>'
  185. ].join('')
  186. const carouselEl = fixtureEl.querySelector('#myCarousel')
  187. const carousel = new Carousel(carouselEl, { wrap: true })
  188. const getActiveId = () => {
  189. return carouselEl.querySelector('.carousel-item.active').getAttribute('id')
  190. }
  191. carouselEl.addEventListener('slid.bs.carousel', e => {
  192. const activeId = getActiveId()
  193. if (activeId === 'two') {
  194. carousel.next()
  195. return
  196. }
  197. if (activeId === 'three') {
  198. carousel.next()
  199. return
  200. }
  201. if (activeId === 'one') {
  202. // carousel wrapped around and slid from 3rd to 1st slide
  203. expect(activeId).toEqual('one')
  204. expect(e.from + 1).toEqual(3)
  205. done()
  206. }
  207. })
  208. carousel.next()
  209. })
  210. it('should stay at the start when the prev method is called and wrap is false', done => {
  211. fixtureEl.innerHTML = [
  212. '<div id="myCarousel" class="carousel slide">',
  213. ' <div class="carousel-inner">',
  214. ' <div id="one" class="carousel-item active"></div>',
  215. ' <div id="two" class="carousel-item"></div>',
  216. ' <div id="three" class="carousel-item">item 3</div>',
  217. ' </div>',
  218. '</div>'
  219. ].join('')
  220. const carouselEl = fixtureEl.querySelector('#myCarousel')
  221. const firstElement = fixtureEl.querySelector('#one')
  222. const carousel = new Carousel(carouselEl, { wrap: false })
  223. carouselEl.addEventListener('slid.bs.carousel', () => {
  224. throw new Error('carousel slid when it should not have slid')
  225. })
  226. carousel.prev()
  227. setTimeout(() => {
  228. expect(firstElement.classList.contains('active')).toEqual(true)
  229. done()
  230. }, 10)
  231. })
  232. it('should not add touch event listeners if touch = false', () => {
  233. fixtureEl.innerHTML = '<div></div>'
  234. const carouselEl = fixtureEl.querySelector('div')
  235. spyOn(Carousel.prototype, '_addTouchEventListeners')
  236. const carousel = new Carousel(carouselEl, {
  237. touch: false
  238. })
  239. expect(carousel._addTouchEventListeners).not.toHaveBeenCalled()
  240. })
  241. it('should not add touch event listeners if touch supported = false', () => {
  242. fixtureEl.innerHTML = '<div></div>'
  243. const carouselEl = fixtureEl.querySelector('div')
  244. const carousel = new Carousel(carouselEl)
  245. EventHandler.off(carouselEl, '.bs-carousel')
  246. carousel._touchSupported = false
  247. spyOn(carousel, '_addTouchEventListeners')
  248. carousel._addEventListeners()
  249. expect(carousel._addTouchEventListeners).not.toHaveBeenCalled()
  250. })
  251. it('should add touch event listeners by default', () => {
  252. fixtureEl.innerHTML = '<div></div>'
  253. const carouselEl = fixtureEl.querySelector('div')
  254. spyOn(Carousel.prototype, '_addTouchEventListeners')
  255. // Headless browser does not support touch events, so need to fake it
  256. // to test that touch events are add properly.
  257. document.documentElement.ontouchstart = () => {}
  258. const carousel = new Carousel(carouselEl)
  259. expect(carousel._addTouchEventListeners).toHaveBeenCalled()
  260. })
  261. it('should allow swiperight and call _slide (prev) with pointer events', done => {
  262. if (!supportPointerEvent) {
  263. expect().nothing()
  264. done()
  265. return
  266. }
  267. document.documentElement.ontouchstart = () => {}
  268. document.head.appendChild(stylesCarousel)
  269. Simulator.setType('pointer')
  270. fixtureEl.innerHTML = [
  271. '<div class="carousel" data-bs-interval="false">',
  272. ' <div class="carousel-inner">',
  273. ' <div id="item" class="carousel-item">',
  274. ' <img alt="">',
  275. ' </div>',
  276. ' <div class="carousel-item active">',
  277. ' <img alt="">',
  278. ' </div>',
  279. ' </div>',
  280. '</div>'
  281. ].join('')
  282. const carouselEl = fixtureEl.querySelector('.carousel')
  283. const item = fixtureEl.querySelector('#item')
  284. const carousel = new Carousel(carouselEl)
  285. spyOn(carousel, '_slide').and.callThrough()
  286. carouselEl.addEventListener('slid.bs.carousel', event => {
  287. expect(item.classList.contains('active')).toEqual(true)
  288. expect(carousel._slide).toHaveBeenCalledWith('right')
  289. expect(event.direction).toEqual('right')
  290. document.head.removeChild(stylesCarousel)
  291. delete document.documentElement.ontouchstart
  292. done()
  293. })
  294. Simulator.gestures.swipe(carouselEl, {
  295. deltaX: 300,
  296. deltaY: 0
  297. })
  298. })
  299. it('should allow swipeleft and call next with pointer events', done => {
  300. if (!supportPointerEvent) {
  301. expect().nothing()
  302. done()
  303. return
  304. }
  305. document.documentElement.ontouchstart = () => {}
  306. document.head.appendChild(stylesCarousel)
  307. Simulator.setType('pointer')
  308. fixtureEl.innerHTML = [
  309. '<div class="carousel" data-bs-interval="false">',
  310. ' <div class="carousel-inner">',
  311. ' <div id="item" class="carousel-item active">',
  312. ' <img alt="">',
  313. ' </div>',
  314. ' <div class="carousel-item">',
  315. ' <img alt="">',
  316. ' </div>',
  317. ' </div>',
  318. '</div>'
  319. ].join('')
  320. const carouselEl = fixtureEl.querySelector('.carousel')
  321. const item = fixtureEl.querySelector('#item')
  322. const carousel = new Carousel(carouselEl)
  323. spyOn(carousel, '_slide').and.callThrough()
  324. carouselEl.addEventListener('slid.bs.carousel', event => {
  325. expect(item.classList.contains('active')).toEqual(false)
  326. expect(carousel._slide).toHaveBeenCalledWith('left')
  327. expect(event.direction).toEqual('left')
  328. document.head.removeChild(stylesCarousel)
  329. delete document.documentElement.ontouchstart
  330. done()
  331. })
  332. Simulator.gestures.swipe(carouselEl, {
  333. pos: [300, 10],
  334. deltaX: -300,
  335. deltaY: 0
  336. })
  337. })
  338. it('should allow swiperight and call _slide (prev) with touch events', done => {
  339. Simulator.setType('touch')
  340. clearPointerEvents()
  341. document.documentElement.ontouchstart = () => {}
  342. fixtureEl.innerHTML = [
  343. '<div class="carousel" data-bs-interval="false">',
  344. ' <div class="carousel-inner">',
  345. ' <div id="item" class="carousel-item">',
  346. ' <img alt="">',
  347. ' </div>',
  348. ' <div class="carousel-item active">',
  349. ' <img alt="">',
  350. ' </div>',
  351. ' </div>',
  352. '</div>'
  353. ].join('')
  354. const carouselEl = fixtureEl.querySelector('.carousel')
  355. const item = fixtureEl.querySelector('#item')
  356. const carousel = new Carousel(carouselEl)
  357. spyOn(carousel, '_slide').and.callThrough()
  358. carouselEl.addEventListener('slid.bs.carousel', event => {
  359. expect(item.classList.contains('active')).toEqual(true)
  360. expect(carousel._slide).toHaveBeenCalledWith('right')
  361. expect(event.direction).toEqual('right')
  362. delete document.documentElement.ontouchstart
  363. restorePointerEvents()
  364. done()
  365. })
  366. Simulator.gestures.swipe(carouselEl, {
  367. deltaX: 300,
  368. deltaY: 0
  369. })
  370. })
  371. it('should allow swipeleft and call _slide (next) with touch events', done => {
  372. Simulator.setType('touch')
  373. clearPointerEvents()
  374. document.documentElement.ontouchstart = () => {}
  375. fixtureEl.innerHTML = [
  376. '<div class="carousel" data-bs-interval="false">',
  377. ' <div class="carousel-inner">',
  378. ' <div id="item" class="carousel-item active">',
  379. ' <img alt="">',
  380. ' </div>',
  381. ' <div class="carousel-item">',
  382. ' <img alt="">',
  383. ' </div>',
  384. ' </div>',
  385. '</div>'
  386. ].join('')
  387. const carouselEl = fixtureEl.querySelector('.carousel')
  388. const item = fixtureEl.querySelector('#item')
  389. const carousel = new Carousel(carouselEl)
  390. spyOn(carousel, '_slide').and.callThrough()
  391. carouselEl.addEventListener('slid.bs.carousel', event => {
  392. expect(item.classList.contains('active')).toEqual(false)
  393. expect(carousel._slide).toHaveBeenCalledWith('left')
  394. expect(event.direction).toEqual('left')
  395. delete document.documentElement.ontouchstart
  396. restorePointerEvents()
  397. done()
  398. })
  399. Simulator.gestures.swipe(carouselEl, {
  400. pos: [300, 10],
  401. deltaX: -300,
  402. deltaY: 0
  403. })
  404. })
  405. it('should not slide when swiping and carousel is sliding', done => {
  406. Simulator.setType('touch')
  407. clearPointerEvents()
  408. document.documentElement.ontouchstart = () => {}
  409. fixtureEl.innerHTML = [
  410. '<div class="carousel" data-bs-interval="false">',
  411. ' <div class="carousel-inner">',
  412. ' <div id="item" class="carousel-item active">',
  413. ' <img alt="">',
  414. ' </div>',
  415. ' <div class="carousel-item">',
  416. ' <img alt="">',
  417. ' </div>',
  418. ' </div>',
  419. '</div>'
  420. ].join('')
  421. const carouselEl = fixtureEl.querySelector('.carousel')
  422. const carousel = new Carousel(carouselEl)
  423. carousel._isSliding = true
  424. spyOn(carousel, '_triggerSlideEvent')
  425. Simulator.gestures.swipe(carouselEl, {
  426. deltaX: 300,
  427. deltaY: 0
  428. })
  429. Simulator.gestures.swipe(carouselEl, {
  430. pos: [300, 10],
  431. deltaX: -300,
  432. deltaY: 0
  433. })
  434. setTimeout(() => {
  435. expect(carousel._triggerSlideEvent).not.toHaveBeenCalled()
  436. delete document.documentElement.ontouchstart
  437. restorePointerEvents()
  438. done()
  439. }, 300)
  440. })
  441. it('should not allow pinch with touch events', done => {
  442. Simulator.setType('touch')
  443. clearPointerEvents()
  444. document.documentElement.ontouchstart = () => {}
  445. fixtureEl.innerHTML = '<div class="carousel" data-bs-interval="false"></div>'
  446. const carouselEl = fixtureEl.querySelector('.carousel')
  447. const carousel = new Carousel(carouselEl)
  448. Simulator.gestures.swipe(carouselEl, {
  449. pos: [300, 10],
  450. deltaX: -300,
  451. deltaY: 0,
  452. touches: 2
  453. }, () => {
  454. restorePointerEvents()
  455. delete document.documentElement.ontouchstart
  456. expect(carousel.touchDeltaX).toEqual(0)
  457. done()
  458. })
  459. })
  460. it('should call pause method on mouse over with pause equal to hover', done => {
  461. fixtureEl.innerHTML = '<div class="carousel"></div>'
  462. const carouselEl = fixtureEl.querySelector('.carousel')
  463. const carousel = new Carousel(carouselEl)
  464. spyOn(carousel, 'pause')
  465. const mouseOverEvent = createEvent('mouseover')
  466. carouselEl.dispatchEvent(mouseOverEvent)
  467. setTimeout(() => {
  468. expect(carousel.pause).toHaveBeenCalled()
  469. done()
  470. }, 10)
  471. })
  472. it('should call cycle on mouse out with pause equal to hover', done => {
  473. fixtureEl.innerHTML = '<div class="carousel"></div>'
  474. const carouselEl = fixtureEl.querySelector('.carousel')
  475. const carousel = new Carousel(carouselEl)
  476. spyOn(carousel, 'cycle')
  477. const mouseOutEvent = createEvent('mouseout')
  478. carouselEl.dispatchEvent(mouseOutEvent)
  479. setTimeout(() => {
  480. expect(carousel.cycle).toHaveBeenCalled()
  481. done()
  482. }, 10)
  483. })
  484. })
  485. describe('next', () => {
  486. it('should not slide if the carousel is sliding', () => {
  487. fixtureEl.innerHTML = '<div></div>'
  488. const carouselEl = fixtureEl.querySelector('div')
  489. const carousel = new Carousel(carouselEl, {})
  490. spyOn(carousel, '_triggerSlideEvent')
  491. carousel._isSliding = true
  492. carousel.next()
  493. expect(carousel._triggerSlideEvent).not.toHaveBeenCalled()
  494. })
  495. it('should not fire slid when slide is prevented', done => {
  496. fixtureEl.innerHTML = '<div></div>'
  497. const carouselEl = fixtureEl.querySelector('div')
  498. const carousel = new Carousel(carouselEl, {})
  499. let slidEvent = false
  500. const doneTest = () => {
  501. setTimeout(() => {
  502. expect(slidEvent).toEqual(false)
  503. done()
  504. }, 20)
  505. }
  506. carouselEl.addEventListener('slide.bs.carousel', e => {
  507. e.preventDefault()
  508. doneTest()
  509. })
  510. carouselEl.addEventListener('slid.bs.carousel', () => {
  511. slidEvent = true
  512. })
  513. carousel.next()
  514. })
  515. it('should fire slide event with: direction, relatedTarget, from and to', done => {
  516. fixtureEl.innerHTML = [
  517. '<div id="myCarousel" class="carousel slide">',
  518. ' <div class="carousel-inner">',
  519. ' <div class="carousel-item active">item 1</div>',
  520. ' <div class="carousel-item">item 2</div>',
  521. ' <div class="carousel-item">item 3</div>',
  522. ' </div>',
  523. '</div>'
  524. ].join('')
  525. const carouselEl = fixtureEl.querySelector('#myCarousel')
  526. const carousel = new Carousel(carouselEl, {})
  527. const onSlide = e => {
  528. expect(e.direction).toEqual('left')
  529. expect(e.relatedTarget.classList.contains('carousel-item')).toEqual(true)
  530. expect(e.from).toEqual(0)
  531. expect(e.to).toEqual(1)
  532. carouselEl.removeEventListener('slide.bs.carousel', onSlide)
  533. carouselEl.addEventListener('slide.bs.carousel', onSlide2)
  534. carousel.prev()
  535. }
  536. const onSlide2 = e => {
  537. expect(e.direction).toEqual('right')
  538. done()
  539. }
  540. carouselEl.addEventListener('slide.bs.carousel', onSlide)
  541. carousel.next()
  542. })
  543. it('should fire slid event with: direction, relatedTarget, from and to', done => {
  544. fixtureEl.innerHTML = [
  545. '<div id="myCarousel" class="carousel slide">',
  546. ' <div class="carousel-inner">',
  547. ' <div class="carousel-item active">item 1</div>',
  548. ' <div class="carousel-item">item 2</div>',
  549. ' <div class="carousel-item">item 3</div>',
  550. ' </div>',
  551. '</div>'
  552. ].join('')
  553. const carouselEl = fixtureEl.querySelector('#myCarousel')
  554. const carousel = new Carousel(carouselEl, {})
  555. const onSlid = e => {
  556. expect(e.direction).toEqual('left')
  557. expect(e.relatedTarget.classList.contains('carousel-item')).toEqual(true)
  558. expect(e.from).toEqual(0)
  559. expect(e.to).toEqual(1)
  560. carouselEl.removeEventListener('slid.bs.carousel', onSlid)
  561. carouselEl.addEventListener('slid.bs.carousel', onSlid2)
  562. carousel.prev()
  563. }
  564. const onSlid2 = e => {
  565. expect(e.direction).toEqual('right')
  566. done()
  567. }
  568. carouselEl.addEventListener('slid.bs.carousel', onSlid)
  569. carousel.next()
  570. })
  571. it('should update the active element to the next item before sliding', () => {
  572. fixtureEl.innerHTML = [
  573. '<div id="myCarousel" class="carousel slide">',
  574. ' <div class="carousel-inner">',
  575. ' <div class="carousel-item active">item 1</div>',
  576. ' <div id="secondItem" class="carousel-item">item 2</div>',
  577. ' <div class="carousel-item">item 3</div>',
  578. ' </div>',
  579. '</div>'
  580. ].join('')
  581. const carouselEl = fixtureEl.querySelector('#myCarousel')
  582. const secondItemEl = fixtureEl.querySelector('#secondItem')
  583. const carousel = new Carousel(carouselEl)
  584. carousel.next()
  585. expect(carousel._activeElement).toEqual(secondItemEl)
  586. })
  587. it('should update indicators if present', done => {
  588. fixtureEl.innerHTML = [
  589. '<div id="myCarousel" class="carousel slide">',
  590. ' <div class="carousel-indicators">',
  591. ' <button type="button" id="firstIndicator" data-bs-target="myCarousel" data-bs-slide-to="0" class="active" aria-current="true" aria-label="Slide 1"></button>',
  592. ' <button type="button" id="secondIndicator" data-bs-target="myCarousel" data-bs-slide-to="1" aria-label="Slide 2"></button>',
  593. ' <button type="button" data-bs-target="myCarousel" data-bs-slide-to="2" aria-label="Slide 3"></button>',
  594. ' </div>',
  595. ' <div class="carousel-inner">',
  596. ' <div class="carousel-item active">item 1</div>',
  597. ' <div class="carousel-item" data-bs-interval="7">item 2</div>',
  598. ' <div class="carousel-item">item 3</div>',
  599. ' </div>',
  600. '</div>'
  601. ].join('')
  602. const carouselEl = fixtureEl.querySelector('#myCarousel')
  603. const firstIndicator = fixtureEl.querySelector('#firstIndicator')
  604. const secondIndicator = fixtureEl.querySelector('#secondIndicator')
  605. const carousel = new Carousel(carouselEl)
  606. carouselEl.addEventListener('slid.bs.carousel', () => {
  607. expect(firstIndicator.classList.contains('active')).toEqual(false)
  608. expect(firstIndicator.hasAttribute('aria-current')).toEqual(false)
  609. expect(secondIndicator.classList.contains('active')).toEqual(true)
  610. expect(secondIndicator.getAttribute('aria-current')).toEqual('true')
  611. done()
  612. })
  613. carousel.next()
  614. })
  615. it('should call next()/prev() instance methods when clicking the respective direction buttons', () => {
  616. fixtureEl.innerHTML = [
  617. '<div id="carousel" class="carousel slide">',
  618. ' <div class="carousel-inner">',
  619. ' <div class="carousel-item active">item 1</div>',
  620. ' <div class="carousel-item">item 2</div>',
  621. ' <div class="carousel-item">item 3</div>',
  622. ' </div>',
  623. ' <button class="carousel-control-prev" type="button" data-bs-target="#carousel" data-bs-slide="prev"></button>',
  624. ' <button class="carousel-control-next" type="button" data-bs-target="#carousel" data-bs-slide="next"></button>',
  625. '</div>'
  626. ].join('')
  627. const carouselEl = fixtureEl.querySelector('#carousel')
  628. const prevBtnEl = fixtureEl.querySelector('.carousel-control-prev')
  629. const nextBtnEl = fixtureEl.querySelector('.carousel-control-next')
  630. const carousel = new Carousel(carouselEl)
  631. const nextSpy = spyOn(carousel, 'next')
  632. const prevSpy = spyOn(carousel, 'prev')
  633. nextBtnEl.click()
  634. prevBtnEl.click()
  635. expect(nextSpy).toHaveBeenCalled()
  636. expect(prevSpy).toHaveBeenCalled()
  637. })
  638. })
  639. describe('nextWhenVisible', () => {
  640. it('should not call next when the page is not visible', () => {
  641. fixtureEl.innerHTML = [
  642. '<div style="display: none;">',
  643. ' <div class="carousel" data-bs-interval="false"></div>',
  644. '</div>'
  645. ].join('')
  646. const carouselEl = fixtureEl.querySelector('.carousel')
  647. const carousel = new Carousel(carouselEl)
  648. spyOn(carousel, 'next')
  649. carousel.nextWhenVisible()
  650. expect(carousel.next).not.toHaveBeenCalled()
  651. })
  652. })
  653. describe('prev', () => {
  654. it('should not slide if the carousel is sliding', () => {
  655. fixtureEl.innerHTML = '<div></div>'
  656. const carouselEl = fixtureEl.querySelector('div')
  657. const carousel = new Carousel(carouselEl, {})
  658. spyOn(carousel, '_triggerSlideEvent')
  659. carousel._isSliding = true
  660. carousel.prev()
  661. expect(carousel._triggerSlideEvent).not.toHaveBeenCalled()
  662. })
  663. })
  664. describe('pause', () => {
  665. it('should call cycle if the carousel have carousel-item-next and carousel-item-prev class', () => {
  666. fixtureEl.innerHTML = [
  667. '<div id="myCarousel" class="carousel slide">',
  668. ' <div class="carousel-inner">',
  669. ' <div class="carousel-item active">item 1</div>',
  670. ' <div class="carousel-item carousel-item-next">item 2</div>',
  671. ' <div class="carousel-item">item 3</div>',
  672. ' </div>',
  673. ' <div class="carousel-control-prev"></div>',
  674. ' <div class="carousel-control-next"></div>',
  675. '</div>'
  676. ].join('')
  677. const carouselEl = fixtureEl.querySelector('#myCarousel')
  678. const carousel = new Carousel(carouselEl)
  679. spyOn(carousel, 'cycle')
  680. spyOn(window, 'clearInterval')
  681. carousel.pause()
  682. expect(carousel.cycle).toHaveBeenCalledWith(true)
  683. expect(window.clearInterval).toHaveBeenCalled()
  684. expect(carousel._isPaused).toEqual(true)
  685. })
  686. it('should not call cycle if nothing is in transition', () => {
  687. fixtureEl.innerHTML = [
  688. '<div id="myCarousel" class="carousel slide">',
  689. ' <div class="carousel-inner">',
  690. ' <div class="carousel-item active">item 1</div>',
  691. ' <div class="carousel-item">item 2</div>',
  692. ' <div class="carousel-item">item 3</div>',
  693. ' </div>',
  694. ' <div class="carousel-control-prev"></div>',
  695. ' <div class="carousel-control-next"></div>',
  696. '</div>'
  697. ].join('')
  698. const carouselEl = fixtureEl.querySelector('#myCarousel')
  699. const carousel = new Carousel(carouselEl)
  700. spyOn(carousel, 'cycle')
  701. spyOn(window, 'clearInterval')
  702. carousel.pause()
  703. expect(carousel.cycle).not.toHaveBeenCalled()
  704. expect(window.clearInterval).toHaveBeenCalled()
  705. expect(carousel._isPaused).toEqual(true)
  706. })
  707. it('should not set is paused at true if an event is passed', () => {
  708. fixtureEl.innerHTML = [
  709. '<div id="myCarousel" class="carousel slide">',
  710. ' <div class="carousel-inner">',
  711. ' <div class="carousel-item active">item 1</div>',
  712. ' <div class="carousel-item">item 2</div>',
  713. ' <div class="carousel-item">item 3</div>',
  714. ' </div>',
  715. ' <div class="carousel-control-prev"></div>',
  716. ' <div class="carousel-control-next"></div>',
  717. '</div>'
  718. ].join('')
  719. const carouselEl = fixtureEl.querySelector('#myCarousel')
  720. const carousel = new Carousel(carouselEl)
  721. const event = createEvent('mouseenter')
  722. spyOn(window, 'clearInterval')
  723. carousel.pause(event)
  724. expect(window.clearInterval).toHaveBeenCalled()
  725. expect(carousel._isPaused).toEqual(false)
  726. })
  727. })
  728. describe('cycle', () => {
  729. it('should set an interval', () => {
  730. fixtureEl.innerHTML = [
  731. '<div id="myCarousel" class="carousel slide">',
  732. ' <div class="carousel-inner">',
  733. ' <div class="carousel-item active">item 1</div>',
  734. ' <div class="carousel-item">item 2</div>',
  735. ' <div class="carousel-item">item 3</div>',
  736. ' </div>',
  737. ' <div class="carousel-control-prev"></div>',
  738. ' <div class="carousel-control-next"></div>',
  739. '</div>'
  740. ].join('')
  741. const carouselEl = fixtureEl.querySelector('#myCarousel')
  742. const carousel = new Carousel(carouselEl)
  743. spyOn(window, 'setInterval').and.callThrough()
  744. carousel.cycle()
  745. expect(window.setInterval).toHaveBeenCalled()
  746. })
  747. it('should not set interval if the carousel is paused', () => {
  748. fixtureEl.innerHTML = [
  749. '<div id="myCarousel" class="carousel slide">',
  750. ' <div class="carousel-inner">',
  751. ' <div class="carousel-item active">item 1</div>',
  752. ' <div class="carousel-item">item 2</div>',
  753. ' <div class="carousel-item">item 3</div>',
  754. ' </div>',
  755. ' <div class="carousel-control-prev"></div>',
  756. ' <div class="carousel-control-next"></div>',
  757. '</div>'
  758. ].join('')
  759. const carouselEl = fixtureEl.querySelector('#myCarousel')
  760. const carousel = new Carousel(carouselEl)
  761. spyOn(window, 'setInterval').and.callThrough()
  762. carousel._isPaused = true
  763. carousel.cycle(true)
  764. expect(window.setInterval).not.toHaveBeenCalled()
  765. })
  766. it('should clear interval if there is one', () => {
  767. fixtureEl.innerHTML = [
  768. '<div id="myCarousel" class="carousel slide">',
  769. ' <div class="carousel-inner">',
  770. ' <div class="carousel-item active">item 1</div>',
  771. ' <div class="carousel-item">item 2</div>',
  772. ' <div class="carousel-item">item 3</div>',
  773. ' </div>',
  774. ' <div class="carousel-control-prev"></div>',
  775. ' <div class="carousel-control-next"></div>',
  776. '</div>'
  777. ].join('')
  778. const carouselEl = fixtureEl.querySelector('#myCarousel')
  779. const carousel = new Carousel(carouselEl)
  780. carousel._interval = setInterval(() => {}, 10)
  781. spyOn(window, 'setInterval').and.callThrough()
  782. spyOn(window, 'clearInterval').and.callThrough()
  783. carousel.cycle()
  784. expect(window.setInterval).toHaveBeenCalled()
  785. expect(window.clearInterval).toHaveBeenCalled()
  786. })
  787. it('should get interval from data attribute on the active item element', () => {
  788. fixtureEl.innerHTML = [
  789. '<div id="myCarousel" class="carousel slide">',
  790. ' <div class="carousel-inner">',
  791. ' <div class="carousel-item active" data-bs-interval="7">item 1</div>',
  792. ' <div id="secondItem" class="carousel-item" data-bs-interval="9385">item 2</div>',
  793. ' <div class="carousel-item">item 3</div>',
  794. ' </div>',
  795. '</div>'
  796. ].join('')
  797. const carouselEl = fixtureEl.querySelector('#myCarousel')
  798. const secondItemEl = fixtureEl.querySelector('#secondItem')
  799. const carousel = new Carousel(carouselEl, {
  800. interval: 1814
  801. })
  802. expect(carousel._config.interval).toEqual(1814)
  803. carousel.cycle()
  804. expect(carousel._config.interval).toEqual(7)
  805. carousel._activeElement = secondItemEl
  806. carousel.cycle()
  807. expect(carousel._config.interval).toEqual(9385)
  808. })
  809. })
  810. describe('to', () => {
  811. it('should go directly to the provided index', done => {
  812. fixtureEl.innerHTML = [
  813. '<div id="myCarousel" class="carousel slide">',
  814. ' <div class="carousel-inner">',
  815. ' <div id="item1" class="carousel-item active">item 1</div>',
  816. ' <div class="carousel-item">item 2</div>',
  817. ' <div id="item3" class="carousel-item">item 3</div>',
  818. ' </div>',
  819. '</div>'
  820. ].join('')
  821. const carouselEl = fixtureEl.querySelector('#myCarousel')
  822. const carousel = new Carousel(carouselEl, {})
  823. expect(fixtureEl.querySelector('.active')).toEqual(fixtureEl.querySelector('#item1'))
  824. carousel.to(2)
  825. carouselEl.addEventListener('slid.bs.carousel', () => {
  826. expect(fixtureEl.querySelector('.active')).toEqual(fixtureEl.querySelector('#item3'))
  827. done()
  828. })
  829. })
  830. it('should return to a previous slide if the provided index is lower than the current', done => {
  831. fixtureEl.innerHTML = [
  832. '<div id="myCarousel" class="carousel slide">',
  833. ' <div class="carousel-inner">',
  834. ' <div class="carousel-item">item 1</div>',
  835. ' <div id="item2" class="carousel-item">item 2</div>',
  836. ' <div id="item3" class="carousel-item active">item 3</div>',
  837. ' </div>',
  838. '</div>'
  839. ].join('')
  840. const carouselEl = fixtureEl.querySelector('#myCarousel')
  841. const carousel = new Carousel(carouselEl, {})
  842. expect(fixtureEl.querySelector('.active')).toEqual(fixtureEl.querySelector('#item3'))
  843. carousel.to(1)
  844. carouselEl.addEventListener('slid.bs.carousel', () => {
  845. expect(fixtureEl.querySelector('.active')).toEqual(fixtureEl.querySelector('#item2'))
  846. done()
  847. })
  848. })
  849. it('should do nothing if a wrong index is provided', () => {
  850. fixtureEl.innerHTML = [
  851. '<div id="myCarousel" class="carousel slide">',
  852. ' <div class="carousel-inner">',
  853. ' <div class="carousel-item active">item 1</div>',
  854. ' <div class="carousel-item" data-bs-interval="7">item 2</div>',
  855. ' <div class="carousel-item">item 3</div>',
  856. ' </div>',
  857. '</div>'
  858. ].join('')
  859. const carouselEl = fixtureEl.querySelector('#myCarousel')
  860. const carousel = new Carousel(carouselEl, {})
  861. const spy = spyOn(carousel, '_slide')
  862. carousel.to(25)
  863. expect(spy).not.toHaveBeenCalled()
  864. spy.calls.reset()
  865. carousel.to(-5)
  866. expect(spy).not.toHaveBeenCalled()
  867. })
  868. it('should call pause and cycle is the provided is the same compare to the current one', () => {
  869. fixtureEl.innerHTML = [
  870. '<div id="myCarousel" class="carousel slide">',
  871. ' <div class="carousel-inner">',
  872. ' <div class="carousel-item active">item 1</div>',
  873. ' <div class="carousel-item" data-bs-interval="7">item 2</div>',
  874. ' <div class="carousel-item">item 3</div>',
  875. ' </div>',
  876. '</div>'
  877. ].join('')
  878. const carouselEl = fixtureEl.querySelector('#myCarousel')
  879. const carousel = new Carousel(carouselEl, {})
  880. spyOn(carousel, '_slide')
  881. spyOn(carousel, 'pause')
  882. spyOn(carousel, 'cycle')
  883. carousel.to(0)
  884. expect(carousel._slide).not.toHaveBeenCalled()
  885. expect(carousel.pause).toHaveBeenCalled()
  886. expect(carousel.cycle).toHaveBeenCalled()
  887. })
  888. it('should wait before performing to if a slide is sliding', done => {
  889. fixtureEl.innerHTML = [
  890. '<div id="myCarousel" class="carousel slide">',
  891. ' <div class="carousel-inner">',
  892. ' <div class="carousel-item active">item 1</div>',
  893. ' <div class="carousel-item" data-bs-interval="7">item 2</div>',
  894. ' <div class="carousel-item">item 3</div>',
  895. ' </div>',
  896. '</div>'
  897. ].join('')
  898. const carouselEl = fixtureEl.querySelector('#myCarousel')
  899. const carousel = new Carousel(carouselEl, {})
  900. spyOn(EventHandler, 'one').and.callThrough()
  901. spyOn(carousel, '_slide')
  902. carousel._isSliding = true
  903. carousel.to(1)
  904. expect(carousel._slide).not.toHaveBeenCalled()
  905. expect(EventHandler.one).toHaveBeenCalled()
  906. spyOn(carousel, 'to')
  907. EventHandler.trigger(carouselEl, 'slid.bs.carousel')
  908. setTimeout(() => {
  909. expect(carousel.to).toHaveBeenCalledWith(1)
  910. done()
  911. })
  912. })
  913. })
  914. describe('rtl function', () => {
  915. it('"_directionToOrder" and "_orderToDirection" must return the right results', () => {
  916. fixtureEl.innerHTML = '<div></div>'
  917. const carouselEl = fixtureEl.querySelector('div')
  918. const carousel = new Carousel(carouselEl, {})
  919. expect(carousel._directionToOrder('left')).toEqual('next')
  920. expect(carousel._directionToOrder('prev')).toEqual('prev')
  921. expect(carousel._directionToOrder('right')).toEqual('prev')
  922. expect(carousel._directionToOrder('next')).toEqual('next')
  923. expect(carousel._orderToDirection('next')).toEqual('left')
  924. expect(carousel._orderToDirection('prev')).toEqual('right')
  925. })
  926. it('"_directionToOrder" and "_orderToDirection" must return the right results when rtl=true', () => {
  927. document.documentElement.dir = 'rtl'
  928. fixtureEl.innerHTML = '<div></div>'
  929. const carouselEl = fixtureEl.querySelector('div')
  930. const carousel = new Carousel(carouselEl, {})
  931. expect(util.isRTL()).toEqual(true, 'rtl has to be true')
  932. expect(carousel._directionToOrder('left')).toEqual('prev')
  933. expect(carousel._directionToOrder('prev')).toEqual('prev')
  934. expect(carousel._directionToOrder('right')).toEqual('next')
  935. expect(carousel._directionToOrder('next')).toEqual('next')
  936. expect(carousel._orderToDirection('next')).toEqual('right')
  937. expect(carousel._orderToDirection('prev')).toEqual('left')
  938. document.documentElement.dir = 'ltl'
  939. })
  940. it('"_slide" has to call _directionToOrder and "_orderToDirection"', () => {
  941. fixtureEl.innerHTML = '<div></div>'
  942. const carouselEl = fixtureEl.querySelector('div')
  943. const carousel = new Carousel(carouselEl, {})
  944. const spy = spyOn(carousel, '_directionToOrder').and.callThrough()
  945. const spy2 = spyOn(carousel, '_orderToDirection').and.callThrough()
  946. carousel._slide('left')
  947. expect(spy).toHaveBeenCalledWith('left')
  948. expect(spy2).toHaveBeenCalledWith('next')
  949. carousel._slide('right')
  950. expect(spy).toHaveBeenCalledWith('right')
  951. expect(spy2).toHaveBeenCalledWith('prev')
  952. })
  953. it('"_slide" has to call "_directionToOrder" and "_orderToDirection" when rtl=true', () => {
  954. document.documentElement.dir = 'rtl'
  955. fixtureEl.innerHTML = '<div></div>'
  956. const carouselEl = fixtureEl.querySelector('div')
  957. const carousel = new Carousel(carouselEl, {})
  958. const spy = spyOn(carousel, '_directionToOrder').and.callThrough()
  959. const spy2 = spyOn(carousel, '_orderToDirection').and.callThrough()
  960. carousel._slide('left')
  961. expect(spy).toHaveBeenCalledWith('left')
  962. expect(spy2).toHaveBeenCalledWith('prev')
  963. carousel._slide('right')
  964. expect(spy).toHaveBeenCalledWith('right')
  965. expect(spy2).toHaveBeenCalledWith('next')
  966. document.documentElement.dir = 'ltl'
  967. })
  968. })
  969. describe('dispose', () => {
  970. it('should destroy a carousel', () => {
  971. fixtureEl.innerHTML = [
  972. '<div id="myCarousel" class="carousel slide">',
  973. ' <div class="carousel-inner">',
  974. ' <div class="carousel-item active">item 1</div>',
  975. ' <div class="carousel-item" data-bs-interval="7">item 2</div>',
  976. ' <div class="carousel-item">item 3</div>',
  977. ' </div>',
  978. '</div>'
  979. ].join('')
  980. const carouselEl = fixtureEl.querySelector('#myCarousel')
  981. const addEventSpy = spyOn(carouselEl, 'addEventListener').and.callThrough()
  982. const removeEventSpy = spyOn(carouselEl, 'removeEventListener').and.callThrough()
  983. // Headless browser does not support touch events, so need to fake it
  984. // to test that touch events are add/removed properly.
  985. document.documentElement.ontouchstart = () => {}
  986. const carousel = new Carousel(carouselEl)
  987. const expectedArgs = [
  988. ['keydown', jasmine.any(Function), jasmine.any(Boolean)],
  989. ['mouseover', jasmine.any(Function), jasmine.any(Boolean)],
  990. ['mouseout', jasmine.any(Function), jasmine.any(Boolean)],
  991. ...(carousel._pointerEvent ?
  992. [
  993. ['pointerdown', jasmine.any(Function), jasmine.any(Boolean)],
  994. ['pointerup', jasmine.any(Function), jasmine.any(Boolean)]
  995. ] :
  996. [
  997. ['touchstart', jasmine.any(Function), jasmine.any(Boolean)],
  998. ['touchmove', jasmine.any(Function), jasmine.any(Boolean)],
  999. ['touchend', jasmine.any(Function), jasmine.any(Boolean)]
  1000. ])
  1001. ]
  1002. expect(addEventSpy.calls.allArgs()).toEqual(expectedArgs)
  1003. carousel.dispose()
  1004. expect(removeEventSpy.calls.allArgs()).toEqual(expectedArgs)
  1005. delete document.documentElement.ontouchstart
  1006. })
  1007. })
  1008. describe('getInstance', () => {
  1009. it('should return carousel instance', () => {
  1010. fixtureEl.innerHTML = '<div></div>'
  1011. const div = fixtureEl.querySelector('div')
  1012. const carousel = new Carousel(div)
  1013. expect(Carousel.getInstance(div)).toEqual(carousel)
  1014. expect(Carousel.getInstance(div)).toBeInstanceOf(Carousel)
  1015. })
  1016. it('should return null when there is no carousel instance', () => {
  1017. fixtureEl.innerHTML = '<div></div>'
  1018. const div = fixtureEl.querySelector('div')
  1019. expect(Carousel.getInstance(div)).toEqual(null)
  1020. })
  1021. })
  1022. describe('getOrCreateInstance', () => {
  1023. it('should return carousel instance', () => {
  1024. fixtureEl.innerHTML = '<div></div>'
  1025. const div = fixtureEl.querySelector('div')
  1026. const carousel = new Carousel(div)
  1027. expect(Carousel.getOrCreateInstance(div)).toEqual(carousel)
  1028. expect(Carousel.getInstance(div)).toEqual(Carousel.getOrCreateInstance(div, {}))
  1029. expect(Carousel.getOrCreateInstance(div)).toBeInstanceOf(Carousel)
  1030. })
  1031. it('should return new instance when there is no carousel instance', () => {
  1032. fixtureEl.innerHTML = '<div></div>'
  1033. const div = fixtureEl.querySelector('div')
  1034. expect(Carousel.getInstance(div)).toEqual(null)
  1035. expect(Carousel.getOrCreateInstance(div)).toBeInstanceOf(Carousel)
  1036. })
  1037. it('should return new instance when there is no carousel instance with given configuration', () => {
  1038. fixtureEl.innerHTML = '<div></div>'
  1039. const div = fixtureEl.querySelector('div')
  1040. expect(Carousel.getInstance(div)).toEqual(null)
  1041. const carousel = Carousel.getOrCreateInstance(div, {
  1042. interval: 1
  1043. })
  1044. expect(carousel).toBeInstanceOf(Carousel)
  1045. expect(carousel._config.interval).toEqual(1)
  1046. })
  1047. it('should return the instance when exists without given configuration', () => {
  1048. fixtureEl.innerHTML = '<div></div>'
  1049. const div = fixtureEl.querySelector('div')
  1050. const carousel = new Carousel(div, {
  1051. interval: 1
  1052. })
  1053. expect(Carousel.getInstance(div)).toEqual(carousel)
  1054. const carousel2 = Carousel.getOrCreateInstance(div, {
  1055. interval: 2
  1056. })
  1057. expect(carousel).toBeInstanceOf(Carousel)
  1058. expect(carousel2).toEqual(carousel)
  1059. expect(carousel2._config.interval).toEqual(1)
  1060. })
  1061. })
  1062. describe('jQueryInterface', () => {
  1063. it('should create a carousel', () => {
  1064. fixtureEl.innerHTML = '<div></div>'
  1065. const div = fixtureEl.querySelector('div')
  1066. jQueryMock.fn.carousel = Carousel.jQueryInterface
  1067. jQueryMock.elements = [div]
  1068. jQueryMock.fn.carousel.call(jQueryMock)
  1069. expect(Carousel.getInstance(div)).not.toBeNull()
  1070. })
  1071. it('should not re create a carousel', () => {
  1072. fixtureEl.innerHTML = '<div></div>'
  1073. const div = fixtureEl.querySelector('div')
  1074. const carousel = new Carousel(div)
  1075. jQueryMock.fn.carousel = Carousel.jQueryInterface
  1076. jQueryMock.elements = [div]
  1077. jQueryMock.fn.carousel.call(jQueryMock)
  1078. expect(Carousel.getInstance(div)).toEqual(carousel)
  1079. })
  1080. it('should call to if the config is a number', () => {
  1081. fixtureEl.innerHTML = '<div></div>'
  1082. const div = fixtureEl.querySelector('div')
  1083. const carousel = new Carousel(div)
  1084. const slideTo = 2
  1085. spyOn(carousel, 'to')
  1086. jQueryMock.fn.carousel = Carousel.jQueryInterface
  1087. jQueryMock.elements = [div]
  1088. jQueryMock.fn.carousel.call(jQueryMock, slideTo)
  1089. expect(carousel.to).toHaveBeenCalledWith(slideTo)
  1090. })
  1091. it('should throw error on undefined method', () => {
  1092. fixtureEl.innerHTML = '<div></div>'
  1093. const div = fixtureEl.querySelector('div')
  1094. const action = 'undefinedMethod'
  1095. jQueryMock.fn.carousel = Carousel.jQueryInterface
  1096. jQueryMock.elements = [div]
  1097. expect(() => {
  1098. jQueryMock.fn.carousel.call(jQueryMock, action)
  1099. }).toThrowError(TypeError, `No method named "${action}"`)
  1100. })
  1101. })
  1102. describe('data-api', () => {
  1103. it('should init carousels with data-bs-ride="carousel" on load', () => {
  1104. fixtureEl.innerHTML = '<div data-bs-ride="carousel"></div>'
  1105. const carouselEl = fixtureEl.querySelector('div')
  1106. const loadEvent = createEvent('load')
  1107. window.dispatchEvent(loadEvent)
  1108. expect(Carousel.getInstance(carouselEl)).not.toBeNull()
  1109. })
  1110. it('should create carousel and go to the next slide on click (with real button controls)', done => {
  1111. fixtureEl.innerHTML = [
  1112. '<div id="myCarousel" class="carousel slide">',
  1113. ' <div class="carousel-inner">',
  1114. ' <div class="carousel-item active">item 1</div>',
  1115. ' <div id="item2" class="carousel-item">item 2</div>',
  1116. ' <div class="carousel-item">item 3</div>',
  1117. ' </div>',
  1118. ' <button class="carousel-control-prev" data-bs-target="#myCarousel" type="button" data-bs-slide="prev"></button>',
  1119. ' <button id="next" class="carousel-control-next" data-bs-target="#myCarousel" type="button" data-bs-slide="next"></div>',
  1120. '</div>'
  1121. ].join('')
  1122. const next = fixtureEl.querySelector('#next')
  1123. const item2 = fixtureEl.querySelector('#item2')
  1124. next.click()
  1125. setTimeout(() => {
  1126. expect(item2.classList.contains('active')).toEqual(true)
  1127. done()
  1128. }, 10)
  1129. })
  1130. it('should create carousel and go to the next slide on click (using links as controls)', done => {
  1131. fixtureEl.innerHTML = [
  1132. '<div id="myCarousel" class="carousel slide">',
  1133. ' <div class="carousel-inner">',
  1134. ' <div class="carousel-item active">item 1</div>',
  1135. ' <div id="item2" class="carousel-item">item 2</div>',
  1136. ' <div class="carousel-item">item 3</div>',
  1137. ' </div>',
  1138. ' <a class="carousel-control-prev" href="#myCarousel" role="button" data-bs-slide="prev"></button>',
  1139. ' <a id="next" class="carousel-control-next" href="#myCarousel" role="button" data-bs-slide="next"></div>',
  1140. '</div>'
  1141. ].join('')
  1142. const next = fixtureEl.querySelector('#next')
  1143. const item2 = fixtureEl.querySelector('#item2')
  1144. next.click()
  1145. setTimeout(() => {
  1146. expect(item2.classList.contains('active')).toEqual(true)
  1147. done()
  1148. }, 10)
  1149. })
  1150. it('should create carousel and go to the next slide on click with data-bs-slide-to', done => {
  1151. fixtureEl.innerHTML = [
  1152. '<div id="myCarousel" class="carousel slide">',
  1153. ' <div class="carousel-inner">',
  1154. ' <div class="carousel-item active">item 1</div>',
  1155. ' <div id="item2" class="carousel-item">item 2</div>',
  1156. ' <div class="carousel-item">item 3</div>',
  1157. ' </div>',
  1158. ' <div id="next" data-bs-target="#myCarousel" data-bs-slide-to="1"></div>',
  1159. '</div>'
  1160. ].join('')
  1161. const next = fixtureEl.querySelector('#next')
  1162. const item2 = fixtureEl.querySelector('#item2')
  1163. next.click()
  1164. setTimeout(() => {
  1165. expect(item2.classList.contains('active')).toEqual(true)
  1166. done()
  1167. }, 10)
  1168. })
  1169. it('should do nothing if no selector on click on arrows', () => {
  1170. fixtureEl.innerHTML = [
  1171. '<div id="myCarousel" class="carousel slide">',
  1172. ' <div class="carousel-inner">',
  1173. ' <div class="carousel-item active">item 1</div>',
  1174. ' <div class="carousel-item">item 2</div>',
  1175. ' <div class="carousel-item">item 3</div>',
  1176. ' </div>',
  1177. ' <button class="carousel-control-prev" data-bs-target="#myCarousel" type="button" data-bs-slide="prev"></button>',
  1178. ' <button id="next" class="carousel-control-next" type="button" data-bs-slide="next"></button>',
  1179. '</div>'
  1180. ].join('')
  1181. const next = fixtureEl.querySelector('#next')
  1182. next.click()
  1183. expect().nothing()
  1184. })
  1185. it('should do nothing if no carousel class on click on arrows', () => {
  1186. fixtureEl.innerHTML = [
  1187. '<div id="myCarousel" class="slide">',
  1188. ' <div class="carousel-inner">',
  1189. ' <div class="carousel-item active">item 1</div>',
  1190. ' <div id="item2" class="carousel-item">item 2</div>',
  1191. ' <div class="carousel-item">item 3</div>',
  1192. ' </div>',
  1193. ' <button class="carousel-control-prev" data-bs-target="#myCarousel" type="button" data-bs-slide="prev"></div>',
  1194. ' <button id="next" class="carousel-control-next" data-bs-target="#myCarousel" type="button" data-bs-slide="next"></div>',
  1195. '</div>'
  1196. ].join('')
  1197. const next = fixtureEl.querySelector('#next')
  1198. next.click()
  1199. expect().nothing()
  1200. })
  1201. })
  1202. })