Switch from QUnit to Jasmine.

This commit is contained in:
Johann-S 2019-03-13 16:23:50 +02:00
parent 08d81c8437
commit c8c2074650
22 changed files with 1435 additions and 1092 deletions

View File

@ -11,10 +11,5 @@ module.exports = {
],
plugins: [
'@babel/plugin-proposal-object-rest-spread'
],
env: {
test: {
plugins: [ 'istanbul' ]
}
}
]
};

View File

@ -12,7 +12,6 @@ const rollup = require('rollup')
const babel = require('rollup-plugin-babel')
const banner = require('./banner.js')
const TEST = process.env.NODE_ENV === 'test'
const plugins = [
babel({
// Only transpile our source code
@ -33,7 +32,7 @@ const bsPlugins = {
Manipulator: path.resolve(__dirname, '../js/src/dom/manipulator.js'),
Polyfill: path.resolve(__dirname, '../js/src/dom/polyfill.js'),
SelectorEngine: path.resolve(__dirname, '../js/src/dom/selector-engine.js'),
Alert: path.resolve(__dirname, '../js/src/alert.js'),
Alert: path.resolve(__dirname, '../js/src/alert/alert.js'),
Button: path.resolve(__dirname, '../js/src/button.js'),
Carousel: path.resolve(__dirname, '../js/src/carousel.js'),
Collapse: path.resolve(__dirname, '../js/src/collapse.js'),
@ -45,12 +44,7 @@ const bsPlugins = {
Toast: path.resolve(__dirname, '../js/src/toast.js'),
Tooltip: path.resolve(__dirname, '../js/src/tooltip.js')
}
const rootPath = TEST ? '../js/coverage/dist/' : '../js/dist/'
if (TEST) {
bsPlugins.Util = path.resolve(__dirname, '../js/src/util/index.js')
bsPlugins.Sanitizer = path.resolve(__dirname, '../js/src/util/sanitizer.js')
}
const rootPath = '../js/dist/'
const defaultPluginConfig = {
external: [

View File

@ -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'

View File

@ -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'

14
js/src/.eslintrc.json Normal file
View File

@ -0,0 +1,14 @@
{
"root": true,
"extends": [
"../../.eslintrc.json"
],
"overrides": [
{
"files": ["**/*.spec.js"],
"env": {
"jasmine": true
}
}
]
}

View File

@ -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

127
js/src/alert/alert.spec.js Normal file
View File

@ -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()
})
})
})

131
js/src/dom/data.spec.js Normal file
View File

@ -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()
})
})
})

View File

@ -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

View File

@ -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)
})
})
})

297
js/src/util/index.spec.js Normal file
View File

@ -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)
})
})
})

View File

@ -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()
})
})
})

View File

@ -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()
describe('_getInstance', () => {
it('should return null if there is no instance', () => {
// Make assertion
expect(Tab._getInstance(fixtureEl)).toEqual(null)
})
$alert.find('.close').trigger('click')
it('should return this instance', () => {
fixtureEl.innerHTML = '<div></div>'
// Make assertion
assert.strictEqual($alert.hasClass('show'), false, 'remove .show class on .close click')
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()
it('should show a tooltip without the animation', done => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>'
var $tooltip = $('<div title="tooltip title"></div>').bootstrapTooltip()
var tooltipInstance = $tooltip.data('bs.tooltip')
var spyShow = sinon.spy(tooltipInstance, 'show')
const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl, {
animation: false
})
$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')
tooltipEl.addEventListener('shown.bs.tooltip', () => {
const tip = document.querySelector('.tooltip')
expect(tip).toBeDefined()
expect(tip.classList.contains('fade')).toEqual(false)
done()
})
tooltip.show()
})
```

View File

@ -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 = ''
}

View File

@ -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
}

View File

@ -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')
})
})

View File

@ -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')
})
})

View File

@ -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)
})
})

View File

@ -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)
})
})

View File

@ -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()
})
})

516
package-lock.json generated
View File

@ -854,42 +854,6 @@
"integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==",
"dev": true
},
"@sinonjs/commons": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.4.0.tgz",
"integrity": "sha512-9jHK3YF/8HtJ9wCAbG+j8cD0i0+ATS9A7gXFqS36TblLPNy6rEEc+SB0imo91eCboGaBYGV/MT1/br/J+EE7Tw==",
"dev": true,
"requires": {
"type-detect": "4.0.8"
}
},
"@sinonjs/formatio": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.1.tgz",
"integrity": "sha512-tsHvOB24rvyvV2+zKMmPkZ7dXX6LSLKZ7aOtXY6Edklp0uRcgGpOsQTTGTcWViFyx4uhWc6GV8QdnALbIbIdeQ==",
"dev": true,
"requires": {
"@sinonjs/commons": "^1",
"@sinonjs/samsam": "^3.1.0"
}
},
"@sinonjs/samsam": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.2.tgz",
"integrity": "sha512-ILO/rR8LfAb60Y1Yfp9vxfYAASK43NFC2mLzpvLUbCQY/Qu8YwReboseu8aheCEkyElZF2L2T9mHcR2bgdvZyA==",
"dev": true,
"requires": {
"@sinonjs/commons": "^1.0.2",
"array-from": "^2.1.1",
"lodash": "^4.17.11"
}
},
"@sinonjs/text-encoding": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz",
"integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==",
"dev": true
},
"@szmarczak/http-timer": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz",
@ -1224,12 +1188,6 @@
"integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=",
"dev": true
},
"array-from": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz",
"integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=",
"dev": true
},
"array-includes": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz",
@ -1395,6 +1353,50 @@
}
}
},
"babel-code-frame": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
"integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=",
"dev": true,
"requires": {
"chalk": "^1.1.3",
"esutils": "^2.0.2",
"js-tokens": "^3.0.2"
},
"dependencies": {
"ansi-styles": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
"dev": true
},
"chalk": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true,
"requires": {
"ansi-styles": "^2.2.1",
"escape-string-regexp": "^1.0.2",
"has-ansi": "^2.0.0",
"strip-ansi": "^3.0.0",
"supports-color": "^2.0.0"
}
},
"js-tokens": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
"integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=",
"dev": true
},
"supports-color": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
"dev": true
}
}
},
"babel-eslint": {
"version": "10.0.2",
"resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.0.2.tgz",
@ -1409,6 +1411,39 @@
"eslint-visitor-keys": "^1.0.0"
}
},
"babel-generator": {
"version": "6.26.1",
"resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz",
"integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==",
"dev": true,
"requires": {
"babel-messages": "^6.23.0",
"babel-runtime": "^6.26.0",
"babel-types": "^6.26.0",
"detect-indent": "^4.0.0",
"jsesc": "^1.3.0",
"lodash": "^4.17.4",
"source-map": "^0.5.7",
"trim-right": "^1.0.1"
},
"dependencies": {
"jsesc": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz",
"integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=",
"dev": true
}
}
},
"babel-messages": {
"version": "6.23.0",
"resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz",
"integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=",
"dev": true,
"requires": {
"babel-runtime": "^6.22.0"
}
},
"babel-plugin-dynamic-import-node": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz",
@ -1430,6 +1465,88 @@
"test-exclude": "^5.2.3"
}
},
"babel-runtime": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
"integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=",
"dev": true,
"requires": {
"core-js": "^2.4.0",
"regenerator-runtime": "^0.11.0"
},
"dependencies": {
"core-js": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz",
"integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==",
"dev": true
}
}
},
"babel-template": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz",
"integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=",
"dev": true,
"requires": {
"babel-runtime": "^6.26.0",
"babel-traverse": "^6.26.0",
"babel-types": "^6.26.0",
"babylon": "^6.18.0",
"lodash": "^4.17.4"
}
},
"babel-traverse": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz",
"integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=",
"dev": true,
"requires": {
"babel-code-frame": "^6.26.0",
"babel-messages": "^6.23.0",
"babel-runtime": "^6.26.0",
"babel-types": "^6.26.0",
"babylon": "^6.18.0",
"debug": "^2.6.8",
"globals": "^9.18.0",
"invariant": "^2.2.2",
"lodash": "^4.17.4"
},
"dependencies": {
"globals": {
"version": "9.18.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz",
"integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==",
"dev": true
}
}
},
"babel-types": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz",
"integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=",
"dev": true,
"requires": {
"babel-runtime": "^6.26.0",
"esutils": "^2.0.2",
"lodash": "^4.17.4",
"to-fast-properties": "^1.0.3"
},
"dependencies": {
"to-fast-properties": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz",
"integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=",
"dev": true
}
}
},
"babylon": {
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz",
"integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==",
"dev": true
},
"backo2": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
@ -2686,6 +2803,12 @@
"integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==",
"dev": true
},
"debounce": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.0.tgz",
"integrity": "sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg==",
"dev": true
},
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@ -2969,6 +3092,15 @@
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=",
"dev": true
},
"detect-indent": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz",
"integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=",
"dev": true,
"requires": {
"repeating": "^2.0.0"
}
},
"detect-libc": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
@ -2981,12 +3113,6 @@
"integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=",
"dev": true
},
"diff": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
"integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
"dev": true
},
"dir-glob": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz",
@ -6185,10 +6311,10 @@
"is-object": "^1.0.1"
}
},
"jquery": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.4.1.tgz",
"integrity": "sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==",
"jasmine-core": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.4.0.tgz",
"integrity": "sha512-HU/YxV4i6GcmiH4duATwAbJQMlE0MsDIR5XmSVxURxKHn3aGAdbY1/ZJFmVRbKtnLwIxxMJD7gYaPsypcbYimg==",
"dev": true
},
"js-base64": {
@ -6203,12 +6329,6 @@
"integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==",
"dev": true
},
"js-reporters": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/js-reporters/-/js-reporters-1.2.1.tgz",
"integrity": "sha1-+IxgjjJKM3OpW8xFrTBeXJecRZs=",
"dev": true
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@ -6323,12 +6443,6 @@
"verror": "1.10.0"
}
},
"just-extend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz",
"integrity": "sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==",
"dev": true
},
"karma": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/karma/-/karma-4.2.0.tgz",
@ -6528,17 +6642,131 @@
"integrity": "sha512-LbZ5/XlIXLeQ3cqnCbYLn+rOVhuMIK9aZwlP6eOLGzWdo1UVp7t6CN3DP4SafiRLjexKwHeKHDm0c38Mtd3VxA==",
"dev": true
},
"karma-qunit": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/karma-qunit/-/karma-qunit-4.0.0.tgz",
"integrity": "sha512-sGuMyTMbiA2zq/aV43uduwf58mMiMDiG6YH3dvaarQnwFLmyRD1i8356TndX8lFeVSOtykowtBtpcdGxyuezTA==",
"karma-jasmine": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-2.0.1.tgz",
"integrity": "sha512-iuC0hmr9b+SNn1DaUD2QEYtUxkS1J+bSJSn7ejdEexs7P8EYvA1CWkEdrDQ+8jVH3AgWlCNwjYsT1chjcNW9lA==",
"dev": true,
"requires": {
"jasmine-core": "^3.3"
}
},
"karma-jasmine-html-reporter": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-1.4.2.tgz",
"integrity": "sha512-7g0gPj8+9JepCNJR9WjDyQ2RkZ375jpdurYQyAYv8PorUCadepl8vrD6LmMqOGcM17cnrynBawQYZHaumgDjBw==",
"dev": true
},
"karma-sinon": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/karma-sinon/-/karma-sinon-1.0.5.tgz",
"integrity": "sha1-TjRD8oMP3s/2JNN0cWPxIX2qKpo=",
"dev": true
"karma-rollup-preprocessor": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/karma-rollup-preprocessor/-/karma-rollup-preprocessor-7.0.2.tgz",
"integrity": "sha512-A+kr5FoiMr/S8dIPij/nuzB9PLhkrh3umFowjumAOKBDVQRhPYs3kKmQ82hP3+2MB6CICqeB4MmiIE4iTwUmDQ==",
"dev": true,
"requires": {
"chokidar": "^3.0.2",
"debounce": "^1.2.0"
},
"dependencies": {
"anymatch": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.0.3.tgz",
"integrity": "sha512-c6IvoeBECQlMVuYUjSwimnhmztImpErfxJzWZhIQinIvQWoGOnB0dLIgifbPHQt5heS6mNlaZG16f06H3C8t1g==",
"dev": true,
"requires": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
}
},
"binary-extensions": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz",
"integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==",
"dev": true
},
"braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"dev": true,
"requires": {
"fill-range": "^7.0.1"
}
},
"chokidar": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.0.2.tgz",
"integrity": "sha512-c4PR2egjNjI1um6bamCQ6bUNPDiyofNQruHvKgHQ4gDUP/ITSVSzNsiI5OWtHOsX323i5ha/kk4YmOZ1Ktg7KA==",
"dev": true,
"requires": {
"anymatch": "^3.0.1",
"braces": "^3.0.2",
"fsevents": "^2.0.6",
"glob-parent": "^5.0.0",
"is-binary-path": "^2.1.0",
"is-glob": "^4.0.1",
"normalize-path": "^3.0.0",
"readdirp": "^3.1.1"
}
},
"fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"dev": true,
"requires": {
"to-regex-range": "^5.0.1"
}
},
"fsevents": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.0.7.tgz",
"integrity": "sha512-a7YT0SV3RB+DjYcppwVDLtn13UQnmg0SWZS7ezZD0UjnLwXmy8Zm21GMVGLaFGimIqcvyMQaOJBrop8MyOp1kQ==",
"dev": true,
"optional": true
},
"glob-parent": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.0.0.tgz",
"integrity": "sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg==",
"dev": true,
"requires": {
"is-glob": "^4.0.1"
}
},
"is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
"requires": {
"binary-extensions": "^2.0.0"
}
},
"is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true
},
"readdirp": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.1.1.tgz",
"integrity": "sha512-XXdSXZrQuvqoETj50+JAitxz1UPdt5dupjT6T5nVB+WvjMv2XKYj+s7hPeAVCXvmJrL36O4YYyWlIC3an2ePiQ==",
"dev": true,
"requires": {
"picomatch": "^2.0.4"
}
},
"to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"requires": {
"is-number": "^7.0.0"
}
}
}
},
"keyv": {
"version": "3.0.0",
@ -7143,27 +7371,6 @@
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
"dev": true
},
"nise": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/nise/-/nise-1.5.0.tgz",
"integrity": "sha512-Z3sfYEkLFzFmL8KY6xnSJLRxwQwYBjOXi/24lb62ZnZiGA0JUzGGTI6TBIgfCSMIDl9Jlu8SRmHNACLTemDHww==",
"dev": true,
"requires": {
"@sinonjs/formatio": "^3.1.0",
"@sinonjs/text-encoding": "^0.7.1",
"just-extend": "^4.0.2",
"lolex": "^4.1.0",
"path-to-regexp": "^1.7.0"
},
"dependencies": {
"lolex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/lolex/-/lolex-4.1.0.tgz",
"integrity": "sha512-BYxIEXiVq5lGIXeVHnsFzqa1TxN5acnKnPCdlZSpzm8viNEOhiigupA4vTQ9HEFQ6nLTQ9wQOgBknJgzUYQ9Aw==",
"dev": true
}
}
},
"node-abi": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.9.0.tgz",
@ -7446,12 +7653,6 @@
}
}
},
"node-watch": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/node-watch/-/node-watch-0.6.0.tgz",
"integrity": "sha512-XAgTL05z75ptd7JSVejH1a2Dm1zmXYhuDr9l230Qk6Z7/7GPcnAs/UyJJ4ggsXSvWil8iOzwQLW0zuGUvHpG8g==",
"dev": true
},
"nodemon": {
"version": "1.19.1",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.19.1.tgz",
@ -8357,23 +8558,6 @@
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
"dev": true
},
"path-to-regexp": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz",
"integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=",
"dev": true,
"requires": {
"isarray": "0.0.1"
},
"dependencies": {
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
"dev": true
}
}
},
"path-type": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
@ -9043,36 +9227,6 @@
"integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=",
"dev": true
},
"qunit": {
"version": "2.9.2",
"resolved": "https://registry.npmjs.org/qunit/-/qunit-2.9.2.tgz",
"integrity": "sha512-wTOYHnioWHcx5wa85Wl15IE7D6zTZe2CQlsodS14yj7s2FZ3MviRnQluspBZsueIDEO7doiuzKlv05yfky1R7w==",
"dev": true,
"requires": {
"commander": "2.12.2",
"js-reporters": "1.2.1",
"minimatch": "3.0.4",
"node-watch": "0.6.0",
"resolve": "1.9.0"
},
"dependencies": {
"commander": {
"version": "2.12.2",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.12.2.tgz",
"integrity": "sha512-BFnaq5ZOGcDN7FlrtBT4xxkgIToalIIxwjxLWVJ8bGTpe1LroqMiqQXdA7ygc7CRvaYS+9zfPGFnJqFSayx+AA==",
"dev": true
},
"resolve": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.9.0.tgz",
"integrity": "sha512-TZNye00tI67lwYvzxCxHGjwTNlUV70io54/Ed4j6PscB8xVfuBJpRenI/o6dVk0cY0PYTY27AgCoGGxRnYuItQ==",
"dev": true,
"requires": {
"path-parse": "^1.0.6"
}
}
}
},
"range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@ -9209,6 +9363,12 @@
"regenerate": "^1.4.0"
}
},
"regenerator-runtime": {
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
"integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==",
"dev": true
},
"regenerator-transform": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.1.tgz",
@ -9519,6 +9679,39 @@
"rollup-pluginutils": "^2.8.1"
}
},
"rollup-plugin-istanbul": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/rollup-plugin-istanbul/-/rollup-plugin-istanbul-2.0.1.tgz",
"integrity": "sha512-5X1ExH74moFY/pHqCrAZQ0V8RyEMCmrUSuo1t1bDzv9UHTd54s79ALDaK8N87PgNzA6UuKtBVAP/tsaSYzIUVQ==",
"dev": true,
"requires": {
"istanbul-lib-instrument": "^1.9.1",
"rollup-pluginutils": "^2.0.1"
},
"dependencies": {
"istanbul-lib-coverage": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz",
"integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==",
"dev": true
},
"istanbul-lib-instrument": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz",
"integrity": "sha512-aWHxfxDqvh/ZlxR8BBaEPVSWDPUkGD63VjGQn3jcw8jCp7sHEMKcrj4xfJn/ABzdMEHiQNyvDQhqm5o8+SQg7A==",
"dev": true,
"requires": {
"babel-generator": "^6.18.0",
"babel-template": "^6.16.0",
"babel-traverse": "^6.18.0",
"babel-types": "^6.18.0",
"babylon": "^6.18.0",
"istanbul-lib-coverage": "^1.2.1",
"semver": "^5.3.0"
}
}
}
},
"rollup-plugin-node-resolve": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-5.2.0.tgz",
@ -10057,29 +10250,6 @@
"simple-concat": "^1.0.0"
}
},
"sinon": {
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/sinon/-/sinon-7.3.2.tgz",
"integrity": "sha512-thErC1z64BeyGiPvF8aoSg0LEnptSaWE7YhdWWbWXgelOyThent7uKOnnEh9zBxDbKixtr5dEko+ws1sZMuFMA==",
"dev": true,
"requires": {
"@sinonjs/commons": "^1.4.0",
"@sinonjs/formatio": "^3.2.1",
"@sinonjs/samsam": "^3.3.1",
"diff": "^3.5.0",
"lolex": "^4.0.1",
"nise": "^1.4.10",
"supports-color": "^5.5.0"
},
"dependencies": {
"lolex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/lolex/-/lolex-4.1.0.tgz",
"integrity": "sha512-BYxIEXiVq5lGIXeVHnsFzqa1TxN5acnKnPCdlZSpzm8viNEOhiigupA4vTQ9HEFQ6nLTQ9wQOgBknJgzUYQ9Aw==",
"dev": true
}
}
},
"slash": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
@ -11293,12 +11463,6 @@
"prelude-ls": "~1.1.2"
}
},
"type-detect": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
"integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
"dev": true
},
"type-fest": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz",

View File

@ -47,7 +47,6 @@
"js-compile-standalone-esm": "rollup --environment ESM:true,BUNDLE:false --config build/rollup.config.js --sourcemap",
"js-compile-bundle": "rollup --environment BUNDLE:true --config build/rollup.config.js --sourcemap",
"js-compile-plugins": "node build/build-plugins.js",
"js-compile-plugins-coverage": "cross-env NODE_ENV=test node build/build-plugins.js",
"js-lint": "npm-run-all --continue-on-error --parallel js-lint-*",
"js-lint-main": "eslint --cache --cache-location .cache/.eslintcache js/src js/tests build/",
"js-lint-docs": "eslint --cache --cache-location .cache/.eslintcache site/",
@ -57,10 +56,9 @@
"js-minify-standalone-esm": "terser --compress --mangle --comments \"/^!/\" --source-map \"content=dist/js/bootstrap.esm.js.map,includeSources,url=bootstrap.esm.min.js.map\" --output dist/js/bootstrap.esm.min.js dist/js/bootstrap.esm.js",
"js-minify-bundle": "terser --compress typeofs=false --mangle --comments \"/^!/\" --source-map \"content=dist/js/bootstrap.bundle.js.map,includeSources,url=bootstrap.bundle.min.js.map\" --output dist/js/bootstrap.bundle.min.js dist/js/bootstrap.bundle.js",
"js-minify-docs": "cross-env-shell terser --mangle --comments \\\"/^!/\\\" --output site/static/docs/$npm_package_version_short/assets/js/docs.min.js site/static/docs/$npm_package_version_short/assets/js/vendor/anchor.min.js site/static/docs/$npm_package_version_short/assets/js/vendor/clipboard.min.js site/static/docs/$npm_package_version_short/assets/js/vendor/bs-custom-file-input.min.js \"site/static/docs/$npm_package_version_short/assets/js/src/*.js\"",
"js-test": "npm-run-all js-test-karma* js-test-integration",
"js-test": "npm-run-all js-test-karma js-test-integration",
"js-debug": "cross-env DEBUG=true karma start js/tests/karma.conf.js",
"js-test-karma": "karma start js/tests/karma.conf.js",
"js-test-karma-bundle": "cross-env BUNDLE=true npm run js-test-karma",
"js-test-integration": "rollup --config js/tests/integration/rollup.bundle.js",
"js-test-cloud": "cross-env BROWSER=true npm run js-test-karma",
"lint": "npm-run-all --parallel js-lint css-lint",
@ -127,29 +125,29 @@
"hammer-simulator": "0.0.1",
"hugo-bin": "^0.43.6",
"ip": "^1.1.5",
"jquery": "^3.4.1",
"jasmine-core": "^3.4.0",
"karma": "^4.2.0",
"karma-browserstack-launcher": "1.4.0",
"karma-chrome-launcher": "^3.0.0",
"karma-coverage-istanbul-reporter": "^2.0.6",
"karma-detect-browsers": "^2.3.3",
"karma-firefox-launcher": "^1.1.0",
"karma-qunit": "^4.0.0",
"karma-sinon": "^1.0.5",
"karma-jasmine": "^2.0.1",
"karma-jasmine-html-reporter": "^1.4.2",
"karma-rollup-preprocessor": "^7.0.0",
"linkinator": "^1.5.0",
"node-sass": "^4.12.0",
"nodemon": "^1.19.1",
"npm-run-all": "^4.1.5",
"popper.js": "^1.15.0",
"postcss-cli": "^6.1.3",
"qunit": "^2.9.2",
"rollup": "^1.17.0",
"rollup-plugin-babel": "^4.3.3",
"rollup-plugin-istanbul": "^2.0.1",
"rollup-plugin-node-resolve": "^5.2.0",
"serve": "^11.1.0",
"shelljs": "^0.8.3",
"shx": "^0.3.2",
"sinon": "^7.3.2",
"stylelint": "^10.1.0",
"stylelint-config-twbs-bootstrap": "^0.3.1",
"terser": "^4.1.2",