[V4] Throw error when a plugin is in transition (#17823)

* Throw error when a plugin is in transition
* Add unit tests about plugins in transition
This commit is contained in:
Johann 2016-12-02 18:52:19 +01:00 committed by Mark Otto
parent 1fb6d8c46a
commit 297c47c3fd
8 changed files with 144 additions and 23 deletions

View File

@ -116,9 +116,10 @@ const Carousel = (($) => {
// public // public
next() { next() {
if (!this._isSliding) { if (this._isSliding) {
this._slide(Direction.NEXT) throw new Error('Carousel is sliding')
} }
this._slide(Direction.NEXT)
} }
nextWhenVisible() { nextWhenVisible() {
@ -129,9 +130,10 @@ const Carousel = (($) => {
} }
prev() { prev() {
if (!this._isSliding) { if (this._isSliding) {
this._slide(Direction.PREVIOUS) throw new Error('Carousel is sliding')
} }
this._slide(Direction.PREVIOUS)
} }
pause(event) { pause(event) {

View File

@ -112,8 +112,11 @@ const Collapse = (($) => {
} }
show() { show() {
if (this._isTransitioning || if (this._isTransitioning) {
$(this._element).hasClass(ClassName.ACTIVE)) { throw new Error('Collapse is transitioning')
}
if ($(this._element).hasClass(ClassName.ACTIVE)) {
return return
} }
@ -193,8 +196,11 @@ const Collapse = (($) => {
} }
hide() { hide() {
if (this._isTransitioning || if (this._isTransitioning) {
!$(this._element).hasClass(ClassName.ACTIVE)) { throw new Error('Collapse is transitioning')
}
if (!$(this._element).hasClass(ClassName.ACTIVE)) {
return return
} }

View File

@ -87,6 +87,7 @@ const Modal = (($) => {
this._isShown = false this._isShown = false
this._isBodyOverflowing = false this._isBodyOverflowing = false
this._ignoreBackdropClick = false this._ignoreBackdropClick = false
this._isTransitioning = false
this._originalBodyPadding = 0 this._originalBodyPadding = 0
this._scrollbarWidth = 0 this._scrollbarWidth = 0
} }
@ -110,6 +111,14 @@ const Modal = (($) => {
} }
show(relatedTarget) { show(relatedTarget) {
if (this._isTransitioning) {
throw new Error('Modal is transitioning')
}
if (Util.supportsTransitionEnd() &&
$(this._element).hasClass(ClassName.FADE)) {
this._isTransitioning = true
}
const showEvent = $.Event(Event.SHOW, { const showEvent = $.Event(Event.SHOW, {
relatedTarget relatedTarget
}) })
@ -152,8 +161,17 @@ const Modal = (($) => {
event.preventDefault() event.preventDefault()
} }
const hideEvent = $.Event(Event.HIDE) if (this._isTransitioning) {
throw new Error('Modal is transitioning')
}
const transition = Util.supportsTransitionEnd() &&
$(this._element).hasClass(ClassName.FADE)
if (transition) {
this._isTransitioning = true
}
const hideEvent = $.Event(Event.HIDE)
$(this._element).trigger(hideEvent) $(this._element).trigger(hideEvent)
if (!this._isShown || hideEvent.isDefaultPrevented()) { if (!this._isShown || hideEvent.isDefaultPrevented()) {
@ -172,9 +190,7 @@ const Modal = (($) => {
$(this._element).off(Event.CLICK_DISMISS) $(this._element).off(Event.CLICK_DISMISS)
$(this._dialog).off(Event.MOUSEDOWN_DISMISS) $(this._dialog).off(Event.MOUSEDOWN_DISMISS)
if (Util.supportsTransitionEnd() && if (transition) {
$(this._element).hasClass(ClassName.FADE)) {
$(this._element) $(this._element)
.one(Util.TRANSITION_END, (event) => this._hideModal(event)) .one(Util.TRANSITION_END, (event) => this._hideModal(event))
.emulateTransitionEnd(TRANSITION_DURATION) .emulateTransitionEnd(TRANSITION_DURATION)
@ -240,6 +256,7 @@ const Modal = (($) => {
if (this._config.focus) { if (this._config.focus) {
this._element.focus() this._element.focus()
} }
this._isTransitioning = false
$(this._element).trigger(shownEvent) $(this._element).trigger(shownEvent)
} }
@ -287,7 +304,8 @@ const Modal = (($) => {
_hideModal() { _hideModal() {
this._element.style.display = 'none' this._element.style.display = 'none'
this._element.setAttribute('aria-hidden', true) this._element.setAttribute('aria-hidden', 'true')
this._isTransitioning = false
this._showBackdrop(() => { this._showBackdrop(() => {
$(document.body).removeClass(ClassName.OPEN) $(document.body).removeClass(ClassName.OPEN)
this._resetAdjustments() this._resetAdjustments()

View File

@ -123,11 +123,12 @@ const Tooltip = (($) => {
constructor(element, config) { constructor(element, config) {
// private // private
this._isEnabled = true this._isEnabled = true
this._timeout = 0 this._timeout = 0
this._hoverState = '' this._hoverState = ''
this._activeTrigger = {} this._activeTrigger = {}
this._tether = null this._isTransitioning = false
this._tether = null
// protected // protected
this.element = element this.element = element
@ -245,9 +246,12 @@ const Tooltip = (($) => {
if ($(this.element).css('display') === 'none') { if ($(this.element).css('display') === 'none') {
throw new Error('Please use show on visible elements') throw new Error('Please use show on visible elements')
} }
const showEvent = $.Event(this.constructor.Event.SHOW)
const showEvent = $.Event(this.constructor.Event.SHOW)
if (this.isWithContent() && this._isEnabled) { if (this.isWithContent() && this._isEnabled) {
if (this._isTransitioning) {
throw new Error('Tooltip is transitioning')
}
$(this.element).trigger(showEvent) $(this.element).trigger(showEvent)
const isInTheDom = $.contains( const isInTheDom = $.contains(
@ -303,7 +307,8 @@ const Tooltip = (($) => {
const complete = () => { const complete = () => {
const prevHoverState = this._hoverState const prevHoverState = this._hoverState
this._hoverState = null this._hoverState = null
this._isTransitioning = false
$(this.element).trigger(this.constructor.Event.SHOWN) $(this.element).trigger(this.constructor.Event.SHOWN)
@ -313,6 +318,7 @@ const Tooltip = (($) => {
} }
if (Util.supportsTransitionEnd() && $(this.tip).hasClass(ClassName.FADE)) { if (Util.supportsTransitionEnd() && $(this.tip).hasClass(ClassName.FADE)) {
this._isTransitioning = true
$(this.tip) $(this.tip)
.one(Util.TRANSITION_END, complete) .one(Util.TRANSITION_END, complete)
.emulateTransitionEnd(Tooltip._TRANSITION_DURATION) .emulateTransitionEnd(Tooltip._TRANSITION_DURATION)
@ -326,6 +332,9 @@ const Tooltip = (($) => {
hide(callback) { hide(callback) {
const tip = this.getTipElement() const tip = this.getTipElement()
const hideEvent = $.Event(this.constructor.Event.HIDE) const hideEvent = $.Event(this.constructor.Event.HIDE)
if (this._isTransitioning) {
throw new Error('Tooltip is transitioning')
}
const complete = () => { const complete = () => {
if (this._hoverState !== HoverState.ACTIVE && tip.parentNode) { if (this._hoverState !== HoverState.ACTIVE && tip.parentNode) {
tip.parentNode.removeChild(tip) tip.parentNode.removeChild(tip)
@ -333,6 +342,7 @@ const Tooltip = (($) => {
this.element.removeAttribute('aria-describedby') this.element.removeAttribute('aria-describedby')
$(this.element).trigger(this.constructor.Event.HIDDEN) $(this.element).trigger(this.constructor.Event.HIDDEN)
this._isTransitioning = false
this.cleanupTether() this.cleanupTether()
if (callback) { if (callback) {
@ -350,7 +360,7 @@ const Tooltip = (($) => {
if (Util.supportsTransitionEnd() && if (Util.supportsTransitionEnd() &&
$(this.tip).hasClass(ClassName.FADE)) { $(this.tip).hasClass(ClassName.FADE)) {
this._isTransitioning = true
$(tip) $(tip)
.one(Util.TRANSITION_END, complete) .one(Util.TRANSITION_END, complete)
.emulateTransitionEnd(TRANSITION_DURATION) .emulateTransitionEnd(TRANSITION_DURATION)

View File

@ -46,11 +46,31 @@
<script src="../../dist/carousel.js"></script> <script src="../../dist/carousel.js"></script>
<script> <script>
$(function() { // Should throw an error because carousel is in transition
function testCarouselTransitionError() {
var err = false
var $carousel = $('#carousel-example-generic')
$carousel.on('slid.bs.carousel', function () {
$carousel.off('slid.bs.carousel')
if (!err) {
alert('No error thrown for : testCarouselTransitionError')
}
})
try {
$carousel.carousel('next').carousel('prev')
}
catch (e) {
err = true
console.error(e.message)
}
}
$(function () {
// Test to show that the carousel doesn't slide when the current tab isn't visible // Test to show that the carousel doesn't slide when the current tab isn't visible
$('#carousel-example-generic').on('slid.bs.carousel', function(event) { $('#carousel-example-generic').on('slid.bs.carousel', function (event) {
console.log('slid at ', event.timeStamp) console.log('slid at ', event.timeStamp)
}) })
testCarouselTransitionError()
}) })
</script> </script>
</body> </body>

View File

@ -61,5 +61,30 @@
<script src="../vendor/jquery.min.js"></script> <script src="../vendor/jquery.min.js"></script>
<script src="../../dist/util.js"></script> <script src="../../dist/util.js"></script>
<script src="../../dist/collapse.js"></script> <script src="../../dist/collapse.js"></script>
<script>
// JavaScript Test
$(function () {
testCollapseTransitionError()
});
// Should throw an error because carousel is in transition
function testCollapseTransitionError() {
var err = false
$('#collapseOne').on('hidden.bs.collapse', function (e) {
$(this).off('hidden.bs.collapse')
if (!err) {
alert('No error thrown for : testCollapseTransitionError')
}
})
try {
$('#collapseOne').collapse('hide').collapse('show')
}
catch (e) {
err = true
console.error(e.message)
}
}
</script>
</body> </body>
</html> </html>

View File

@ -188,6 +188,26 @@
} }
} }
// Should throw an error because modal is in transition
function testModalTransitionError() {
var err = false
// Close #myModal
$('#myModal').on('shown.bs.modal', function () {
$('#myModal').modal('hide').off('shown.bs.modal')
if (!err) {
alert('No error thrown for : testModalTransitionError')
}
})
try {
$('#myModal').modal('show').modal('hide')
}
catch (e) {
err = true
console.error(e.message)
}
}
$(function () { $(function () {
$('[data-toggle="popover"]').popover() $('[data-toggle="popover"]').popover()
$('[data-toggle="tooltip"]').tooltip() $('[data-toggle="tooltip"]').tooltip()
@ -200,6 +220,7 @@
$('#firefoxModal').on('focus', reportFirefoxTestResult.bind(false)) $('#firefoxModal').on('focus', reportFirefoxTestResult.bind(false))
$('#ff-bug-input').on('focus', reportFirefoxTestResult.bind(true)) $('#ff-bug-input').on('focus', reportFirefoxTestResult.bind(true))
}) })
testModalTransitionError()
}) })
</script> </script>
</body> </body>

View File

@ -42,7 +42,26 @@
<script> <script>
$(function () { $(function () {
$('[data-toggle="tooltip"]').tooltip() $('[data-toggle="tooltip"]').tooltip()
testTooltipTransitionError()
}) })
// Should throw an error because tooltip is in transition
function testTooltipTransitionError() {
var err = false
$('#btnOne').on('shown.bs.tooltip', function () {
$('#btnOne').tooltip('hide').off('shown.bs.tooltip')
if (!err) {
alert('No error thrown for : testTooltipTransitionError')
}
})
try {
$('#btnOne').tooltip('show').tooltip('hide')
}
catch (e) {
err = true
console.error(e.message)
}
}
</script> </script>
</body> </body>
</html> </html>