aboutsummaryrefslogtreecommitdiff
path: root/js/tests
diff options
context:
space:
mode:
authoralpadev <[email protected]>2021-06-03 13:44:16 +0200
committerGitHub <[email protected]>2021-06-03 14:44:16 +0300
commit4a5029ea29ac75243dfec68153051292fc70f5cf (patch)
tree7d0a74707b0f1ab8254610a53dcbf92eabae81b4 /js/tests
parent071a288d39f70719c771cd56ded6c36b5d150a83 (diff)
downloadbootstrap-4a5029ea29ac75243dfec68153051292fc70f5cf.tar.xz
bootstrap-4a5029ea29ac75243dfec68153051292fc70f5cf.zip
Fix handling of transitionend events dispatched by nested elements(#33845)
Fix handling of transitionend events dispatched by nested elements Properly handle events from nested elements Change `emulateTransitionEnd` to `executeAfterTransition` &&
Diffstat (limited to 'js/tests')
-rw-r--r--js/tests/unit/modal.spec.js23
-rw-r--r--js/tests/unit/tooltip.spec.js2
-rw-r--r--js/tests/unit/util/index.spec.js153
3 files changed, 148 insertions, 30 deletions
diff --git a/js/tests/unit/modal.spec.js b/js/tests/unit/modal.spec.js
index f73ac40b5..2974495ca 100644
--- a/js/tests/unit/modal.spec.js
+++ b/js/tests/unit/modal.spec.js
@@ -539,6 +539,29 @@ describe('Modal', () => {
modal.show()
})
+ it('should not queue multiple callbacks when clicking outside of modal-content and backdrop = static', done => {
+ fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog" style="transition-duration: 50ms;"></div></div>'
+
+ const modalEl = fixtureEl.querySelector('.modal')
+ const modal = new Modal(modalEl, {
+ backdrop: 'static'
+ })
+
+ modalEl.addEventListener('shown.bs.modal', () => {
+ const spy = spyOn(modal, '_queueCallback').and.callThrough()
+
+ modalEl.click()
+ modalEl.click()
+
+ setTimeout(() => {
+ expect(spy).toHaveBeenCalledTimes(1)
+ done()
+ }, 20)
+ })
+
+ modal.show()
+ })
+
it('should enforce focus', done => {
fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog"></div></div>'
diff --git a/js/tests/unit/tooltip.spec.js b/js/tests/unit/tooltip.spec.js
index 27c7a350b..b5a34bfe0 100644
--- a/js/tests/unit/tooltip.spec.js
+++ b/js/tests/unit/tooltip.spec.js
@@ -446,7 +446,7 @@ describe('Tooltip', () => {
const tooltip = new Tooltip(tooltipEl)
document.documentElement.ontouchstart = noop
- spyOn(EventHandler, 'on')
+ spyOn(EventHandler, 'on').and.callThrough()
tooltipEl.addEventListener('shown.bs.tooltip', () => {
expect(document.querySelector('.tooltip')).not.toBeNull()
diff --git a/js/tests/unit/util/index.spec.js b/js/tests/unit/util/index.spec.js
index 737ecacfd..04ad6bf43 100644
--- a/js/tests/unit/util/index.spec.js
+++ b/js/tests/unit/util/index.spec.js
@@ -157,12 +157,13 @@ describe('Util', () => {
describe('triggerTransitionEnd', () => {
it('should trigger transitionend event', done => {
- fixtureEl.innerHTML = '<div style="transition: all 300ms ease-out;"></div>'
+ fixtureEl.innerHTML = '<div></div>'
const el = fixtureEl.querySelector('div')
+ const spy = spyOn(el, 'dispatchEvent').and.callThrough()
el.addEventListener('transitionend', () => {
- expect().nothing()
+ expect(spy).toHaveBeenCalled()
done()
})
@@ -226,33 +227,6 @@ describe('Util', () => {
})
})
- describe('emulateTransitionEnd', () => {
- it('should emulate transition end', () => {
- fixtureEl.innerHTML = '<div></div>'
-
- const el = document.querySelector('div')
- const spy = spyOn(window, 'setTimeout')
-
- Util.emulateTransitionEnd(el, 10)
- expect(spy).toHaveBeenCalled()
- })
-
- it('should not emulate transition end if already triggered', done => {
- fixtureEl.innerHTML = '<div></div>'
-
- const el = fixtureEl.querySelector('div')
- const spy = spyOn(el, 'removeEventListener')
-
- Util.emulateTransitionEnd(el, 10)
- Util.triggerTransitionEnd(el)
-
- setTimeout(() => {
- expect(spy).toHaveBeenCalled()
- done()
- }, 20)
- })
- })
-
describe('typeCheckConfig', () => {
const namePlugin = 'collapse'
@@ -660,6 +634,127 @@ describe('Util', () => {
})
})
+ describe('executeAfterTransition', () => {
+ it('should immediately execute a function when waitForTransition parameter is false', () => {
+ const el = document.createElement('div')
+ const callbackSpy = jasmine.createSpy('callback spy')
+ const eventListenerSpy = spyOn(el, 'addEventListener')
+
+ Util.executeAfterTransition(callbackSpy, el, false)
+
+ expect(callbackSpy).toHaveBeenCalled()
+ expect(eventListenerSpy).not.toHaveBeenCalled()
+ })
+
+ it('should execute a function when a transitionend event is dispatched', () => {
+ const el = document.createElement('div')
+ const callbackSpy = jasmine.createSpy('callback spy')
+
+ spyOn(window, 'getComputedStyle').and.returnValue({
+ transitionDuration: '0.05s',
+ transitionDelay: '0s'
+ })
+
+ Util.executeAfterTransition(callbackSpy, el)
+
+ el.dispatchEvent(new TransitionEvent('transitionend'))
+
+ expect(callbackSpy).toHaveBeenCalled()
+ })
+
+ it('should execute a function after a computed CSS transition duration and there was no transitionend event dispatched', done => {
+ const el = document.createElement('div')
+ const callbackSpy = jasmine.createSpy('callback spy')
+
+ spyOn(window, 'getComputedStyle').and.returnValue({
+ transitionDuration: '0.05s',
+ transitionDelay: '0s'
+ })
+
+ Util.executeAfterTransition(callbackSpy, el)
+
+ setTimeout(() => {
+ expect(callbackSpy).toHaveBeenCalled()
+ done()
+ }, 70)
+ })
+
+ it('should not execute a function a second time after a computed CSS transition duration and if a transitionend event has already been dispatched', done => {
+ const el = document.createElement('div')
+ const callbackSpy = jasmine.createSpy('callback spy')
+
+ spyOn(window, 'getComputedStyle').and.returnValue({
+ transitionDuration: '0.05s',
+ transitionDelay: '0s'
+ })
+
+ Util.executeAfterTransition(callbackSpy, el)
+
+ setTimeout(() => {
+ el.dispatchEvent(new TransitionEvent('transitionend'))
+ }, 50)
+
+ setTimeout(() => {
+ expect(callbackSpy).toHaveBeenCalledTimes(1)
+ done()
+ }, 70)
+ })
+
+ it('should not trigger a transitionend event if another transitionend event had already happened', done => {
+ const el = document.createElement('div')
+
+ spyOn(window, 'getComputedStyle').and.returnValue({
+ transitionDuration: '0.05s',
+ transitionDelay: '0s'
+ })
+
+ Util.executeAfterTransition(() => {}, el)
+
+ // simulate a event dispatched by the browser
+ el.dispatchEvent(new TransitionEvent('transitionend'))
+
+ const dispatchSpy = spyOn(el, 'dispatchEvent').and.callThrough()
+
+ setTimeout(() => {
+ // setTimeout should not have triggered another transitionend event.
+ expect(dispatchSpy).not.toHaveBeenCalled()
+ done()
+ }, 70)
+ })
+
+ it('should ignore transitionend events from nested elements', done => {
+ fixtureEl.innerHTML = [
+ '<div class="outer">',
+ ' <div class="nested"></div>',
+ '</div>'
+ ].join('')
+
+ const outer = fixtureEl.querySelector('.outer')
+ const nested = fixtureEl.querySelector('.nested')
+ const callbackSpy = jasmine.createSpy('callback spy')
+
+ spyOn(window, 'getComputedStyle').and.returnValue({
+ transitionDuration: '0.05s',
+ transitionDelay: '0s'
+ })
+
+ Util.executeAfterTransition(callbackSpy, outer)
+
+ nested.dispatchEvent(new TransitionEvent('transitionend', {
+ bubbles: true
+ }))
+
+ setTimeout(() => {
+ expect(callbackSpy).not.toHaveBeenCalled()
+ }, 20)
+
+ setTimeout(() => {
+ expect(callbackSpy).toHaveBeenCalled()
+ done()
+ }, 70)
+ })
+ })
+
describe('getNextActiveElement', () => {
it('should return first element if active not exists or not given and shouldGetNext is either true, or false with cycling being disabled', () => {
const array = ['a', 'b', 'c', 'd']