carousel.spec.js 51 KB

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