aboutsummaryrefslogtreecommitdiff
path: root/js/tests
diff options
context:
space:
mode:
authorPriyansh <[email protected]>2021-11-28 14:27:57 -0500
committerGitHub <[email protected]>2021-11-28 14:27:57 -0500
commitd53094ec16ba385faae2973ddee648698b32ab24 (patch)
tree9fe907f4f5a4ed57fff915526f36eca9dd05f07e /js/tests
parent52cd86f8710f8049a744b5bcb9f4a7ce19114b6e (diff)
parent5290080d4df3047d04c8a232bca5dc7f8eaa4bc6 (diff)
downloadbootstrap-d53094ec16ba385faae2973ddee648698b32ab24.tar.xz
bootstrap-d53094ec16ba385faae2973ddee648698b32ab24.zip
Merge branch 'twbs:main' into main
Diffstat (limited to 'js/tests')
-rw-r--r--js/tests/unit/alert.spec.js2
-rw-r--r--js/tests/unit/button.spec.js8
-rw-r--r--js/tests/unit/carousel.spec.js43
-rw-r--r--js/tests/unit/collapse.spec.js2
-rw-r--r--js/tests/unit/dom/data.spec.js5
-rw-r--r--js/tests/unit/dom/event-handler.spec.js2
-rw-r--r--js/tests/unit/dom/manipulator.spec.js2
-rw-r--r--js/tests/unit/dom/selector-engine.spec.js2
-rw-r--r--js/tests/unit/dropdown.spec.js8
-rw-r--r--js/tests/unit/jquery.spec.js3
-rw-r--r--js/tests/unit/modal.spec.js37
-rw-r--r--js/tests/unit/offcanvas.spec.js4
-rw-r--r--js/tests/unit/popover.spec.js9
-rw-r--r--js/tests/unit/scrollspy.spec.js2
-rw-r--r--js/tests/unit/tab.spec.js2
-rw-r--r--js/tests/unit/toast.spec.js2
-rw-r--r--js/tests/unit/tooltip.spec.js81
-rw-r--r--js/tests/unit/util/index.spec.js2
-rw-r--r--js/tests/unit/util/sanitizer.spec.js25
-rw-r--r--js/tests/unit/util/swipe.spec.js281
-rw-r--r--js/tests/unit/util/template-factory.spec.js305
21 files changed, 726 insertions, 101 deletions
diff --git a/js/tests/unit/alert.spec.js b/js/tests/unit/alert.spec.js
index 72cd23d89..cdda997c9 100644
--- a/js/tests/unit/alert.spec.js
+++ b/js/tests/unit/alert.spec.js
@@ -1,7 +1,5 @@
import Alert from '../../src/alert'
import { getTransitionDurationFromElement } from '../../src/util/index'
-
-/** Test helpers */
import { clearFixture, getFixture, jQueryMock } from '../helpers/fixture'
describe('Alert', () => {
diff --git a/js/tests/unit/button.spec.js b/js/tests/unit/button.spec.js
index be99177e8..e24ff5cb0 100644
--- a/js/tests/unit/button.spec.js
+++ b/js/tests/unit/button.spec.js
@@ -1,11 +1,5 @@
import Button from '../../src/button'
-
-/** Test helpers */
-import {
- getFixture,
- clearFixture,
- jQueryMock
-} from '../helpers/fixture'
+import { getFixture, clearFixture, jQueryMock } from '../helpers/fixture'
describe('Button', () => {
let fixtureEl
diff --git a/js/tests/unit/carousel.spec.js b/js/tests/unit/carousel.spec.js
index 9e5cfea86..a138f3ad5 100644
--- a/js/tests/unit/carousel.spec.js
+++ b/js/tests/unit/carousel.spec.js
@@ -1,9 +1,8 @@
import Carousel from '../../src/carousel'
import EventHandler from '../../src/dom/event-handler'
-
-/** Test helpers */
import { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture'
-import * as util from '../../src/util'
+import { isRTL, noop } from '../../src/util/index'
+import Swipe from '../../src/util/swipe'
describe('Carousel', () => {
const { Simulator, PointerEvent } = window
@@ -303,23 +302,24 @@ describe('Carousel', () => {
})
expect(carousel._addTouchEventListeners).not.toHaveBeenCalled()
+ expect(carousel._swipeHelper).toBeNull()
})
it('should not add touch event listeners if touch supported = false', () => {
fixtureEl.innerHTML = '<div></div>'
const carouselEl = fixtureEl.querySelector('div')
+ spyOn(Swipe, 'isSupported').and.returnValue(false)
const carousel = new Carousel(carouselEl)
-
- EventHandler.off(carouselEl, '.bs-carousel')
- carousel._touchSupported = false
+ EventHandler.off(carouselEl, Carousel.EVENT_KEY)
spyOn(carousel, '_addTouchEventListeners')
carousel._addEventListeners()
expect(carousel._addTouchEventListeners).not.toHaveBeenCalled()
+ expect(carousel._swipeHelper).toBeNull()
})
it('should add touch event listeners by default', () => {
@@ -331,7 +331,7 @@ describe('Carousel', () => {
// Headless browser does not support touch events, so need to fake it
// to test that touch events are add properly.
- document.documentElement.ontouchstart = () => {}
+ document.documentElement.ontouchstart = noop
const carousel = new Carousel(carouselEl)
expect(carousel._addTouchEventListeners).toHaveBeenCalled()
@@ -344,7 +344,7 @@ describe('Carousel', () => {
return
}
- document.documentElement.ontouchstart = () => {}
+ document.documentElement.ontouchstart = noop
document.head.append(stylesCarousel)
Simulator.setType('pointer')
@@ -389,7 +389,7 @@ describe('Carousel', () => {
return
}
- document.documentElement.ontouchstart = () => {}
+ document.documentElement.ontouchstart = noop
document.head.append(stylesCarousel)
Simulator.setType('pointer')
@@ -431,7 +431,7 @@ describe('Carousel', () => {
it('should allow swiperight and call _slide (prev) with touch events', done => {
Simulator.setType('touch')
clearPointerEvents()
- document.documentElement.ontouchstart = () => {}
+ document.documentElement.ontouchstart = noop
fixtureEl.innerHTML = [
'<div class="carousel" data-bs-interval="false">',
@@ -470,7 +470,7 @@ describe('Carousel', () => {
it('should allow swipeleft and call _slide (next) with touch events', done => {
Simulator.setType('touch')
clearPointerEvents()
- document.documentElement.ontouchstart = () => {}
+ document.documentElement.ontouchstart = noop
fixtureEl.innerHTML = [
'<div class="carousel" data-bs-interval="false">',
@@ -510,7 +510,7 @@ describe('Carousel', () => {
it('should not slide when swiping and carousel is sliding', done => {
Simulator.setType('touch')
clearPointerEvents()
- document.documentElement.ontouchstart = () => {}
+ document.documentElement.ontouchstart = noop
fixtureEl.innerHTML = [
'<div class="carousel" data-bs-interval="false">',
@@ -553,7 +553,7 @@ describe('Carousel', () => {
it('should not allow pinch with touch events', done => {
Simulator.setType('touch')
clearPointerEvents()
- document.documentElement.ontouchstart = () => {}
+ document.documentElement.ontouchstart = noop
fixtureEl.innerHTML = '<div class="carousel" data-bs-interval="false"></div>'
@@ -568,7 +568,7 @@ describe('Carousel', () => {
}, () => {
restorePointerEvents()
delete document.documentElement.ontouchstart
- expect(carousel.touchDeltaX).toEqual(0)
+ expect(carousel._swipeHelper._deltaX).toEqual(0)
done()
})
})
@@ -978,7 +978,7 @@ describe('Carousel', () => {
const carouselEl = fixtureEl.querySelector('#myCarousel')
const carousel = new Carousel(carouselEl)
- carousel._interval = setInterval(() => {}, 10)
+ carousel._interval = setInterval(noop, 10)
spyOn(window, 'setInterval').and.callThrough()
spyOn(window, 'clearInterval').and.callThrough()
@@ -1175,7 +1175,7 @@ describe('Carousel', () => {
const carouselEl = fixtureEl.querySelector('div')
const carousel = new Carousel(carouselEl, {})
- expect(util.isRTL()).toEqual(true, 'rtl has to be true')
+ expect(isRTL()).toEqual(true, 'rtl has to be true')
expect(carousel._directionToOrder('left')).toEqual('prev')
expect(carousel._directionToOrder('prev')).toEqual('prev')
@@ -1239,19 +1239,20 @@ describe('Carousel', () => {
const carouselEl = fixtureEl.querySelector('#myCarousel')
const addEventSpy = spyOn(carouselEl, 'addEventListener').and.callThrough()
- const removeEventSpy = spyOn(carouselEl, 'removeEventListener').and.callThrough()
+ const removeEventSpy = spyOn(EventHandler, 'off').and.callThrough()
// Headless browser does not support touch events, so need to fake it
// to test that touch events are add/removed properly.
- document.documentElement.ontouchstart = () => {}
+ document.documentElement.ontouchstart = noop
const carousel = new Carousel(carouselEl)
+ const swipeHelperSpy = spyOn(carousel._swipeHelper, 'dispose').and.callThrough()
const expectedArgs = [
['keydown', jasmine.any(Function), jasmine.any(Boolean)],
['mouseover', jasmine.any(Function), jasmine.any(Boolean)],
['mouseout', jasmine.any(Function), jasmine.any(Boolean)],
- ...(carousel._pointerEvent ?
+ ...(carousel._swipeHelper._supportPointerEvents ?
[
['pointerdown', jasmine.any(Function), jasmine.any(Boolean)],
['pointerup', jasmine.any(Function), jasmine.any(Boolean)]
@@ -1267,7 +1268,9 @@ describe('Carousel', () => {
carousel.dispose()
- expect(removeEventSpy.calls.allArgs()).toEqual(expectedArgs)
+ expect(carousel._swipeHelper).toBeNull()
+ expect(removeEventSpy).toHaveBeenCalledWith(carouselEl, Carousel.EVENT_KEY)
+ expect(swipeHelperSpy).toHaveBeenCalled()
delete document.documentElement.ontouchstart
})
diff --git a/js/tests/unit/collapse.spec.js b/js/tests/unit/collapse.spec.js
index da709bb85..89d20a6d8 100644
--- a/js/tests/unit/collapse.spec.js
+++ b/js/tests/unit/collapse.spec.js
@@ -1,7 +1,5 @@
import Collapse from '../../src/collapse'
import EventHandler from '../../src/dom/event-handler'
-
-/** Test helpers */
import { clearFixture, getFixture, jQueryMock } from '../helpers/fixture'
describe('Collapse', () => {
diff --git a/js/tests/unit/dom/data.spec.js b/js/tests/unit/dom/data.spec.js
index a00d3b734..2560caff7 100644
--- a/js/tests/unit/dom/data.spec.js
+++ b/js/tests/unit/dom/data.spec.js
@@ -1,6 +1,4 @@
import Data from '../../../src/dom/data'
-
-/** Test helpers */
import { getFixture, clearFixture } from '../../helpers/fixture'
describe('Data', () => {
@@ -90,8 +88,8 @@ describe('Data', () => {
expect(Data.get(div, TEST_KEY)).toBeNull()
})
+ /* eslint-disable no-console */
it('should console.error a message if called with multiple keys', () => {
- /* eslint-disable no-console */
console.error = jasmine.createSpy('console.error')
const data = { ...TEST_DATA }
@@ -103,4 +101,5 @@ describe('Data', () => {
expect(console.error).toHaveBeenCalled()
expect(Data.get(div, UNKNOWN_KEY)).toBe(null)
})
+ /* eslint-enable no-console */
})
diff --git a/js/tests/unit/dom/event-handler.spec.js b/js/tests/unit/dom/event-handler.spec.js
index 45f2d6e55..19d63492b 100644
--- a/js/tests/unit/dom/event-handler.spec.js
+++ b/js/tests/unit/dom/event-handler.spec.js
@@ -1,6 +1,4 @@
import EventHandler from '../../../src/dom/event-handler'
-
-/** Test helpers */
import { getFixture, clearFixture } from '../../helpers/fixture'
describe('EventHandler', () => {
diff --git a/js/tests/unit/dom/manipulator.spec.js b/js/tests/unit/dom/manipulator.spec.js
index 13d0c3d17..61ffe7455 100644
--- a/js/tests/unit/dom/manipulator.spec.js
+++ b/js/tests/unit/dom/manipulator.spec.js
@@ -1,6 +1,4 @@
import Manipulator from '../../../src/dom/manipulator'
-
-/** Test helpers */
import { getFixture, clearFixture } from '../../helpers/fixture'
describe('Manipulator', () => {
diff --git a/js/tests/unit/dom/selector-engine.spec.js b/js/tests/unit/dom/selector-engine.spec.js
index 08c3ae818..09c85a88a 100644
--- a/js/tests/unit/dom/selector-engine.spec.js
+++ b/js/tests/unit/dom/selector-engine.spec.js
@@ -1,6 +1,4 @@
import SelectorEngine from '../../../src/dom/selector-engine'
-
-/** Test helpers */
import { getFixture, clearFixture } from '../../helpers/fixture'
describe('SelectorEngine', () => {
diff --git a/js/tests/unit/dropdown.spec.js b/js/tests/unit/dropdown.spec.js
index a3c60ff65..f099e9990 100644
--- a/js/tests/unit/dropdown.spec.js
+++ b/js/tests/unit/dropdown.spec.js
@@ -1,8 +1,6 @@
import Dropdown from '../../src/dropdown'
import EventHandler from '../../src/dom/event-handler'
-import { noop } from '../../src/util'
-
-/** Test helpers */
+import { noop } from '../../src/util/index'
import { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture'
describe('Dropdown', () => {
@@ -225,7 +223,7 @@ describe('Dropdown', () => {
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown)
- document.documentElement.ontouchstart = () => {}
+ document.documentElement.ontouchstart = noop
spyOn(EventHandler, 'on')
spyOn(EventHandler, 'off')
@@ -891,7 +889,7 @@ describe('Dropdown', () => {
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown)
- document.documentElement.ontouchstart = () => {}
+ document.documentElement.ontouchstart = noop
spyOn(EventHandler, 'off')
btnDropdown.addEventListener('shown.bs.dropdown', () => {
diff --git a/js/tests/unit/jquery.spec.js b/js/tests/unit/jquery.spec.js
index 7513341a4..1c9258bd1 100644
--- a/js/tests/unit/jquery.spec.js
+++ b/js/tests/unit/jquery.spec.js
@@ -1,4 +1,5 @@
/* eslint-env jquery */
+
import Alert from '../../src/alert'
import Button from '../../src/button'
import Carousel from '../../src/carousel'
@@ -11,8 +12,6 @@ import ScrollSpy from '../../src/scrollspy'
import Tab from '../../src/tab'
import Toast from '../../src/toast'
import Tooltip from '../../src/tooltip'
-
-/** Test helpers */
import { getFixture, clearFixture } from '../helpers/fixture'
describe('jQuery', () => {
diff --git a/js/tests/unit/modal.spec.js b/js/tests/unit/modal.spec.js
index 211c7140f..613b0a0a1 100644
--- a/js/tests/unit/modal.spec.js
+++ b/js/tests/unit/modal.spec.js
@@ -1,8 +1,6 @@
import Modal from '../../src/modal'
import EventHandler from '../../src/dom/event-handler'
import ScrollBarHelper from '../../src/util/scrollbar'
-
-/** Test helpers */
import { clearBodyAndDocument, clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture'
describe('Modal', () => {
@@ -434,6 +432,38 @@ describe('Modal', () => {
modal.show()
})
+ it('should not close modal when clicking on modal-content', done => {
+ fixtureEl.innerHTML = [
+ '<div class="modal">',
+ ' <div class="modal-dialog">',
+ ' <div class="modal-content"></div>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const modalEl = fixtureEl.querySelector('.modal')
+ const modal = new Modal(modalEl)
+
+ const shownCallback = () => {
+ setTimeout(() => {
+ expect(modal._isShown).toEqual(true)
+ done()
+ }, 10)
+ }
+
+ modalEl.addEventListener('shown.bs.modal', () => {
+ fixtureEl.querySelector('.modal-dialog').click()
+ fixtureEl.querySelector('.modal-content').click()
+ shownCallback()
+ })
+
+ modalEl.addEventListener('hidden.bs.modal', () => {
+ throw new Error('Should not hide a modal')
+ })
+
+ modal.show()
+ })
+
it('should not close modal when clicking outside of modal-content if backdrop = false', done => {
fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog"></div></div>'
@@ -610,6 +640,7 @@ describe('Modal', () => {
const modalEl = fixtureEl.querySelector('.modal')
const modal = new Modal(modalEl)
+ const backdropSpy = spyOn(modal._backdrop, 'hide').and.callThrough()
modalEl.addEventListener('shown.bs.modal', () => {
modal.hide()
@@ -624,7 +655,7 @@ describe('Modal', () => {
expect(modalEl.getAttribute('role')).toBeNull()
expect(modalEl.getAttribute('aria-hidden')).toEqual('true')
expect(modalEl.style.display).toEqual('none')
- expect(document.querySelector('.modal-backdrop')).toBeNull()
+ expect(backdropSpy).toHaveBeenCalled()
done()
})
diff --git a/js/tests/unit/offcanvas.spec.js b/js/tests/unit/offcanvas.spec.js
index 877d2e7f3..3eda50520 100644
--- a/js/tests/unit/offcanvas.spec.js
+++ b/js/tests/unit/offcanvas.spec.js
@@ -1,9 +1,7 @@
import Offcanvas from '../../src/offcanvas'
import EventHandler from '../../src/dom/event-handler'
-
-/** Test helpers */
import { clearBodyAndDocument, clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture'
-import { isVisible } from '../../src/util'
+import { isVisible } from '../../src/util/index'
import ScrollBarHelper from '../../src/util/scrollbar'
describe('Offcanvas', () => {
diff --git a/js/tests/unit/popover.spec.js b/js/tests/unit/popover.spec.js
index c068e2fab..b3bba3180 100644
--- a/js/tests/unit/popover.spec.js
+++ b/js/tests/unit/popover.spec.js
@@ -1,6 +1,4 @@
import Popover from '../../src/popover'
-
-/** Test helpers */
import { clearFixture, getFixture, jQueryMock } from '../helpers/fixture'
describe('Popover', () => {
@@ -164,8 +162,8 @@ describe('Popover', () => {
const popover = new Popover(popoverEl, {
content: 'Popover content'
})
-
- const spy = spyOn(popover, 'setContent').and.callThrough()
+ expect(popover._templateFactory).toBeNull()
+ let spy = null
let times = 1
popoverEl.addEventListener('hidden.bs.popover', () => {
@@ -173,11 +171,12 @@ describe('Popover', () => {
})
popoverEl.addEventListener('shown.bs.popover', () => {
+ spy = spy || spyOn(popover._templateFactory, 'constructor').and.callThrough()
const popoverDisplayed = document.querySelector('.popover')
expect(popoverDisplayed).not.toBeNull()
expect(popoverDisplayed.querySelector('.popover-body').textContent).toEqual('Popover content')
- expect(spy).toHaveBeenCalledTimes(1)
+ expect(spy).toHaveBeenCalledTimes(0)
if (times > 1) {
done()
}
diff --git a/js/tests/unit/scrollspy.spec.js b/js/tests/unit/scrollspy.spec.js
index ad44d5b3c..f64b8f1dc 100644
--- a/js/tests/unit/scrollspy.spec.js
+++ b/js/tests/unit/scrollspy.spec.js
@@ -1,7 +1,5 @@
import ScrollSpy from '../../src/scrollspy'
import Manipulator from '../../src/dom/manipulator'
-
-/** Test helpers */
import { getFixture, clearFixture, createEvent, jQueryMock } from '../helpers/fixture'
describe('ScrollSpy', () => {
diff --git a/js/tests/unit/tab.spec.js b/js/tests/unit/tab.spec.js
index 4bd9c7a73..05f9db2ec 100644
--- a/js/tests/unit/tab.spec.js
+++ b/js/tests/unit/tab.spec.js
@@ -1,6 +1,4 @@
import Tab from '../../src/tab'
-
-/** Test helpers */
import { getFixture, clearFixture, jQueryMock } from '../helpers/fixture'
describe('Tab', () => {
diff --git a/js/tests/unit/toast.spec.js b/js/tests/unit/toast.spec.js
index c491650b1..4b84bf2c5 100644
--- a/js/tests/unit/toast.spec.js
+++ b/js/tests/unit/toast.spec.js
@@ -1,6 +1,4 @@
import Toast from '../../src/toast'
-
-/** Test helpers */
import { getFixture, clearFixture, createEvent, jQueryMock } from '../helpers/fixture'
describe('Toast', () => {
diff --git a/js/tests/unit/tooltip.spec.js b/js/tests/unit/tooltip.spec.js
index 01ab1b149..4a7022234 100644
--- a/js/tests/unit/tooltip.spec.js
+++ b/js/tests/unit/tooltip.spec.js
@@ -1,8 +1,6 @@
import Tooltip from '../../src/tooltip'
import EventHandler from '../../src/dom/event-handler'
import { noop } from '../../src/util/index'
-
-/** Test helpers */
import { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture'
describe('Tooltip', () => {
@@ -467,13 +465,12 @@ describe('Tooltip', () => {
})
tooltipEl.addEventListener('inserted.bs.tooltip', () => {
- expect(tooltip.getTipElement().classList.contains('bs-tooltip-bottom')).toEqual(true)
+ expect(tooltip.getTipElement().classList.contains('bs-tooltip-auto')).toEqual(true)
})
tooltipEl.addEventListener('shown.bs.tooltip', () => {
- const tooltipShown = document.querySelector('.tooltip')
-
- expect(tooltipShown.classList.contains('bs-tooltip-bottom')).toEqual(true)
+ expect(tooltip.getTipElement().classList.contains('bs-tooltip-auto')).toEqual(true)
+ expect(tooltip.getTipElement().getAttribute('data-popper-placement')).toEqual('bottom')
done()
})
@@ -701,6 +698,7 @@ describe('Tooltip', () => {
setTimeout(() => {
expect(tooltip.getTipElement().classList.contains('show')).toEqual(true)
+ expect(document.querySelectorAll('.tooltip').length).toEqual(1)
done()
}, 200)
}, 0)
@@ -1043,7 +1041,7 @@ describe('Tooltip', () => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl)
+ const tooltip = new Tooltip(tooltipEl, { animation: false })
const tip = tooltip.getTipElement()
@@ -1053,6 +1051,35 @@ describe('Tooltip', () => {
expect(tip.classList.contains('fade')).toEqual(false)
expect(tip.querySelector('.tooltip-inner').textContent).toEqual('Another tooltip')
})
+
+ it('should re-show tip if it was already shown', () => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" data-bs-title="Another tooltip">'
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
+ tooltip.show()
+ const tip = () => tooltip.getTipElement()
+
+ expect(tip().classList.contains('show')).toEqual(true)
+ tooltip.setContent({ '.tooltip-inner': 'foo' })
+
+ expect(tip().classList.contains('show')).toEqual(true)
+ expect(tip().querySelector('.tooltip-inner').textContent).toEqual('foo')
+ })
+
+ it('should keep tip hidden, if it was already hidden before', () => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" data-bs-title="Another tooltip">'
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
+ const tip = () => tooltip.getTipElement()
+
+ expect(tip().classList.contains('show')).toEqual(false)
+ tooltip.setContent({ '.tooltip-inner': 'foo' })
+
+ expect(tip().classList.contains('show')).toEqual(false)
+ expect(tip().querySelector('.tooltip-inner').textContent).toEqual('foo')
+ })
})
describe('updateAttachment', () => {
@@ -1065,7 +1092,7 @@ describe('Tooltip', () => {
})
tooltipEl.addEventListener('inserted.bs.tooltip', () => {
- expect(tooltip.getTipElement().classList.contains('bs-tooltip-end')).toEqual(true)
+ expect(tooltip.getTipElement().classList.contains('bs-tooltip-auto')).toEqual(true)
done()
})
@@ -1081,7 +1108,7 @@ describe('Tooltip', () => {
})
tooltipEl.addEventListener('inserted.bs.tooltip', () => {
- expect(tooltip.getTipElement().classList.contains('bs-tooltip-start')).toEqual(true)
+ expect(tooltip.getTipElement().classList.contains('bs-tooltip-auto')).toEqual(true)
done()
})
@@ -1089,34 +1116,17 @@ describe('Tooltip', () => {
})
})
- describe('setElementContent', () => {
+ describe('setContent', () => {
it('should do nothing if the element is null', () => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl)
- tooltip.setElementContent(null, null)
+ tooltip.setContent({ '.tooltip': null })
expect().nothing()
})
- it('should add the content as a child of the element', () => {
- fixtureEl.innerHTML = [
- '<a href="#" rel="tooltip" title="Another tooltip">',
- '<div id="childContent"></div>'
- ].join('')
-
- const tooltipEl = fixtureEl.querySelector('a')
- const childContent = fixtureEl.querySelector('div')
- const tooltip = new Tooltip(tooltipEl, {
- html: true
- })
-
- tooltip.setElementContent(tooltip.getTipElement(), childContent)
-
- expect(childContent.parentNode).toEqual(tooltip.getTipElement())
- })
-
it('should do nothing if the content is a child of the element', () => {
fixtureEl.innerHTML = [
'<a href="#" rel="tooltip" title="Another tooltip">',
@@ -1130,7 +1140,7 @@ describe('Tooltip', () => {
})
tooltip.getTipElement().append(childContent)
- tooltip.setElementContent(tooltip.getTipElement(), childContent)
+ tooltip.setContent({ '.tooltip': childContent })
expect().nothing()
})
@@ -1147,7 +1157,7 @@ describe('Tooltip', () => {
html: true
})
- tooltip.setElementContent(tooltip.getTipElement(), { 0: childContent, jquery: 'jQuery' })
+ tooltip.setContent({ '.tooltip': { 0: childContent, jquery: 'jQuery' } })
expect(childContent.parentNode).toEqual(tooltip.getTipElement())
})
@@ -1162,7 +1172,7 @@ describe('Tooltip', () => {
const childContent = fixtureEl.querySelector('div')
const tooltip = new Tooltip(tooltipEl)
- tooltip.setElementContent(tooltip.getTipElement(), childContent)
+ tooltip.setContent({ '.tooltip': childContent })
expect(childContent.textContent).toEqual(tooltip.getTipElement().textContent)
})
@@ -1176,7 +1186,7 @@ describe('Tooltip', () => {
html: true
})
- tooltip.setElementContent(tooltip.getTipElement(), '<div id="childContent">Tooltip</div>')
+ tooltip.setContent({ '.tooltip': '<div id="childContent">Tooltip</div>' })
expect(tooltip.getTipElement().querySelector('div').id).toEqual('childContent')
})
@@ -1189,12 +1199,13 @@ describe('Tooltip', () => {
html: true
})
- tooltip.setElementContent(tooltip.getTipElement(), [
+ const content = [
'<div id="childContent">',
' <button type="button">test btn</button>',
'</div>'
- ].join(''))
+ ].join('')
+ tooltip.setContent({ '.tooltip': content })
expect(tooltip.getTipElement().querySelector('div').id).toEqual('childContent')
expect(tooltip.getTipElement().querySelector('button')).toEqual(null)
})
@@ -1205,7 +1216,7 @@ describe('Tooltip', () => {
const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl)
- tooltip.setElementContent(tooltip.getTipElement(), 'test')
+ tooltip.setContent({ '.tooltip': 'test' })
expect(tooltip.getTipElement().textContent).toEqual('test')
})
diff --git a/js/tests/unit/util/index.spec.js b/js/tests/unit/util/index.spec.js
index 38e94dc6b..ccfe5e2c2 100644
--- a/js/tests/unit/util/index.spec.js
+++ b/js/tests/unit/util/index.spec.js
@@ -1,6 +1,4 @@
import * as Util from '../../../src/util/index'
-
-/** Test helpers */
import { clearFixture, getFixture } from '../../helpers/fixture'
describe('Util', () => {
diff --git a/js/tests/unit/util/sanitizer.spec.js b/js/tests/unit/util/sanitizer.spec.js
index 7379d221f..28d624c87 100644
--- a/js/tests/unit/util/sanitizer.spec.js
+++ b/js/tests/unit/util/sanitizer.spec.js
@@ -23,6 +23,31 @@ describe('Sanitizer', () => {
expect(result).not.toContain('href="javascript:alert(7)')
})
+ it('should sanitize template and work with multiple regex', () => {
+ const template = [
+ '<div>',
+ ' <a href="javascript:alert(7)" aria-label="This is a link" data-foo="bar">Click me</a>',
+ ' <span>Some content</span>',
+ '</div>'
+ ].join('')
+
+ const myDefaultAllowList = DefaultAllowlist
+ // With the default allow list
+ let result = sanitizeHtml(template, myDefaultAllowList, null)
+
+ // `data-foo` won't be present
+ expect(result).not.toContain('data-foo="bar"')
+
+ // Add the following regex too
+ myDefaultAllowList['*'].push(/^data-foo/)
+
+ result = sanitizeHtml(template, myDefaultAllowList, null)
+
+ expect(result).not.toContain('href="javascript:alert(7)') // This is in the default list
+ expect(result).toContain('aria-label="This is a link"') // This is in the default list
+ expect(result).toContain('data-foo="bar"') // We explicitly allow this
+ })
+
it('should allow aria attributes and safe attributes', () => {
const template = [
'<div aria-pressed="true">',
diff --git a/js/tests/unit/util/swipe.spec.js b/js/tests/unit/util/swipe.spec.js
new file mode 100644
index 000000000..474e34f65
--- /dev/null
+++ b/js/tests/unit/util/swipe.spec.js
@@ -0,0 +1,281 @@
+import { clearFixture, getFixture } from '../../helpers/fixture'
+import EventHandler from '../../../src/dom/event-handler'
+import Swipe from '../../../src/util/swipe'
+import { noop } from '../../../src/util'
+
+describe('Swipe', () => {
+ const { Simulator, PointerEvent } = window
+ const originWinPointerEvent = PointerEvent
+ const supportPointerEvent = Boolean(PointerEvent)
+
+ let fixtureEl
+ let swipeEl
+ const clearPointerEvents = () => {
+ window.PointerEvent = null
+ }
+
+ const restorePointerEvents = () => {
+ window.PointerEvent = originWinPointerEvent
+ }
+
+ // The headless browser does not support touch events, so we need to fake it
+ // in order to test that touch events are added properly
+ const defineDocumentElementOntouchstart = () => {
+ document.documentElement.ontouchstart = noop
+ }
+
+ const deleteDocumentElementOntouchstart = () => {
+ delete document.documentElement.ontouchstart
+ }
+
+ const mockSwipeGesture = (element, options = {}, type = 'touch') => {
+ Simulator.setType(type)
+ const _options = { deltaX: 0, deltaY: 0, ...options }
+
+ Simulator.gestures.swipe(element, _options)
+ }
+
+ beforeEach(() => {
+ fixtureEl = getFixture()
+ const cssStyle = [
+ '<style>',
+ ' #fixture .pointer-event {',
+ ' touch-action: pan-y;',
+ ' }',
+ ' #fixture div {',
+ ' width: 300px;',
+ ' height: 300px;',
+ ' }',
+ '</style>'
+ ].join('')
+
+ fixtureEl.innerHTML = '<div id="swipeEl"></div>' + cssStyle
+ swipeEl = fixtureEl.querySelector('div')
+ })
+
+ afterEach(() => {
+ clearFixture()
+ deleteDocumentElementOntouchstart()
+ })
+
+ describe('constructor', () => {
+ it('should add touch event listeners by default', () => {
+ defineDocumentElementOntouchstart()
+
+ spyOn(Swipe.prototype, '_initEvents').and.callThrough()
+ const swipe = new Swipe(swipeEl)
+ expect(swipe._initEvents).toHaveBeenCalled()
+ })
+
+ it('should not add touch event listeners if touch is not supported', () => {
+ spyOn(Swipe, 'isSupported').and.returnValue(false)
+
+ spyOn(Swipe.prototype, '_initEvents').and.callThrough()
+ const swipe = new Swipe(swipeEl)
+
+ expect(swipe._initEvents).not.toHaveBeenCalled()
+ })
+ })
+
+ describe('Config', () => {
+ it('Test leftCallback', done => {
+ const spyRight = jasmine.createSpy('spy')
+ clearPointerEvents()
+ defineDocumentElementOntouchstart()
+ // eslint-disable-next-line no-new
+ new Swipe(swipeEl, {
+ leftCallback: () => {
+ expect(spyRight).not.toHaveBeenCalled()
+ restorePointerEvents()
+ done()
+ },
+ rightCallback: spyRight
+ })
+
+ mockSwipeGesture(swipeEl, {
+ pos: [300, 10],
+ deltaX: -300
+ })
+ })
+
+ it('Test rightCallback', done => {
+ const spyLeft = jasmine.createSpy('spy')
+ clearPointerEvents()
+ defineDocumentElementOntouchstart()
+ // eslint-disable-next-line no-new
+ new Swipe(swipeEl, {
+ rightCallback: () => {
+ expect(spyLeft).not.toHaveBeenCalled()
+ restorePointerEvents()
+ done()
+ },
+ leftCallback: spyLeft
+ })
+
+ mockSwipeGesture(swipeEl, {
+ pos: [10, 10],
+ deltaX: 300
+ })
+ })
+
+ it('Test endCallback', done => {
+ clearPointerEvents()
+ defineDocumentElementOntouchstart()
+ let isFirstTime = true
+
+ const callback = () => {
+ if (isFirstTime) {
+ isFirstTime = false
+ return
+ }
+
+ expect().nothing()
+ restorePointerEvents()
+ done()
+ }
+
+ // eslint-disable-next-line no-new
+ new Swipe(swipeEl, {
+ endCallback: callback
+ })
+ mockSwipeGesture(swipeEl, {
+ pos: [10, 10],
+ deltaX: 300
+ })
+
+ mockSwipeGesture(swipeEl, {
+ pos: [300, 10],
+ deltaX: -300
+ })
+ })
+ })
+
+ describe('Functionality on PointerEvents', () => {
+ it('should not allow pinch with touch events', () => {
+ Simulator.setType('touch')
+ clearPointerEvents()
+ deleteDocumentElementOntouchstart()
+
+ const swipe = new Swipe(swipeEl)
+ spyOn(swipe, '_handleSwipe')
+
+ mockSwipeGesture(swipeEl, {
+ pos: [300, 10],
+ deltaX: -300,
+ deltaY: 0,
+ touches: 2
+ })
+
+ restorePointerEvents()
+ expect(swipe._handleSwipe).not.toHaveBeenCalled()
+ })
+
+ it('should allow swipeRight and call "rightCallback" with pointer events', done => {
+ if (!supportPointerEvent) {
+ expect().nothing()
+ done()
+ return
+ }
+
+ const style = '#fixture .pointer-event { touch-action: none !important; }'
+ fixtureEl.innerHTML += style
+
+ defineDocumentElementOntouchstart()
+ // eslint-disable-next-line no-new
+ new Swipe(swipeEl, {
+ rightCallback: () => {
+ deleteDocumentElementOntouchstart()
+ expect().nothing()
+ done()
+ }
+ })
+
+ mockSwipeGesture(swipeEl, { deltaX: 300 }, 'pointer')
+ })
+
+ it('should allow swipeLeft and call "leftCallback" with pointer events', done => {
+ if (!supportPointerEvent) {
+ expect().nothing()
+ done()
+ return
+ }
+
+ const style = '#fixture .pointer-event { touch-action: none !important; }'
+ fixtureEl.innerHTML += style
+
+ defineDocumentElementOntouchstart()
+ // eslint-disable-next-line no-new
+ new Swipe(swipeEl, {
+ leftCallback: () => {
+ expect().nothing()
+ deleteDocumentElementOntouchstart()
+ done()
+ }
+ })
+
+ mockSwipeGesture(swipeEl, {
+ pos: [300, 10],
+ deltaX: -300
+ }, 'pointer')
+ })
+ })
+
+ describe('Dispose', () => {
+ it('should call EventHandler.off', () => {
+ defineDocumentElementOntouchstart()
+ spyOn(EventHandler, 'off').and.callThrough()
+ const swipe = new Swipe(swipeEl)
+
+ swipe.dispose()
+ expect(EventHandler.off).toHaveBeenCalledWith(swipeEl, '.bs.swipe')
+ })
+
+ it('should destroy', () => {
+ const addEventSpy = spyOn(fixtureEl, 'addEventListener').and.callThrough()
+ const removeEventSpy = spyOn(EventHandler, 'off').and.callThrough()
+ defineDocumentElementOntouchstart()
+
+ const swipe = new Swipe(fixtureEl)
+
+ const expectedArgs =
+ swipe._supportPointerEvents ?
+ [
+ ['pointerdown', jasmine.any(Function), jasmine.any(Boolean)],
+ ['pointerup', jasmine.any(Function), jasmine.any(Boolean)]
+ ] :
+ [
+ ['touchstart', jasmine.any(Function), jasmine.any(Boolean)],
+ ['touchmove', jasmine.any(Function), jasmine.any(Boolean)],
+ ['touchend', jasmine.any(Function), jasmine.any(Boolean)]
+ ]
+
+ expect(addEventSpy.calls.allArgs()).toEqual(expectedArgs)
+
+ swipe.dispose()
+
+ expect(removeEventSpy).toHaveBeenCalledWith(fixtureEl, '.bs.swipe')
+ deleteDocumentElementOntouchstart()
+ })
+ })
+
+ describe('"isSupported" static', () => {
+ it('should return "true" if "touchstart" exists in document element)', () => {
+ Object.defineProperty(window.navigator, 'maxTouchPoints', () => 0)
+ defineDocumentElementOntouchstart()
+
+ expect(Swipe.isSupported()).toBeTrue()
+ })
+
+ it('should return "false" if "touchstart" not exists in document element and "navigator.maxTouchPoints" are zero (0)', () => {
+ Object.defineProperty(window.navigator, 'maxTouchPoints', () => 0)
+ deleteDocumentElementOntouchstart()
+
+ if ('ontouchstart' in document.documentElement) {
+ expect().nothing()
+ return
+ }
+
+ expect(Swipe.isSupported()).toBeFalse()
+ })
+ })
+})
diff --git a/js/tests/unit/util/template-factory.spec.js b/js/tests/unit/util/template-factory.spec.js
new file mode 100644
index 000000000..842c480c2
--- /dev/null
+++ b/js/tests/unit/util/template-factory.spec.js
@@ -0,0 +1,305 @@
+import { clearFixture, getFixture } from '../../helpers/fixture'
+import TemplateFactory from '../../../src/util/template-factory'
+
+describe('TemplateFactory', () => {
+ let fixtureEl
+
+ beforeAll(() => {
+ fixtureEl = getFixture()
+ })
+
+ afterEach(() => {
+ clearFixture()
+ })
+
+ describe('NAME', () => {
+ it('should return plugin NAME', () => {
+ expect(TemplateFactory.NAME).toEqual('TemplateFactory')
+ })
+ })
+
+ describe('Default', () => {
+ it('should return plugin default config', () => {
+ expect(TemplateFactory.Default).toEqual(jasmine.any(Object))
+ })
+ })
+
+ describe('toHtml', () => {
+ describe('Sanitization', () => {
+ it('should use "sanitizeHtml" to sanitize template', () => {
+ const factory = new TemplateFactory({
+ sanitize: true,
+ template: '<div><a href="javascript:alert(7)">Click me</a></div>'
+ })
+ const spy = spyOn(factory, '_maybeSanitize').and.callThrough()
+
+ expect(factory.toHtml().innerHTML).not.toContain('href="javascript:alert(7)')
+ expect(spy).toHaveBeenCalled()
+ })
+
+ it('should not sanitize template', () => {
+ const factory = new TemplateFactory({
+ sanitize: false,
+ template: '<div><a href="javascript:alert(7)">Click me</a></div>'
+ })
+ const spy = spyOn(factory, '_maybeSanitize').and.callThrough()
+
+ expect(factory.toHtml().innerHTML).toContain('href="javascript:alert(7)')
+ expect(spy).toHaveBeenCalled()
+ })
+
+ it('should use "sanitizeHtml" to sanitize content', () => {
+ const factory = new TemplateFactory({
+ sanitize: true,
+ html: true,
+ template: '<div id="foo"></div>',
+ content: { '#foo': '<a href="javascript:alert(7)">Click me</a>' }
+ })
+ expect(factory.toHtml().innerHTML).not.toContain('href="javascript:alert(7)')
+ })
+
+ it('should not sanitize content', () => {
+ const factory = new TemplateFactory({
+ sanitize: false,
+ html: true,
+ template: '<div id="foo"></div>',
+ content: { '#foo': '<a href="javascript:alert(7)">Click me</a>' }
+ })
+ expect(factory.toHtml().innerHTML).toContain('href="javascript:alert(7)')
+ })
+
+ it('should sanitize content only if "config.html" is enabled', () => {
+ const factory = new TemplateFactory({
+ sanitize: true,
+ html: false,
+ template: '<div id="foo"></div>',
+ content: { '#foo': '<a href="javascript:alert(7)">Click me</a>' }
+ })
+ const spy = spyOn(factory, '_maybeSanitize').and.callThrough()
+
+ expect(spy).not.toHaveBeenCalled()
+ })
+ })
+
+ describe('Extra Class', () => {
+ it('should add extra class', () => {
+ const factory = new TemplateFactory({
+ extraClass: 'testClass'
+ })
+ expect(factory.toHtml().classList.contains('testClass')).toBeTrue()
+ })
+
+ it('should add extra classes', () => {
+ const factory = new TemplateFactory({
+ extraClass: 'testClass testClass2'
+ })
+ expect(factory.toHtml().classList.contains('testClass')).toBeTrue()
+ expect(factory.toHtml().classList.contains('testClass2')).toBeTrue()
+ })
+
+ it('should resolve class if function is given', () => {
+ const factory = new TemplateFactory({
+ extraClass: arg => {
+ expect(arg).toEqual(factory)
+ return 'testClass'
+ }
+ })
+
+ expect(factory.toHtml().classList.contains('testClass')).toBeTrue()
+ })
+ })
+ })
+
+ describe('Content', () => {
+ it('add simple text content', () => {
+ const template = [
+ '<div>' +
+ '<div class="foo"></div>' +
+ '<div class="foo2"></div>' +
+ '</div>'
+ ].join(' ')
+
+ const factory = new TemplateFactory({
+ template,
+ content: {
+ '.foo': 'bar',
+ '.foo2': 'bar2'
+ }
+ })
+
+ const html = factory.toHtml()
+ expect(html.querySelector('.foo').textContent).toBe('bar')
+ expect(html.querySelector('.foo2').textContent).toBe('bar2')
+ })
+
+ it('should not fill template if selector not exists', () => {
+ const factory = new TemplateFactory({
+ sanitize: true,
+ html: true,
+ template: '<div id="foo"></div>',
+ content: { '#bar': 'test' }
+ })
+
+ expect(factory.toHtml().outerHTML).toBe('<div id="foo"></div>')
+ })
+
+ it('should remove template selector, if content is null', () => {
+ const factory = new TemplateFactory({
+ sanitize: true,
+ html: true,
+ template: '<div><div id="foo"></div></div>',
+ content: { '#foo': null }
+ })
+
+ expect(factory.toHtml().outerHTML).toBe('<div></div>')
+ })
+
+ it('should resolve content if is function', () => {
+ const factory = new TemplateFactory({
+ sanitize: true,
+ html: true,
+ template: '<div><div id="foo"></div></div>',
+ content: { '#foo': () => null }
+ })
+
+ expect(factory.toHtml().outerHTML).toBe('<div></div>')
+ })
+
+ it('if content is element and "config.html=false", should put content\'s textContent', () => {
+ fixtureEl.innerHTML = '<div>foo<span>bar</span></div>'
+ const contentElement = fixtureEl.querySelector('div')
+
+ const factory = new TemplateFactory({
+ html: false,
+ template: '<div><div id="foo"></div></div>',
+ content: { '#foo': contentElement }
+ })
+
+ const fooEl = factory.toHtml().querySelector('#foo')
+ expect(fooEl.innerHTML).not.toBe(contentElement.innerHTML)
+ expect(fooEl.textContent).toBe(contentElement.textContent)
+ expect(fooEl.textContent).toBe('foobar')
+ })
+
+ it('if content is element and "config.html=true", should put content\'s outerHtml as child', () => {
+ fixtureEl.innerHTML = '<div>foo<span>bar</span></div>'
+ const contentElement = fixtureEl.querySelector('div')
+
+ const factory = new TemplateFactory({
+ html: true,
+ template: '<div><div id="foo"></div></div>',
+ content: { '#foo': contentElement }
+ })
+
+ const fooEl = factory.toHtml().querySelector('#foo')
+ expect(fooEl.innerHTML).toBe(contentElement.outerHTML)
+ expect(fooEl.textContent).toBe(contentElement.textContent)
+ })
+ })
+
+ describe('getContent', () => {
+ it('should get content as array', () => {
+ const factory = new TemplateFactory({
+ content: {
+ '.foo': 'bar',
+ '.foo2': 'bar2'
+ }
+ })
+ expect(factory.getContent()).toEqual(['bar', 'bar2'])
+ })
+
+ it('should filter empties', () => {
+ const factory = new TemplateFactory({
+ content: {
+ '.foo': 'bar',
+ '.foo2': '',
+ '.foo3': null,
+ '.foo4': () => 2,
+ '.foo5': () => null
+ }
+ })
+ expect(factory.getContent()).toEqual(['bar', 2])
+ })
+ })
+
+ describe('hasContent', () => {
+ it('should return true, if it has', () => {
+ const factory = new TemplateFactory({
+ content: {
+ '.foo': 'bar',
+ '.foo2': 'bar2',
+ '.foo3': ''
+ }
+ })
+ expect(factory.hasContent()).toBeTrue()
+ })
+
+ it('should return false, if filtered content is empty', () => {
+ const factory = new TemplateFactory({
+ content: {
+ '.foo2': '',
+ '.foo3': null,
+ '.foo4': () => null
+ }
+ })
+ expect(factory.hasContent()).toBeFalse()
+ })
+ })
+ describe('changeContent', () => {
+ it('should change Content', () => {
+ const template = [
+ '<div>' +
+ '<div class="foo"></div>' +
+ '<div class="foo2"></div>' +
+ '</div>'
+ ].join(' ')
+
+ const factory = new TemplateFactory({
+ template,
+ content: {
+ '.foo': 'bar',
+ '.foo2': 'bar2'
+ }
+ })
+
+ const html = selector => factory.toHtml().querySelector(selector).textContent
+ expect(html('.foo')).toEqual('bar')
+ expect(html('.foo2')).toEqual('bar2')
+ factory.changeContent({
+ '.foo': 'test',
+ '.foo2': 'test2'
+ })
+
+ expect(html('.foo')).toEqual('test')
+ expect(html('.foo2')).toEqual('test2')
+ })
+
+ it('should change only the given, content', () => {
+ const template = [
+ '<div>' +
+ '<div class="foo"></div>' +
+ '<div class="foo2"></div>' +
+ '</div>'
+ ].join(' ')
+
+ const factory = new TemplateFactory({
+ template,
+ content: {
+ '.foo': 'bar',
+ '.foo2': 'bar2'
+ }
+ })
+
+ const html = selector => factory.toHtml().querySelector(selector).textContent
+ expect(html('.foo')).toEqual('bar')
+ expect(html('.foo2')).toEqual('bar2')
+ factory.changeContent({
+ '.foo': 'test',
+ '.wrong': 'wrong'
+ })
+
+ expect(html('.foo')).toEqual('test')
+ expect(html('.foo2')).toEqual('bar2')
+ })
+ })
+})