diff options
Diffstat (limited to 'js/tests')
| -rw-r--r-- | js/tests/unit/alert.spec.js | 2 | ||||
| -rw-r--r-- | js/tests/unit/button.spec.js | 8 | ||||
| -rw-r--r-- | js/tests/unit/carousel.spec.js | 43 | ||||
| -rw-r--r-- | js/tests/unit/collapse.spec.js | 2 | ||||
| -rw-r--r-- | js/tests/unit/dom/data.spec.js | 5 | ||||
| -rw-r--r-- | js/tests/unit/dom/event-handler.spec.js | 2 | ||||
| -rw-r--r-- | js/tests/unit/dom/manipulator.spec.js | 2 | ||||
| -rw-r--r-- | js/tests/unit/dom/selector-engine.spec.js | 2 | ||||
| -rw-r--r-- | js/tests/unit/dropdown.spec.js | 8 | ||||
| -rw-r--r-- | js/tests/unit/jquery.spec.js | 3 | ||||
| -rw-r--r-- | js/tests/unit/modal.spec.js | 37 | ||||
| -rw-r--r-- | js/tests/unit/offcanvas.spec.js | 4 | ||||
| -rw-r--r-- | js/tests/unit/popover.spec.js | 9 | ||||
| -rw-r--r-- | js/tests/unit/scrollspy.spec.js | 2 | ||||
| -rw-r--r-- | js/tests/unit/tab.spec.js | 2 | ||||
| -rw-r--r-- | js/tests/unit/toast.spec.js | 2 | ||||
| -rw-r--r-- | js/tests/unit/tooltip.spec.js | 81 | ||||
| -rw-r--r-- | js/tests/unit/util/index.spec.js | 2 | ||||
| -rw-r--r-- | js/tests/unit/util/sanitizer.spec.js | 25 | ||||
| -rw-r--r-- | js/tests/unit/util/swipe.spec.js | 281 | ||||
| -rw-r--r-- | js/tests/unit/util/template-factory.spec.js | 305 |
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') + }) + }) +}) |
