diff options
-rw-r--r-- | .babelrc.js | 7 | ||||
-rw-r--r-- | build/build-plugins.js | 10 | ||||
-rw-r--r-- | js/index.esm.js | 2 | ||||
-rw-r--r-- | js/index.umd.js | 2 | ||||
-rw-r--r-- | js/src/.eslintrc.json | 14 | ||||
-rw-r--r-- | js/src/alert/alert.js (renamed from js/src/alert.js) | 12 | ||||
-rw-r--r-- | js/src/alert/alert.spec.js | 127 | ||||
-rw-r--r-- | js/src/dom/data.spec.js | 131 | ||||
-rw-r--r-- | js/src/dom/event-handler.js | 4 | ||||
-rw-r--r-- | js/src/dom/event-handler.spec.js | 327 | ||||
-rw-r--r-- | js/src/util/index.spec.js | 297 | ||||
-rw-r--r-- | js/src/util/sanitizer.spec.js | 70 | ||||
-rw-r--r-- | js/tests/README.md | 76 | ||||
-rw-r--r-- | js/tests/helpers/fixture.js | 20 | ||||
-rw-r--r-- | js/tests/karma.conf.js | 118 | ||||
-rw-r--r-- | js/tests/unit/alert.js | 123 | ||||
-rw-r--r-- | js/tests/unit/dom/data.js | 83 | ||||
-rw-r--r-- | js/tests/unit/dom/event-handler.js | 340 | ||||
-rw-r--r-- | js/tests/unit/util/index.js | 191 | ||||
-rw-r--r-- | js/tests/unit/util/sanitizer.js | 51 | ||||
-rw-r--r-- | package-lock.json | 516 | ||||
-rw-r--r-- | package.json | 14 |
22 files changed, 1439 insertions, 1096 deletions
diff --git a/.babelrc.js b/.babelrc.js index 44a5099541..6fdc98a4e7 100644 --- a/.babelrc.js +++ b/.babelrc.js @@ -11,10 +11,5 @@ module.exports = { ], plugins: [ '@babel/plugin-proposal-object-rest-spread' - ], - env: { - test: { - plugins: [ 'istanbul' ] - } - } + ] }; diff --git a/build/build-plugins.js b/build/build-plugins.js index 20f7547fef..471707035b 100644 --- a/build/build-plugins.js +++ b/build/build-plugins.js @@ -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: [ diff --git a/js/index.esm.js b/js/index.esm.js index e49218a1e8..e3a851537c 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 0a1d5ac0b4..039e6d1bb4 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 0000000000..18ffdc003d --- /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 23ac9f25d4..99164bd195 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 0000000000..cb7b57b7f3 --- /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 0000000000..46018dd5ca --- /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 2dff885354..3310b41abd 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 0000000000..dc5c5c30ce --- /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 0000000000..4b2d416856 --- /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 0000000000..6dadd29a5a --- /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 3b079d92ce..3a01d33997 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 0000000000..524d544448 --- /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 e3851663a0..b5b9fb3736 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 c034356187..0000000000 --- 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 63d25b292d..0000000000 --- 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 076a28a8a1..0000000000 --- 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 19d3891d3a..0000000000 --- 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 4120f0767c..0000000000 --- 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() - }) -}) diff --git a/package-lock.json b/package-lock.json index 16e73d3680..22d48079cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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,18 +6642,132 @@ "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==", - "dev": true + "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-sinon": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/karma-sinon/-/karma-sinon-1.0.5.tgz", - "integrity": "sha1-TjRD8oMP3s/2JNN0cWPxIX2qKpo=", + "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-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", "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz", @@ -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", diff --git a/package.json b/package.json index 693bfdce4a..0a9ebfe61e 100644 --- a/package.json +++ b/package.json @@ -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", |