aboutsummaryrefslogtreecommitdiff
path: root/js
diff options
context:
space:
mode:
authorJohann-S <[email protected]>2019-03-13 16:23:50 +0200
committerJohann-S <[email protected]>2019-07-23 14:23:50 +0200
commitc8c207465043d940aa031570f0bce5e8fff9ffcf (patch)
tree482f430ee84aa4cae8f76cebdec75791a60a50d1 /js
parent08d81c843706c2b952fadb703f3f9be709a8ca6a (diff)
downloadbootstrap-c8c207465043d940aa031570f0bce5e8fff9ffcf.tar.xz
bootstrap-c8c207465043d940aa031570f0bce5e8fff9ffcf.zip
Switch from QUnit to Jasmine.
Diffstat (limited to 'js')
-rw-r--r--js/index.esm.js2
-rw-r--r--js/index.umd.js2
-rw-r--r--js/src/.eslintrc.json14
-rw-r--r--js/src/alert/alert.js (renamed from js/src/alert.js)12
-rw-r--r--js/src/alert/alert.spec.js127
-rw-r--r--js/src/dom/data.spec.js131
-rw-r--r--js/src/dom/event-handler.js4
-rw-r--r--js/src/dom/event-handler.spec.js327
-rw-r--r--js/src/util/index.spec.js297
-rw-r--r--js/src/util/sanitizer.spec.js70
-rw-r--r--js/tests/README.md76
-rw-r--r--js/tests/helpers/fixture.js20
-rw-r--r--js/tests/karma.conf.js118
-rw-r--r--js/tests/unit/alert.js123
-rw-r--r--js/tests/unit/dom/data.js83
-rw-r--r--js/tests/unit/dom/event-handler.js340
-rw-r--r--js/tests/unit/util/index.js191
-rw-r--r--js/tests/unit/util/sanitizer.js51
18 files changed, 1090 insertions, 898 deletions
diff --git a/js/index.esm.js b/js/index.esm.js
index e49218a1e..e3a851537 100644
--- a/js/index.esm.js
+++ b/js/index.esm.js
@@ -5,7 +5,7 @@
* --------------------------------------------------------------------------
*/
-import Alert from './src/alert'
+import Alert from './src/alert/alert'
import Button from './src/button'
import Carousel from './src/carousel'
import Collapse from './src/collapse'
diff --git a/js/index.umd.js b/js/index.umd.js
index 0a1d5ac0b..039e6d1bb 100644
--- a/js/index.umd.js
+++ b/js/index.umd.js
@@ -5,7 +5,7 @@
* --------------------------------------------------------------------------
*/
-import Alert from './src/alert'
+import Alert from './src/alert/alert'
import Button from './src/button'
import Carousel from './src/carousel'
import Collapse from './src/collapse'
diff --git a/js/src/.eslintrc.json b/js/src/.eslintrc.json
new file mode 100644
index 000000000..18ffdc003
--- /dev/null
+++ b/js/src/.eslintrc.json
@@ -0,0 +1,14 @@
+{
+ "root": true,
+ "extends": [
+ "../../.eslintrc.json"
+ ],
+ "overrides": [
+ {
+ "files": ["**/*.spec.js"],
+ "env": {
+ "jasmine": true
+ }
+ }
+ ]
+}
diff --git a/js/src/alert.js b/js/src/alert/alert.js
index 23ac9f25d..99164bd19 100644
--- a/js/src/alert.js
+++ b/js/src/alert/alert.js
@@ -11,10 +11,10 @@ import {
emulateTransitionEnd,
getSelectorFromElement,
getTransitionDurationFromElement
-} from './util/index'
-import Data from './dom/data'
-import EventHandler from './dom/event-handler'
-import SelectorEngine from './dom/selector-engine'
+} from '../util/index'
+import Data from '../dom/data'
+import EventHandler from '../dom/event-handler'
+import SelectorEngine from '../dom/selector-engine'
/**
* ------------------------------------------------------------------------
@@ -53,6 +53,7 @@ const ClassName = {
class Alert {
constructor(element) {
this._element = element
+
if (this._element) {
Data.setData(element, DATA_KEY, this)
}
@@ -118,7 +119,7 @@ class Alert {
const transitionDuration = getTransitionDurationFromElement(element)
EventHandler
- .one(element, TRANSITION_END, event => this._destroyElement(element, event))
+ .one(element, TRANSITION_END, () => this._destroyElement(element))
emulateTransitionEnd(element, transitionDuration)
}
@@ -176,6 +177,7 @@ EventHandler
* add .alert to jQuery only if jQuery is present
*/
+/* istanbul ignore if */
if (typeof $ !== 'undefined') {
const JQUERY_NO_CONFLICT = $.fn[NAME]
$.fn[NAME] = Alert._jQueryInterface
diff --git a/js/src/alert/alert.spec.js b/js/src/alert/alert.spec.js
new file mode 100644
index 000000000..cb7b57b7f
--- /dev/null
+++ b/js/src/alert/alert.spec.js
@@ -0,0 +1,127 @@
+import Alert from './alert'
+import { makeArray, getTransitionDurationFromElement } from '../util/index'
+
+/** Test helpers */
+import { getFixture, clearFixture } from '../../tests/helpers/fixture'
+
+describe('Alert', () => {
+ let fixtureEl
+
+ beforeAll(() => {
+ fixtureEl = getFixture()
+ })
+
+ afterEach(() => {
+ clearFixture()
+ })
+
+ it('should return version', () => {
+ expect(typeof Alert.VERSION).toEqual('string')
+ })
+
+ describe('data-api', () => {
+ it('should close an alert without instanciate it manually', () => {
+ fixtureEl.innerHTML = [
+ '<div class="alert">',
+ ' <button type="button" data-dismiss="alert">x</button>',
+ '</div>'
+ ].join('')
+
+ const button = document.querySelector('button')
+
+ button.click()
+ expect(makeArray(document.querySelectorAll('.alert')).length).toEqual(0)
+ })
+
+ it('should close an alert without instanciate it manually with the parent selector', () => {
+ fixtureEl.innerHTML = [
+ '<div class="alert">',
+ ' <button type="button" data-target=".alert" data-dismiss="alert">x</button>',
+ '</div>'
+ ].join('')
+
+ const button = document.querySelector('button')
+
+ button.click()
+ expect(makeArray(document.querySelectorAll('.alert')).length).toEqual(0)
+ })
+ })
+
+ describe('close', () => {
+ it('should close an alert', done => {
+ const spy = jasmine.createSpy('spy', getTransitionDurationFromElement)
+ fixtureEl.innerHTML = '<div class="alert"></div>'
+
+ const alertEl = document.querySelector('.alert')
+ const alert = new Alert(alertEl)
+
+ alertEl.addEventListener('closed.bs.alert', () => {
+ expect(makeArray(document.querySelectorAll('.alert')).length).toEqual(0)
+ expect(spy).not.toHaveBeenCalled()
+ done()
+ })
+
+ alert.close()
+ })
+
+ it('should close alert with fade class', done => {
+ fixtureEl.innerHTML = '<div class="alert fade"></div>'
+
+ const alertEl = document.querySelector('.alert')
+ const alert = new Alert(alertEl)
+
+ alertEl.addEventListener('transitionend', () => {
+ expect().nothing()
+ })
+
+ alertEl.addEventListener('closed.bs.alert', () => {
+ expect(makeArray(document.querySelectorAll('.alert')).length).toEqual(0)
+ done()
+ })
+
+ alert.close()
+ })
+
+ it('should not remove alert if close event is prevented', done => {
+ fixtureEl.innerHTML = '<div class="alert"></div>'
+
+ const alertEl = document.querySelector('.alert')
+ const alert = new Alert(alertEl)
+
+ const endTest = () => {
+ setTimeout(() => {
+ expect(alert._removeElement).not.toHaveBeenCalled()
+ done()
+ }, 10)
+ }
+
+ spyOn(alert, '_removeElement')
+
+ alertEl.addEventListener('close.bs.alert', event => {
+ event.preventDefault()
+ endTest()
+ })
+
+ alertEl.addEventListener('closed.bs.alert', () => {
+ endTest()
+ })
+
+ alert.close()
+ })
+ })
+
+ describe('dispose', () => {
+ it('should dispose an alert', () => {
+ fixtureEl.innerHTML = '<div class="alert"></div>'
+
+ const alertEl = document.querySelector('.alert')
+ const alert = new Alert(alertEl)
+
+ expect(Alert._getInstance(alertEl)).toBeDefined()
+
+ alert.dispose()
+
+ expect(Alert._getInstance(alertEl)).toBeNull()
+ })
+ })
+})
diff --git a/js/src/dom/data.spec.js b/js/src/dom/data.spec.js
new file mode 100644
index 000000000..46018dd5c
--- /dev/null
+++ b/js/src/dom/data.spec.js
@@ -0,0 +1,131 @@
+import Data from './data'
+
+/** Test helpers */
+import { getFixture, clearFixture } from '../../tests/helpers/fixture'
+
+describe('Data', () => {
+ let fixtureEl
+
+ beforeAll(() => {
+ fixtureEl = getFixture()
+ })
+
+ afterEach(() => {
+ clearFixture()
+ })
+
+ describe('setData', () => {
+ it('should set data in an element by adding a key attribute', () => {
+ fixtureEl.innerHTML = '<div></div>'
+
+ const div = fixtureEl.querySelector('div')
+ const data = {
+ test: 'bsData'
+ }
+
+ Data.setData(div, 'test', data)
+ expect(div.key).toBeDefined()
+ })
+
+ it('should change data if something is already stored', () => {
+ fixtureEl.innerHTML = '<div></div>'
+
+ const div = fixtureEl.querySelector('div')
+ const data = {
+ test: 'bsData'
+ }
+
+ Data.setData(div, 'test', data)
+
+ data.test = 'bsData2'
+ Data.setData(div, 'test', data)
+
+ expect(div.key).toBeDefined()
+ })
+ })
+
+ describe('getData', () => {
+ it('should return stored data', () => {
+ fixtureEl.innerHTML = '<div></div>'
+
+ const div = fixtureEl.querySelector('div')
+ const data = {
+ test: 'bsData'
+ }
+
+ Data.setData(div, 'test', data)
+ expect(Data.getData(div, 'test')).toEqual(data)
+ })
+
+ it('should return null on undefined element', () => {
+ expect(Data.getData(null)).toEqual(null)
+ expect(Data.getData(undefined)).toEqual(null)
+ })
+
+ it('should return null when an element have nothing stored', () => {
+ fixtureEl.innerHTML = '<div></div>'
+
+ const div = fixtureEl.querySelector('div')
+
+ expect(Data.getData(div, 'test')).toEqual(null)
+ })
+
+ it('should return null when an element have nothing stored with the provided key', () => {
+ fixtureEl.innerHTML = '<div></div>'
+
+ const div = fixtureEl.querySelector('div')
+ const data = {
+ test: 'bsData'
+ }
+
+ Data.setData(div, 'test', data)
+
+ expect(Data.getData(div, 'test2')).toEqual(null)
+ })
+ })
+
+ describe('removeData', () => {
+ it('should do nothing when an element have nothing stored', () => {
+ fixtureEl.innerHTML = '<div></div>'
+
+ const div = fixtureEl.querySelector('div')
+
+ Data.removeData(div, 'test')
+ expect().nothing()
+ })
+
+ it('should should do nothing if it\'s not a valid key provided', () => {
+ fixtureEl.innerHTML = '<div></div>'
+
+ const div = fixtureEl.querySelector('div')
+ const data = {
+ test: 'bsData'
+ }
+
+ Data.setData(div, 'test', data)
+
+ expect(div.key).toBeDefined()
+
+ Data.removeData(div, 'test2')
+
+ expect(div.key).toBeDefined()
+ })
+
+ it('should remove data if something is stored', () => {
+ fixtureEl.innerHTML = '<div></div>'
+
+ const div = fixtureEl.querySelector('div')
+ const data = {
+ test: 'bsData'
+ }
+
+ Data.setData(div, 'test', data)
+
+ expect(div.key).toBeDefined()
+
+ Data.removeData(div, 'test')
+
+ expect(div.key).toBeUndefined()
+ })
+ })
+})
diff --git a/js/src/dom/event-handler.js b/js/src/dom/event-handler.js
index 2dff88535..3310b41ab 100644
--- a/js/src/dom/event-handler.js
+++ b/js/src/dom/event-handler.js
@@ -192,7 +192,9 @@ function addHandler(element, originalTypeEvent, handler, delegationFn, oneOff) {
}
const uid = getUidEvent(originalHandler, originalTypeEvent.replace(namespaceRegex, ''))
- const fn = delegation ? bootstrapDelegationHandler(element, handler, delegationFn) : bootstrapHandler(element, handler)
+ const fn = delegation ?
+ bootstrapDelegationHandler(element, handler, delegationFn) :
+ bootstrapHandler(element, handler)
fn.delegationSelector = delegation ? handler : null
fn.originalHandler = originalHandler
diff --git a/js/src/dom/event-handler.spec.js b/js/src/dom/event-handler.spec.js
new file mode 100644
index 000000000..dc5c5c30c
--- /dev/null
+++ b/js/src/dom/event-handler.spec.js
@@ -0,0 +1,327 @@
+import EventHandler from './event-handler'
+
+/** Test helpers */
+import { getFixture, clearFixture } from '../../tests/helpers/fixture'
+
+describe('EventHandler', () => {
+ let fixtureEl
+
+ beforeAll(() => {
+ fixtureEl = getFixture()
+ })
+
+ afterEach(() => {
+ clearFixture()
+ })
+
+ describe('on', () => {
+ it('should not add event listener if the event is not a string', () => {
+ fixtureEl.innerHTML = '<div></div>'
+
+ const div = fixtureEl.querySelector('div')
+
+ EventHandler.on(div, null, () => {})
+ EventHandler.on(null, 'click', () => {})
+
+ expect().nothing()
+ })
+
+ it('should add event listener', done => {
+ fixtureEl.innerHTML = '<div></div>'
+
+ const div = fixtureEl.querySelector('div')
+
+ EventHandler.on(div, 'click', () => {
+ expect().nothing()
+ done()
+ })
+
+ div.click()
+ })
+
+ it('should add namespaced event listener', done => {
+ fixtureEl.innerHTML = '<div></div>'
+
+ const div = fixtureEl.querySelector('div')
+
+ EventHandler.on(div, 'bs.namespace', () => {
+ expect().nothing()
+ done()
+ })
+
+ EventHandler.trigger(div, 'bs.namespace')
+ })
+
+ it('should add native namespaced event listener', done => {
+ fixtureEl.innerHTML = '<div></div>'
+
+ const div = fixtureEl.querySelector('div')
+
+ EventHandler.on(div, 'click.namespace', () => {
+ expect().nothing()
+ done()
+ })
+
+ EventHandler.trigger(div, 'click')
+ })
+
+ it('should handle event delegation', done => {
+ EventHandler.on(document, 'click', '.test', () => {
+ expect().nothing()
+ done()
+ })
+
+ fixtureEl.innerHTML = '<div class="test"></div>'
+
+ const div = fixtureEl.querySelector('div')
+
+ div.click()
+ })
+ })
+
+ describe('one', () => {
+ it('should call listener just one', done => {
+ fixtureEl.innerHTML = '<div></div>'
+
+ let called = 0
+ const div = fixtureEl.querySelector('div')
+ const obj = {
+ oneListener() {
+ called++
+ }
+ }
+
+ EventHandler.one(div, 'bootstrap', obj.oneListener)
+
+ EventHandler.trigger(div, 'bootstrap')
+ EventHandler.trigger(div, 'bootstrap')
+
+ setTimeout(() => {
+ expect(called).toEqual(1)
+ done()
+ }, 20)
+ })
+ })
+
+ describe('off', () => {
+ it('should not remove a listener', () => {
+ fixtureEl.innerHTML = '<div></div>'
+ const div = fixtureEl.querySelector('div')
+
+ EventHandler.off(div, null, () => {})
+ EventHandler.off(null, 'click', () => {})
+ expect().nothing()
+ })
+
+ it('should remove a listener', done => {
+ fixtureEl.innerHTML = '<div></div>'
+ const div = fixtureEl.querySelector('div')
+
+ let called = 0
+ const handler = () => {
+ called++
+ }
+
+ EventHandler.on(div, 'foobar', handler)
+ EventHandler.trigger(div, 'foobar')
+
+ EventHandler.off(div, 'foobar', handler)
+ EventHandler.trigger(div, 'foobar')
+
+ setTimeout(() => {
+ expect(called).toEqual(1)
+ done()
+ }, 20)
+ })
+
+ it('should remove all the events', done => {
+ fixtureEl.innerHTML = '<div></div>'
+ const div = fixtureEl.querySelector('div')
+
+ let called = 0
+
+ EventHandler.on(div, 'foobar', () => {
+ called++
+ })
+ EventHandler.on(div, 'foobar', () => {
+ called++
+ })
+ EventHandler.trigger(div, 'foobar')
+
+ EventHandler.off(div, 'foobar')
+ EventHandler.trigger(div, 'foobar')
+
+ setTimeout(() => {
+ expect(called).toEqual(2)
+ done()
+ }, 20)
+ })
+
+ it('should remove all the namespaced listeners if namespace is passed', done => {
+ fixtureEl.innerHTML = '<div></div>'
+ const div = fixtureEl.querySelector('div')
+
+ let called = 0
+
+ EventHandler.on(div, 'foobar.namespace', () => {
+ called++
+ })
+ EventHandler.on(div, 'foofoo.namespace', () => {
+ called++
+ })
+ EventHandler.trigger(div, 'foobar.namespace')
+ EventHandler.trigger(div, 'foofoo.namespace')
+
+ EventHandler.off(div, '.namespace')
+ EventHandler.trigger(div, 'foobar.namespace')
+ EventHandler.trigger(div, 'foofoo.namespace')
+
+ setTimeout(() => {
+ expect(called).toEqual(2)
+ done()
+ }, 20)
+ })
+
+ it('should remove the namespaced listeners', done => {
+ fixtureEl.innerHTML = '<div></div>'
+ const div = fixtureEl.querySelector('div')
+
+ let calledCallback1 = 0
+ let calledCallback2 = 0
+
+ EventHandler.on(div, 'foobar.namespace', () => {
+ calledCallback1++
+ })
+ EventHandler.on(div, 'foofoo.namespace', () => {
+ calledCallback2++
+ })
+
+ EventHandler.trigger(div, 'foobar.namespace')
+ EventHandler.off(div, 'foobar.namespace')
+ EventHandler.trigger(div, 'foobar.namespace')
+
+ EventHandler.trigger(div, 'foofoo.namespace')
+
+ setTimeout(() => {
+ expect(calledCallback1).toEqual(1)
+ expect(calledCallback2).toEqual(1)
+ done()
+ }, 20)
+ })
+
+ it('should remove the all the namespaced listeners for native events', done => {
+ fixtureEl.innerHTML = '<div></div>'
+ const div = fixtureEl.querySelector('div')
+
+ let called = 0
+
+ EventHandler.on(div, 'click.namespace', () => {
+ called++
+ })
+ EventHandler.on(div, 'click.namespace2', () => {
+ called++
+ })
+
+ EventHandler.trigger(div, 'click')
+ EventHandler.off(div, 'click')
+ EventHandler.trigger(div, 'click')
+
+ setTimeout(() => {
+ expect(called).toEqual(2)
+ done()
+ }, 20)
+ })
+
+ it('should remove the specified namespaced listeners for native events', done => {
+ fixtureEl.innerHTML = '<div></div>'
+ const div = fixtureEl.querySelector('div')
+
+ let called1 = 0
+ let called2 = 0
+
+ EventHandler.on(div, 'click.namespace', () => {
+ called1++
+ })
+ EventHandler.on(div, 'click.namespace2', () => {
+ called2++
+ })
+ EventHandler.trigger(div, 'click')
+
+ EventHandler.off(div, 'click.namespace')
+ EventHandler.trigger(div, 'click')
+
+ setTimeout(() => {
+ expect(called1).toEqual(1)
+ expect(called2).toEqual(2)
+ done()
+ }, 20)
+ })
+
+ it('should remove a listener registered by .one', done => {
+ fixtureEl.innerHTML = '<div></div>'
+
+ const div = fixtureEl.querySelector('div')
+ const handler = () => {
+ throw new Error('called')
+ }
+
+ EventHandler.one(div, 'foobar', handler)
+ EventHandler.off(div, 'foobar', handler)
+
+ EventHandler.trigger(div, 'foobar')
+ setTimeout(() => {
+ expect().nothing()
+ done()
+ }, 20)
+ })
+
+ it('should remove the correct delegated event listener', () => {
+ const element = document.createElement('div')
+ const subelement = document.createElement('span')
+ element.appendChild(subelement)
+
+ const anchor = document.createElement('a')
+ element.appendChild(anchor)
+
+ let i = 0
+ const handler = () => {
+ i++
+ }
+
+ EventHandler.on(element, 'click', 'a', handler)
+ EventHandler.on(element, 'click', 'span', handler)
+
+ fixtureEl.appendChild(element)
+
+ EventHandler.trigger(anchor, 'click')
+ EventHandler.trigger(subelement, 'click')
+
+ // first listeners called
+ expect(i === 2).toEqual(true)
+
+ EventHandler.off(element, 'click', 'span', handler)
+ EventHandler.trigger(subelement, 'click')
+
+ // removed listener not called
+ expect(i === 2).toEqual(true)
+
+ EventHandler.trigger(anchor, 'click')
+
+ // not removed listener called
+ expect(i === 3).toEqual(true)
+
+ EventHandler.on(element, 'click', 'span', handler)
+ EventHandler.trigger(anchor, 'click')
+ EventHandler.trigger(subelement, 'click')
+
+ // listener re-registered
+ expect(i === 5).toEqual(true)
+
+ EventHandler.off(element, 'click', 'span')
+ EventHandler.trigger(subelement, 'click')
+
+ // listener removed again
+ expect(i === 5).toEqual(true)
+ })
+ })
+})
diff --git a/js/src/util/index.spec.js b/js/src/util/index.spec.js
new file mode 100644
index 000000000..4b2d41685
--- /dev/null
+++ b/js/src/util/index.spec.js
@@ -0,0 +1,297 @@
+import * as Util from './index'
+
+/** Test helpers */
+import { getFixture, clearFixture } from '../../tests/helpers/fixture'
+
+describe('Util', () => {
+ let fixtureEl
+
+ beforeAll(() => {
+ fixtureEl = getFixture()
+ })
+
+ afterEach(() => {
+ clearFixture()
+ })
+
+ describe('getUID', () => {
+ it('should generate uid', () => {
+ const uid = Util.getUID('bs')
+ const uid2 = Util.getUID('bs')
+
+ expect(uid).not.toEqual(uid2)
+ })
+ })
+
+ describe('getSelectorFromElement', () => {
+ it('should get selector from data-target', () => {
+ fixtureEl.innerHTML = [
+ '<div id="test" data-target=".target"></div>',
+ '<div class="target"></div>'
+ ].join('')
+
+ const testEl = fixtureEl.querySelector('#test')
+
+ expect(Util.getSelectorFromElement(testEl)).toEqual('.target')
+ })
+
+ it('should get selector from href if no data-target set', () => {
+ fixtureEl.innerHTML = [
+ '<a id="test" href=".target"></a>',
+ '<div class="target"></div>'
+ ].join('')
+
+ const testEl = fixtureEl.querySelector('#test')
+
+ expect(Util.getSelectorFromElement(testEl)).toEqual('.target')
+ })
+
+ it('should get selector from href if data-target equal to #', () => {
+ fixtureEl.innerHTML = [
+ '<a id="test" data-target="#" href=".target"></a>',
+ '<div class="target"></div>'
+ ].join('')
+
+ const testEl = fixtureEl.querySelector('#test')
+
+ expect(Util.getSelectorFromElement(testEl)).toEqual('.target')
+ })
+
+ it('should return null if selector not found', () => {
+ fixtureEl.innerHTML = '<a id="test" href=".target"></a>'
+
+ const testEl = fixtureEl.querySelector('#test')
+
+ expect(Util.getSelectorFromElement(testEl)).toBeNull()
+ })
+ })
+
+ describe('getTransitionDurationFromElement', () => {
+ it('should get transition from element', () => {
+ fixtureEl.innerHTML = '<div style="transition: all 300ms ease-out;"></div>'
+
+ expect(Util.getTransitionDurationFromElement(fixtureEl.querySelector('div'))).toEqual(300)
+ })
+
+ it('should return 0 if the element is undefined or null', () => {
+ expect(Util.getTransitionDurationFromElement(null)).toEqual(0)
+ expect(Util.getTransitionDurationFromElement(undefined)).toEqual(0)
+ })
+
+ it('should return 0 if the element do not possess transition', () => {
+ fixtureEl.innerHTML = '<div></div>'
+
+ expect(Util.getTransitionDurationFromElement(fixtureEl.querySelector('div'))).toEqual(0)
+ })
+ })
+
+ describe('triggerTransitionEnd', () => {
+ it('should trigger transitionend event', done => {
+ fixtureEl.innerHTML = '<div style="transition: all 300ms ease-out;"></div>'
+
+ const el = fixtureEl.querySelector('div')
+
+ el.addEventListener('transitionend', () => {
+ expect().nothing()
+ done()
+ })
+
+ Util.triggerTransitionEnd(el)
+ })
+ })
+
+ describe('isElement', () => {
+ it('should detect if the parameter is an element or not', () => {
+ fixtureEl.innerHTML = '<div></div>'
+
+ const el = document.querySelector('div')
+
+ expect(Util.isElement(el)).toEqual(el.nodeType)
+ expect(Util.isElement({})).toEqual(undefined)
+ })
+
+ it('should detect jQuery element', () => {
+ fixtureEl.innerHTML = '<div></div>'
+
+ const el = document.querySelector('div')
+ const fakejQuery = {
+ 0: el
+ }
+
+ expect(Util.isElement(fakejQuery)).toEqual(el.nodeType)
+ })
+ })
+
+ 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', () => {
+ it('should check type of the config object', () => {
+ const namePlugin = 'collapse'
+ const defaultType = {
+ toggle: 'boolean',
+ parent: '(string|element)'
+ }
+ const config = {
+ toggle: true,
+ parent: 777
+ }
+
+ expect(() => {
+ Util.typeCheckConfig(namePlugin, config, defaultType)
+ }).toThrow(new Error('COLLAPSE: Option "parent" provided type "number" but expected type "(string|element)".'))
+ })
+ })
+
+ describe('makeArray', () => {
+ it('should convert node list to array', () => {
+ const nodeList = document.querySelectorAll('div')
+
+ expect(Array.isArray(nodeList)).toEqual(false)
+ expect(Array.isArray(Util.makeArray(nodeList))).toEqual(true)
+ })
+
+ it('should return an empty array if the nodeList is undefined', () => {
+ expect(Util.makeArray(null)).toEqual([])
+ expect(Util.makeArray(undefined)).toEqual([])
+ })
+ })
+
+ describe('isVisible', () => {
+ it('should return false if the element is not defined', () => {
+ expect(Util.isVisible(null)).toEqual(false)
+ expect(Util.isVisible(undefined)).toEqual(false)
+ })
+
+ it('should return false if the element provided is not a dom element', () => {
+ expect(Util.isVisible({})).toEqual(false)
+ })
+
+ it('should return false if the element is not visible with display none', () => {
+ fixtureEl.innerHTML = '<div style="display: none;"></div>'
+
+ const div = fixtureEl.querySelector('div')
+
+ expect(Util.isVisible(div)).toEqual(false)
+ })
+
+ it('should return false if the element is not visible with visibility hidden', () => {
+ fixtureEl.innerHTML = '<div style="visibility: hidden;"></div>'
+
+ const div = fixtureEl.querySelector('div')
+
+ expect(Util.isVisible(div)).toEqual(false)
+ })
+
+ it('should return false if the parent element is not visible', () => {
+ fixtureEl.innerHTML = '<div></div>'
+
+ const div = fixtureEl.querySelector('div')
+
+ expect(Util.isVisible(div)).toEqual(false)
+ })
+
+ it('should return true if the element is visible', () => {
+ fixtureEl.innerHTML = [
+ '<div>',
+ ' <div id="element"></div>',
+ '</div>'
+ ].join('')
+
+ const div = fixtureEl.querySelector('#element')
+
+ expect(Util.isVisible(div)).toEqual(true)
+ })
+ })
+
+ describe('findShadowRoot', () => {
+ it('should return null if shadow dom is not available', () => {
+ // Only for newer browsers
+ if (!document.documentElement.attachShadow) {
+ expect().nothing()
+ return
+ }
+
+ fixtureEl.innerHTML = '<div></div>'
+
+ const div = fixtureEl.querySelector('div')
+
+ spyOn(document.documentElement, 'attachShadow').and.returnValue(null)
+
+ expect(Util.findShadowRoot(div)).toEqual(null)
+ })
+
+ it('should return null when we do not find a shadow root', () => {
+ // Only for newer browsers
+ if (!document.documentElement.attachShadow) {
+ expect().nothing()
+ return
+ }
+
+ spyOn(document, 'getRootNode').and.returnValue(undefined)
+
+ expect(Util.findShadowRoot(document)).toEqual(null)
+ })
+
+ it('should return the shadow root when found', () => {
+ // Only for newer browsers
+ if (!document.documentElement.attachShadow) {
+ expect().nothing()
+ return
+ }
+
+ fixtureEl.innerHTML = '<div></div>'
+
+ const div = fixtureEl.querySelector('div')
+ const shadowRoot = div.attachShadow({
+ mode: 'open'
+ })
+
+ expect(Util.findShadowRoot(shadowRoot)).toEqual(shadowRoot)
+
+ shadowRoot.innerHTML = '<button>Shadow Button</button>'
+
+ expect(Util.findShadowRoot(shadowRoot.firstChild)).toEqual(shadowRoot)
+ })
+ })
+
+ describe('noop', () => {
+ it('should return a function', () => {
+ expect(typeof Util.noop()).toEqual('function')
+ })
+ })
+
+ describe('reflow', () => {
+ it('should return element offset height to force the reflow', () => {
+ fixtureEl.innerHTML = '<div></div>'
+
+ const div = fixtureEl.querySelector('div')
+
+ expect(Util.reflow(div)).toEqual(0)
+ })
+ })
+})
diff --git a/js/src/util/sanitizer.spec.js b/js/src/util/sanitizer.spec.js
new file mode 100644
index 000000000..6dadd29a5
--- /dev/null
+++ b/js/src/util/sanitizer.spec.js
@@ -0,0 +1,70 @@
+import { DefaultWhitelist, sanitizeHtml } from './sanitizer'
+
+describe('Sanitizer', () => {
+ describe('sanitizeHtml', () => {
+ it('should return the same on empty string', () => {
+ const empty = ''
+
+ const result = sanitizeHtml(empty, DefaultWhitelist, null)
+
+ expect(result).toEqual(empty)
+ })
+
+ it('should sanitize template by removing tags with XSS', () => {
+ const template = [
+ '<div>',
+ ' <a href="javascript:alert(7)">Click me</a>',
+ ' <span>Some content</span>',
+ '</div>'
+ ].join('')
+
+ const result = sanitizeHtml(template, DefaultWhitelist, null)
+
+ expect(result.indexOf('script') === -1).toEqual(true)
+ })
+
+ it('should allow aria attributes and safe attributes', () => {
+ const template = [
+ '<div aria-pressed="true">',
+ ' <span class="test">Some content</span>',
+ '</div>'
+ ].join('')
+
+ const result = sanitizeHtml(template, DefaultWhitelist, null)
+
+ expect(result.indexOf('aria-pressed') !== -1).toEqual(true)
+ expect(result.indexOf('class="test"') !== -1).toEqual(true)
+ })
+
+ it('should remove not whitelist tags', () => {
+ const template = [
+ '<div>',
+ ' <script>alert(7)</script>',
+ '</div>'
+ ].join('')
+
+ const result = sanitizeHtml(template, DefaultWhitelist, null)
+
+ expect(result.indexOf('<script>') === -1).toEqual(true)
+ })
+
+ it('should not use native api to sanitize if a custom function passed', () => {
+ const template = [
+ '<div>',
+ ' <span>Some content</span>',
+ '</div>'
+ ].join('')
+
+ function mySanitize(htmlUnsafe) {
+ return htmlUnsafe
+ }
+
+ spyOn(DOMParser.prototype, 'parseFromString')
+
+ const result = sanitizeHtml(template, DefaultWhitelist, mySanitize)
+
+ expect(result).toEqual(template)
+ expect(DOMParser.prototype.parseFromString).not.toHaveBeenCalled()
+ })
+ })
+})
diff --git a/js/tests/README.md b/js/tests/README.md
index 3b079d92c..3a01d3399 100644
--- a/js/tests/README.md
+++ b/js/tests/README.md
@@ -1,20 +1,16 @@
## How does Bootstrap's test suite work?
-Bootstrap uses [QUnit](https://qunitjs.com/) and [Sinon](https://sinonjs.org/). Each plugin has a file dedicated to its tests in `unit/<plugin-name>.js`.
+Bootstrap uses [Jasmine](https://jasmine.github.io/). Each plugin has a file dedicated to its tests in `src/<plugin-name>/<plugin-name>.spec.js`.
-* `unit/` contains the unit test files for each Bootstrap plugin.
-* `vendor/` contains third-party testing-related code (QUnit, jQuery and Sinon).
* `visual/` contains "visual" tests which are run interactively in real browsers and require manual verification by humans.
To run the unit test suite via [Karma](https://karma-runner.github.io/), run `npm run js-test`.
-
-To run the unit test suite via a real web browser, open `index.html` in the browser.
-
+To run the unit test suite via [Karma](https://karma-runner.github.io/) and debug, run `npm run js-debug`.
## How do I add a new unit test?
-1. Locate and open the file dedicated to the plugin which you need to add tests to (`unit/<plugin-name>.js`).
-2. Review the [QUnit API Documentation](https://api.qunitjs.com/) and use the existing tests as references for how to structure your new tests.
+1. Locate and open the file dedicated to the plugin which you need to add tests to (`src/<plugin-name>/<plugin-name>.spec.js`).
+2. Review the [Jasmine API Documentation](https://jasmine.github.io/pages/docs_home.html) and use the existing tests as references for how to structure your new tests.
3. Write the necessary unit test(s) for the new or revised functionality.
4. Run `npm run js-test` to see the results of your newly-added test(s).
@@ -23,47 +19,53 @@ To run the unit test suite via a real web browser, open `index.html` in the brow
## What should a unit test look like?
* Each test should have a unique name clearly stating what unit is being tested.
+* Each test should be in the corresponding `describe`.
* Each test should test only one unit per test, although one test can include several assertions. Create multiple tests for multiple units of functionality.
-* Each test should begin with [`assert.expect`](https://api.qunitjs.com/assert/expect/) to ensure that the expected assertions are run.
+* Each test should use [`expect`](https://jasmine.github.io/api/edge/matchers.html) to ensure something is expected.
* Each test should follow the project's [JavaScript Code Guidelines](https://github.com/twbs/bootstrap/blob/master/CONTRIBUTING.md#js)
## Code coverage
-Currently we're aiming for at least 80% test coverage for our code. To ensure your changes meet or exceed this limit, run `npm run js-compile && npm run js-test` and open the file in `js/coverage/lcov-report/index.html` to see the code coverage for each plugin. See more details when you select a plugin and ensure your change is fully covered by unit tests.
+Currently we're aiming for at least 90% test coverage for our code. To ensure your changes meet or exceed this limit, run `npm run js-compile && npm run js-test` and open the file in `js/coverage/lcov-report/index.html` to see the code coverage for each plugin. See more details when you select a plugin and ensure your change is fully covered by unit tests.
### Example tests
```js
// Synchronous test
-QUnit.test('should describe the unit being tested', function (assert) {
- assert.expect(1)
- var templateHTML = '<div class="alert alert-danger fade show">' +
- '<a class="close" href="#" data-dismiss="alert">×</a>' +
- '<p><strong>Template necessary for the test.</p>' +
- '</div>'
- var $alert = $(templateHTML).appendTo('#qunit-fixture').bootstrapAlert()
-
- $alert.find('.close').trigger('click')
-
- // Make assertion
- assert.strictEqual($alert.hasClass('show'), false, 'remove .show class on .close click')
+describe('_getInstance', () => {
+ it('should return null if there is no instance', () => {
+ // Make assertion
+ expect(Tab._getInstance(fixtureEl)).toEqual(null)
+ })
+
+ it('should return this instance', () => {
+ fixtureEl.innerHTML = '<div></div>'
+
+ const divEl = fixtureEl.querySelector('div')
+ const tab = new Tab(divEl)
+
+ // Make assertion
+ expect(Tab._getInstance(divEl)).toEqual(tab)
+ })
})
// Asynchronous test
-QUnit.test('should describe the unit being tested', function (assert) {
- assert.expect(2)
- var done = assert.async()
-
- var $tooltip = $('<div title="tooltip title"></div>').bootstrapTooltip()
- var tooltipInstance = $tooltip.data('bs.tooltip')
- var spyShow = sinon.spy(tooltipInstance, 'show')
-
- $tooltip.appendTo('#qunit-fixture')
- .on('shown.bs.tooltip', function () {
- assert.ok(true, '"shown" event was fired after calling "show"')
- assert.ok(spyShow.called, 'show called')
- done()
- })
- .bootstrapTooltip('show')
+it('should show a tooltip without the animation', done => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>'
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl, {
+ animation: false
+ })
+
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ const tip = document.querySelector('.tooltip')
+
+ expect(tip).toBeDefined()
+ expect(tip.classList.contains('fade')).toEqual(false)
+ done()
+ })
+
+ tooltip.show()
})
```
diff --git a/js/tests/helpers/fixture.js b/js/tests/helpers/fixture.js
new file mode 100644
index 000000000..524d54444
--- /dev/null
+++ b/js/tests/helpers/fixture.js
@@ -0,0 +1,20 @@
+const fixtureId = 'fixture'
+
+export const getFixture = () => {
+ let fixtureEl = document.getElementById(fixtureId)
+
+ if (!fixtureEl) {
+ fixtureEl = document.createElement('div')
+ fixtureEl.setAttribute('id', fixtureId)
+ fixtureEl.style.display = 'none'
+ document.body.appendChild(fixtureEl)
+ }
+
+ return fixtureEl
+}
+
+export const clearFixture = () => {
+ const fixtureEl = getFixture()
+
+ fixtureEl.innerHTML = ''
+}
diff --git a/js/tests/karma.conf.js b/js/tests/karma.conf.js
index e3851663a..b5b9fb373 100644
--- a/js/tests/karma.conf.js
+++ b/js/tests/karma.conf.js
@@ -7,22 +7,19 @@ const {
browsers,
browsersKeys
} = require('./browsers')
+const babel = require('rollup-plugin-babel')
+const istanbul = require('rollup-plugin-istanbul')
const { env } = process
-const bundle = env.BUNDLE === 'true'
const browserStack = env.BROWSER === 'true'
const debug = env.DEBUG === 'true'
-
-const jqueryFile = 'node_modules/jquery/dist/jquery.slim.min.js'
-
const frameworks = [
- 'qunit',
- 'sinon'
+ 'jasmine'
]
const plugins = [
- 'karma-qunit',
- 'karma-sinon'
+ 'karma-jasmine',
+ 'karma-rollup-preprocessor'
]
const reporters = ['dots']
@@ -49,10 +46,35 @@ const customLaunchers = {
}
}
-let files = [
- 'node_modules/popper.js/dist/umd/popper.min.js',
- 'node_modules/hammer-simulator/index.js'
-]
+const rollupPreprocessor = {
+ plugins: [
+ istanbul({
+ exclude: ['js/src/**/*.spec.js']
+ }),
+ babel({
+ // Only transpile our source code
+ exclude: 'node_modules/**',
+ // Include only required helpers
+ externalHelpersWhitelist: [
+ 'defineProperties',
+ 'createClass',
+ 'inheritsLoose',
+ 'defineProperty',
+ 'objectSpread2'
+ ],
+ plugins: [
+ '@babel/plugin-proposal-object-rest-spread'
+ ]
+ })
+ ],
+ output: {
+ format: 'iife',
+ name: 'bootstrapTest',
+ sourcemap: 'inline'
+ }
+}
+
+let files = []
const conf = {
basePath: '../..',
@@ -62,28 +84,11 @@ const conf = {
singleRun: true,
concurrency: Infinity,
client: {
- qunit: {
- showUI: true
- }
+ clearContext: false
}
}
-if (bundle) {
- frameworks.push('detectBrowsers')
- plugins.push(
- 'karma-chrome-launcher',
- 'karma-firefox-launcher',
- 'karma-detect-browsers'
- )
- conf.customLaunchers = customLaunchers
- conf.detectBrowsers = detectBrowsers
- files = files.concat([
- jqueryFile,
- 'js/tests/unit/tests-polyfills.js',
- 'dist/js/bootstrap.js',
- 'js/tests/unit/!(tests-polyfills).js'
- ])
-} else if (browserStack) {
+if (browserStack) {
conf.hostname = ip.address()
conf.browserStack = {
username: env.BROWSER_STACK_USERNAME,
@@ -92,27 +97,17 @@ if (bundle) {
project: 'Bootstrap',
retryLimit: 2
}
- plugins.push('karma-browserstack-launcher')
+ plugins.push('karma-browserstack-launcher', 'karma-jasmine-html-reporter')
conf.customLaunchers = browsers
conf.browsers = browsersKeys
- reporters.push('BrowserStack')
+ reporters.push('BrowserStack', 'kjhtml')
files = files.concat([
- jqueryFile,
- 'js/tests/unit/tests-polyfills.js',
- 'js/coverage/dist/util/index.js',
- 'js/coverage/dist/util/sanitizer.js',
- 'js/coverage/dist/dom/polyfill.js',
- 'js/coverage/dist/dom/event-handler.js',
- 'js/coverage/dist/dom/selector-engine.js',
- 'js/coverage/dist/dom/data.js',
- 'js/coverage/dist/dom/manipulator.js',
- 'js/coverage/dist/dom/!(polyfill).js',
- 'js/coverage/dist/tooltip.js',
- 'js/coverage/dist/!(util|index|tooltip).js', // include all of our js/dist files except util.js, index.js and tooltip.js
- 'js/tests/unit/!(tests-polyfills).js',
- 'js/tests/unit/dom/*.js',
- 'js/tests/unit/util/*.js'
+ { pattern: 'js/src/**/*.spec.js', watched: false }
])
+ conf.preprocessors = {
+ 'js/src/**/*.spec.js': ['rollup']
+ }
+ conf.rollupPreprocessor = rollupPreprocessor
} else {
frameworks.push('detectBrowsers')
plugins.push(
@@ -122,23 +117,13 @@ if (bundle) {
'karma-coverage-istanbul-reporter'
)
files = files.concat([
- jqueryFile,
- 'js/tests/unit/tests-polyfills.js',
- 'js/coverage/dist/util/index.js',
- 'js/coverage/dist/util/sanitizer.js',
- 'js/coverage/dist/dom/polyfill.js',
- 'js/coverage/dist/dom/event-handler.js',
- 'js/coverage/dist/dom/selector-engine.js',
- 'js/coverage/dist/dom/data.js',
- 'js/coverage/dist/dom/manipulator.js',
- 'js/coverage/dist/dom/!(polyfill).js',
- 'js/coverage/dist/tooltip.js',
- 'js/coverage/dist/!(util|index|tooltip).js', // include all of our js/dist files except util.js, index.js and tooltip.js
- 'js/tests/unit/!(tests-polyfills).js',
- 'js/tests/unit/dom/*.js',
- 'js/tests/unit/util/*.js'
+ { pattern: 'js/src/**/*.spec.js', watched: true }
])
reporters.push('coverage-istanbul')
+ conf.preprocessors = {
+ 'js/src/**/*.spec.js': ['rollup']
+ }
+ conf.rollupPreprocessor = rollupPreprocessor
conf.customLaunchers = customLaunchers
conf.detectBrowsers = detectBrowsers
conf.coverageIstanbulReporter = {
@@ -148,8 +133,8 @@ if (bundle) {
emitWarning: false,
global: {
statements: 90,
- branches: 86,
- functions: 89,
+ branches: 90,
+ functions: 90,
lines: 90
},
each: {
@@ -166,6 +151,9 @@ if (bundle) {
}
if (debug) {
+ conf.hostname = ip.address()
+ plugins.push('karma-jasmine-html-reporter')
+ reporters.push('kjhtml')
conf.singleRun = false
conf.autoWatch = true
}
diff --git a/js/tests/unit/alert.js b/js/tests/unit/alert.js
deleted file mode 100644
index c03435618..000000000
--- a/js/tests/unit/alert.js
+++ /dev/null
@@ -1,123 +0,0 @@
-$(function () {
- 'use strict'
-
- var Alert = typeof window.bootstrap === 'undefined' ? window.Alert : window.bootstrap.Alert
-
- QUnit.module('alert plugin')
-
- QUnit.test('should be defined on jquery object', function (assert) {
- assert.expect(1)
- assert.ok($(document.body).alert, 'alert method is defined')
- })
-
- QUnit.module('alert', {
- beforeEach: function () {
- // Run all tests in noConflict mode -- it's the only way to ensure that the plugin works in noConflict mode
- $.fn.bootstrapAlert = $.fn.alert.noConflict()
- },
- afterEach: function () {
- $.fn.alert = $.fn.bootstrapAlert
- delete $.fn.bootstrapAlert
- $('#qunit-fixture').html('')
- }
- })
-
- QUnit.test('should provide no conflict', function (assert) {
- assert.expect(1)
- assert.strictEqual(typeof $.fn.alert, 'undefined', 'alert was set back to undefined (org value)')
- })
-
- QUnit.test('should return jquery collection containing the element', function (assert) {
- assert.expect(2)
- var $el = $('<div/>')
- var $alert = $el.bootstrapAlert()
- assert.ok($alert instanceof $, 'returns jquery collection')
- assert.strictEqual($alert[0], $el[0], 'collection contains element')
- })
-
- QUnit.test('should fade element out on clicking .close', function (assert) {
- assert.expect(1)
- var alertHTML = '<div class="alert alert-danger fade show">' +
- '<a class="close" href="#" data-dismiss="alert">×</a>' +
- '<p><strong>Holy guacamole!</strong> Best check yo self, you\'re not looking too good.</p>' +
- '</div>'
-
- var $alert = $(alertHTML).bootstrapAlert().appendTo($('#qunit-fixture'))
-
- var closeBtn = $alert.find('.close')[0]
- closeBtn.dispatchEvent(new Event('click'))
- assert.strictEqual($alert.hasClass('show'), false, 'remove .show class on .close click')
- })
-
- QUnit.test('should remove element when clicking .close', function (assert) {
- assert.expect(2)
- var done = assert.async()
- var alertHTML = '<div class="alert alert-danger fade show">' +
- '<a class="close" href="#" data-dismiss="alert">×</a>' +
- '<p><strong>Holy guacamole!</strong> Best check yo self, you\'re not looking too good.</p>' +
- '</div>'
- var $alert = $(alertHTML).appendTo('#qunit-fixture').bootstrapAlert()
-
- assert.notEqual($('#qunit-fixture').find('.alert').length, 0, 'element added to dom')
-
- $alert[0].addEventListener('closed.bs.alert', function () {
- assert.strictEqual($('#qunit-fixture').find('.alert').length, 0, 'element removed from dom')
- done()
- })
-
- var closeBtn = $alert.find('.close')[0]
- closeBtn.dispatchEvent(new Event('click'))
- })
-
- QUnit.test('should not fire closed when close is prevented', function (assert) {
- assert.expect(1)
- var done = assert.async()
- var $alert = $('<div class="alert"/>')
- $alert.appendTo('#qunit-fixture')
-
- $alert[0].addEventListener('close.bs.alert', function (e) {
- e.preventDefault()
- assert.ok(true, 'close event fired')
- done()
- })
- $alert[0].addEventListener('closed.bs.alert', function () {
- assert.ok(false, 'closed event fired')
- })
-
- $alert.bootstrapAlert('close')
- })
-
- QUnit.test('close should use internal _element if no element provided', function (assert) {
- assert.expect(1)
-
- var done = assert.async()
- var $el = $('<div/>')
- var $alert = $el.bootstrapAlert()
- var alertInstance = Alert._getInstance($alert[0])
-
- $alert[0].addEventListener('closed.bs.alert', function () {
- assert.ok('alert closed')
- done()
- })
-
- alertInstance.close()
- })
-
- QUnit.test('dispose should remove data and the element', function (assert) {
- assert.expect(2)
-
- var $el = $('<div/>')
- var $alert = $el.bootstrapAlert()
-
- assert.ok(typeof Alert._getInstance($alert[0]) !== 'undefined')
-
- Alert._getInstance($alert[0]).dispose()
-
- assert.ok(Alert._getInstance($alert[0]) === null)
- })
-
- QUnit.test('should return the version', function (assert) {
- assert.expect(1)
- assert.strictEqual(typeof Alert.VERSION, 'string')
- })
-})
diff --git a/js/tests/unit/dom/data.js b/js/tests/unit/dom/data.js
deleted file mode 100644
index 63d25b292..000000000
--- a/js/tests/unit/dom/data.js
+++ /dev/null
@@ -1,83 +0,0 @@
-$(function () {
- 'use strict'
-
- QUnit.module('data')
-
- QUnit.test('should be defined', function (assert) {
- assert.expect(1)
- assert.ok(Data, 'Data is defined')
- })
-
- QUnit.test('should set data in a element', function (assert) {
- assert.expect(1)
-
- var $div = $('<div />').appendTo('#qunit-fixture')
- var data = {
- test: 'bsData'
- }
- Data.setData($div[0], 'test', data)
-
- assert.ok($div[0].key, 'element have a data key')
- })
-
- QUnit.test('should get data from an element', function (assert) {
- assert.expect(1)
-
- var $div = $('<div />').appendTo('#qunit-fixture')
- var data = {
- test: 'bsData'
- }
- Data.setData($div[0], 'test', data)
-
- assert.strictEqual(Data.getData($div[0], 'test'), data)
- })
-
- QUnit.test('should return null if nothing is stored', function (assert) {
- assert.expect(1)
- assert.ok(Data.getData(document.body, 'test') === null)
- })
-
- QUnit.test('should return null if nothing is stored with an existing key', function (assert) {
- assert.expect(1)
-
- var $div = $('<div />').appendTo('#qunit-fixture')
- $div[0].key = {
- key: 'test2',
- data: 'woot woot'
- }
-
- assert.ok(Data.getData($div[0], 'test') === null)
- })
-
- QUnit.test('should delete data', function (assert) {
- assert.expect(2)
-
- var $div = $('<div />').appendTo('#qunit-fixture')
- var data = {
- test: 'bsData'
- }
- Data.setData($div[0], 'test', data)
- assert.ok(Data.getData($div[0], 'test') !== null)
-
- Data.removeData($div[0], 'test')
- assert.ok(Data.getData($div[0], 'test') === null)
- })
-
- QUnit.test('should delete nothing if there are nothing', function (assert) {
- assert.expect(0)
-
- Data.removeData(document.body, 'test')
- })
-
- QUnit.test('should delete nothing if not the good key', function (assert) {
- assert.expect(0)
-
- var $div = $('<div />').appendTo('#qunit-fixture')
- $div[0].key = {
- key: 'test2',
- data: 'woot woot'
- }
-
- Data.removeData($div[0], 'test')
- })
-})
diff --git a/js/tests/unit/dom/event-handler.js b/js/tests/unit/dom/event-handler.js
deleted file mode 100644
index 076a28a8a..000000000
--- a/js/tests/unit/dom/event-handler.js
+++ /dev/null
@@ -1,340 +0,0 @@
-$(function () {
- 'use strict'
-
- QUnit.module('eventHandler')
-
- QUnit.test('should be defined', function (assert) {
- assert.expect(1)
- assert.ok(EventHandler, 'EventHandler is defined')
- })
-
- QUnit.test('should trigger event correctly', function (assert) {
- assert.expect(1)
-
- var element = document.createElement('div')
- element.addEventListener('foobar', function () {
- assert.ok(true, 'listener called')
- })
-
- EventHandler.trigger(element, 'foobar')
- })
-
- QUnit.test('should trigger event through jQuery event system', function (assert) {
- assert.expect(1)
-
- var element = document.createElement('div')
- $(element).on('foobar', function () {
- assert.ok(true, 'listener called')
- })
-
- EventHandler.trigger(element, 'foobar')
- })
-
- QUnit.test('should trigger namespaced event through jQuery event system', function (assert) {
- assert.expect(2)
-
- var element = document.createElement('div')
- $(element).on('foobar.namespace', function () {
- assert.ok(true, 'first listener called')
- })
- element.addEventListener('foobar.namespace', function () {
- assert.ok(true, 'second listener called')
- })
-
- EventHandler.trigger(element, 'foobar.namespace')
- })
-
- QUnit.test('should mirror preventDefault', function (assert) {
- assert.expect(2)
-
- var element = document.createElement('div')
- $(element).on('foobar.namespace', function (event) {
- event.preventDefault()
- assert.ok(true, 'first listener called')
- })
- element.addEventListener('foobar.namespace', function (event) {
- assert.ok(event.defaultPrevented, 'defaultPrevented is true in second listener')
- })
-
- EventHandler.trigger(element, 'foobar.namespace')
- })
-
- QUnit.test('should mirror preventDefault for native events', function (assert) {
- assert.expect(2)
-
- var element = document.createElement('div')
- document.body.appendChild(element)
-
- $(element).on('click', function (event) {
- event.preventDefault()
- assert.ok(true, 'first listener called')
- })
- element.addEventListener('click', function (event) {
- assert.ok(event.defaultPrevented, 'defaultPrevented is true in second listener')
- })
-
- EventHandler.trigger(element, 'click')
- document.body.removeChild(element)
- })
-
- QUnit.test('on should add event listener', function (assert) {
- assert.expect(1)
-
- var element = document.createElement('div')
- EventHandler.on(element, 'foobar', function () {
- assert.ok(true, 'listener called')
- })
-
- EventHandler.trigger(element, 'foobar')
- })
-
- QUnit.test('on should add namespaced event listener', function (assert) {
- assert.expect(1)
-
- var element = document.createElement('div')
- EventHandler.on(element, 'foobar.namespace', function () {
- assert.ok(true, 'listener called')
- })
-
- EventHandler.trigger(element, 'foobar.namespace')
- })
-
- QUnit.test('on should add native namespaced event listener', function (assert) {
- assert.expect(1)
-
- var element = document.createElement('div')
- document.body.appendChild(element)
- EventHandler.on(element, 'click.namespace', function () {
- assert.ok(true, 'listener called')
- })
-
- EventHandler.trigger(element, 'click')
- document.body.removeChild(element)
- })
-
- QUnit.test('on should add delegated event listener', function (assert) {
- assert.expect(1)
-
- var element = document.createElement('div')
- var subelement = document.createElement('span')
- element.appendChild(subelement)
-
- var anchor = document.createElement('a')
- element.appendChild(anchor)
-
- EventHandler.on(element, 'click.namespace', 'a', function () {
- assert.ok(true, 'listener called')
- })
-
- EventHandler.on(element, 'click', 'span', function () {
- assert.notOk(true, 'listener should not be called')
- })
-
- document.body.appendChild(element)
- EventHandler.trigger(anchor, 'click')
- document.body.removeChild(element)
- })
-
- QUnit.test('on should add delegated event listener if delegated selector differs', function (assert) {
- assert.expect(1)
-
- var element = document.createElement('div')
- var subelement = document.createElement('span')
- element.appendChild(subelement)
-
- var anchor = document.createElement('a')
- element.appendChild(anchor)
-
- var i = 0
- var handler = function () {
- i++
- }
-
- EventHandler.on(element, 'click', 'a', handler)
- EventHandler.on(element, 'click', 'span', handler)
-
- document.body.appendChild(element)
- EventHandler.trigger(anchor, 'click')
- EventHandler.trigger(subelement, 'click')
- document.body.removeChild(element)
-
- assert.ok(i === 2, 'listeners called')
- })
-
- QUnit.test('one should remove the listener after the event', function (assert) {
- assert.expect(1)
-
- var element = document.createElement('div')
- EventHandler.one(element, 'foobar', function () {
- assert.ok(true, 'listener called')
- })
-
- EventHandler.trigger(element, 'foobar')
- EventHandler.trigger(element, 'foobar')
- })
-
- QUnit.test('off should remove a listener', function (assert) {
- assert.expect(1)
-
- var element = document.createElement('div')
- var handler = function () {
- assert.ok(true, 'listener called')
- }
-
- EventHandler.on(element, 'foobar', handler)
- EventHandler.trigger(element, 'foobar')
-
- EventHandler.off(element, 'foobar', handler)
- EventHandler.trigger(element, 'foobar')
- })
-
- QUnit.test('off should remove all the listeners', function (assert) {
- assert.expect(2)
-
- var element = document.createElement('div')
-
- EventHandler.on(element, 'foobar', function () {
- assert.ok(true, 'first listener called')
- })
- EventHandler.on(element, 'foobar', function () {
- assert.ok(true, 'second listener called')
- })
- EventHandler.trigger(element, 'foobar')
-
- EventHandler.off(element, 'foobar')
- EventHandler.trigger(element, 'foobar')
- })
-
- QUnit.test('off should remove all the namespaced listeners if namespace is passed', function (assert) {
- assert.expect(2)
-
- var element = document.createElement('div')
-
- EventHandler.on(element, 'foobar.namespace', function () {
- assert.ok(true, 'first listener called')
- })
- EventHandler.on(element, 'foofoo.namespace', function () {
- assert.ok(true, 'second listener called')
- })
- EventHandler.trigger(element, 'foobar.namespace')
- EventHandler.trigger(element, 'foofoo.namespace')
-
- EventHandler.off(element, '.namespace')
- EventHandler.trigger(element, 'foobar.namespace')
- EventHandler.trigger(element, 'foofoo.namespace')
- })
-
- QUnit.test('off should remove the namespaced listeners', function (assert) {
- assert.expect(2)
-
- var element = document.createElement('div')
-
- EventHandler.on(element, 'foobar.namespace', function () {
- assert.ok(true, 'first listener called')
- })
- EventHandler.on(element, 'foofoo.namespace', function () {
- assert.ok(true, 'second listener called')
- })
- EventHandler.trigger(element, 'foobar.namespace')
-
- EventHandler.off(element, 'foobar.namespace')
- EventHandler.trigger(element, 'foobar.namespace')
-
- EventHandler.trigger(element, 'foofoo.namespace')
- })
-
- QUnit.test('off should remove the all the namespaced listeners for native events', function (assert) {
- assert.expect(2)
-
- var element = document.createElement('div')
- document.body.appendChild(element)
-
- EventHandler.on(element, 'click.namespace', function () {
- assert.ok(true, 'first listener called')
- })
- EventHandler.on(element, 'click.namespace2', function () {
- assert.ok(true, 'second listener called')
- })
- EventHandler.trigger(element, 'click')
-
- EventHandler.off(element, 'click')
- EventHandler.trigger(element, 'click')
- document.body.removeChild(element)
- })
-
- QUnit.test('off should remove the specified namespaced listeners for native events', function (assert) {
- assert.expect(3)
-
- var element = document.createElement('div')
- document.body.appendChild(element)
-
- EventHandler.on(element, 'click.namespace', function () {
- assert.ok(true, 'first listener called')
- })
- EventHandler.on(element, 'click.namespace2', function () {
- assert.ok(true, 'second listener called')
- })
- EventHandler.trigger(element, 'click')
-
- EventHandler.off(element, 'click.namespace')
- EventHandler.trigger(element, 'click')
- document.body.removeChild(element)
- })
-
- QUnit.test('off should remove a listener registered by .one', function (assert) {
- assert.expect(0)
-
- var element = document.createElement('div')
- var handler = function () {
- assert.notOk(true, 'listener called')
- }
-
- EventHandler.one(element, 'foobar', handler)
- EventHandler.off(element, 'foobar', handler)
-
- EventHandler.trigger(element, 'foobar')
- })
-
- QUnit.test('off should remove the correct delegated event listener', function (assert) {
- assert.expect(5)
-
- var element = document.createElement('div')
- var subelement = document.createElement('span')
- element.appendChild(subelement)
-
- var anchor = document.createElement('a')
- element.appendChild(anchor)
-
- var i = 0
- var handler = function () {
- i++
- }
-
- EventHandler.on(element, 'click', 'a', handler)
- EventHandler.on(element, 'click', 'span', handler)
-
- document.body.appendChild(element)
-
- EventHandler.trigger(anchor, 'click')
- EventHandler.trigger(subelement, 'click')
- assert.ok(i === 2, 'first listeners called')
-
- EventHandler.off(element, 'click', 'span', handler)
- EventHandler.trigger(subelement, 'click')
- assert.ok(i === 2, 'removed listener not called')
-
- EventHandler.trigger(anchor, 'click')
- assert.ok(i === 3, 'not removed listener called')
-
- EventHandler.on(element, 'click', 'span', handler)
- EventHandler.trigger(anchor, 'click')
- EventHandler.trigger(subelement, 'click')
- assert.ok(i === 5, 'listener re-registered')
-
- EventHandler.off(element, 'click', 'span')
- EventHandler.trigger(subelement, 'click')
- assert.ok(i === 5, 'listener removed again')
-
- document.body.removeChild(element)
- })
-})
diff --git a/js/tests/unit/util/index.js b/js/tests/unit/util/index.js
deleted file mode 100644
index 19d3891d3..000000000
--- a/js/tests/unit/util/index.js
+++ /dev/null
@@ -1,191 +0,0 @@
-$(function () {
- 'use strict'
-
- QUnit.module('util', {
- afterEach: function () {
- $('#qunit-fixture').html('')
- }
- })
-
- QUnit.test('Util.getSelectorFromElement should return the correct element', function (assert) {
- assert.expect(2)
-
- var $el = $('<div data-target="body"></div>').appendTo($('#qunit-fixture'))
- assert.strictEqual(Util.getSelectorFromElement($el[0]), 'body')
-
- // Not found element
- var $el2 = $('<div data-target="#fakeDiv"></div>').appendTo($('#qunit-fixture'))
- assert.strictEqual(Util.getSelectorFromElement($el2[0]), null)
- })
-
- QUnit.test('Util.getSelectorFromElement should return null when there is a bad selector', function (assert) {
- assert.expect(2)
-
- var $el = $('<div data-target="#1"></div>').appendTo($('#qunit-fixture'))
-
- assert.strictEqual(Util.getSelectorFromElement($el[0]), null)
-
- var $el2 = $('<a href="/posts"></a>').appendTo($('#qunit-fixture'))
-
- assert.strictEqual(Util.getSelectorFromElement($el2[0]), null)
- })
-
- QUnit.test('Util.typeCheckConfig should thrown an error when a bad config is passed', function (assert) {
- assert.expect(1)
- var namePlugin = 'collapse'
- var defaultType = {
- toggle: 'boolean',
- parent: '(string|element)'
- }
- var config = {
- toggle: true,
- parent: 777
- }
-
- try {
- Util.typeCheckConfig(namePlugin, config, defaultType)
- } catch (error) {
- assert.strictEqual(error.message, 'COLLAPSE: Option "parent" provided type "number" but expected type "(string|element)".')
- }
- })
-
- QUnit.test('Util.isElement should check if we passed an element or not', function (assert) {
- assert.expect(3)
- var $div = $('<div id="test"></div>').appendTo($('#qunit-fixture'))
-
- assert.strictEqual(Util.isElement($div), 1)
- assert.strictEqual(Util.isElement($div[0]), 1)
- assert.strictEqual(typeof Util.isElement({}) === 'undefined', true)
- })
-
- QUnit.test('Util.getTransitionDurationFromElement should accept transition durations in milliseconds', function (assert) {
- assert.expect(1)
- var $div = $('<div style="transition: all 300ms ease-out;"></div>').appendTo($('#qunit-fixture'))
-
- assert.strictEqual(Util.getTransitionDurationFromElement($div[0]), 300)
- })
-
- QUnit.test('Util.getTransitionDurationFromElement should accept transition durations in seconds', function (assert) {
- assert.expect(1)
- var $div = $('<div style="transition: all .4s ease-out;"></div>').appendTo($('#qunit-fixture'))
-
- assert.strictEqual(Util.getTransitionDurationFromElement($div[0]), 400)
- })
-
- QUnit.test('Util.getTransitionDurationFromElement should return the addition of transition-delay and transition-duration', function (assert) {
- assert.expect(2)
- var $fixture = $('#qunit-fixture')
- var $div = $('<div style="transition: all 0s 150ms ease-out;"></div>').appendTo($fixture)
- var $div2 = $('<div style="transition: all .25s 30ms ease-out;"></div>').appendTo($fixture)
-
- assert.strictEqual(Util.getTransitionDurationFromElement($div[0]), 150)
- assert.strictEqual(Util.getTransitionDurationFromElement($div2[0]), 280)
- })
-
- QUnit.test('Util.getTransitionDurationFromElement should get the first transition duration if multiple transition durations are defined', function (assert) {
- assert.expect(1)
- var $div = $('<div style="transition: transform .3s ease-out, opacity .2s;"></div>').appendTo($('#qunit-fixture'))
-
- assert.strictEqual(Util.getTransitionDurationFromElement($div[0]), 300)
- })
-
- QUnit.test('Util.getTransitionDurationFromElement should return 0 if transition duration is not defined', function (assert) {
- assert.expect(1)
- var $div = $('<div></div>').appendTo($('#qunit-fixture'))
-
- assert.strictEqual(Util.getTransitionDurationFromElement($div[0]), 0)
- })
-
- QUnit.test('Util.getTransitionDurationFromElement should return 0 if element is not found in DOM', function (assert) {
- assert.expect(1)
- var $div = $('#fake-id')
-
- assert.strictEqual(Util.getTransitionDurationFromElement($div[0]), 0)
- })
-
- QUnit.test('Util.getUID should generate a new id uniq', function (assert) {
- assert.expect(2)
- var id = Util.getUID('test')
- var id2 = Util.getUID('test')
-
- assert.ok(id !== id2, id + ' !== ' + id2)
-
- id = Util.getUID('test')
- $('<div id="' + id + '"></div>').appendTo($('#qunit-fixture'))
-
- id2 = Util.getUID('test')
- assert.ok(id !== id2, id + ' !== ' + id2)
- })
-
- QUnit.test('Util.findShadowRoot should find the shadow DOM root', function (assert) {
- // Only for newer browsers
- if (!document.documentElement.attachShadow) {
- assert.expect(0)
- return
- }
-
- assert.expect(2)
- var $div = $('<div id="test"></div>').appendTo($('#qunit-fixture'))
- var shadowRoot = $div[0].attachShadow({
- mode: 'open'
- })
-
- assert.equal(shadowRoot, Util.findShadowRoot(shadowRoot))
- shadowRoot.innerHTML = '<button>Shadow Button</button>'
- assert.equal(shadowRoot, Util.findShadowRoot(shadowRoot.firstChild))
- })
-
- QUnit.test('Util.findShadowRoot should return null when attachShadow is not available', function (assert) {
- assert.expect(1)
-
- var $div = $('<div id="test"></div>').appendTo($('#qunit-fixture'))
- if (document.documentElement.attachShadow) {
- var sandbox = sinon.createSandbox()
-
- sandbox.replace(document.documentElement, 'attachShadow', function () {
- // to avoid empty function
- return $div
- })
-
- assert.equal(null, Util.findShadowRoot($div[0]))
- sandbox.restore()
- } else {
- assert.equal(null, Util.findShadowRoot($div[0]))
- }
- })
-
- QUnit.test('noop should return an empty function', function (assert) {
- assert.expect(1)
- Util.noop().call()
- assert.ok(typeof Util.noop() === 'function')
- })
-
- QUnit.test('should return jQuery if present', function (assert) {
- assert.expect(2)
-
- assert.equal(Util.jQuery, $)
-
- $.noConflict()
-
- assert.equal(Util.jQuery, jQuery)
-
- window.$ = jQuery
- })
-
- QUnit.test('Util.emulateTransitionEnd should emulate transition end', function (assert) {
- assert.expect(1)
- var $div = $('<div></div>').appendTo($('#qunit-fixture'))
-
- var spy = sinon.spy($div[0], 'removeEventListener')
-
- Util.emulateTransitionEnd($div[0], 7)
-
- assert.ok(spy.notCalled)
- })
-
- QUnit.test('Util.makeArray should return empty array on null', function (assert) {
- assert.expect(1)
-
- assert.ok(Util.makeArray(null).length === 0)
- })
-})
diff --git a/js/tests/unit/util/sanitizer.js b/js/tests/unit/util/sanitizer.js
deleted file mode 100644
index 4120f0767..000000000
--- a/js/tests/unit/util/sanitizer.js
+++ /dev/null
@@ -1,51 +0,0 @@
-$(function () {
- 'use strict'
-
- QUnit.module('sanitizer', {
- afterEach: function () {
- $('#qunit-fixture').html('')
- }
- })
-
- QUnit.test('should export a default white list', function (assert) {
- assert.expect(1)
-
- assert.ok(Sanitizer.DefaultWhitelist)
- })
-
- QUnit.test('should sanitize template by removing tags with XSS', function (assert) {
- assert.expect(1)
-
- var template = [
- '<div>',
- ' <a href="javascript:alert(7)">Click me</a>',
- ' <span>Some content</span>',
- '</div>'
- ].join('')
-
- var result = Sanitizer.sanitizeHtml(template, Sanitizer.DefaultWhitelist, null)
-
- assert.strictEqual(result.indexOf('script'), -1)
- })
-
- QUnit.test('should not use native api to sanitize if a custom function passed', function (assert) {
- assert.expect(2)
-
- var template = [
- '<div>',
- ' <span>Some content</span>',
- '</div>'
- ].join('')
-
- function mySanitize(htmlUnsafe) {
- return htmlUnsafe
- }
-
- var spy = sinon.spy(DOMParser.prototype, 'parseFromString')
- var result = Sanitizer.sanitizeHtml(template, Sanitizer.DefaultWhitelist, mySanitize)
-
- assert.strictEqual(result, template)
- assert.strictEqual(spy.called, false)
- spy.restore()
- })
-})