const Default = {
interval: 5000,
keyboard: true,
- slide: false,
pause: 'hover',
- wrap: true,
- touch: true
+ ride: false,
+ touch: true,
+ wrap: true
}
const DefaultType = {
interval: '(number|boolean)',
keyboard: 'boolean',
- slide: '(boolean|string)',
+ ride: '(boolean|string)',
pause: '(string|boolean)',
- wrap: 'boolean',
- touch: 'boolean'
+ touch: 'boolean',
+ wrap: 'boolean'
}
/**
this._interval = null
this._activeElement = null
- this._stayPaused = false
this._isSliding = false
this.touchTimeout = null
this._swipeHelper = null
this._indicatorsElement = SelectorEngine.findOne(SELECTOR_INDICATORS, this._element)
this._addEventListeners()
+
+ if (this._config.ride === CLASS_NAME_CAROUSEL) {
+ this.cycle()
+ }
}
// Getters
this._slide(ORDER_PREV)
}
- pause(event) {
- if (!event) {
- this._stayPaused = true
- }
-
+ pause() {
if (this._isSliding) {
triggerTransitionEnd(this._element)
- this.cycle(true)
}
this._clearInterval()
}
- cycle(event) {
- if (!event) {
- this._stayPaused = false
- }
-
+ cycle() {
this._clearInterval()
- if (this._config.interval && !this._stayPaused) {
- this._updateInterval()
+ this._updateInterval()
- this._interval = setInterval(() => this.nextWhenVisible(), this._config.interval)
+ this._interval = setInterval(() => this.nextWhenVisible(), this._config.interval)
+ }
+
+ _maybeEnableCycle() {
+ if (!this._config.ride) {
+ return
+ }
+
+ if (this._isSliding) {
+ EventHandler.one(this._element, EVENT_SLID, () => this.cycle())
+ return
}
+
+ this.cycle()
}
to(index) {
const activeIndex = this._getItemIndex(this._getActive())
if (activeIndex === index) {
- this.pause()
- this.cycle()
return
}
}
if (this._config.pause === 'hover') {
- EventHandler.on(this._element, EVENT_MOUSEENTER, event => this.pause(event))
- EventHandler.on(this._element, EVENT_MOUSELEAVE, event => this.cycle(event))
+ EventHandler.on(this._element, EVENT_MOUSEENTER, () => this.pause())
+ EventHandler.on(this._element, EVENT_MOUSELEAVE, () => this._maybeEnableCycle())
}
if (this._config.touch && Swipe.isSupported()) {
clearTimeout(this.touchTimeout)
}
- this.touchTimeout = setTimeout(event => this.cycle(event), TOUCHEVENT_COMPAT_WAIT + this._config.interval)
+ this.touchTimeout = setTimeout(() => this._maybeEnableCycle(), TOUCHEVENT_COMPAT_WAIT + this._config.interval)
}
const swipeConfig = {
return
}
- this._isSliding = true
-
const isCycling = Boolean(this._interval)
- if (isCycling) {
- this.pause()
- }
+ this.pause()
+
+ this._isSliding = true
this._setActiveIndicatorElement(nextElementIndex)
this._activeElement = nextElement
}
data[config]()
- return
- }
-
- if (data._config.interval && data._config.ride) {
- data.pause()
- data.cycle()
}
})
}
if (slideIndex) {
carousel.to(slideIndex)
+ carousel._maybeEnableCycle()
return
}
if (Manipulator.getDataAttribute(this, 'slide') === 'next') {
carousel.next()
+ carousel._maybeEnableCycle()
return
}
carousel.prev()
+ carousel._maybeEnableCycle()
})
EventHandler.on(window, EVENT_LOAD_DATA_API, () => {
expect(carouselByElement._element).toEqual(carouselEl)
})
+ it('should start cycling if `ride`===`carousel`', () => {
+ fixtureEl.innerHTML = '<div id="myCarousel" class="carousel slide" data-bs-ride="carousel"></div>'
+
+ const carousel = new Carousel('#myCarousel')
+ expect(carousel._interval).not.toBeNull()
+ })
+
+ it('should not start cycling if `ride`!==`carousel`', () => {
+ fixtureEl.innerHTML = '<div id="myCarousel" class="carousel slide" data-bs-ride="true"></div>'
+
+ const carousel = new Carousel('#myCarousel')
+ expect(carousel._interval).toBeNull()
+ })
+
it('should go to next item if right arrow key is pressed', () => {
return new Promise(resolve => {
fixtureEl.innerHTML = [
})
})
+ it('should ignore keyboard events if data-bs-keyboard=false', () => {
+ fixtureEl.innerHTML = [
+ '<div id="myCarousel" class="carousel slide" data-bs-keyboard="false">',
+ ' <div class="carousel-inner">',
+ ' <div class="carousel-item active">item 1</div>',
+ ' <div id="item2" class="carousel-item">item 2</div>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ spyOn(EventHandler, 'trigger').and.callThrough()
+ const carouselEl = fixtureEl.querySelector('#myCarousel')
+ // eslint-disable-next-line no-new
+ new Carousel('#myCarousel')
+ expect(EventHandler.trigger).not.toHaveBeenCalledWith(carouselEl, 'keydown.bs.carousel', jasmine.any(Function))
+ })
+
+ it('should ignore mouse events if data-bs-pause=false', () => {
+ fixtureEl.innerHTML = [
+ '<div id="myCarousel" class="carousel slide" data-bs-pause="false">',
+ ' <div class="carousel-inner">',
+ ' <div class="carousel-item active">item 1</div>',
+ ' <div id="item2" class="carousel-item">item 2</div>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ spyOn(EventHandler, 'trigger').and.callThrough()
+ const carouselEl = fixtureEl.querySelector('#myCarousel')
+ // eslint-disable-next-line no-new
+ new Carousel('#myCarousel')
+ expect(EventHandler.trigger).not.toHaveBeenCalledWith(carouselEl, 'hover.bs.carousel', jasmine.any(Function))
+ })
+
it('should go to previous item if left arrow key is pressed', () => {
return new Promise(resolve => {
fixtureEl.innerHTML = [
})
})
- it('should call cycle on mouse out with pause equal to hover', () => {
+ it('should call `maybeCycle` on mouse out with pause equal to hover', () => {
return new Promise(resolve => {
- fixtureEl.innerHTML = '<div class="carousel"></div>'
+ fixtureEl.innerHTML = '<div class="carousel" data-bs-ride="true"></div>'
const carouselEl = fixtureEl.querySelector('.carousel')
const carousel = new Carousel(carouselEl)
+ spyOn(carousel, '_maybeEnableCycle').and.callThrough()
spyOn(carousel, 'cycle')
const mouseOutEvent = createEvent('mouseout')
carouselEl.dispatchEvent(mouseOutEvent)
setTimeout(() => {
+ expect(carousel._maybeEnableCycle).toHaveBeenCalled()
expect(carousel.cycle).toHaveBeenCalled()
resolve()
}, 10)
expect(carousel._activeElement).toEqual(secondItemEl)
})
+ it('should continue cycling if it was already', () => {
+ fixtureEl.innerHTML = [
+ '<div id="myCarousel" class="carousel slide">',
+ ' <div class="carousel-inner">',
+ ' <div class="carousel-item active">item 1</div>',
+ ' <div class="carousel-item">item 2</div>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const carouselEl = fixtureEl.querySelector('#myCarousel')
+ const carousel = new Carousel(carouselEl)
+ spyOn(carousel, 'cycle')
+
+ carousel.next()
+ expect(carousel.cycle).not.toHaveBeenCalled()
+
+ carousel.cycle()
+ carousel.next()
+ expect(carousel.cycle).toHaveBeenCalled()
+ })
+
it('should update indicators if present', () => {
return new Promise(resolve => {
fixtureEl.innerHTML = [
const carousel = new Carousel(carouselEl)
const nextSpy = spyOn(carousel, 'next')
const prevSpy = spyOn(carousel, 'prev')
+ spyOn(carousel, '_maybeEnableCycle')
nextBtnEl.click()
prevBtnEl.click()
expect(nextSpy).toHaveBeenCalled()
expect(prevSpy).toHaveBeenCalled()
+ expect(carousel._maybeEnableCycle).toHaveBeenCalled()
})
})
})
describe('pause', () => {
- it('should call cycle if the carousel have carousel-item-next or carousel-item-prev class, cause is sliding', () => {
- fixtureEl.innerHTML = [
- '<div id="myCarousel" class="carousel slide">',
- ' <div class="carousel-inner">',
- ' <div class="carousel-item active">item 1</div>',
- ' <div class="carousel-item carousel-item-next">item 2</div>',
- ' <div class="carousel-item">item 3</div>',
- ' </div>',
- ' <div class="carousel-control-prev"></div>',
- ' <div class="carousel-control-next"></div>',
- '</div>'
- ].join('')
-
- const carouselEl = fixtureEl.querySelector('#myCarousel')
- const carousel = new Carousel(carouselEl)
-
- spyOn(carousel, 'cycle')
- spyOn(carousel, '_clearInterval')
-
- carousel._slide('next')
- carousel.pause()
-
- expect(carousel.cycle).toHaveBeenCalledWith(true)
- expect(carousel._clearInterval).toHaveBeenCalled()
- expect(carousel._stayPaused).toBeTrue()
- })
-
- it('should not call cycle if nothing is in transition', () => {
- fixtureEl.innerHTML = [
- '<div id="myCarousel" class="carousel slide">',
- ' <div class="carousel-inner">',
- ' <div class="carousel-item active">item 1</div>',
- ' <div class="carousel-item">item 2</div>',
- ' <div class="carousel-item">item 3</div>',
- ' </div>',
- ' <div class="carousel-control-prev"></div>',
- ' <div class="carousel-control-next"></div>',
- '</div>'
- ].join('')
-
- const carouselEl = fixtureEl.querySelector('#myCarousel')
- const carousel = new Carousel(carouselEl)
-
- spyOn(carousel, 'cycle')
- spyOn(carousel, '_clearInterval')
-
- carousel.pause()
-
- expect(carousel.cycle).not.toHaveBeenCalled()
- expect(carousel._clearInterval).toHaveBeenCalled()
- expect(carousel._stayPaused).toBeTrue()
- })
-
- it('should not set is paused at true if an event is passed', () => {
- fixtureEl.innerHTML = [
- '<div id="myCarousel" class="carousel slide">',
- ' <div class="carousel-inner">',
- ' <div class="carousel-item active">item 1</div>',
- ' <div class="carousel-item">item 2</div>',
- ' <div class="carousel-item">item 3</div>',
- ' </div>',
- ' <div class="carousel-control-prev"></div>',
- ' <div class="carousel-control-next"></div>',
- '</div>'
- ].join('')
-
- const carouselEl = fixtureEl.querySelector('#myCarousel')
- const carousel = new Carousel(carouselEl)
- const event = createEvent('mouseenter')
+ it('should trigger transitionend if the carousel have carousel-item-next or carousel-item-prev class, cause is sliding', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div id="myCarousel" class="carousel slide">',
+ ' <div class="carousel-inner">',
+ ' <div class="carousel-item active">item 1</div>',
+ ' <div class="carousel-item carousel-item-next">item 2</div>',
+ ' <div class="carousel-item">item 3</div>',
+ ' </div>',
+ ' <div class="carousel-control-prev"></div>',
+ ' <div class="carousel-control-next"></div>',
+ '</div>'
+ ].join('')
- spyOn(carousel, '_clearInterval')
+ const carouselEl = fixtureEl.querySelector('#myCarousel')
+ const carousel = new Carousel(carouselEl)
- carousel.pause(event)
+ carouselEl.addEventListener('transitionend', () => {
+ expect(carousel._clearInterval).toHaveBeenCalled()
+ resolve()
+ })
- expect(carousel._clearInterval).toHaveBeenCalled()
- expect(carousel._stayPaused).toBeFalse()
+ spyOn(carousel, '_clearInterval')
+ carousel._slide('next')
+ carousel.pause()
+ })
})
})
expect(window.setInterval).toHaveBeenCalled()
})
- it('should not set interval if the carousel is paused', () => {
- fixtureEl.innerHTML = [
- '<div id="myCarousel" class="carousel slide">',
- ' <div class="carousel-inner">',
- ' <div class="carousel-item active">item 1</div>',
- ' <div class="carousel-item">item 2</div>',
- ' <div class="carousel-item">item 3</div>',
- ' </div>',
- ' <div class="carousel-control-prev"></div>',
- ' <div class="carousel-control-next"></div>',
- '</div>'
- ].join('')
-
- const carouselEl = fixtureEl.querySelector('#myCarousel')
- const carousel = new Carousel(carouselEl)
-
- spyOn(window, 'setInterval').and.callThrough()
-
- carousel._stayPaused = true
- carousel.cycle(true)
-
- expect(window.setInterval).not.toHaveBeenCalled()
- })
-
it('should clear interval if there is one', () => {
fixtureEl.innerHTML = [
'<div id="myCarousel" class="carousel slide">',
expect(spy).not.toHaveBeenCalled()
})
- it('should call pause and cycle is the provided is the same compare to the current one', () => {
+ it('should not continue if the provided is the same compare to the current one', () => {
fixtureEl.innerHTML = [
'<div id="myCarousel" class="carousel slide">',
' <div class="carousel-inner">',
carousel.to(0)
expect(carousel._slide).not.toHaveBeenCalled()
- expect(carousel.pause).toHaveBeenCalled()
- expect(carousel.cycle).toHaveBeenCalled()
})
it('should wait before performing to if a slide is sliding', () => {
const loadEvent = createEvent('load')
window.dispatchEvent(loadEvent)
-
- expect(Carousel.getInstance(carouselEl)).not.toBeNull()
+ const carousel = Carousel.getInstance(carouselEl)
+ expect(carousel._interval).not.toBeNull()
})
it('should create carousel and go to the next slide on click (with real button controls)', () => {
it('should create carousel and go to the next slide on click with data-bs-slide-to', () => {
return new Promise(resolve => {
fixtureEl.innerHTML = [
- '<div id="myCarousel" class="carousel slide">',
+ '<div id="myCarousel" class="carousel slide" data-bs-ride="true">',
' <div class="carousel-inner">',
' <div class="carousel-item active">item 1</div>',
' <div id="item2" class="carousel-item">item 2</div>',
setTimeout(() => {
expect(item2).toHaveClass('active')
+ expect(Carousel.getInstance('#myCarousel')._interval).not.toBeNull()
resolve()
}, 10)
})