aboutsummaryrefslogtreecommitdiff
path: root/js/tests
diff options
context:
space:
mode:
authorBobby <[email protected]>2024-08-16 20:47:33 -0400
committerGitHub <[email protected]>2024-08-16 20:47:33 -0400
commit6b28433d9cfde435be8ec2bd6cf91e6324d08865 (patch)
tree8343c27b8b95ff5639233e81cf157f92e5688466 /js/tests
parentd53094ec16ba385faae2973ddee648698b32ab24 (diff)
parent048f56f51460df75e92a2f7b472e1c56baeb68f7 (diff)
downloadbootstrap-6b28433d9cfde435be8ec2bd6cf91e6324d08865.tar.xz
bootstrap-6b28433d9cfde435be8ec2bd6cf91e6324d08865.zip
Merge branch 'twbs:main' into mainHEADmain
Diffstat (limited to 'js/tests')
-rw-r--r--js/tests/README.md30
-rw-r--r--js/tests/browsers.js8
-rw-r--r--js/tests/helpers/fixture.js45
-rw-r--r--js/tests/integration/bundle-modularity.js2
-rw-r--r--js/tests/integration/rollup.bundle-modularity.js4
-rw-r--r--js/tests/integration/rollup.bundle.js2
-rw-r--r--js/tests/karma.conf.js46
-rw-r--r--js/tests/unit/.eslintrc.json13
-rw-r--r--js/tests/unit/alert.spec.js132
-rw-r--r--js/tests/unit/base-component.spec.js39
-rw-r--r--js/tests/unit/button.spec.js28
-rw-r--r--js/tests/unit/carousel.spec.js1474
-rw-r--r--js/tests/unit/collapse.spec.js1211
-rw-r--r--js/tests/unit/dom/data.spec.js15
-rw-r--r--js/tests/unit/dom/event-handler.spec.js579
-rw-r--r--js/tests/unit/dom/manipulator.spec.js101
-rw-r--r--js/tests/unit/dom/selector-engine.spec.js234
-rw-r--r--js/tests/unit/dropdown.spec.js3018
-rw-r--r--js/tests/unit/jquery.spec.js56
-rw-r--r--js/tests/unit/modal.spec.js1473
-rw-r--r--js/tests/unit/offcanvas.spec.js797
-rw-r--r--js/tests/unit/popover.spec.js329
-rw-r--r--js/tests/unit/scrollspy.spec.js1195
-rw-r--r--js/tests/unit/tab.spec.js1372
-rw-r--r--js/tests/unit/toast.spec.js696
-rw-r--r--js/tests/unit/tooltip.spec.js1478
-rw-r--r--js/tests/unit/util/backdrop.spec.js441
-rw-r--r--js/tests/unit/util/component-functions.spec.js40
-rw-r--r--js/tests/unit/util/config.spec.js166
-rw-r--r--js/tests/unit/util/focustrap.spec.js266
-rw-r--r--js/tests/unit/util/index.spec.js499
-rw-r--r--js/tests/unit/util/sanitizer.spec.js84
-rw-r--r--js/tests/unit/util/scrollbar.spec.js146
-rw-r--r--js/tests/unit/util/swipe.spec.js230
-rw-r--r--js/tests/unit/util/template-factory.spec.js59
-rw-r--r--js/tests/visual/alert.html6
-rw-r--r--js/tests/visual/button.html10
-rw-r--r--js/tests/visual/carousel.html19
-rw-r--r--js/tests/visual/collapse.html7
-rw-r--r--js/tests/visual/dropdown.html37
-rw-r--r--js/tests/visual/floating-label.html337
-rw-r--r--js/tests/visual/input.html78
-rw-r--r--js/tests/visual/modal.html109
-rw-r--r--js/tests/visual/popover.html16
-rw-r--r--js/tests/visual/scrollspy.html22
-rw-r--r--js/tests/visual/tab.html11
-rw-r--r--js/tests/visual/toast.html33
-rw-r--r--js/tests/visual/tooltip.html75
48 files changed, 9779 insertions, 7289 deletions
diff --git a/js/tests/README.md b/js/tests/README.md
index 1e96a6b5d..79d05d444 100644
--- a/js/tests/README.md
+++ b/js/tests/README.md
@@ -35,7 +35,7 @@ Currently we're aiming for at least 90% test coverage for our code. To ensure yo
describe('getInstance', () => {
it('should return null if there is no instance', () => {
// Make assertion
- expect(Tab.getInstance(fixtureEl)).toEqual(null)
+ expect(Tab.getInstance(fixtureEl)).toBeNull()
})
it('should return this instance', () => {
@@ -50,22 +50,24 @@ describe('getInstance', () => {
})
// Asynchronous test
-it('should show a tooltip without the animation', done => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
+it('should show a tooltip without the animation', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
- const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl, {
- animation: false
- })
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl, {
+ animation: false
+ })
- tooltipEl.addEventListener('shown.bs.tooltip', () => {
- const tip = document.querySelector('.tooltip')
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ const tip = document.querySelector('.tooltip')
- expect(tip).not.toBeNull()
- expect(tip.classList.contains('fade')).toEqual(false)
- done()
- })
+ expect(tip).not.toBeNull()
+ expect(tip.classList.contains('fade')).toEqual(false)
+ resolve()
+ })
- tooltip.show()
+ tooltip.show()
+ })
})
```
diff --git a/js/tests/browsers.js b/js/tests/browsers.js
index 665b26a2e..c515e64a0 100644
--- a/js/tests/browsers.js
+++ b/js/tests/browsers.js
@@ -1,6 +1,7 @@
-/* eslint-env node */
/* eslint-disable camelcase */
+'use strict'
+
const browsers = {
safariMac: {
base: 'BrowserStack',
@@ -74,9 +75,6 @@ const browsers = {
}
}
-const browsersKeys = Object.keys(browsers)
-
module.exports = {
- browsers,
- browsersKeys
+ browsers
}
diff --git a/js/tests/helpers/fixture.js b/js/tests/helpers/fixture.js
index 02915af44..5ad14e1db 100644
--- a/js/tests/helpers/fixture.js
+++ b/js/tests/helpers/fixture.js
@@ -1,41 +1,38 @@
const fixtureId = 'fixture'
export const getFixture = () => {
- let fixtureEl = document.getElementById(fixtureId)
-
- if (!fixtureEl) {
- fixtureEl = document.createElement('div')
- fixtureEl.setAttribute('id', fixtureId)
- fixtureEl.style.position = 'absolute'
- fixtureEl.style.top = '-10000px'
- fixtureEl.style.left = '-10000px'
- fixtureEl.style.width = '10000px'
- fixtureEl.style.height = '10000px'
- document.body.append(fixtureEl)
+ let fixtureElement = document.getElementById(fixtureId)
+
+ if (!fixtureElement) {
+ fixtureElement = document.createElement('div')
+ fixtureElement.setAttribute('id', fixtureId)
+ fixtureElement.style.position = 'absolute'
+ fixtureElement.style.top = '-10000px'
+ fixtureElement.style.left = '-10000px'
+ fixtureElement.style.width = '10000px'
+ fixtureElement.style.height = '10000px'
+ document.body.append(fixtureElement)
}
- return fixtureEl
+ return fixtureElement
}
export const clearFixture = () => {
- const fixtureEl = getFixture()
+ const fixtureElement = getFixture()
- fixtureEl.innerHTML = ''
+ fixtureElement.innerHTML = ''
}
-export const createEvent = (eventName, params = {}) => {
- const event = document.createEvent('Event')
-
- event.initEvent(eventName, Boolean(params.bubbles), Boolean(params.cancelable))
- return event
+export const createEvent = (eventName, parameters = {}) => {
+ return new Event(eventName, parameters)
}
export const jQueryMock = {
elements: undefined,
fn: {},
each(fn) {
- for (const el of this.elements) {
- fn.call(el)
+ for (const element of this.elements) {
+ fn.call(element)
}
}
}
@@ -43,8 +40,8 @@ export const jQueryMock = {
export const clearBodyAndDocument = () => {
const attributes = ['data-bs-padding-right', 'style']
- for (const attr of attributes) {
- document.documentElement.removeAttribute(attr)
- document.body.removeAttribute(attr)
+ for (const attribute of attributes) {
+ document.documentElement.removeAttribute(attribute)
+ document.body.removeAttribute(attribute)
}
}
diff --git a/js/tests/integration/bundle-modularity.js b/js/tests/integration/bundle-modularity.js
index 8546141b1..3c1eec944 100644
--- a/js/tests/integration/bundle-modularity.js
+++ b/js/tests/integration/bundle-modularity.js
@@ -1,3 +1,5 @@
+/* eslint-disable import/extensions, import/no-unassigned-import */
+
import Tooltip from '../../dist/tooltip'
import '../../dist/carousel'
diff --git a/js/tests/integration/rollup.bundle-modularity.js b/js/tests/integration/rollup.bundle-modularity.js
index a8670ca8c..63d651525 100644
--- a/js/tests/integration/rollup.bundle-modularity.js
+++ b/js/tests/integration/rollup.bundle-modularity.js
@@ -1,7 +1,7 @@
-/* eslint-env node */
+'use strict'
const commonjs = require('@rollup/plugin-commonjs')
-const configRollup = require('./rollup.bundle')
+const configRollup = require('./rollup.bundle.js')
const config = {
...configRollup,
diff --git a/js/tests/integration/rollup.bundle.js b/js/tests/integration/rollup.bundle.js
index caddcab48..8b3c578a2 100644
--- a/js/tests/integration/rollup.bundle.js
+++ b/js/tests/integration/rollup.bundle.js
@@ -1,4 +1,4 @@
-/* eslint-env node */
+'use strict'
const { babel } = require('@rollup/plugin-babel')
const { nodeResolve } = require('@rollup/plugin-node-resolve')
diff --git a/js/tests/karma.conf.js b/js/tests/karma.conf.js
index d85f2441c..36bf7f2df 100644
--- a/js/tests/karma.conf.js
+++ b/js/tests/karma.conf.js
@@ -1,18 +1,12 @@
-/* eslint-env node */
-
'use strict'
-const path = require('path')
+const path = require('node:path')
const ip = require('ip')
const { babel } = require('@rollup/plugin-babel')
const istanbul = require('rollup-plugin-istanbul')
const { nodeResolve } = require('@rollup/plugin-node-resolve')
const replace = require('@rollup/plugin-replace')
-
-const {
- browsers,
- browsersKeys
-} = require('./browsers')
+const { browsers } = require('./browsers.js')
const ENV = process.env
const BROWSERSTACK = Boolean(ENV.BROWSERSTACK)
@@ -54,7 +48,7 @@ const detectBrowsers = {
}
}
-const conf = {
+const config = {
basePath: '../..',
port: 9876,
colors: true,
@@ -105,17 +99,17 @@ const conf = {
}
if (BROWSERSTACK) {
- conf.hostname = ip.address()
- conf.browserStack = {
+ config.hostname = ip.address()
+ config.browserStack = {
username: ENV.BROWSER_STACK_USERNAME,
accessKey: ENV.BROWSER_STACK_ACCESS_KEY,
- build: `bootstrap-${ENV.GITHUB_SHA ? ENV.GITHUB_SHA.slice(0, 7) + '-' : ''}${new Date().toISOString()}`,
+ build: `bootstrap-${ENV.GITHUB_SHA ? `${ENV.GITHUB_SHA.slice(0, 7)}-` : ''}${new Date().toISOString()}`,
project: 'Bootstrap',
retryLimit: 2
}
plugins.push('karma-browserstack-launcher', 'karma-jasmine-html-reporter')
- conf.customLaunchers = browsers
- conf.browsers = browsersKeys
+ config.customLaunchers = browsers
+ config.browsers = Object.keys(browsers)
reporters.push('BrowserStack', 'kjhtml')
} else if (JQUERY_TEST) {
frameworks.push('detectBrowsers')
@@ -124,8 +118,8 @@ if (BROWSERSTACK) {
'karma-firefox-launcher',
'karma-detect-browsers'
)
- conf.detectBrowsers = detectBrowsers
- conf.files = [
+ config.detectBrowsers = detectBrowsers
+ config.files = [
'node_modules/jquery/dist/jquery.slim.min.js',
{
pattern: 'js/tests/unit/jquery.spec.js',
@@ -141,8 +135,8 @@ if (BROWSERSTACK) {
'karma-coverage-istanbul-reporter'
)
reporters.push('coverage-istanbul')
- conf.detectBrowsers = detectBrowsers
- conf.coverageIstanbulReporter = {
+ config.detectBrowsers = detectBrowsers
+ config.coverageIstanbulReporter = {
dir: path.resolve(__dirname, '../coverage/'),
reports: ['lcov', 'text-summary'],
thresholds: {
@@ -157,19 +151,19 @@ if (BROWSERSTACK) {
}
if (DEBUG) {
- conf.hostname = ip.address()
+ config.hostname = ip.address()
plugins.push('karma-jasmine-html-reporter')
reporters.push('kjhtml')
- conf.singleRun = false
- conf.autoWatch = true
+ config.singleRun = false
+ config.autoWatch = true
}
}
-conf.frameworks = frameworks
-conf.plugins = plugins
-conf.reporters = reporters
+config.frameworks = frameworks
+config.plugins = plugins
+config.reporters = reporters
module.exports = karmaConfig => {
- conf.logLevel = karmaConfig.LOG_ERROR
- karmaConfig.set(conf)
+ config.logLevel = karmaConfig.LOG_ERROR
+ karmaConfig.set(config)
}
diff --git a/js/tests/unit/.eslintrc.json b/js/tests/unit/.eslintrc.json
deleted file mode 100644
index 6362a1acf..000000000
--- a/js/tests/unit/.eslintrc.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "extends": [
- "../../../.eslintrc.json"
- ],
- "env": {
- "jasmine": true
- },
- "rules": {
- "unicorn/consistent-function-scoping": "off",
- "unicorn/no-useless-undefined": "off",
- "unicorn/prefer-add-event-listener": "off"
- }
-}
diff --git a/js/tests/unit/alert.spec.js b/js/tests/unit/alert.spec.js
index cdda997c9..97cc3cc53 100644
--- a/js/tests/unit/alert.spec.js
+++ b/js/tests/unit/alert.spec.js
@@ -1,6 +1,6 @@
-import Alert from '../../src/alert'
-import { getTransitionDurationFromElement } from '../../src/util/index'
-import { clearFixture, getFixture, jQueryMock } from '../helpers/fixture'
+import Alert from '../../src/alert.js'
+import { getTransitionDurationFromElement } from '../../src/util/index.js'
+import { clearFixture, getFixture, jQueryMock } from '../helpers/fixture.js'
describe('Alert', () => {
let fixtureEl
@@ -25,7 +25,7 @@ describe('Alert', () => {
})
it('should return version', () => {
- expect(typeof Alert.VERSION).toEqual('string')
+ expect(Alert.VERSION).toEqual(jasmine.any(String))
})
describe('DATA_KEY', () => {
@@ -45,7 +45,7 @@ describe('Alert', () => {
const button = document.querySelector('button')
button.click()
- expect(document.querySelectorAll('.alert').length).toEqual(0)
+ expect(document.querySelectorAll('.alert')).toHaveSize(0)
})
it('should close an alert without instantiating it manually with the parent selector', () => {
@@ -58,65 +58,71 @@ describe('Alert', () => {
const button = document.querySelector('button')
button.click()
- expect(document.querySelectorAll('.alert').length).toEqual(0)
+ expect(document.querySelectorAll('.alert')).toHaveSize(0)
})
})
describe('close', () => {
- it('should close an alert', done => {
- const spy = jasmine.createSpy('spy', getTransitionDurationFromElement)
- fixtureEl.innerHTML = '<div class="alert"></div>'
+ it('should close an alert', () => {
+ return new Promise(resolve => {
+ const spy = jasmine.createSpy('spy', getTransitionDurationFromElement)
+ fixtureEl.innerHTML = '<div class="alert"></div>'
- const alertEl = document.querySelector('.alert')
- const alert = new Alert(alertEl)
+ const alertEl = document.querySelector('.alert')
+ const alert = new Alert(alertEl)
- alertEl.addEventListener('closed.bs.alert', () => {
- expect(document.querySelectorAll('.alert').length).toEqual(0)
- expect(spy).not.toHaveBeenCalled()
- done()
- })
+ alertEl.addEventListener('closed.bs.alert', () => {
+ expect(document.querySelectorAll('.alert')).toHaveSize(0)
+ expect(spy).not.toHaveBeenCalled()
+ resolve()
+ })
- alert.close()
+ alert.close()
+ })
})
- it('should close alert with fade class', done => {
- fixtureEl.innerHTML = '<div class="alert fade"></div>'
+ it('should close alert with fade class', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div class="alert fade"></div>'
- const alertEl = document.querySelector('.alert')
- const alert = new Alert(alertEl)
+ const alertEl = document.querySelector('.alert')
+ const alert = new Alert(alertEl)
- alertEl.addEventListener('transitionend', () => {
- expect().nothing()
- })
+ alertEl.addEventListener('transitionend', () => {
+ expect().nothing()
+ })
- alertEl.addEventListener('closed.bs.alert', () => {
- expect(document.querySelectorAll('.alert').length).toEqual(0)
- done()
- })
+ alertEl.addEventListener('closed.bs.alert', () => {
+ expect(document.querySelectorAll('.alert')).toHaveSize(0)
+ resolve()
+ })
- alert.close()
+ alert.close()
+ })
})
- it('should not remove alert if close event is prevented', done => {
- fixtureEl.innerHTML = '<div class="alert"></div>'
+ it('should not remove alert if close event is prevented', () => {
+ return new Promise((resolve, reject) => {
+ fixtureEl.innerHTML = '<div class="alert"></div>'
- const getAlert = () => document.querySelector('.alert')
- const alertEl = getAlert()
- const alert = new Alert(alertEl)
+ const getAlert = () => document.querySelector('.alert')
+ const alertEl = getAlert()
+ const alert = new Alert(alertEl)
- alertEl.addEventListener('close.bs.alert', event => {
- event.preventDefault()
- setTimeout(() => {
- expect(getAlert()).not.toBeNull()
- done()
- }, 10)
- })
+ alertEl.addEventListener('close.bs.alert', event => {
+ event.preventDefault()
+ setTimeout(() => {
+ expect(getAlert()).not.toBeNull()
+ resolve()
+ }, 10)
+ })
- alertEl.addEventListener('closed.bs.alert', () => {
- throw new Error('should not fire closed event')
- })
+ alertEl.addEventListener('closed.bs.alert', () => {
+ reject(new Error('should not fire closed event'))
+ })
- alert.close()
+ alert.close()
+ })
})
})
@@ -142,14 +148,14 @@ describe('Alert', () => {
const alertEl = fixtureEl.querySelector('.alert')
const alert = new Alert(alertEl)
- spyOn(alert, 'close')
+ const spy = spyOn(alert, 'close')
jQueryMock.fn.alert = Alert.jQueryInterface
jQueryMock.elements = [alertEl]
jQueryMock.fn.alert.call(jQueryMock, 'close')
- expect(alert.close).toHaveBeenCalled()
+ expect(spy).toHaveBeenCalled()
})
it('should create new alert instance and call close', () => {
@@ -179,6 +185,34 @@ describe('Alert', () => {
expect(Alert.getInstance(alertEl)).not.toBeNull()
expect(fixtureEl.querySelector('.alert')).not.toBeNull()
})
+
+ it('should throw an error on undefined method', () => {
+ fixtureEl.innerHTML = '<div></div>'
+
+ const div = fixtureEl.querySelector('div')
+ const action = 'undefinedMethod'
+
+ jQueryMock.fn.alert = Alert.jQueryInterface
+ jQueryMock.elements = [div]
+
+ expect(() => {
+ jQueryMock.fn.alert.call(jQueryMock, action)
+ }).toThrowError(TypeError, `No method named "${action}"`)
+ })
+
+ it('should throw an error on protected method', () => {
+ fixtureEl.innerHTML = '<div></div>'
+
+ const div = fixtureEl.querySelector('div')
+ const action = '_getConfig'
+
+ jQueryMock.fn.alert = Alert.jQueryInterface
+ jQueryMock.elements = [div]
+
+ expect(() => {
+ jQueryMock.fn.alert.call(jQueryMock, action)
+ }).toThrowError(TypeError, `No method named "${action}"`)
+ })
})
describe('getInstance', () => {
@@ -197,7 +231,7 @@ describe('Alert', () => {
const div = fixtureEl.querySelector('div')
- expect(Alert.getInstance(div)).toEqual(null)
+ expect(Alert.getInstance(div)).toBeNull()
})
})
@@ -218,7 +252,7 @@ describe('Alert', () => {
const div = fixtureEl.querySelector('div')
- expect(Alert.getInstance(div)).toEqual(null)
+ expect(Alert.getInstance(div)).toBeNull()
expect(Alert.getOrCreateInstance(div)).toBeInstanceOf(Alert)
})
})
diff --git a/js/tests/unit/base-component.spec.js b/js/tests/unit/base-component.spec.js
index b8ec83f12..5b7d52e23 100644
--- a/js/tests/unit/base-component.spec.js
+++ b/js/tests/unit/base-component.spec.js
@@ -1,7 +1,7 @@
-import BaseComponent from '../../src/base-component'
-import { clearFixture, getFixture } from '../helpers/fixture'
-import EventHandler from '../../src/dom/event-handler'
-import { noop } from '../../src/util'
+import BaseComponent from '../../src/base-component.js'
+import EventHandler from '../../src/dom/event-handler.js'
+import { noop } from '../../src/util/index.js'
+import { clearFixture, getFixture } from '../helpers/fixture.js'
class DummyClass extends BaseComponent {
constructor(element) {
@@ -37,7 +37,7 @@ describe('Base Component', () => {
describe('Static Methods', () => {
describe('VERSION', () => {
it('should return version', () => {
- expect(typeof DummyClass.VERSION).toEqual('string')
+ expect(DummyClass.VERSION).toEqual(jasmine.any(String))
})
})
@@ -48,6 +48,13 @@ describe('Base Component', () => {
})
describe('NAME', () => {
+ it('should throw an Error if it is not initialized', () => {
+ expect(() => {
+ // eslint-disable-next-line no-unused-expressions
+ BaseComponent.NAME
+ }).toThrowError(Error)
+ })
+
it('should return plugin NAME', () => {
expect(DummyClass.NAME).toEqual(name)
})
@@ -59,6 +66,7 @@ describe('Base Component', () => {
})
})
})
+
describe('Public Methods', () => {
describe('constructor', () => {
it('should accept element, either passed as a CSS selector or DOM element', () => {
@@ -74,7 +82,19 @@ describe('Base Component', () => {
expect(elInstance._element).toEqual(el)
expect(selectorInstance._element).toEqual(fixtureEl.querySelector('#bar'))
})
+
+ it('should not initialize and add element record to Data (caching), if argument `element` is not an HTML element', () => {
+ fixtureEl.innerHTML = ''
+
+ const el = fixtureEl.querySelector('#foo')
+ const elInstance = new DummyClass(el)
+ const selectorInstance = new DummyClass('#bar')
+
+ expect(elInstance._element).not.toBeDefined()
+ expect(selectorInstance._element).not.toBeDefined()
+ })
})
+
describe('dispose', () => {
it('should dispose an component', () => {
createInstance()
@@ -88,11 +108,11 @@ describe('Base Component', () => {
it('should de-register element event listeners', () => {
createInstance()
- spyOn(EventHandler, 'off')
+ const spy = spyOn(EventHandler, 'off')
instance.dispose()
- expect(EventHandler.off).toHaveBeenCalledWith(element, DummyClass.EVENT_KEY)
+ expect(spy).toHaveBeenCalledWith(element, DummyClass.EVENT_KEY)
})
})
@@ -123,9 +143,10 @@ describe('Base Component', () => {
const div = fixtureEl.querySelector('div')
- expect(DummyClass.getInstance(div)).toEqual(null)
+ expect(DummyClass.getInstance(div)).toBeNull()
})
})
+
describe('getOrCreateInstance', () => {
it('should return an instance', () => {
createInstance()
@@ -139,7 +160,7 @@ describe('Base Component', () => {
fixtureEl.innerHTML = '<div id="foo"></div>'
element = fixtureEl.querySelector('#foo')
- expect(DummyClass.getInstance(element)).toEqual(null)
+ expect(DummyClass.getInstance(element)).toBeNull()
expect(DummyClass.getOrCreateInstance(element)).toBeInstanceOf(DummyClass)
})
})
diff --git a/js/tests/unit/button.spec.js b/js/tests/unit/button.spec.js
index e24ff5cb0..6624fee7c 100644
--- a/js/tests/unit/button.spec.js
+++ b/js/tests/unit/button.spec.js
@@ -1,5 +1,5 @@
-import Button from '../../src/button'
-import { getFixture, clearFixture, jQueryMock } from '../helpers/fixture'
+import Button from '../../src/button.js'
+import { clearFixture, getFixture, jQueryMock } from '../helpers/fixture.js'
describe('Button', () => {
let fixtureEl
@@ -45,19 +45,19 @@ describe('Button', () => {
const divTest = fixtureEl.querySelector('.test')
const btnTestParent = fixtureEl.querySelector('.testParent')
- expect(btn.classList.contains('active')).toEqual(false)
+ expect(btn).not.toHaveClass('active')
btn.click()
- expect(btn.classList.contains('active')).toEqual(true)
+ expect(btn).toHaveClass('active')
btn.click()
- expect(btn.classList.contains('active')).toEqual(false)
+ expect(btn).not.toHaveClass('active')
divTest.click()
- expect(btnTestParent.classList.contains('active')).toEqual(true)
+ expect(btnTestParent).toHaveClass('active')
})
})
@@ -69,12 +69,12 @@ describe('Button', () => {
const button = new Button(btnEl)
expect(btnEl.getAttribute('aria-pressed')).toEqual('false')
- expect(btnEl.classList.contains('active')).toEqual(false)
+ expect(btnEl).not.toHaveClass('active')
button.toggle()
expect(btnEl.getAttribute('aria-pressed')).toEqual('true')
- expect(btnEl.classList.contains('active')).toEqual(true)
+ expect(btnEl).toHaveClass('active')
})
})
@@ -100,14 +100,14 @@ describe('Button', () => {
const btnEl = fixtureEl.querySelector('.btn')
const button = new Button(btnEl)
- spyOn(button, 'toggle')
+ const spy = spyOn(button, 'toggle')
jQueryMock.fn.button = Button.jQueryInterface
jQueryMock.elements = [btnEl]
jQueryMock.fn.button.call(jQueryMock, 'toggle')
- expect(button.toggle).toHaveBeenCalled()
+ expect(spy).toHaveBeenCalled()
})
it('should create new button instance and call toggle', () => {
@@ -121,7 +121,7 @@ describe('Button', () => {
jQueryMock.fn.button.call(jQueryMock, 'toggle')
expect(Button.getInstance(btnEl)).not.toBeNull()
- expect(btnEl.classList.contains('active')).toEqual(true)
+ expect(btnEl).toHaveClass('active')
})
it('should just create a button instance without calling toggle', () => {
@@ -135,7 +135,7 @@ describe('Button', () => {
jQueryMock.fn.button.call(jQueryMock)
expect(Button.getInstance(btnEl)).not.toBeNull()
- expect(btnEl.classList.contains('active')).toEqual(false)
+ expect(btnEl).not.toHaveClass('active')
})
})
@@ -155,7 +155,7 @@ describe('Button', () => {
const div = fixtureEl.querySelector('div')
- expect(Button.getInstance(div)).toEqual(null)
+ expect(Button.getInstance(div)).toBeNull()
})
})
@@ -176,7 +176,7 @@ describe('Button', () => {
const div = fixtureEl.querySelector('div')
- expect(Button.getInstance(div)).toEqual(null)
+ expect(Button.getInstance(div)).toBeNull()
expect(Button.getOrCreateInstance(div)).toBeInstanceOf(Button)
})
})
diff --git a/js/tests/unit/carousel.spec.js b/js/tests/unit/carousel.spec.js
index a138f3ad5..2960eb5ce 100644
--- a/js/tests/unit/carousel.spec.js
+++ b/js/tests/unit/carousel.spec.js
@@ -1,8 +1,10 @@
-import Carousel from '../../src/carousel'
-import EventHandler from '../../src/dom/event-handler'
-import { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture'
-import { isRTL, noop } from '../../src/util/index'
-import Swipe from '../../src/util/swipe'
+import Carousel from '../../src/carousel.js'
+import EventHandler from '../../src/dom/event-handler.js'
+import { isRTL, noop } from '../../src/util/index.js'
+import Swipe from '../../src/util/swipe.js'
+import {
+ clearFixture, createEvent, getFixture, jQueryMock
+} from '../helpers/fixture.js'
describe('Carousel', () => {
const { Simulator, PointerEvent } = window
@@ -63,94 +65,148 @@ describe('Carousel', () => {
expect(carouselByElement._element).toEqual(carouselEl)
})
- it('should go to next item if right arrow key is pressed', done => {
- fixtureEl.innerHTML = [
- '<div id="myCarousel" class="carousel slide">',
- ' <div class="carousel-inner">',
- ' <div class="carousel-item active">item 1</div>',
- ' <div id="item2" class="carousel-item">item 2</div>',
- ' <div class="carousel-item">item 3</div>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should start cycling if `ride`===`carousel`', () => {
+ fixtureEl.innerHTML = '<div id="myCarousel" class="carousel slide" data-bs-ride="carousel"></div>'
- const carouselEl = fixtureEl.querySelector('#myCarousel')
- const carousel = new Carousel(carouselEl, {
- keyboard: true
- })
+ const carousel = new Carousel('#myCarousel')
+ expect(carousel._interval).not.toBeNull()
+ })
- spyOn(carousel, '_keydown').and.callThrough()
+ it('should not start cycling if `ride`!==`carousel`', () => {
+ fixtureEl.innerHTML = '<div id="myCarousel" class="carousel slide" data-bs-ride="true"></div>'
- carouselEl.addEventListener('slid.bs.carousel', () => {
- expect(fixtureEl.querySelector('.active')).toEqual(fixtureEl.querySelector('#item2'))
- expect(carousel._keydown).toHaveBeenCalled()
- done()
- })
+ const carousel = new Carousel('#myCarousel')
+ expect(carousel._interval).toBeNull()
+ })
- const keydown = createEvent('keydown')
- keydown.key = 'ArrowRight'
+ it('should go to next item if right arrow key is pressed', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div id="myCarousel" class="carousel slide">',
+ ' <div class="carousel-inner">',
+ ' <div class="carousel-item active">item 1</div>',
+ ' <div id="item2" class="carousel-item">item 2</div>',
+ ' <div class="carousel-item">item 3</div>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const carouselEl = fixtureEl.querySelector('#myCarousel')
+ const carousel = new Carousel(carouselEl, {
+ keyboard: true
+ })
+
+ const spy = spyOn(carousel, '_keydown').and.callThrough()
+
+ carouselEl.addEventListener('slid.bs.carousel', () => {
+ expect(fixtureEl.querySelector('.active')).toEqual(fixtureEl.querySelector('#item2'))
+ expect(spy).toHaveBeenCalled()
+ resolve()
+ })
- carouselEl.dispatchEvent(keydown)
+ const keydown = createEvent('keydown')
+ keydown.key = 'ArrowRight'
+
+ carouselEl.dispatchEvent(keydown)
+ })
})
- it('should go to previous item if left arrow key is pressed', done => {
+ it('should ignore keyboard events if data-bs-keyboard=false', () => {
fixtureEl.innerHTML = [
- '<div id="myCarousel" class="carousel slide">',
+ '<div id="myCarousel" class="carousel slide" data-bs-keyboard="false">',
' <div class="carousel-inner">',
- ' <div id="item1" class="carousel-item">item 1</div>',
- ' <div class="carousel-item active">item 2</div>',
- ' <div class="carousel-item">item 3</div>',
+ ' <div class="carousel-item active">item 1</div>',
+ ' <div id="item2" class="carousel-item">item 2</div>',
' </div>',
'</div>'
].join('')
+ const spy = spyOn(EventHandler, 'trigger').and.callThrough()
const carouselEl = fixtureEl.querySelector('#myCarousel')
- const carousel = new Carousel(carouselEl, {
- keyboard: true
- })
-
- spyOn(carousel, '_keydown').and.callThrough()
-
- carouselEl.addEventListener('slid.bs.carousel', () => {
- expect(fixtureEl.querySelector('.active')).toEqual(fixtureEl.querySelector('#item1'))
- expect(carousel._keydown).toHaveBeenCalled()
- done()
- })
-
- const keydown = createEvent('keydown')
- keydown.key = 'ArrowLeft'
-
- carouselEl.dispatchEvent(keydown)
+ // eslint-disable-next-line no-new
+ new Carousel('#myCarousel')
+ expect(spy).not.toHaveBeenCalledWith(carouselEl, 'keydown.bs.carousel', jasmine.any(Function))
})
- it('should not prevent keydown if key is not ARROW_LEFT or ARROW_RIGHT', done => {
+ it('should ignore mouse events if data-bs-pause=false', () => {
fixtureEl.innerHTML = [
- '<div id="myCarousel" class="carousel slide">',
+ '<div id="myCarousel" class="carousel slide" data-bs-pause="false">',
' <div class="carousel-inner">',
' <div class="carousel-item active">item 1</div>',
- ' <div class="carousel-item">item 2</div>',
- ' <div class="carousel-item">item 3</div>',
+ ' <div id="item2" class="carousel-item">item 2</div>',
' </div>',
'</div>'
].join('')
+ const spy = spyOn(EventHandler, 'trigger').and.callThrough()
const carouselEl = fixtureEl.querySelector('#myCarousel')
- const carousel = new Carousel(carouselEl, {
- keyboard: true
- })
+ // eslint-disable-next-line no-new
+ new Carousel('#myCarousel')
+ expect(spy).not.toHaveBeenCalledWith(carouselEl, 'hover.bs.carousel', jasmine.any(Function))
+ })
+
+ it('should go to previous item if left arrow key is pressed', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div id="myCarousel" class="carousel slide">',
+ ' <div class="carousel-inner">',
+ ' <div id="item1" class="carousel-item">item 1</div>',
+ ' <div class="carousel-item active">item 2</div>',
+ ' <div class="carousel-item">item 3</div>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const carouselEl = fixtureEl.querySelector('#myCarousel')
+ const carousel = new Carousel(carouselEl, {
+ keyboard: true
+ })
+
+ const spy = spyOn(carousel, '_keydown').and.callThrough()
+
+ carouselEl.addEventListener('slid.bs.carousel', () => {
+ expect(fixtureEl.querySelector('.active')).toEqual(fixtureEl.querySelector('#item1'))
+ expect(spy).toHaveBeenCalled()
+ resolve()
+ })
- spyOn(carousel, '_keydown').and.callThrough()
+ const keydown = createEvent('keydown')
+ keydown.key = 'ArrowLeft'
- carouselEl.addEventListener('keydown', event => {
- expect(carousel._keydown).toHaveBeenCalled()
- expect(event.defaultPrevented).toEqual(false)
- done()
+ carouselEl.dispatchEvent(keydown)
})
+ })
+
+ it('should not prevent keydown if key is not ARROW_LEFT or ARROW_RIGHT', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div id="myCarousel" class="carousel slide">',
+ ' <div class="carousel-inner">',
+ ' <div class="carousel-item active">item 1</div>',
+ ' <div class="carousel-item">item 2</div>',
+ ' <div class="carousel-item">item 3</div>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const carouselEl = fixtureEl.querySelector('#myCarousel')
+ const carousel = new Carousel(carouselEl, {
+ keyboard: true
+ })
+
+ const spy = spyOn(carousel, '_keydown').and.callThrough()
+
+ carouselEl.addEventListener('keydown', event => {
+ expect(spy).toHaveBeenCalled()
+ expect(event.defaultPrevented).toBeFalse()
+ resolve()
+ })
- const keydown = createEvent('keydown')
- keydown.key = 'ArrowDown'
+ const keydown = createEvent('keydown')
+ keydown.key = 'ArrowDown'
- carouselEl.dispatchEvent(keydown)
+ carouselEl.dispatchEvent(keydown)
+ })
})
it('should ignore keyboard events within <input>s and <textarea>s', () => {
@@ -208,7 +264,7 @@ describe('Carousel', () => {
const carouselEl = fixtureEl.querySelector('div')
const carousel = new Carousel(carouselEl, {})
- spyOn(carousel, '_triggerSlideEvent')
+ const spy = spyOn(EventHandler, 'trigger')
carousel._isSliding = true
@@ -219,75 +275,77 @@ describe('Carousel', () => {
carouselEl.dispatchEvent(keydown)
}
- expect(carousel._triggerSlideEvent).not.toHaveBeenCalled()
+ expect(spy).not.toHaveBeenCalled()
})
- it('should wrap around from end to start when wrap option is true', done => {
- fixtureEl.innerHTML = [
- '<div id="myCarousel" class="carousel slide">',
- ' <div class="carousel-inner">',
- ' <div id="one" class="carousel-item active"></div>',
- ' <div id="two" class="carousel-item"></div>',
- ' <div id="three" class="carousel-item">item 3</div>',
- ' </div>',
- '</div>'
- ].join('')
-
- const carouselEl = fixtureEl.querySelector('#myCarousel')
- const carousel = new Carousel(carouselEl, { wrap: true })
- const getActiveId = () => {
- return carouselEl.querySelector('.carousel-item.active').getAttribute('id')
- }
-
- carouselEl.addEventListener('slid.bs.carousel', event => {
- const activeId = getActiveId()
-
- if (activeId === 'two') {
- carousel.next()
- return
- }
-
- if (activeId === 'three') {
- carousel.next()
- return
- }
-
- if (activeId === 'one') {
- // carousel wrapped around and slid from 3rd to 1st slide
- expect(activeId).toEqual('one')
- expect(event.from + 1).toEqual(3)
- done()
- }
+ it('should wrap around from end to start when wrap option is true', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div id="myCarousel" class="carousel slide">',
+ ' <div class="carousel-inner">',
+ ' <div id="one" class="carousel-item active"></div>',
+ ' <div id="two" class="carousel-item"></div>',
+ ' <div id="three" class="carousel-item">item 3</div>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const carouselEl = fixtureEl.querySelector('#myCarousel')
+ const carousel = new Carousel(carouselEl, { wrap: true })
+ const getActiveId = () => carouselEl.querySelector('.carousel-item.active').getAttribute('id')
+
+ carouselEl.addEventListener('slid.bs.carousel', event => {
+ const activeId = getActiveId()
+
+ if (activeId === 'two') {
+ carousel.next()
+ return
+ }
+
+ if (activeId === 'three') {
+ carousel.next()
+ return
+ }
+
+ if (activeId === 'one') {
+ // carousel wrapped around and slid from 3rd to 1st slide
+ expect(activeId).toEqual('one')
+ expect(event.from + 1).toEqual(3)
+ resolve()
+ }
+ })
+
+ carousel.next()
})
-
- carousel.next()
})
- it('should stay at the start when the prev method is called and wrap is false', done => {
- fixtureEl.innerHTML = [
- '<div id="myCarousel" class="carousel slide">',
- ' <div class="carousel-inner">',
- ' <div id="one" class="carousel-item active"></div>',
- ' <div id="two" class="carousel-item"></div>',
- ' <div id="three" class="carousel-item">item 3</div>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should stay at the start when the prev method is called and wrap is false', () => {
+ return new Promise((resolve, reject) => {
+ fixtureEl.innerHTML = [
+ '<div id="myCarousel" class="carousel slide">',
+ ' <div class="carousel-inner">',
+ ' <div id="one" class="carousel-item active"></div>',
+ ' <div id="two" class="carousel-item"></div>',
+ ' <div id="three" class="carousel-item">item 3</div>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const carouselEl = fixtureEl.querySelector('#myCarousel')
- const firstElement = fixtureEl.querySelector('#one')
- const carousel = new Carousel(carouselEl, { wrap: false })
+ const carouselEl = fixtureEl.querySelector('#myCarousel')
+ const firstElement = fixtureEl.querySelector('#one')
+ const carousel = new Carousel(carouselEl, { wrap: false })
- carouselEl.addEventListener('slid.bs.carousel', () => {
- throw new Error('carousel slid when it should not have slid')
- })
+ carouselEl.addEventListener('slid.bs.carousel', () => {
+ reject(new Error('carousel slid when it should not have slid'))
+ })
- carousel.prev()
+ carousel.prev()
- setTimeout(() => {
- expect(firstElement.classList.contains('active')).toEqual(true)
- done()
- }, 10)
+ setTimeout(() => {
+ expect(firstElement).toHaveClass('active')
+ resolve()
+ }, 10)
+ })
})
it('should not add touch event listeners if touch = false', () => {
@@ -295,13 +353,13 @@ describe('Carousel', () => {
const carouselEl = fixtureEl.querySelector('div')
- spyOn(Carousel.prototype, '_addTouchEventListeners')
+ const spy = spyOn(Carousel.prototype, '_addTouchEventListeners')
const carousel = new Carousel(carouselEl, {
touch: false
})
- expect(carousel._addTouchEventListeners).not.toHaveBeenCalled()
+ expect(spy).not.toHaveBeenCalled()
expect(carousel._swipeHelper).toBeNull()
})
@@ -314,11 +372,11 @@ describe('Carousel', () => {
const carousel = new Carousel(carouselEl)
EventHandler.off(carouselEl, Carousel.EVENT_KEY)
- spyOn(carousel, '_addTouchEventListeners')
+ const spy = spyOn(carousel, '_addTouchEventListeners')
carousel._addEventListeners()
- expect(carousel._addTouchEventListeners).not.toHaveBeenCalled()
+ expect(spy).not.toHaveBeenCalled()
expect(carousel._swipeHelper).toBeNull()
})
@@ -337,274 +395,292 @@ describe('Carousel', () => {
expect(carousel._addTouchEventListeners).toHaveBeenCalled()
})
- it('should allow swiperight and call _slide (prev) with pointer events', done => {
- if (!supportPointerEvent) {
- expect().nothing()
- done()
- return
- }
-
- document.documentElement.ontouchstart = noop
- document.head.append(stylesCarousel)
- Simulator.setType('pointer')
-
- fixtureEl.innerHTML = [
- '<div class="carousel" data-bs-interval="false">',
- ' <div class="carousel-inner">',
- ' <div id="item" class="carousel-item">',
- ' <img alt="">',
- ' </div>',
- ' <div class="carousel-item active">',
- ' <img alt="">',
- ' </div>',
- ' </div>',
- '</div>'
- ].join('')
-
- const carouselEl = fixtureEl.querySelector('.carousel')
- const item = fixtureEl.querySelector('#item')
- const carousel = new Carousel(carouselEl)
-
- spyOn(carousel, '_slide').and.callThrough()
-
- carouselEl.addEventListener('slid.bs.carousel', event => {
- expect(item.classList.contains('active')).toEqual(true)
- expect(carousel._slide).toHaveBeenCalledWith('right')
- expect(event.direction).toEqual('right')
- stylesCarousel.remove()
- delete document.documentElement.ontouchstart
- done()
- })
+ it('should allow swiperight and call _slide (prev) with pointer events', () => {
+ return new Promise(resolve => {
+ if (!supportPointerEvent) {
+ expect().nothing()
+ resolve()
+ return
+ }
- Simulator.gestures.swipe(carouselEl, {
- deltaX: 300,
- deltaY: 0
+ document.documentElement.ontouchstart = noop
+ document.head.append(stylesCarousel)
+ Simulator.setType('pointer')
+
+ fixtureEl.innerHTML = [
+ '<div class="carousel">',
+ ' <div class="carousel-inner">',
+ ' <div id="item" class="carousel-item">',
+ ' <img alt="">',
+ ' </div>',
+ ' <div class="carousel-item active">',
+ ' <img alt="">',
+ ' </div>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const carouselEl = fixtureEl.querySelector('.carousel')
+ const item = fixtureEl.querySelector('#item')
+ const carousel = new Carousel(carouselEl)
+
+ const spy = spyOn(carousel, '_slide').and.callThrough()
+
+ carouselEl.addEventListener('slid.bs.carousel', event => {
+ expect(item).toHaveClass('active')
+ expect(spy).toHaveBeenCalledWith('prev')
+ expect(event.direction).toEqual('right')
+ stylesCarousel.remove()
+ delete document.documentElement.ontouchstart
+ resolve()
+ })
+
+ Simulator.gestures.swipe(carouselEl, {
+ deltaX: 300,
+ deltaY: 0
+ })
})
})
- it('should allow swipeleft and call next with pointer events', done => {
- if (!supportPointerEvent) {
- expect().nothing()
- done()
- return
- }
-
- document.documentElement.ontouchstart = noop
- document.head.append(stylesCarousel)
- Simulator.setType('pointer')
-
- fixtureEl.innerHTML = [
- '<div class="carousel" data-bs-interval="false">',
- ' <div class="carousel-inner">',
- ' <div id="item" class="carousel-item active">',
- ' <img alt="">',
- ' </div>',
- ' <div class="carousel-item">',
- ' <img alt="">',
- ' </div>',
- ' </div>',
- '</div>'
- ].join('')
-
- const carouselEl = fixtureEl.querySelector('.carousel')
- const item = fixtureEl.querySelector('#item')
- const carousel = new Carousel(carouselEl)
-
- spyOn(carousel, '_slide').and.callThrough()
-
- carouselEl.addEventListener('slid.bs.carousel', event => {
- expect(item.classList.contains('active')).toEqual(false)
- expect(carousel._slide).toHaveBeenCalledWith('left')
- expect(event.direction).toEqual('left')
- stylesCarousel.remove()
- delete document.documentElement.ontouchstart
- done()
- })
+ it('should allow swipeleft and call next with pointer events', () => {
+ return new Promise(resolve => {
+ if (!supportPointerEvent) {
+ expect().nothing()
+ resolve()
+ return
+ }
- Simulator.gestures.swipe(carouselEl, {
- pos: [300, 10],
- deltaX: -300,
- deltaY: 0
+ document.documentElement.ontouchstart = noop
+ document.head.append(stylesCarousel)
+ Simulator.setType('pointer')
+
+ fixtureEl.innerHTML = [
+ '<div class="carousel">',
+ ' <div class="carousel-inner">',
+ ' <div id="item" class="carousel-item active">',
+ ' <img alt="">',
+ ' </div>',
+ ' <div class="carousel-item">',
+ ' <img alt="">',
+ ' </div>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const carouselEl = fixtureEl.querySelector('.carousel')
+ const item = fixtureEl.querySelector('#item')
+ const carousel = new Carousel(carouselEl)
+
+ const spy = spyOn(carousel, '_slide').and.callThrough()
+
+ carouselEl.addEventListener('slid.bs.carousel', event => {
+ expect(item).not.toHaveClass('active')
+ expect(spy).toHaveBeenCalledWith('next')
+ expect(event.direction).toEqual('left')
+ stylesCarousel.remove()
+ delete document.documentElement.ontouchstart
+ resolve()
+ })
+
+ Simulator.gestures.swipe(carouselEl, {
+ pos: [300, 10],
+ deltaX: -300,
+ deltaY: 0
+ })
})
})
- it('should allow swiperight and call _slide (prev) with touch events', done => {
- Simulator.setType('touch')
- clearPointerEvents()
- document.documentElement.ontouchstart = noop
-
- fixtureEl.innerHTML = [
- '<div class="carousel" data-bs-interval="false">',
- ' <div class="carousel-inner">',
- ' <div id="item" class="carousel-item">',
- ' <img alt="">',
- ' </div>',
- ' <div class="carousel-item active">',
- ' <img alt="">',
- ' </div>',
- ' </div>',
- '</div>'
- ].join('')
-
- const carouselEl = fixtureEl.querySelector('.carousel')
- const item = fixtureEl.querySelector('#item')
- const carousel = new Carousel(carouselEl)
-
- spyOn(carousel, '_slide').and.callThrough()
-
- carouselEl.addEventListener('slid.bs.carousel', event => {
- expect(item.classList.contains('active')).toEqual(true)
- expect(carousel._slide).toHaveBeenCalledWith('right')
- expect(event.direction).toEqual('right')
- delete document.documentElement.ontouchstart
- restorePointerEvents()
- done()
- })
-
- Simulator.gestures.swipe(carouselEl, {
- deltaX: 300,
- deltaY: 0
+ it('should allow swiperight and call _slide (prev) with touch events', () => {
+ return new Promise(resolve => {
+ Simulator.setType('touch')
+ clearPointerEvents()
+ document.documentElement.ontouchstart = noop
+
+ fixtureEl.innerHTML = [
+ '<div class="carousel">',
+ ' <div class="carousel-inner">',
+ ' <div id="item" class="carousel-item">',
+ ' <img alt="">',
+ ' </div>',
+ ' <div class="carousel-item active">',
+ ' <img alt="">',
+ ' </div>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const carouselEl = fixtureEl.querySelector('.carousel')
+ const item = fixtureEl.querySelector('#item')
+ const carousel = new Carousel(carouselEl)
+
+ const spy = spyOn(carousel, '_slide').and.callThrough()
+
+ carouselEl.addEventListener('slid.bs.carousel', event => {
+ expect(item).toHaveClass('active')
+ expect(spy).toHaveBeenCalledWith('prev')
+ expect(event.direction).toEqual('right')
+ delete document.documentElement.ontouchstart
+ restorePointerEvents()
+ resolve()
+ })
+
+ Simulator.gestures.swipe(carouselEl, {
+ deltaX: 300,
+ deltaY: 0
+ })
})
})
- it('should allow swipeleft and call _slide (next) with touch events', done => {
- Simulator.setType('touch')
- clearPointerEvents()
- document.documentElement.ontouchstart = noop
-
- fixtureEl.innerHTML = [
- '<div class="carousel" data-bs-interval="false">',
- ' <div class="carousel-inner">',
- ' <div id="item" class="carousel-item active">',
- ' <img alt="">',
- ' </div>',
- ' <div class="carousel-item">',
- ' <img alt="">',
- ' </div>',
- ' </div>',
- '</div>'
- ].join('')
-
- const carouselEl = fixtureEl.querySelector('.carousel')
- const item = fixtureEl.querySelector('#item')
- const carousel = new Carousel(carouselEl)
-
- spyOn(carousel, '_slide').and.callThrough()
-
- carouselEl.addEventListener('slid.bs.carousel', event => {
- expect(item.classList.contains('active')).toEqual(false)
- expect(carousel._slide).toHaveBeenCalledWith('left')
- expect(event.direction).toEqual('left')
- delete document.documentElement.ontouchstart
- restorePointerEvents()
- done()
- })
-
- Simulator.gestures.swipe(carouselEl, {
- pos: [300, 10],
- deltaX: -300,
- deltaY: 0
+ it('should allow swipeleft and call _slide (next) with touch events', () => {
+ return new Promise(resolve => {
+ Simulator.setType('touch')
+ clearPointerEvents()
+ document.documentElement.ontouchstart = noop
+
+ fixtureEl.innerHTML = [
+ '<div class="carousel">',
+ ' <div class="carousel-inner">',
+ ' <div id="item" class="carousel-item active">',
+ ' <img alt="">',
+ ' </div>',
+ ' <div class="carousel-item">',
+ ' <img alt="">',
+ ' </div>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const carouselEl = fixtureEl.querySelector('.carousel')
+ const item = fixtureEl.querySelector('#item')
+ const carousel = new Carousel(carouselEl)
+
+ const spy = spyOn(carousel, '_slide').and.callThrough()
+
+ carouselEl.addEventListener('slid.bs.carousel', event => {
+ expect(item).not.toHaveClass('active')
+ expect(spy).toHaveBeenCalledWith('next')
+ expect(event.direction).toEqual('left')
+ delete document.documentElement.ontouchstart
+ restorePointerEvents()
+ resolve()
+ })
+
+ Simulator.gestures.swipe(carouselEl, {
+ pos: [300, 10],
+ deltaX: -300,
+ deltaY: 0
+ })
})
})
- it('should not slide when swiping and carousel is sliding', done => {
- Simulator.setType('touch')
- clearPointerEvents()
- document.documentElement.ontouchstart = noop
-
- fixtureEl.innerHTML = [
- '<div class="carousel" data-bs-interval="false">',
- ' <div class="carousel-inner">',
- ' <div id="item" class="carousel-item active">',
- ' <img alt="">',
- ' </div>',
- ' <div class="carousel-item">',
- ' <img alt="">',
- ' </div>',
- ' </div>',
- '</div>'
- ].join('')
-
- const carouselEl = fixtureEl.querySelector('.carousel')
- const carousel = new Carousel(carouselEl)
- carousel._isSliding = true
-
- spyOn(carousel, '_triggerSlideEvent')
+ it('should not slide when swiping and carousel is sliding', () => {
+ return new Promise(resolve => {
+ Simulator.setType('touch')
+ clearPointerEvents()
+ document.documentElement.ontouchstart = noop
+
+ fixtureEl.innerHTML = [
+ '<div class="carousel">',
+ ' <div class="carousel-inner">',
+ ' <div id="item" class="carousel-item active">',
+ ' <img alt="">',
+ ' </div>',
+ ' <div class="carousel-item">',
+ ' <img alt="">',
+ ' </div>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const carouselEl = fixtureEl.querySelector('.carousel')
+ const carousel = new Carousel(carouselEl)
+ carousel._isSliding = true
+
+ const spy = spyOn(EventHandler, 'trigger')
+
+ Simulator.gestures.swipe(carouselEl, {
+ deltaX: 300,
+ deltaY: 0
+ })
+
+ Simulator.gestures.swipe(carouselEl, {
+ pos: [300, 10],
+ deltaX: -300,
+ deltaY: 0
+ })
- Simulator.gestures.swipe(carouselEl, {
- deltaX: 300,
- deltaY: 0
- })
-
- Simulator.gestures.swipe(carouselEl, {
- pos: [300, 10],
- deltaX: -300,
- deltaY: 0
+ setTimeout(() => {
+ expect(spy).not.toHaveBeenCalled()
+ delete document.documentElement.ontouchstart
+ restorePointerEvents()
+ resolve()
+ }, 300)
})
-
- setTimeout(() => {
- expect(carousel._triggerSlideEvent).not.toHaveBeenCalled()
- delete document.documentElement.ontouchstart
- restorePointerEvents()
- done()
- }, 300)
})
- it('should not allow pinch with touch events', done => {
- Simulator.setType('touch')
- clearPointerEvents()
- document.documentElement.ontouchstart = noop
-
- fixtureEl.innerHTML = '<div class="carousel" data-bs-interval="false"></div>'
-
- const carouselEl = fixtureEl.querySelector('.carousel')
- const carousel = new Carousel(carouselEl)
-
- Simulator.gestures.swipe(carouselEl, {
- pos: [300, 10],
- deltaX: -300,
- deltaY: 0,
- touches: 2
- }, () => {
- restorePointerEvents()
- delete document.documentElement.ontouchstart
- expect(carousel._swipeHelper._deltaX).toEqual(0)
- done()
+ it('should not allow pinch with touch events', () => {
+ return new Promise(resolve => {
+ Simulator.setType('touch')
+ clearPointerEvents()
+ document.documentElement.ontouchstart = noop
+
+ fixtureEl.innerHTML = '<div class="carousel"></div>'
+
+ const carouselEl = fixtureEl.querySelector('.carousel')
+ const carousel = new Carousel(carouselEl)
+
+ Simulator.gestures.swipe(carouselEl, {
+ pos: [300, 10],
+ deltaX: -300,
+ deltaY: 0,
+ touches: 2
+ }, () => {
+ restorePointerEvents()
+ delete document.documentElement.ontouchstart
+ expect(carousel._swipeHelper._deltaX).toEqual(0)
+ resolve()
+ })
})
})
- it('should call pause method on mouse over with pause equal to hover', done => {
- fixtureEl.innerHTML = '<div class="carousel"></div>'
+ it('should call pause method on mouse over with pause equal to hover', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div class="carousel"></div>'
- const carouselEl = fixtureEl.querySelector('.carousel')
- const carousel = new Carousel(carouselEl)
+ const carouselEl = fixtureEl.querySelector('.carousel')
+ const carousel = new Carousel(carouselEl)
- spyOn(carousel, 'pause')
+ const spy = spyOn(carousel, 'pause')
- const mouseOverEvent = createEvent('mouseover')
- carouselEl.dispatchEvent(mouseOverEvent)
+ const mouseOverEvent = createEvent('mouseover')
+ carouselEl.dispatchEvent(mouseOverEvent)
- setTimeout(() => {
- expect(carousel.pause).toHaveBeenCalled()
- done()
- }, 10)
+ setTimeout(() => {
+ expect(spy).toHaveBeenCalled()
+ resolve()
+ }, 10)
+ })
})
- it('should call cycle on mouse out with pause equal to hover', done => {
- fixtureEl.innerHTML = '<div class="carousel"></div>'
+ it('should call `maybeEnableCycle` on mouse out with pause equal to hover', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div class="carousel" data-bs-ride="true"></div>'
- const carouselEl = fixtureEl.querySelector('.carousel')
- const carousel = new Carousel(carouselEl)
+ const carouselEl = fixtureEl.querySelector('.carousel')
+ const carousel = new Carousel(carouselEl)
- spyOn(carousel, 'cycle')
+ const spyEnable = spyOn(carousel, '_maybeEnableCycle').and.callThrough()
+ const spyCycle = spyOn(carousel, 'cycle')
- const mouseOutEvent = createEvent('mouseout')
- carouselEl.dispatchEvent(mouseOutEvent)
+ const mouseOutEvent = createEvent('mouseout')
+ carouselEl.dispatchEvent(mouseOutEvent)
- setTimeout(() => {
- expect(carousel.cycle).toHaveBeenCalled()
- done()
- }, 10)
+ setTimeout(() => {
+ expect(spyEnable).toHaveBeenCalled()
+ expect(spyCycle).toHaveBeenCalled()
+ resolve()
+ }, 10)
+ })
})
})
@@ -615,108 +691,114 @@ describe('Carousel', () => {
const carouselEl = fixtureEl.querySelector('div')
const carousel = new Carousel(carouselEl, {})
- spyOn(carousel, '_triggerSlideEvent')
+ const spy = spyOn(EventHandler, 'trigger')
carousel._isSliding = true
carousel.next()
- expect(carousel._triggerSlideEvent).not.toHaveBeenCalled()
+ expect(spy).not.toHaveBeenCalled()
})
- it('should not fire slid when slide is prevented', done => {
- fixtureEl.innerHTML = '<div></div>'
+ it('should not fire slid when slide is prevented', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div></div>'
- const carouselEl = fixtureEl.querySelector('div')
- const carousel = new Carousel(carouselEl, {})
- let slidEvent = false
+ const carouselEl = fixtureEl.querySelector('div')
+ const carousel = new Carousel(carouselEl, {})
+ let slidEvent = false
- const doneTest = () => {
- setTimeout(() => {
- expect(slidEvent).toEqual(false)
- done()
- }, 20)
- }
+ const doneTest = () => {
+ setTimeout(() => {
+ expect(slidEvent).toBeFalse()
+ resolve()
+ }, 20)
+ }
- carouselEl.addEventListener('slide.bs.carousel', event => {
- event.preventDefault()
- doneTest()
- })
+ carouselEl.addEventListener('slide.bs.carousel', event => {
+ event.preventDefault()
+ doneTest()
+ })
- carouselEl.addEventListener('slid.bs.carousel', () => {
- slidEvent = true
- })
+ carouselEl.addEventListener('slid.bs.carousel', () => {
+ slidEvent = true
+ })
- carousel.next()
+ carousel.next()
+ })
})
- it('should fire slide event with: direction, relatedTarget, from and to', done => {
- fixtureEl.innerHTML = [
- '<div id="myCarousel" class="carousel slide">',
- ' <div class="carousel-inner">',
- ' <div class="carousel-item active">item 1</div>',
- ' <div class="carousel-item">item 2</div>',
- ' <div class="carousel-item">item 3</div>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should fire slide event with: direction, relatedTarget, from and to', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div id="myCarousel" class="carousel slide">',
+ ' <div class="carousel-inner">',
+ ' <div class="carousel-item active">item 1</div>',
+ ' <div class="carousel-item">item 2</div>',
+ ' <div class="carousel-item">item 3</div>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const carouselEl = fixtureEl.querySelector('#myCarousel')
- const carousel = new Carousel(carouselEl, {})
+ const carouselEl = fixtureEl.querySelector('#myCarousel')
+ const carousel = new Carousel(carouselEl, {})
- const onSlide = event => {
- expect(event.direction).toEqual('left')
- expect(event.relatedTarget.classList.contains('carousel-item')).toEqual(true)
- expect(event.from).toEqual(0)
- expect(event.to).toEqual(1)
+ const onSlide = event => {
+ expect(event.direction).toEqual('left')
+ expect(event.relatedTarget).toHaveClass('carousel-item')
+ expect(event.from).toEqual(0)
+ expect(event.to).toEqual(1)
- carouselEl.removeEventListener('slide.bs.carousel', onSlide)
- carouselEl.addEventListener('slide.bs.carousel', onSlide2)
+ carouselEl.removeEventListener('slide.bs.carousel', onSlide)
+ carouselEl.addEventListener('slide.bs.carousel', onSlide2)
- carousel.prev()
- }
+ carousel.prev()
+ }
- const onSlide2 = event => {
- expect(event.direction).toEqual('right')
- done()
- }
+ const onSlide2 = event => {
+ expect(event.direction).toEqual('right')
+ resolve()
+ }
- carouselEl.addEventListener('slide.bs.carousel', onSlide)
- carousel.next()
+ carouselEl.addEventListener('slide.bs.carousel', onSlide)
+ carousel.next()
+ })
})
- it('should fire slid event with: direction, relatedTarget, from and to', done => {
- fixtureEl.innerHTML = [
- '<div id="myCarousel" class="carousel slide">',
- ' <div class="carousel-inner">',
- ' <div class="carousel-item active">item 1</div>',
- ' <div class="carousel-item">item 2</div>',
- ' <div class="carousel-item">item 3</div>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should fire slid event with: direction, relatedTarget, from and to', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div id="myCarousel" class="carousel slide">',
+ ' <div class="carousel-inner">',
+ ' <div class="carousel-item active">item 1</div>',
+ ' <div class="carousel-item">item 2</div>',
+ ' <div class="carousel-item">item 3</div>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const carouselEl = fixtureEl.querySelector('#myCarousel')
- const carousel = new Carousel(carouselEl, {})
+ const carouselEl = fixtureEl.querySelector('#myCarousel')
+ const carousel = new Carousel(carouselEl, {})
- const onSlid = event => {
- expect(event.direction).toEqual('left')
- expect(event.relatedTarget.classList.contains('carousel-item')).toEqual(true)
- expect(event.from).toEqual(0)
- expect(event.to).toEqual(1)
+ const onSlid = event => {
+ expect(event.direction).toEqual('left')
+ expect(event.relatedTarget).toHaveClass('carousel-item')
+ expect(event.from).toEqual(0)
+ expect(event.to).toEqual(1)
- carouselEl.removeEventListener('slid.bs.carousel', onSlid)
- carouselEl.addEventListener('slid.bs.carousel', onSlid2)
+ carouselEl.removeEventListener('slid.bs.carousel', onSlid)
+ carouselEl.addEventListener('slid.bs.carousel', onSlid2)
- carousel.prev()
- }
+ carousel.prev()
+ }
- const onSlid2 = event => {
- expect(event.direction).toEqual('right')
- done()
- }
+ const onSlid2 = event => {
+ expect(event.direction).toEqual('right')
+ resolve()
+ }
- carouselEl.addEventListener('slid.bs.carousel', onSlid)
- carousel.next()
+ carouselEl.addEventListener('slid.bs.carousel', onSlid)
+ carousel.next()
+ })
})
it('should update the active element to the next item before sliding', () => {
@@ -739,36 +821,60 @@ describe('Carousel', () => {
expect(carousel._activeElement).toEqual(secondItemEl)
})
- it('should update indicators if present', done => {
+ it('should continue cycling if it was already', () => {
fixtureEl.innerHTML = [
'<div id="myCarousel" class="carousel slide">',
- ' <div class="carousel-indicators">',
- ' <button type="button" id="firstIndicator" data-bs-target="myCarousel" data-bs-slide-to="0" class="active" aria-current="true" aria-label="Slide 1"></button>',
- ' <button type="button" id="secondIndicator" data-bs-target="myCarousel" data-bs-slide-to="1" aria-label="Slide 2"></button>',
- ' <button type="button" data-bs-target="myCarousel" data-bs-slide-to="2" aria-label="Slide 3"></button>',
- ' </div>',
' <div class="carousel-inner">',
' <div class="carousel-item active">item 1</div>',
- ' <div class="carousel-item" data-bs-interval="7">item 2</div>',
- ' <div class="carousel-item">item 3</div>',
+ ' <div class="carousel-item">item 2</div>',
' </div>',
'</div>'
].join('')
const carouselEl = fixtureEl.querySelector('#myCarousel')
- const firstIndicator = fixtureEl.querySelector('#firstIndicator')
- const secondIndicator = fixtureEl.querySelector('#secondIndicator')
const carousel = new Carousel(carouselEl)
+ const spy = spyOn(carousel, 'cycle')
- carouselEl.addEventListener('slid.bs.carousel', () => {
- expect(firstIndicator.classList.contains('active')).toEqual(false)
- expect(firstIndicator.hasAttribute('aria-current')).toEqual(false)
- expect(secondIndicator.classList.contains('active')).toEqual(true)
- expect(secondIndicator.getAttribute('aria-current')).toEqual('true')
- done()
- })
+ carousel.next()
+ expect(spy).not.toHaveBeenCalled()
+ carousel.cycle()
carousel.next()
+ expect(spy).toHaveBeenCalledTimes(1)
+ })
+
+ it('should update indicators if present', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div id="myCarousel" class="carousel slide">',
+ ' <div class="carousel-indicators">',
+ ' <button type="button" id="firstIndicator" data-bs-target="myCarousel" data-bs-slide-to="0" class="active" aria-current="true" aria-label="Slide 1"></button>',
+ ' <button type="button" id="secondIndicator" data-bs-target="myCarousel" data-bs-slide-to="1" aria-label="Slide 2"></button>',
+ ' <button type="button" data-bs-target="myCarousel" data-bs-slide-to="2" aria-label="Slide 3"></button>',
+ ' </div>',
+ ' <div class="carousel-inner">',
+ ' <div class="carousel-item active">item 1</div>',
+ ' <div class="carousel-item" data-bs-interval="7">item 2</div>',
+ ' <div class="carousel-item">item 3</div>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const carouselEl = fixtureEl.querySelector('#myCarousel')
+ const firstIndicator = fixtureEl.querySelector('#firstIndicator')
+ const secondIndicator = fixtureEl.querySelector('#secondIndicator')
+ const carousel = new Carousel(carouselEl)
+
+ carouselEl.addEventListener('slid.bs.carousel', () => {
+ expect(firstIndicator).not.toHaveClass('active')
+ expect(firstIndicator.hasAttribute('aria-current')).toBeFalse()
+ expect(secondIndicator).toHaveClass('active')
+ expect(secondIndicator.getAttribute('aria-current')).toEqual('true')
+ resolve()
+ })
+
+ carousel.next()
+ })
})
it('should call next()/prev() instance methods when clicking the respective direction buttons', () => {
@@ -791,12 +897,14 @@ describe('Carousel', () => {
const carousel = new Carousel(carouselEl)
const nextSpy = spyOn(carousel, 'next')
const prevSpy = spyOn(carousel, 'prev')
+ const spyEnable = spyOn(carousel, '_maybeEnableCycle')
nextBtnEl.click()
prevBtnEl.click()
expect(nextSpy).toHaveBeenCalled()
expect(prevSpy).toHaveBeenCalled()
+ expect(spyEnable).toHaveBeenCalled()
})
})
@@ -804,18 +912,18 @@ describe('Carousel', () => {
it('should not call next when the page is not visible', () => {
fixtureEl.innerHTML = [
'<div style="display: none;">',
- ' <div class="carousel" data-bs-interval="false"></div>',
+ ' <div class="carousel"></div>',
'</div>'
].join('')
const carouselEl = fixtureEl.querySelector('.carousel')
const carousel = new Carousel(carouselEl)
- spyOn(carousel, 'next')
+ const spy = spyOn(carousel, 'next')
carousel.nextWhenVisible()
- expect(carousel.next).not.toHaveBeenCalled()
+ expect(spy).not.toHaveBeenCalled()
})
})
@@ -826,91 +934,42 @@ describe('Carousel', () => {
const carouselEl = fixtureEl.querySelector('div')
const carousel = new Carousel(carouselEl, {})
- spyOn(carousel, '_triggerSlideEvent')
+ const spy = spyOn(EventHandler, 'trigger')
carousel._isSliding = true
carousel.prev()
- expect(carousel._triggerSlideEvent).not.toHaveBeenCalled()
+ expect(spy).not.toHaveBeenCalled()
})
})
describe('pause', () => {
- it('should call cycle if the carousel have carousel-item-next and carousel-item-prev class', () => {
- fixtureEl.innerHTML = [
- '<div id="myCarousel" class="carousel slide">',
- ' <div class="carousel-inner">',
- ' <div class="carousel-item active">item 1</div>',
- ' <div class="carousel-item carousel-item-next">item 2</div>',
- ' <div class="carousel-item">item 3</div>',
- ' </div>',
- ' <div class="carousel-control-prev"></div>',
- ' <div class="carousel-control-next"></div>',
- '</div>'
- ].join('')
-
- const carouselEl = fixtureEl.querySelector('#myCarousel')
- const carousel = new Carousel(carouselEl)
-
- spyOn(carousel, 'cycle')
- spyOn(window, 'clearInterval')
-
- carousel.pause()
-
- expect(carousel.cycle).toHaveBeenCalledWith(true)
- expect(window.clearInterval).toHaveBeenCalled()
- expect(carousel._isPaused).toEqual(true)
- })
-
- it('should not call cycle if nothing is in transition', () => {
- fixtureEl.innerHTML = [
- '<div id="myCarousel" class="carousel slide">',
- ' <div class="carousel-inner">',
- ' <div class="carousel-item active">item 1</div>',
- ' <div class="carousel-item">item 2</div>',
- ' <div class="carousel-item">item 3</div>',
- ' </div>',
- ' <div class="carousel-control-prev"></div>',
- ' <div class="carousel-control-next"></div>',
- '</div>'
- ].join('')
-
- const carouselEl = fixtureEl.querySelector('#myCarousel')
- const carousel = new Carousel(carouselEl)
-
- spyOn(carousel, 'cycle')
- spyOn(window, 'clearInterval')
-
- carousel.pause()
-
- expect(carousel.cycle).not.toHaveBeenCalled()
- expect(window.clearInterval).toHaveBeenCalled()
- expect(carousel._isPaused).toEqual(true)
- })
-
- it('should not set is paused at true if an event is passed', () => {
- fixtureEl.innerHTML = [
- '<div id="myCarousel" class="carousel slide">',
- ' <div class="carousel-inner">',
- ' <div class="carousel-item active">item 1</div>',
- ' <div class="carousel-item">item 2</div>',
- ' <div class="carousel-item">item 3</div>',
- ' </div>',
- ' <div class="carousel-control-prev"></div>',
- ' <div class="carousel-control-next"></div>',
- '</div>'
- ].join('')
-
- const carouselEl = fixtureEl.querySelector('#myCarousel')
- const carousel = new Carousel(carouselEl)
- const event = createEvent('mouseenter')
-
- spyOn(window, 'clearInterval')
-
- carousel.pause(event)
-
- expect(window.clearInterval).toHaveBeenCalled()
- expect(carousel._isPaused).toEqual(false)
+ it('should trigger transitionend if the carousel have carousel-item-next or carousel-item-prev class, cause is sliding', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div id="myCarousel" class="carousel slide">',
+ ' <div class="carousel-inner">',
+ ' <div class="carousel-item active">item 1</div>',
+ ' <div class="carousel-item carousel-item-next">item 2</div>',
+ ' <div class="carousel-item">item 3</div>',
+ ' </div>',
+ ' <div class="carousel-control-prev"></div>',
+ ' <div class="carousel-control-next"></div>',
+ '</div>'
+ ].join('')
+
+ const carouselEl = fixtureEl.querySelector('#myCarousel')
+ const carousel = new Carousel(carouselEl)
+ const spy = spyOn(carousel, '_clearInterval')
+
+ carouselEl.addEventListener('transitionend', () => {
+ expect(spy).toHaveBeenCalled()
+ resolve()
+ })
+
+ carousel._slide('next')
+ carousel.pause()
+ })
})
})
@@ -931,35 +990,11 @@ describe('Carousel', () => {
const carouselEl = fixtureEl.querySelector('#myCarousel')
const carousel = new Carousel(carouselEl)
- spyOn(window, 'setInterval').and.callThrough()
+ const spy = spyOn(window, 'setInterval').and.callThrough()
carousel.cycle()
- expect(window.setInterval).toHaveBeenCalled()
- })
-
- it('should not set interval if the carousel is paused', () => {
- fixtureEl.innerHTML = [
- '<div id="myCarousel" class="carousel slide">',
- ' <div class="carousel-inner">',
- ' <div class="carousel-item active">item 1</div>',
- ' <div class="carousel-item">item 2</div>',
- ' <div class="carousel-item">item 3</div>',
- ' </div>',
- ' <div class="carousel-control-prev"></div>',
- ' <div class="carousel-control-next"></div>',
- '</div>'
- ].join('')
-
- const carouselEl = fixtureEl.querySelector('#myCarousel')
- const carousel = new Carousel(carouselEl)
-
- spyOn(window, 'setInterval').and.callThrough()
-
- carousel._isPaused = true
- carousel.cycle(true)
-
- expect(window.setInterval).not.toHaveBeenCalled()
+ expect(spy).toHaveBeenCalled()
})
it('should clear interval if there is one', () => {
@@ -980,13 +1015,13 @@ describe('Carousel', () => {
carousel._interval = setInterval(noop, 10)
- spyOn(window, 'setInterval').and.callThrough()
- spyOn(window, 'clearInterval').and.callThrough()
+ const spySet = spyOn(window, 'setInterval').and.callThrough()
+ const spyClear = spyOn(window, 'clearInterval').and.callThrough()
carousel.cycle()
- expect(window.setInterval).toHaveBeenCalled()
- expect(window.clearInterval).toHaveBeenCalled()
+ expect(spySet).toHaveBeenCalled()
+ expect(spyClear).toHaveBeenCalled()
})
it('should get interval from data attribute on the active item element', () => {
@@ -1020,51 +1055,55 @@ describe('Carousel', () => {
})
describe('to', () => {
- it('should go directly to the provided index', done => {
- fixtureEl.innerHTML = [
- '<div id="myCarousel" class="carousel slide">',
- ' <div class="carousel-inner">',
- ' <div id="item1" class="carousel-item active">item 1</div>',
- ' <div class="carousel-item">item 2</div>',
- ' <div id="item3" class="carousel-item">item 3</div>',
- ' </div>',
- '</div>'
- ].join('')
-
- const carouselEl = fixtureEl.querySelector('#myCarousel')
- const carousel = new Carousel(carouselEl, {})
+ it('should go directly to the provided index', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div id="myCarousel" class="carousel slide">',
+ ' <div class="carousel-inner">',
+ ' <div id="item1" class="carousel-item active">item 1</div>',
+ ' <div class="carousel-item">item 2</div>',
+ ' <div id="item3" class="carousel-item">item 3</div>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const carouselEl = fixtureEl.querySelector('#myCarousel')
+ const carousel = new Carousel(carouselEl, {})
- expect(fixtureEl.querySelector('.active')).toEqual(fixtureEl.querySelector('#item1'))
+ expect(fixtureEl.querySelector('.active')).toEqual(fixtureEl.querySelector('#item1'))
- carousel.to(2)
+ carousel.to(2)
- carouselEl.addEventListener('slid.bs.carousel', () => {
- expect(fixtureEl.querySelector('.active')).toEqual(fixtureEl.querySelector('#item3'))
- done()
+ carouselEl.addEventListener('slid.bs.carousel', () => {
+ expect(fixtureEl.querySelector('.active')).toEqual(fixtureEl.querySelector('#item3'))
+ resolve()
+ })
})
})
- it('should return to a previous slide if the provided index is lower than the current', done => {
- fixtureEl.innerHTML = [
- '<div id="myCarousel" class="carousel slide">',
- ' <div class="carousel-inner">',
- ' <div class="carousel-item">item 1</div>',
- ' <div id="item2" class="carousel-item">item 2</div>',
- ' <div id="item3" class="carousel-item active">item 3</div>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should return to a previous slide if the provided index is lower than the current', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div id="myCarousel" class="carousel slide">',
+ ' <div class="carousel-inner">',
+ ' <div class="carousel-item">item 1</div>',
+ ' <div id="item2" class="carousel-item">item 2</div>',
+ ' <div id="item3" class="carousel-item active">item 3</div>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const carouselEl = fixtureEl.querySelector('#myCarousel')
- const carousel = new Carousel(carouselEl, {})
+ const carouselEl = fixtureEl.querySelector('#myCarousel')
+ const carousel = new Carousel(carouselEl, {})
- expect(fixtureEl.querySelector('.active')).toEqual(fixtureEl.querySelector('#item3'))
+ expect(fixtureEl.querySelector('.active')).toEqual(fixtureEl.querySelector('#item3'))
- carousel.to(1)
+ carousel.to(1)
- carouselEl.addEventListener('slid.bs.carousel', () => {
- expect(fixtureEl.querySelector('.active')).toEqual(fixtureEl.querySelector('#item2'))
- done()
+ carouselEl.addEventListener('slid.bs.carousel', () => {
+ expect(fixtureEl.querySelector('.active')).toEqual(fixtureEl.querySelector('#item2'))
+ resolve()
+ })
})
})
@@ -1095,7 +1134,7 @@ describe('Carousel', () => {
expect(spy).not.toHaveBeenCalled()
})
- it('should call pause and cycle is the provided is the same compare to the current one', () => {
+ it('should not continue if the provided is the same compare to the current one', () => {
fixtureEl.innerHTML = [
'<div id="myCarousel" class="carousel slide">',
' <div class="carousel-inner">',
@@ -1109,50 +1148,49 @@ describe('Carousel', () => {
const carouselEl = fixtureEl.querySelector('#myCarousel')
const carousel = new Carousel(carouselEl, {})
- spyOn(carousel, '_slide')
- spyOn(carousel, 'pause')
- spyOn(carousel, 'cycle')
+ const spy = spyOn(carousel, '_slide')
carousel.to(0)
- expect(carousel._slide).not.toHaveBeenCalled()
- expect(carousel.pause).toHaveBeenCalled()
- expect(carousel.cycle).toHaveBeenCalled()
+ expect(spy).not.toHaveBeenCalled()
})
- it('should wait before performing to if a slide is sliding', done => {
- fixtureEl.innerHTML = [
- '<div id="myCarousel" class="carousel slide">',
- ' <div class="carousel-inner">',
- ' <div class="carousel-item active">item 1</div>',
- ' <div class="carousel-item" data-bs-interval="7">item 2</div>',
- ' <div class="carousel-item">item 3</div>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should wait before performing to if a slide is sliding', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div id="myCarousel" class="carousel slide">',
+ ' <div class="carousel-inner">',
+ ' <div class="carousel-item active">item 1</div>',
+ ' <div class="carousel-item" data-bs-interval="7">item 2</div>',
+ ' <div class="carousel-item">item 3</div>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const carouselEl = fixtureEl.querySelector('#myCarousel')
- const carousel = new Carousel(carouselEl, {})
+ const carouselEl = fixtureEl.querySelector('#myCarousel')
+ const carousel = new Carousel(carouselEl, {})
- spyOn(EventHandler, 'one').and.callThrough()
- spyOn(carousel, '_slide')
+ const spyOne = spyOn(EventHandler, 'one').and.callThrough()
+ const spySlide = spyOn(carousel, '_slide')
- carousel._isSliding = true
- carousel.to(1)
+ carousel._isSliding = true
+ carousel.to(1)
- expect(carousel._slide).not.toHaveBeenCalled()
- expect(EventHandler.one).toHaveBeenCalled()
+ expect(spySlide).not.toHaveBeenCalled()
+ expect(spyOne).toHaveBeenCalled()
- spyOn(carousel, 'to')
+ const spyTo = spyOn(carousel, 'to')
- EventHandler.trigger(carouselEl, 'slid.bs.carousel')
+ EventHandler.trigger(carouselEl, 'slid.bs.carousel')
- setTimeout(() => {
- expect(carousel.to).toHaveBeenCalledWith(1)
- done()
+ setTimeout(() => {
+ expect(spyTo).toHaveBeenCalledWith(1)
+ resolve()
+ })
})
})
})
+
describe('rtl function', () => {
it('"_directionToOrder" and "_orderToDirection" must return the right results', () => {
fixtureEl.innerHTML = '<div></div>'
@@ -1161,9 +1199,7 @@ describe('Carousel', () => {
const carousel = new Carousel(carouselEl, {})
expect(carousel._directionToOrder('left')).toEqual('next')
- expect(carousel._directionToOrder('prev')).toEqual('prev')
expect(carousel._directionToOrder('right')).toEqual('prev')
- expect(carousel._directionToOrder('next')).toEqual('next')
expect(carousel._orderToDirection('next')).toEqual('left')
expect(carousel._orderToDirection('prev')).toEqual('right')
@@ -1175,12 +1211,10 @@ describe('Carousel', () => {
const carouselEl = fixtureEl.querySelector('div')
const carousel = new Carousel(carouselEl, {})
- expect(isRTL()).toEqual(true, 'rtl has to be true')
+ expect(isRTL()).toBeTrue()
expect(carousel._directionToOrder('left')).toEqual('prev')
- expect(carousel._directionToOrder('prev')).toEqual('prev')
expect(carousel._directionToOrder('right')).toEqual('next')
- expect(carousel._directionToOrder('next')).toEqual('next')
expect(carousel._orderToDirection('next')).toEqual('right')
expect(carousel._orderToDirection('prev')).toEqual('left')
@@ -1192,16 +1226,14 @@ describe('Carousel', () => {
const carouselEl = fixtureEl.querySelector('div')
const carousel = new Carousel(carouselEl, {})
- const spy = spyOn(carousel, '_directionToOrder').and.callThrough()
- const spy2 = spyOn(carousel, '_orderToDirection').and.callThrough()
- carousel._slide('left')
- expect(spy).toHaveBeenCalledWith('left')
- expect(spy2).toHaveBeenCalledWith('next')
+ const spy = spyOn(carousel, '_orderToDirection').and.callThrough()
- carousel._slide('right')
- expect(spy).toHaveBeenCalledWith('right')
- expect(spy2).toHaveBeenCalledWith('prev')
+ carousel._slide(carousel._directionToOrder('left'))
+ expect(spy).toHaveBeenCalledWith('next')
+
+ carousel._slide(carousel._directionToOrder('right'))
+ expect(spy).toHaveBeenCalledWith('prev')
})
it('"_slide" has to call "_directionToOrder" and "_orderToDirection" when rtl=true', () => {
@@ -1210,16 +1242,13 @@ describe('Carousel', () => {
const carouselEl = fixtureEl.querySelector('div')
const carousel = new Carousel(carouselEl, {})
- const spy = spyOn(carousel, '_directionToOrder').and.callThrough()
- const spy2 = spyOn(carousel, '_orderToDirection').and.callThrough()
+ const spy = spyOn(carousel, '_orderToDirection').and.callThrough()
- carousel._slide('left')
- expect(spy).toHaveBeenCalledWith('left')
- expect(spy2).toHaveBeenCalledWith('prev')
+ carousel._slide(carousel._directionToOrder('left'))
+ expect(spy).toHaveBeenCalledWith('prev')
- carousel._slide('right')
- expect(spy).toHaveBeenCalledWith('right')
- expect(spy2).toHaveBeenCalledWith('next')
+ carousel._slide(carousel._directionToOrder('right'))
+ expect(spy).toHaveBeenCalledWith('next')
document.documentElement.dir = 'ltl'
})
@@ -1292,7 +1321,7 @@ describe('Carousel', () => {
const div = fixtureEl.querySelector('div')
- expect(Carousel.getInstance(div)).toEqual(null)
+ expect(Carousel.getInstance(div)).toBeNull()
})
})
@@ -1313,7 +1342,7 @@ describe('Carousel', () => {
const div = fixtureEl.querySelector('div')
- expect(Carousel.getInstance(div)).toEqual(null)
+ expect(Carousel.getInstance(div)).toBeNull()
expect(Carousel.getOrCreateInstance(div)).toBeInstanceOf(Carousel)
})
@@ -1322,7 +1351,7 @@ describe('Carousel', () => {
const div = fixtureEl.querySelector('div')
- expect(Carousel.getInstance(div)).toEqual(null)
+ expect(Carousel.getInstance(div)).toBeNull()
const carousel = Carousel.getOrCreateInstance(div, {
interval: 1
})
@@ -1385,14 +1414,14 @@ describe('Carousel', () => {
const carousel = new Carousel(div)
const slideTo = 2
- spyOn(carousel, 'to')
+ const spy = spyOn(carousel, 'to')
jQueryMock.fn.carousel = Carousel.jQueryInterface
jQueryMock.elements = [div]
jQueryMock.fn.carousel.call(jQueryMock, slideTo)
- expect(carousel.to).toHaveBeenCalledWith(slideTo)
+ expect(spy).toHaveBeenCalledWith(slideTo)
})
it('should throw error on undefined method', () => {
@@ -1418,79 +1447,86 @@ describe('Carousel', () => {
const loadEvent = createEvent('load')
window.dispatchEvent(loadEvent)
-
- expect(Carousel.getInstance(carouselEl)).not.toBeNull()
+ const carousel = Carousel.getInstance(carouselEl)
+ expect(carousel._interval).not.toBeNull()
})
- it('should create carousel and go to the next slide on click (with real button controls)', done => {
- fixtureEl.innerHTML = [
- '<div id="myCarousel" class="carousel slide">',
- ' <div class="carousel-inner">',
- ' <div class="carousel-item active">item 1</div>',
- ' <div id="item2" class="carousel-item">item 2</div>',
- ' <div class="carousel-item">item 3</div>',
- ' </div>',
- ' <button class="carousel-control-prev" data-bs-target="#myCarousel" type="button" data-bs-slide="prev"></button>',
- ' <button id="next" class="carousel-control-next" data-bs-target="#myCarousel" type="button" data-bs-slide="next"></div>',
- '</div>'
- ].join('')
+ it('should create carousel and go to the next slide on click (with real button controls)', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div id="myCarousel" class="carousel slide">',
+ ' <div class="carousel-inner">',
+ ' <div class="carousel-item active">item 1</div>',
+ ' <div id="item2" class="carousel-item">item 2</div>',
+ ' <div class="carousel-item">item 3</div>',
+ ' </div>',
+ ' <button class="carousel-control-prev" data-bs-target="#myCarousel" type="button" data-bs-slide="prev"></button>',
+ ' <button id="next" class="carousel-control-next" data-bs-target="#myCarousel" type="button" data-bs-slide="next"></button>',
+ '</div>'
+ ].join('')
- const next = fixtureEl.querySelector('#next')
- const item2 = fixtureEl.querySelector('#item2')
+ const next = fixtureEl.querySelector('#next')
+ const item2 = fixtureEl.querySelector('#item2')
- next.click()
+ next.click()
- setTimeout(() => {
- expect(item2.classList.contains('active')).toEqual(true)
- done()
- }, 10)
+ setTimeout(() => {
+ expect(item2).toHaveClass('active')
+ resolve()
+ }, 10)
+ })
})
- it('should create carousel and go to the next slide on click (using links as controls)', done => {
- fixtureEl.innerHTML = [
- '<div id="myCarousel" class="carousel slide">',
- ' <div class="carousel-inner">',
- ' <div class="carousel-item active">item 1</div>',
- ' <div id="item2" class="carousel-item">item 2</div>',
- ' <div class="carousel-item">item 3</div>',
- ' </div>',
- ' <a class="carousel-control-prev" href="#myCarousel" role="button" data-bs-slide="prev"></button>',
- ' <a id="next" class="carousel-control-next" href="#myCarousel" role="button" data-bs-slide="next"></div>',
- '</div>'
- ].join('')
+ it('should create carousel and go to the next slide on click (using links as controls)', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div id="myCarousel" class="carousel slide">',
+ ' <div class="carousel-inner">',
+ ' <div class="carousel-item active">item 1</div>',
+ ' <div id="item2" class="carousel-item">item 2</div>',
+ ' <div class="carousel-item">item 3</div>',
+ ' </div>',
+ ' <a class="carousel-control-prev" href="#myCarousel" role="button" data-bs-slide="prev"></a>',
+ ' <a id="next" class="carousel-control-next" href="#myCarousel" role="button" data-bs-slide="next"></a>',
+ '</div>'
+ ].join('')
- const next = fixtureEl.querySelector('#next')
- const item2 = fixtureEl.querySelector('#item2')
+ const next = fixtureEl.querySelector('#next')
+ const item2 = fixtureEl.querySelector('#item2')
- next.click()
+ next.click()
- setTimeout(() => {
- expect(item2.classList.contains('active')).toEqual(true)
- done()
- }, 10)
+ setTimeout(() => {
+ expect(item2).toHaveClass('active')
+ resolve()
+ }, 10)
+ })
})
- it('should create carousel and go to the next slide on click with data-bs-slide-to', done => {
- fixtureEl.innerHTML = [
- '<div id="myCarousel" class="carousel slide">',
- ' <div class="carousel-inner">',
- ' <div class="carousel-item active">item 1</div>',
- ' <div id="item2" class="carousel-item">item 2</div>',
- ' <div class="carousel-item">item 3</div>',
- ' </div>',
- ' <div id="next" data-bs-target="#myCarousel" data-bs-slide-to="1"></div>',
- '</div>'
- ].join('')
+ it('should create carousel and go to the next slide on click with data-bs-slide-to', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div id="myCarousel" class="carousel slide" data-bs-ride="true">',
+ ' <div class="carousel-inner">',
+ ' <div class="carousel-item active">item 1</div>',
+ ' <div id="item2" class="carousel-item">item 2</div>',
+ ' <div class="carousel-item">item 3</div>',
+ ' </div>',
+ ' <div id="next" data-bs-target="#myCarousel" data-bs-slide-to="1"></div>',
+ '</div>'
+ ].join('')
- const next = fixtureEl.querySelector('#next')
- const item2 = fixtureEl.querySelector('#item2')
+ const next = fixtureEl.querySelector('#next')
+ const item2 = fixtureEl.querySelector('#item2')
- next.click()
+ next.click()
- setTimeout(() => {
- expect(item2.classList.contains('active')).toEqual(true)
- done()
- }, 10)
+ setTimeout(() => {
+ expect(item2).toHaveClass('active')
+ expect(Carousel.getInstance('#myCarousel')._interval).not.toBeNull()
+ resolve()
+ }, 10)
+ })
})
it('should do nothing if no selector on click on arrows', () => {
@@ -1521,8 +1557,8 @@ describe('Carousel', () => {
' <div id="item2" class="carousel-item">item 2</div>',
' <div class="carousel-item">item 3</div>',
' </div>',
- ' <button class="carousel-control-prev" data-bs-target="#myCarousel" type="button" data-bs-slide="prev"></div>',
- ' <button id="next" class="carousel-control-next" data-bs-target="#myCarousel" type="button" data-bs-slide="next"></div>',
+ ' <button class="carousel-control-prev" data-bs-target="#myCarousel" type="button" data-bs-slide="prev"></button>',
+ ' <button id="next" class="carousel-control-next" data-bs-target="#myCarousel" type="button" data-bs-slide="next"></button>',
'</div>'
].join('')
diff --git a/js/tests/unit/collapse.spec.js b/js/tests/unit/collapse.spec.js
index 89d20a6d8..58c536752 100644
--- a/js/tests/unit/collapse.spec.js
+++ b/js/tests/unit/collapse.spec.js
@@ -1,6 +1,6 @@
-import Collapse from '../../src/collapse'
-import EventHandler from '../../src/dom/event-handler'
-import { clearFixture, getFixture, jQueryMock } from '../helpers/fixture'
+import Collapse from '../../src/collapse.js'
+import EventHandler from '../../src/dom/event-handler.js'
+import { clearFixture, getFixture, jQueryMock } from '../helpers/fixture.js'
describe('Collapse', () => {
let fixtureEl
@@ -112,11 +112,11 @@ describe('Collapse', () => {
const collapseEl = fixtureEl.querySelector('div')
const collapse = new Collapse(collapseEl)
- spyOn(collapse, 'show')
+ const spy = spyOn(collapse, 'show')
collapse.toggle()
- expect(collapse.show).toHaveBeenCalled()
+ expect(spy).toHaveBeenCalled()
})
it('should call hide method if show class is present', () => {
@@ -127,44 +127,46 @@ describe('Collapse', () => {
toggle: false
})
- spyOn(collapse, 'hide')
+ const spy = spyOn(collapse, 'hide')
collapse.toggle()
- expect(collapse.hide).toHaveBeenCalled()
+ expect(spy).toHaveBeenCalled()
})
- it('should find collapse children if they have collapse class too not only data-bs-parent', done => {
- fixtureEl.innerHTML = [
- '<div class="my-collapse">',
- ' <div class="item">',
- ' <a data-bs-toggle="collapse" href="#">Toggle item 1</a>',
- ' <div id="collapse1" class="collapse show">Lorem ipsum 1</div>',
- ' </div>',
- ' <div class="item">',
- ' <a id="triggerCollapse2" data-bs-toggle="collapse" href="#">Toggle item 2</a>',
- ' <div id="collapse2" class="collapse">Lorem ipsum 2</div>',
- ' </div>',
- '</div>'
- ].join('')
-
- const parent = fixtureEl.querySelector('.my-collapse')
- const collapseEl1 = fixtureEl.querySelector('#collapse1')
- const collapseEl2 = fixtureEl.querySelector('#collapse2')
-
- const collapseList = [].concat(...fixtureEl.querySelectorAll('.collapse'))
- .map(el => new Collapse(el, {
- parent,
- toggle: false
- }))
+ it('should find collapse children if they have collapse class too not only data-bs-parent', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="my-collapse">',
+ ' <div class="item">',
+ ' <a data-bs-toggle="collapse" href="#">Toggle item 1</a>',
+ ' <div id="collapse1" class="collapse show">Lorem ipsum 1</div>',
+ ' </div>',
+ ' <div class="item">',
+ ' <a id="triggerCollapse2" data-bs-toggle="collapse" href="#">Toggle item 2</a>',
+ ' <div id="collapse2" class="collapse">Lorem ipsum 2</div>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const parent = fixtureEl.querySelector('.my-collapse')
+ const collapseEl1 = fixtureEl.querySelector('#collapse1')
+ const collapseEl2 = fixtureEl.querySelector('#collapse2')
+
+ const collapseList = [].concat(...fixtureEl.querySelectorAll('.collapse'))
+ .map(el => new Collapse(el, {
+ parent,
+ toggle: false
+ }))
+
+ collapseEl2.addEventListener('shown.bs.collapse', () => {
+ expect(collapseEl2).toHaveClass('show')
+ expect(collapseEl1).not.toHaveClass('show')
+ resolve()
+ })
- collapseEl2.addEventListener('shown.bs.collapse', () => {
- expect(collapseEl2.classList.contains('show')).toEqual(true)
- expect(collapseEl1.classList.contains('show')).toEqual(false)
- done()
+ collapseList[1].toggle()
})
-
- collapseList[1].toggle()
})
})
@@ -172,7 +174,7 @@ describe('Collapse', () => {
it('should do nothing if is transitioning', () => {
fixtureEl.innerHTML = '<div></div>'
- spyOn(EventHandler, 'trigger')
+ const spy = spyOn(EventHandler, 'trigger')
const collapseEl = fixtureEl.querySelector('div')
const collapse = new Collapse(collapseEl, {
@@ -182,13 +184,13 @@ describe('Collapse', () => {
collapse._isTransitioning = true
collapse.show()
- expect(EventHandler.trigger).not.toHaveBeenCalled()
+ expect(spy).not.toHaveBeenCalled()
})
it('should do nothing if already shown', () => {
fixtureEl.innerHTML = '<div class="show"></div>'
- spyOn(EventHandler, 'trigger')
+ const spy = spyOn(EventHandler, 'trigger')
const collapseEl = fixtureEl.querySelector('div')
const collapse = new Collapse(collapseEl, {
@@ -197,205 +199,218 @@ describe('Collapse', () => {
collapse.show()
- expect(EventHandler.trigger).not.toHaveBeenCalled()
+ expect(spy).not.toHaveBeenCalled()
})
- it('should show a collapsed element', done => {
- fixtureEl.innerHTML = '<div class="collapse" style="height: 0px;"></div>'
+ it('should show a collapsed element', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div class="collapse" style="height: 0px;"></div>'
- const collapseEl = fixtureEl.querySelector('div')
- const collapse = new Collapse(collapseEl, {
- toggle: false
- })
+ const collapseEl = fixtureEl.querySelector('div')
+ const collapse = new Collapse(collapseEl, {
+ toggle: false
+ })
- collapseEl.addEventListener('show.bs.collapse', () => {
- expect(collapseEl.style.height).toEqual('0px')
- })
- collapseEl.addEventListener('shown.bs.collapse', () => {
- expect(collapseEl.classList.contains('show')).toEqual(true)
- expect(collapseEl.style.height).toEqual('')
- done()
- })
+ collapseEl.addEventListener('show.bs.collapse', () => {
+ expect(collapseEl.style.height).toEqual('0px')
+ })
+ collapseEl.addEventListener('shown.bs.collapse', () => {
+ expect(collapseEl).toHaveClass('show')
+ expect(collapseEl.style.height).toEqual('')
+ resolve()
+ })
- collapse.show()
+ collapse.show()
+ })
})
- it('should show a collapsed element on width', done => {
- fixtureEl.innerHTML = '<div class="collapse collapse-horizontal" style="width: 0px;"></div>'
+ it('should show a collapsed element on width', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div class="collapse collapse-horizontal" style="width: 0px;"></div>'
- const collapseEl = fixtureEl.querySelector('div')
- const collapse = new Collapse(collapseEl, {
- toggle: false
- })
+ const collapseEl = fixtureEl.querySelector('div')
+ const collapse = new Collapse(collapseEl, {
+ toggle: false
+ })
- collapseEl.addEventListener('show.bs.collapse', () => {
- expect(collapseEl.style.width).toEqual('0px')
- })
- collapseEl.addEventListener('shown.bs.collapse', () => {
- expect(collapseEl.classList.contains('show')).toEqual(true)
- expect(collapseEl.style.width).toEqual('')
- done()
- })
+ collapseEl.addEventListener('show.bs.collapse', () => {
+ expect(collapseEl.style.width).toEqual('0px')
+ })
+ collapseEl.addEventListener('shown.bs.collapse', () => {
+ expect(collapseEl).toHaveClass('show')
+ expect(collapseEl.style.width).toEqual('')
+ resolve()
+ })
- collapse.show()
+ collapse.show()
+ })
})
- it('should collapse only the first collapse', done => {
- fixtureEl.innerHTML = [
- '<div class="card" id="accordion1">',
- ' <div id="collapse1" class="collapse"></div>',
- '</div>',
- '<div class="card" id="accordion2">',
- ' <div id="collapse2" class="collapse show"></div>',
- '</div>'
- ].join('')
+ it('should collapse only the first collapse', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="card" id="accordion1">',
+ ' <div id="collapse1" class="collapse"></div>',
+ '</div>',
+ '<div class="card" id="accordion2">',
+ ' <div id="collapse2" class="collapse show"></div>',
+ '</div>'
+ ].join('')
+
+ const el1 = fixtureEl.querySelector('#collapse1')
+ const el2 = fixtureEl.querySelector('#collapse2')
+ const collapse = new Collapse(el1, {
+ toggle: false
+ })
- const el1 = fixtureEl.querySelector('#collapse1')
- const el2 = fixtureEl.querySelector('#collapse2')
- const collapse = new Collapse(el1, {
- toggle: false
- })
+ el1.addEventListener('shown.bs.collapse', () => {
+ expect(el1).toHaveClass('show')
+ expect(el2).toHaveClass('show')
+ resolve()
+ })
- el1.addEventListener('shown.bs.collapse', () => {
- expect(el1.classList.contains('show')).toEqual(true)
- expect(el2.classList.contains('show')).toEqual(true)
- done()
+ collapse.show()
})
-
- collapse.show()
})
- it('should be able to handle toggling of other children siblings', done => {
- fixtureEl.innerHTML = [
- '<div id="parentGroup" class="accordion">',
- ' <div id="parentHeader" class="accordion-header">',
- ' <button data-bs-target="#parentContent" data-bs-toggle="collapse" role="button" class="accordion-toggle">Parent</button>',
- ' </div>',
- ' <div id="parentContent" class="accordion-collapse collapse" aria-labelledby="parentHeader" data-bs-parent="#parentGroup">',
- ' <div class="accordion-body">',
- ' <div id="childGroup" class="accordion">',
- ' <div class="accordion-item">',
- ' <div id="childHeader1" class="accordion-header">',
- ' <button data-bs-target="#childContent1" data-bs-toggle="collapse" role="button" class="accordion-toggle">Child 1</button>',
- ' </div>',
- ' <div id="childContent1" class="accordion-collapse collapse" aria-labelledby="childHeader1" data-bs-parent="#childGroup">',
- ' <div>content</div>',
- ' </div>',
- ' </div>',
- ' <div class="accordion-item">',
- ' <div id="childHeader2" class="accordion-header">',
- ' <button data-bs-target="#childContent2" data-bs-toggle="collapse" role="button" class="accordion-toggle">Child 2</button>',
- ' </div>',
- ' <div id="childContent2" class="accordion-collapse collapse" aria-labelledby="childHeader2" data-bs-parent="#childGroup">',
- ' <div>content</div>',
- ' </div>',
- ' </div>',
- ' </div>',
- ' </div>',
- ' </div>',
- '</div>'
- ].join('')
-
- const el = selector => fixtureEl.querySelector(selector)
-
- const parentBtn = el('[data-bs-target="#parentContent"]')
- const childBtn1 = el('[data-bs-target="#childContent1"]')
- const childBtn2 = el('[data-bs-target="#childContent2"]')
-
- const parentCollapseEl = el('#parentContent')
- const childCollapseEl1 = el('#childContent1')
- const childCollapseEl2 = el('#childContent2')
+ it('should be able to handle toggling of other children siblings', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div id="parentGroup" class="accordion">',
+ ' <div class="accordion-header">',
+ ' <button data-bs-target="#parentContent" data-bs-toggle="collapse" class="accordion-toggle">Parent</button>',
+ ' </div>',
+ ' <div id="parentContent" class="accordion-collapse collapse" data-bs-parent="#parentGroup">',
+ ' <div class="accordion-body">',
+ ' <div id="childGroup" class="accordion">',
+ ' <div class="accordion-item">',
+ ' <div class="accordion-header">',
+ ' <button data-bs-target="#childContent1" data-bs-toggle="collapse" class="accordion-toggle">Child 1</button>',
+ ' </div>',
+ ' <div id="childContent1" class="accordion-collapse collapse" data-bs-parent="#childGroup">',
+ ' <div>content</div>',
+ ' </div>',
+ ' </div>',
+ ' <div class="accordion-item">',
+ ' <div class="accordion-header">',
+ ' <button data-bs-target="#childContent2" data-bs-toggle="collapse" class="accordion-toggle">Child 2</button>',
+ ' </div>',
+ ' <div id="childContent2" class="accordion-collapse collapse" data-bs-parent="#childGroup">',
+ ' <div>content</div>',
+ ' </div>',
+ ' </div>',
+ ' </div>',
+ ' </div>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const el = selector => fixtureEl.querySelector(selector)
+
+ const parentBtn = el('[data-bs-target="#parentContent"]')
+ const childBtn1 = el('[data-bs-target="#childContent1"]')
+ const childBtn2 = el('[data-bs-target="#childContent2"]')
+
+ const parentCollapseEl = el('#parentContent')
+ const childCollapseEl1 = el('#childContent1')
+ const childCollapseEl2 = el('#childContent2')
+
+ parentCollapseEl.addEventListener('shown.bs.collapse', () => {
+ expect(parentCollapseEl).toHaveClass('show')
+ childBtn1.click()
+ })
+ childCollapseEl1.addEventListener('shown.bs.collapse', () => {
+ expect(childCollapseEl1).toHaveClass('show')
+ childBtn2.click()
+ })
+ childCollapseEl2.addEventListener('shown.bs.collapse', () => {
+ expect(childCollapseEl2).toHaveClass('show')
+ expect(childCollapseEl1).not.toHaveClass('show')
+ resolve()
+ })
- parentCollapseEl.addEventListener('shown.bs.collapse', () => {
- expect(parentCollapseEl.classList.contains('show')).toEqual(true)
- childBtn1.click()
+ parentBtn.click()
})
- childCollapseEl1.addEventListener('shown.bs.collapse', () => {
- expect(childCollapseEl1.classList.contains('show')).toEqual(true)
- childBtn2.click()
- })
- childCollapseEl2.addEventListener('shown.bs.collapse', () => {
- expect(childCollapseEl2.classList.contains('show')).toEqual(true)
- expect(childCollapseEl1.classList.contains('show')).toEqual(false)
- done()
- })
-
- parentBtn.click()
})
- it('should not change tab tabpanels descendants on accordion', done => {
- fixtureEl.innerHTML = [
- '<div class="accordion" id="accordionExample">',
- ' <div class="accordion-item">',
- ' <h2 class="accordion-header" id="headingOne">',
- ' <button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">',
- ' Accordion Item #1',
- ' </button>',
- ' </h2>',
- ' <div id="collapseOne" class="accordion-collapse collapse show" aria-labelledby="headingOne" data-bs-parent="#accordionExample">',
- ' <div class="accordion-body">',
- ' <nav>',
- ' <div class="nav nav-tabs" id="nav-tab" role="tablist">',
- ' <button class="nav-link active" id="nav-home-tab" data-bs-toggle="tab" data-bs-target="#nav-home" type="button" role="tab" aria-controls="nav-home" aria-selected="true">Home</button>',
- ' <button class="nav-link" id="nav-profile-tab" data-bs-toggle="tab" data-bs-target="#nav-profile" type="button" role="tab" aria-controls="nav-profile" aria-selected="false">Profile</button>',
- ' </div>',
- ' </nav>',
- ' <div class="tab-content" id="nav-tabContent">',
- ' <div class="tab-pane fade show active" id="nav-home" role="tabpanel" aria-labelledby="nav-home-tab">Home</div>',
- ' <div class="tab-pane fade" id="nav-profile" role="tabpanel" aria-labelledby="nav-profile-tab">Profile</div>',
- ' </div>',
- ' </div>',
- ' </div>',
- ' </div>',
- ' </div>'
- ].join('')
- const el = fixtureEl.querySelector('#collapseOne')
- const activeTabPane = fixtureEl.querySelector('#nav-home')
- const collapse = new Collapse(el)
- let times = 1
+ it('should not change tab tabpanels descendants on accordion', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="accordion" id="accordionExample">',
+ ' <div class="accordion-item">',
+ ' <h2 class="accordion-header">',
+ ' <button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">',
+ ' Accordion Item #1',
+ ' </button>',
+ ' </h2>',
+ ' <div id="collapseOne" class="accordion-collapse collapse show" data-bs-parent="#accordionExample">',
+ ' <div class="accordion-body">',
+ ' <nav>',
+ ' <div class="nav nav-tabs" id="nav-tab" role="tablist">',
+ ' <button class="nav-link active" id="nav-home-tab" data-bs-toggle="tab" data-bs-target="#nav-home" type="button" role="tab" aria-controls="nav-home" aria-selected="true">Home</button>',
+ ' <button class="nav-link" id="nav-profile-tab" data-bs-toggle="tab" data-bs-target="#nav-profile" type="button" role="tab" aria-controls="nav-profile" aria-selected="false">Profile</button>',
+ ' </div>',
+ ' </nav>',
+ ' <div class="tab-content" id="nav-tabContent">',
+ ' <div class="tab-pane fade show active" id="nav-home" role="tabpanel" aria-labelledby="nav-home-tab">Home</div>',
+ ' <div class="tab-pane fade" id="nav-profile" role="tabpanel" aria-labelledby="nav-profile-tab">Profile</div>',
+ ' </div>',
+ ' </div>',
+ ' </div>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const el = fixtureEl.querySelector('#collapseOne')
+ const activeTabPane = fixtureEl.querySelector('#nav-home')
+ const collapse = new Collapse(el)
+ let times = 1
+
+ el.addEventListener('hidden.bs.collapse', () => {
+ collapse.show()
+ })
- el.addEventListener('hidden.bs.collapse', () => {
- collapse.show()
- })
+ el.addEventListener('shown.bs.collapse', () => {
+ expect(activeTabPane).toHaveClass('show')
+ times++
+ if (times === 2) {
+ resolve()
+ }
- el.addEventListener('shown.bs.collapse', () => {
- expect(activeTabPane.classList.contains('show')).toEqual(true)
- times++
- if (times === 2) {
- done()
- }
+ collapse.hide()
+ })
- collapse.hide()
+ collapse.show()
})
-
- collapse.show()
})
- it('should not fire shown when show is prevented', done => {
- fixtureEl.innerHTML = '<div class="collapse"></div>'
+ it('should not fire shown when show is prevented', () => {
+ return new Promise((resolve, reject) => {
+ fixtureEl.innerHTML = '<div class="collapse"></div>'
- const collapseEl = fixtureEl.querySelector('div')
- const collapse = new Collapse(collapseEl, {
- toggle: false
- })
+ const collapseEl = fixtureEl.querySelector('div')
+ const collapse = new Collapse(collapseEl, {
+ toggle: false
+ })
- const expectEnd = () => {
- setTimeout(() => {
- expect().nothing()
- done()
- }, 10)
- }
+ const expectEnd = () => {
+ setTimeout(() => {
+ expect().nothing()
+ resolve()
+ }, 10)
+ }
- collapseEl.addEventListener('show.bs.collapse', event => {
- event.preventDefault()
- expectEnd()
- })
+ collapseEl.addEventListener('show.bs.collapse', event => {
+ event.preventDefault()
+ expectEnd()
+ })
- collapseEl.addEventListener('shown.bs.collapse', () => {
- throw new Error('should not fire shown event')
- })
+ collapseEl.addEventListener('shown.bs.collapse', () => {
+ reject(new Error('should not fire shown event'))
+ })
- collapse.show()
+ collapse.show()
+ })
})
})
@@ -403,7 +418,7 @@ describe('Collapse', () => {
it('should do nothing if is transitioning', () => {
fixtureEl.innerHTML = '<div></div>'
- spyOn(EventHandler, 'trigger')
+ const spy = spyOn(EventHandler, 'trigger')
const collapseEl = fixtureEl.querySelector('div')
const collapse = new Collapse(collapseEl, {
@@ -413,13 +428,13 @@ describe('Collapse', () => {
collapse._isTransitioning = true
collapse.hide()
- expect(EventHandler.trigger).not.toHaveBeenCalled()
+ expect(spy).not.toHaveBeenCalled()
})
it('should do nothing if already shown', () => {
fixtureEl.innerHTML = '<div></div>'
- spyOn(EventHandler, 'trigger')
+ const spy = spyOn(EventHandler, 'trigger')
const collapseEl = fixtureEl.querySelector('div')
const collapse = new Collapse(collapseEl, {
@@ -428,51 +443,55 @@ describe('Collapse', () => {
collapse.hide()
- expect(EventHandler.trigger).not.toHaveBeenCalled()
+ expect(spy).not.toHaveBeenCalled()
})
- it('should hide a collapsed element', done => {
- fixtureEl.innerHTML = '<div class="collapse show"></div>'
+ it('should hide a collapsed element', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div class="collapse show"></div>'
- const collapseEl = fixtureEl.querySelector('div')
- const collapse = new Collapse(collapseEl, {
- toggle: false
- })
+ const collapseEl = fixtureEl.querySelector('div')
+ const collapse = new Collapse(collapseEl, {
+ toggle: false
+ })
- collapseEl.addEventListener('hidden.bs.collapse', () => {
- expect(collapseEl.classList.contains('show')).toEqual(false)
- expect(collapseEl.style.height).toEqual('')
- done()
- })
+ collapseEl.addEventListener('hidden.bs.collapse', () => {
+ expect(collapseEl).not.toHaveClass('show')
+ expect(collapseEl.style.height).toEqual('')
+ resolve()
+ })
- collapse.hide()
+ collapse.hide()
+ })
})
- it('should not fire hidden when hide is prevented', done => {
- fixtureEl.innerHTML = '<div class="collapse show"></div>'
+ it('should not fire hidden when hide is prevented', () => {
+ return new Promise((resolve, reject) => {
+ fixtureEl.innerHTML = '<div class="collapse show"></div>'
- const collapseEl = fixtureEl.querySelector('div')
- const collapse = new Collapse(collapseEl, {
- toggle: false
- })
+ const collapseEl = fixtureEl.querySelector('div')
+ const collapse = new Collapse(collapseEl, {
+ toggle: false
+ })
- const expectEnd = () => {
- setTimeout(() => {
- expect().nothing()
- done()
- }, 10)
- }
+ const expectEnd = () => {
+ setTimeout(() => {
+ expect().nothing()
+ resolve()
+ }, 10)
+ }
- collapseEl.addEventListener('hide.bs.collapse', event => {
- event.preventDefault()
- expectEnd()
- })
+ collapseEl.addEventListener('hide.bs.collapse', event => {
+ event.preventDefault()
+ expectEnd()
+ })
- collapseEl.addEventListener('hidden.bs.collapse', () => {
- throw new Error('should not fire hidden event')
- })
+ collapseEl.addEventListener('hidden.bs.collapse', () => {
+ reject(new Error('should not fire hidden event'))
+ })
- collapse.hide()
+ collapse.hide()
+ })
})
})
@@ -489,416 +508,438 @@ describe('Collapse', () => {
collapse.dispose()
- expect(Collapse.getInstance(collapseEl)).toEqual(null)
+ expect(Collapse.getInstance(collapseEl)).toBeNull()
})
})
describe('data-api', () => {
- it('should prevent url change if click on nested elements', done => {
- fixtureEl.innerHTML = [
- '<a role="button" data-bs-toggle="collapse" class="collapsed" href="#collapse">',
- ' <span id="nested"></span>',
- '</a>',
- '<div id="collapse" class="collapse"></div>'
- ].join('')
-
- const triggerEl = fixtureEl.querySelector('a')
- const nestedTriggerEl = fixtureEl.querySelector('#nested')
-
- spyOn(Event.prototype, 'preventDefault').and.callThrough()
+ it('should prevent url change if click on nested elements', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<a role="button" data-bs-toggle="collapse" class="collapsed" href="#collapse">',
+ ' <span id="nested"></span>',
+ '</a>',
+ '<div id="collapse" class="collapse"></div>'
+ ].join('')
+
+ const triggerEl = fixtureEl.querySelector('a')
+ const nestedTriggerEl = fixtureEl.querySelector('#nested')
+
+ const spy = spyOn(Event.prototype, 'preventDefault').and.callThrough()
+
+ triggerEl.addEventListener('click', event => {
+ expect(event.target.isEqualNode(nestedTriggerEl)).toBeTrue()
+ expect(event.delegateTarget.isEqualNode(triggerEl)).toBeTrue()
+ expect(spy).toHaveBeenCalled()
+ resolve()
+ })
- triggerEl.addEventListener('click', event => {
- expect(event.target.isEqualNode(nestedTriggerEl)).toEqual(true)
- expect(event.delegateTarget.isEqualNode(triggerEl)).toEqual(true)
- expect(Event.prototype.preventDefault).toHaveBeenCalled()
- done()
+ nestedTriggerEl.click()
})
-
- nestedTriggerEl.click()
})
- it('should show multiple collapsed elements', done => {
- fixtureEl.innerHTML = [
- '<a role="button" data-bs-toggle="collapse" class="collapsed" href=".multi"></a>',
- '<div id="collapse1" class="collapse multi"></div>',
- '<div id="collapse2" class="collapse multi"></div>'
- ].join('')
-
- const trigger = fixtureEl.querySelector('a')
- const collapse1 = fixtureEl.querySelector('#collapse1')
- const collapse2 = fixtureEl.querySelector('#collapse2')
+ it('should show multiple collapsed elements', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<a role="button" data-bs-toggle="collapse" class="collapsed" href=".multi"></a>',
+ '<div id="collapse1" class="collapse multi"></div>',
+ '<div id="collapse2" class="collapse multi"></div>'
+ ].join('')
+
+ const trigger = fixtureEl.querySelector('a')
+ const collapse1 = fixtureEl.querySelector('#collapse1')
+ const collapse2 = fixtureEl.querySelector('#collapse2')
+
+ collapse2.addEventListener('shown.bs.collapse', () => {
+ expect(trigger.getAttribute('aria-expanded')).toEqual('true')
+ expect(trigger).not.toHaveClass('collapsed')
+ expect(collapse1).toHaveClass('show')
+ expect(collapse1).toHaveClass('show')
+ resolve()
+ })
- collapse2.addEventListener('shown.bs.collapse', () => {
- expect(trigger.getAttribute('aria-expanded')).toEqual('true')
- expect(trigger.classList.contains('collapsed')).toEqual(false)
- expect(collapse1.classList.contains('show')).toEqual(true)
- expect(collapse1.classList.contains('show')).toEqual(true)
- done()
+ trigger.click()
})
-
- trigger.click()
})
- it('should hide multiple collapsed elements', done => {
- fixtureEl.innerHTML = [
- '<a role="button" data-bs-toggle="collapse" href=".multi"></a>',
- '<div id="collapse1" class="collapse multi show"></div>',
- '<div id="collapse2" class="collapse multi show"></div>'
- ].join('')
-
- const trigger = fixtureEl.querySelector('a')
- const collapse1 = fixtureEl.querySelector('#collapse1')
- const collapse2 = fixtureEl.querySelector('#collapse2')
+ it('should hide multiple collapsed elements', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<a role="button" data-bs-toggle="collapse" href=".multi"></a>',
+ '<div id="collapse1" class="collapse multi show"></div>',
+ '<div id="collapse2" class="collapse multi show"></div>'
+ ].join('')
+
+ const trigger = fixtureEl.querySelector('a')
+ const collapse1 = fixtureEl.querySelector('#collapse1')
+ const collapse2 = fixtureEl.querySelector('#collapse2')
+
+ collapse2.addEventListener('hidden.bs.collapse', () => {
+ expect(trigger.getAttribute('aria-expanded')).toEqual('false')
+ expect(trigger).toHaveClass('collapsed')
+ expect(collapse1).not.toHaveClass('show')
+ expect(collapse1).not.toHaveClass('show')
+ resolve()
+ })
- collapse2.addEventListener('hidden.bs.collapse', () => {
- expect(trigger.getAttribute('aria-expanded')).toEqual('false')
- expect(trigger.classList.contains('collapsed')).toEqual(true)
- expect(collapse1.classList.contains('show')).toEqual(false)
- expect(collapse1.classList.contains('show')).toEqual(false)
- done()
+ trigger.click()
})
-
- trigger.click()
})
- it('should remove "collapsed" class from target when collapse is shown', done => {
- fixtureEl.innerHTML = [
- '<a id="link1" role="button" data-bs-toggle="collapse" class="collapsed" href="#" data-bs-target="#test1"></a>',
- '<a id="link2" role="button" data-bs-toggle="collapse" class="collapsed" href="#" data-bs-target="#test1"></a>',
- '<div id="test1"></div>'
- ].join('')
-
- const link1 = fixtureEl.querySelector('#link1')
- const link2 = fixtureEl.querySelector('#link2')
- const collapseTest1 = fixtureEl.querySelector('#test1')
+ it('should remove "collapsed" class from target when collapse is shown', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<a id="link1" role="button" data-bs-toggle="collapse" class="collapsed" href="#" data-bs-target="#test1"></a>',
+ '<a id="link2" role="button" data-bs-toggle="collapse" class="collapsed" href="#" data-bs-target="#test1"></a>',
+ '<div id="test1"></div>'
+ ].join('')
+
+ const link1 = fixtureEl.querySelector('#link1')
+ const link2 = fixtureEl.querySelector('#link2')
+ const collapseTest1 = fixtureEl.querySelector('#test1')
+
+ collapseTest1.addEventListener('shown.bs.collapse', () => {
+ expect(link1.getAttribute('aria-expanded')).toEqual('true')
+ expect(link2.getAttribute('aria-expanded')).toEqual('true')
+ expect(link1).not.toHaveClass('collapsed')
+ expect(link2).not.toHaveClass('collapsed')
+ resolve()
+ })
- collapseTest1.addEventListener('shown.bs.collapse', () => {
- expect(link1.getAttribute('aria-expanded')).toEqual('true')
- expect(link2.getAttribute('aria-expanded')).toEqual('true')
- expect(link1.classList.contains('collapsed')).toEqual(false)
- expect(link2.classList.contains('collapsed')).toEqual(false)
- done()
+ link1.click()
})
-
- link1.click()
})
- it('should add "collapsed" class to target when collapse is hidden', done => {
- fixtureEl.innerHTML = [
- '<a id="link1" role="button" data-bs-toggle="collapse" href="#" data-bs-target="#test1"></a>',
- '<a id="link2" role="button" data-bs-toggle="collapse" href="#" data-bs-target="#test1"></a>',
- '<div id="test1" class="show"></div>'
- ].join('')
-
- const link1 = fixtureEl.querySelector('#link1')
- const link2 = fixtureEl.querySelector('#link2')
- const collapseTest1 = fixtureEl.querySelector('#test1')
+ it('should add "collapsed" class to target when collapse is hidden', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<a id="link1" role="button" data-bs-toggle="collapse" href="#" data-bs-target="#test1"></a>',
+ '<a id="link2" role="button" data-bs-toggle="collapse" href="#" data-bs-target="#test1"></a>',
+ '<div id="test1" class="show"></div>'
+ ].join('')
+
+ const link1 = fixtureEl.querySelector('#link1')
+ const link2 = fixtureEl.querySelector('#link2')
+ const collapseTest1 = fixtureEl.querySelector('#test1')
+
+ collapseTest1.addEventListener('hidden.bs.collapse', () => {
+ expect(link1.getAttribute('aria-expanded')).toEqual('false')
+ expect(link2.getAttribute('aria-expanded')).toEqual('false')
+ expect(link1).toHaveClass('collapsed')
+ expect(link2).toHaveClass('collapsed')
+ resolve()
+ })
- collapseTest1.addEventListener('hidden.bs.collapse', () => {
- expect(link1.getAttribute('aria-expanded')).toEqual('false')
- expect(link2.getAttribute('aria-expanded')).toEqual('false')
- expect(link1.classList.contains('collapsed')).toEqual(true)
- expect(link2.classList.contains('collapsed')).toEqual(true)
- done()
+ link1.click()
})
-
- link1.click()
})
- it('should allow accordion to use children other than card', done => {
- fixtureEl.innerHTML = [
- '<div id="accordion">',
- ' <div class="item">',
- ' <a id="linkTrigger" data-bs-toggle="collapse" href="#collapseOne" aria-expanded="false" aria-controls="collapseOne"></a>',
- ' <div id="collapseOne" class="collapse" role="tabpanel" aria-labelledby="headingThree" data-bs-parent="#accordion"></div>',
- ' </div>',
- ' <div class="item">',
- ' <a id="linkTriggerTwo" data-bs-toggle="collapse" href="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo"></a>',
- ' <div id="collapseTwo" class="collapse show" role="tabpanel" aria-labelledby="headingTwo" data-bs-parent="#accordion"></div>',
- ' </div>',
- '</div>'
- ].join('')
-
- const trigger = fixtureEl.querySelector('#linkTrigger')
- const triggerTwo = fixtureEl.querySelector('#linkTriggerTwo')
- const collapseOne = fixtureEl.querySelector('#collapseOne')
- const collapseTwo = fixtureEl.querySelector('#collapseTwo')
-
- collapseOne.addEventListener('shown.bs.collapse', () => {
- expect(collapseOne.classList.contains('show')).toEqual(true)
- expect(collapseTwo.classList.contains('show')).toEqual(false)
+ it('should allow accordion to use children other than card', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div id="accordion">',
+ ' <div class="item">',
+ ' <a id="linkTrigger" data-bs-toggle="collapse" href="#collapseOne" aria-expanded="false" aria-controls="collapseOne"></a>',
+ ' <div id="collapseOne" class="collapse" role="tabpanel" data-bs-parent="#accordion"></div>',
+ ' </div>',
+ ' <div class="item">',
+ ' <a id="linkTriggerTwo" data-bs-toggle="collapse" href="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo"></a>',
+ ' <div id="collapseTwo" class="collapse show" role="tabpanel" data-bs-parent="#accordion"></div>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const trigger = fixtureEl.querySelector('#linkTrigger')
+ const triggerTwo = fixtureEl.querySelector('#linkTriggerTwo')
+ const collapseOne = fixtureEl.querySelector('#collapseOne')
+ const collapseTwo = fixtureEl.querySelector('#collapseTwo')
+
+ collapseOne.addEventListener('shown.bs.collapse', () => {
+ expect(collapseOne).toHaveClass('show')
+ expect(collapseTwo).not.toHaveClass('show')
+
+ collapseTwo.addEventListener('shown.bs.collapse', () => {
+ expect(collapseOne).not.toHaveClass('show')
+ expect(collapseTwo).toHaveClass('show')
+ resolve()
+ })
- collapseTwo.addEventListener('shown.bs.collapse', () => {
- expect(collapseOne.classList.contains('show')).toEqual(false)
- expect(collapseTwo.classList.contains('show')).toEqual(true)
- done()
+ triggerTwo.click()
})
- triggerTwo.click()
+ trigger.click()
})
-
- trigger.click()
})
- it('should not prevent event for input', done => {
- fixtureEl.innerHTML = [
- '<input type="checkbox" data-bs-toggle="collapse" data-bs-target="#collapsediv1">',
- '<div id="collapsediv1"></div>'
- ].join('')
+ it('should not prevent event for input', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<input type="checkbox" data-bs-toggle="collapse" data-bs-target="#collapsediv1">',
+ '<div id="collapsediv1"></div>'
+ ].join('')
- const target = fixtureEl.querySelector('input')
- const collapseEl = fixtureEl.querySelector('#collapsediv1')
+ const target = fixtureEl.querySelector('input')
+ const collapseEl = fixtureEl.querySelector('#collapsediv1')
- collapseEl.addEventListener('shown.bs.collapse', () => {
- expect(collapseEl.classList.contains('show')).toEqual(true)
- expect(target.checked).toEqual(true)
- done()
- })
+ collapseEl.addEventListener('shown.bs.collapse', () => {
+ expect(collapseEl).toHaveClass('show')
+ expect(target.checked).toBeTrue()
+ resolve()
+ })
- target.click()
+ target.click()
+ })
})
- it('should allow accordion to contain nested elements', done => {
- fixtureEl.innerHTML = [
- '<div id="accordion">',
- ' <div class="row">',
- ' <div class="col-lg-6">',
- ' <div class="item">',
- ' <a id="linkTrigger" data-bs-toggle="collapse" href="#collapseOne" aria-expanded="false" aria-controls="collapseOne"></a>',
- ' <div id="collapseOne" class="collapse" role="tabpanel" aria-labelledby="headingThree" data-bs-parent="#accordion"></div>',
- ' </div>',
- ' </div>',
- ' <div class="col-lg-6">',
- ' <div class="item">',
- ' <a id="linkTriggerTwo" data-bs-toggle="collapse" href="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo"></a>',
- ' <div id="collapseTwo" class="collapse show" role="tabpanel" aria-labelledby="headingTwo" data-bs-parent="#accordion"></div>',
- ' </div>',
- ' </div>',
- ' </div>',
- '</div>'
- ].join('')
-
- const triggerEl = fixtureEl.querySelector('#linkTrigger')
- const triggerTwoEl = fixtureEl.querySelector('#linkTriggerTwo')
- const collapseOneEl = fixtureEl.querySelector('#collapseOne')
- const collapseTwoEl = fixtureEl.querySelector('#collapseTwo')
-
- collapseOneEl.addEventListener('shown.bs.collapse', () => {
- expect(collapseOneEl.classList.contains('show')).toEqual(true)
- expect(triggerEl.classList.contains('collapsed')).toEqual(false)
- expect(triggerEl.getAttribute('aria-expanded')).toEqual('true')
-
- expect(collapseTwoEl.classList.contains('show')).toEqual(false)
- expect(triggerTwoEl.classList.contains('collapsed')).toEqual(true)
- expect(triggerTwoEl.getAttribute('aria-expanded')).toEqual('false')
-
- collapseTwoEl.addEventListener('shown.bs.collapse', () => {
- expect(collapseOneEl.classList.contains('show')).toEqual(false)
- expect(triggerEl.classList.contains('collapsed')).toEqual(true)
- expect(triggerEl.getAttribute('aria-expanded')).toEqual('false')
+ it('should allow accordion to contain nested elements', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div id="accordion">',
+ ' <div class="row">',
+ ' <div class="col-lg-6">',
+ ' <div class="item">',
+ ' <a id="linkTrigger" data-bs-toggle="collapse" href="#collapseOne" aria-expanded="false" aria-controls="collapseOne"></a>',
+ ' <div id="collapseOne" class="collapse" role="tabpanel" data-bs-parent="#accordion"></div>',
+ ' </div>',
+ ' </div>',
+ ' <div class="col-lg-6">',
+ ' <div class="item">',
+ ' <a id="linkTriggerTwo" data-bs-toggle="collapse" href="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo"></a>',
+ ' <div id="collapseTwo" class="collapse show" role="tabpanel" data-bs-parent="#accordion"></div>',
+ ' </div>',
+ ' </div>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const triggerEl = fixtureEl.querySelector('#linkTrigger')
+ const triggerTwoEl = fixtureEl.querySelector('#linkTriggerTwo')
+ const collapseOneEl = fixtureEl.querySelector('#collapseOne')
+ const collapseTwoEl = fixtureEl.querySelector('#collapseTwo')
+
+ collapseOneEl.addEventListener('shown.bs.collapse', () => {
+ expect(collapseOneEl).toHaveClass('show')
+ expect(triggerEl).not.toHaveClass('collapsed')
+ expect(triggerEl.getAttribute('aria-expanded')).toEqual('true')
+
+ expect(collapseTwoEl).not.toHaveClass('show')
+ expect(triggerTwoEl).toHaveClass('collapsed')
+ expect(triggerTwoEl.getAttribute('aria-expanded')).toEqual('false')
+
+ collapseTwoEl.addEventListener('shown.bs.collapse', () => {
+ expect(collapseOneEl).not.toHaveClass('show')
+ expect(triggerEl).toHaveClass('collapsed')
+ expect(triggerEl.getAttribute('aria-expanded')).toEqual('false')
+
+ expect(collapseTwoEl).toHaveClass('show')
+ expect(triggerTwoEl).not.toHaveClass('collapsed')
+ expect(triggerTwoEl.getAttribute('aria-expanded')).toEqual('true')
+ resolve()
+ })
- expect(collapseTwoEl.classList.contains('show')).toEqual(true)
- expect(triggerTwoEl.classList.contains('collapsed')).toEqual(false)
- expect(triggerTwoEl.getAttribute('aria-expanded')).toEqual('true')
- done()
+ triggerTwoEl.click()
})
- triggerTwoEl.click()
+ triggerEl.click()
})
-
- triggerEl.click()
})
- it('should allow accordion to target multiple elements', done => {
- fixtureEl.innerHTML = [
- '<div id="accordion">',
- ' <a id="linkTriggerOne" data-bs-toggle="collapse" data-bs-target=".collapseOne" href="#" aria-expanded="false" aria-controls="collapseOne"></a>',
- ' <a id="linkTriggerTwo" data-bs-toggle="collapse" data-bs-target=".collapseTwo" href="#" aria-expanded="false" aria-controls="collapseTwo"></a>',
- ' <div id="collapseOneOne" class="collapse collapseOne" role="tabpanel" data-bs-parent="#accordion"></div>',
- ' <div id="collapseOneTwo" class="collapse collapseOne" role="tabpanel" data-bs-parent="#accordion"></div>',
- ' <div id="collapseTwoOne" class="collapse collapseTwo" role="tabpanel" data-bs-parent="#accordion"></div>',
- ' <div id="collapseTwoTwo" class="collapse collapseTwo" role="tabpanel" data-bs-parent="#accordion"></div>',
- '</div>'
- ].join('')
-
- const trigger = fixtureEl.querySelector('#linkTriggerOne')
- const triggerTwo = fixtureEl.querySelector('#linkTriggerTwo')
- const collapseOneOne = fixtureEl.querySelector('#collapseOneOne')
- const collapseOneTwo = fixtureEl.querySelector('#collapseOneTwo')
- const collapseTwoOne = fixtureEl.querySelector('#collapseTwoOne')
- const collapseTwoTwo = fixtureEl.querySelector('#collapseTwoTwo')
- const collapsedElements = {
- one: false,
- two: false
- }
-
- function firstTest() {
- expect(collapseOneOne.classList.contains('show')).toEqual(true)
- expect(collapseOneTwo.classList.contains('show')).toEqual(true)
-
- expect(collapseTwoOne.classList.contains('show')).toEqual(false)
- expect(collapseTwoTwo.classList.contains('show')).toEqual(false)
-
- triggerTwo.click()
- }
-
- function secondTest() {
- expect(collapseOneOne.classList.contains('show')).toEqual(false)
- expect(collapseOneTwo.classList.contains('show')).toEqual(false)
-
- expect(collapseTwoOne.classList.contains('show')).toEqual(true)
- expect(collapseTwoTwo.classList.contains('show')).toEqual(true)
- done()
- }
-
- collapseOneOne.addEventListener('shown.bs.collapse', () => {
- if (collapsedElements.one) {
- firstTest()
- } else {
- collapsedElements.one = true
+ it('should allow accordion to target multiple elements', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div id="accordion">',
+ ' <a id="linkTriggerOne" data-bs-toggle="collapse" data-bs-target=".collapseOne" href="#" aria-expanded="false" aria-controls="collapseOne"></a>',
+ ' <a id="linkTriggerTwo" data-bs-toggle="collapse" data-bs-target=".collapseTwo" href="#" aria-expanded="false" aria-controls="collapseTwo"></a>',
+ ' <div id="collapseOneOne" class="collapse collapseOne" role="tabpanel" data-bs-parent="#accordion"></div>',
+ ' <div id="collapseOneTwo" class="collapse collapseOne" role="tabpanel" data-bs-parent="#accordion"></div>',
+ ' <div id="collapseTwoOne" class="collapse collapseTwo" role="tabpanel" data-bs-parent="#accordion"></div>',
+ ' <div id="collapseTwoTwo" class="collapse collapseTwo" role="tabpanel" data-bs-parent="#accordion"></div>',
+ '</div>'
+ ].join('')
+
+ const trigger = fixtureEl.querySelector('#linkTriggerOne')
+ const triggerTwo = fixtureEl.querySelector('#linkTriggerTwo')
+ const collapseOneOne = fixtureEl.querySelector('#collapseOneOne')
+ const collapseOneTwo = fixtureEl.querySelector('#collapseOneTwo')
+ const collapseTwoOne = fixtureEl.querySelector('#collapseTwoOne')
+ const collapseTwoTwo = fixtureEl.querySelector('#collapseTwoTwo')
+ const collapsedElements = {
+ one: false,
+ two: false
}
- })
- collapseOneTwo.addEventListener('shown.bs.collapse', () => {
- if (collapsedElements.one) {
- firstTest()
- } else {
- collapsedElements.one = true
- }
- })
+ function firstTest() {
+ expect(collapseOneOne).toHaveClass('show')
+ expect(collapseOneTwo).toHaveClass('show')
- collapseTwoOne.addEventListener('shown.bs.collapse', () => {
- if (collapsedElements.two) {
- secondTest()
- } else {
- collapsedElements.two = true
- }
- })
+ expect(collapseTwoOne).not.toHaveClass('show')
+ expect(collapseTwoTwo).not.toHaveClass('show')
- collapseTwoTwo.addEventListener('shown.bs.collapse', () => {
- if (collapsedElements.two) {
- secondTest()
- } else {
- collapsedElements.two = true
+ triggerTwo.click()
}
- })
- trigger.click()
- })
+ function secondTest() {
+ expect(collapseOneOne).not.toHaveClass('show')
+ expect(collapseOneTwo).not.toHaveClass('show')
- it('should collapse accordion children but not nested accordion children', done => {
- fixtureEl.innerHTML = [
- '<div id="accordion">',
- ' <div class="item">',
- ' <a id="linkTrigger" data-bs-toggle="collapse" href="#collapseOne" aria-expanded="false" aria-controls="collapseOne"></a>',
- ' <div id="collapseOne" data-bs-parent="#accordion" class="collapse" role="tabpanel" aria-labelledby="headingThree">',
- ' <div id="nestedAccordion">',
- ' <div class="item">',
- ' <a id="nestedLinkTrigger" data-bs-toggle="collapse" href="#nestedCollapseOne" aria-expanded="false" aria-controls="nestedCollapseOne"></a>',
- ' <div id="nestedCollapseOne" data-bs-parent="#nestedAccordion" class="collapse" role="tabpanel" aria-labelledby="headingThree"></div>',
- ' </div>',
- ' </div>',
- ' </div>',
- ' </div>',
- ' <div class="item">',
- ' <a id="linkTriggerTwo" data-bs-toggle="collapse" href="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo"></a>',
- ' <div id="collapseTwo" data-bs-parent="#accordion" class="collapse show" role="tabpanel" aria-labelledby="headingTwo"></div>',
- ' </div>',
- '</div>'
- ].join('')
+ expect(collapseTwoOne).toHaveClass('show')
+ expect(collapseTwoTwo).toHaveClass('show')
+ resolve()
+ }
- const trigger = fixtureEl.querySelector('#linkTrigger')
- const triggerTwo = fixtureEl.querySelector('#linkTriggerTwo')
- const nestedTrigger = fixtureEl.querySelector('#nestedLinkTrigger')
- const collapseOne = fixtureEl.querySelector('#collapseOne')
- const collapseTwo = fixtureEl.querySelector('#collapseTwo')
- const nestedCollapseOne = fixtureEl.querySelector('#nestedCollapseOne')
-
- function handlerCollapseOne() {
- expect(collapseOne.classList.contains('show')).toEqual(true)
- expect(collapseTwo.classList.contains('show')).toEqual(false)
- expect(nestedCollapseOne.classList.contains('show')).toEqual(false)
-
- nestedCollapseOne.addEventListener('shown.bs.collapse', handlerNestedCollapseOne)
- nestedTrigger.click()
- collapseOne.removeEventListener('shown.bs.collapse', handlerCollapseOne)
- }
+ collapseOneOne.addEventListener('shown.bs.collapse', () => {
+ if (collapsedElements.one) {
+ firstTest()
+ } else {
+ collapsedElements.one = true
+ }
+ })
- function handlerNestedCollapseOne() {
- expect(collapseOne.classList.contains('show')).toEqual(true)
- expect(collapseTwo.classList.contains('show')).toEqual(false)
- expect(nestedCollapseOne.classList.contains('show')).toEqual(true)
+ collapseOneTwo.addEventListener('shown.bs.collapse', () => {
+ if (collapsedElements.one) {
+ firstTest()
+ } else {
+ collapsedElements.one = true
+ }
+ })
- collapseTwo.addEventListener('shown.bs.collapse', () => {
- expect(collapseOne.classList.contains('show')).toEqual(false)
- expect(collapseTwo.classList.contains('show')).toEqual(true)
- expect(nestedCollapseOne.classList.contains('show')).toEqual(true)
- done()
+ collapseTwoOne.addEventListener('shown.bs.collapse', () => {
+ if (collapsedElements.two) {
+ secondTest()
+ } else {
+ collapsedElements.two = true
+ }
})
- triggerTwo.click()
- nestedCollapseOne.removeEventListener('shown.bs.collapse', handlerNestedCollapseOne)
- }
+ collapseTwoTwo.addEventListener('shown.bs.collapse', () => {
+ if (collapsedElements.two) {
+ secondTest()
+ } else {
+ collapsedElements.two = true
+ }
+ })
- collapseOne.addEventListener('shown.bs.collapse', handlerCollapseOne)
- trigger.click()
+ trigger.click()
+ })
})
- it('should add "collapsed" class and set aria-expanded to triggers only when all the targeted collapse are hidden', done => {
- fixtureEl.innerHTML = [
- '<a id="trigger1" role="button" data-bs-toggle="collapse" href="#test1"></a>',
- '<a id="trigger2" role="button" data-bs-toggle="collapse" href="#test2"></a>',
- '<a id="trigger3" role="button" data-bs-toggle="collapse" href=".multi"></a>',
- '<div id="test1" class="multi"></div>',
- '<div id="test2" class="multi"></div>'
- ].join('')
+ it('should collapse accordion children but not nested accordion children', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div id="accordion">',
+ ' <div class="item">',
+ ' <a id="linkTrigger" data-bs-toggle="collapse" href="#collapseOne" aria-expanded="false" aria-controls="collapseOne"></a>',
+ ' <div id="collapseOne" data-bs-parent="#accordion" class="collapse" role="tabpanel">',
+ ' <div id="nestedAccordion">',
+ ' <div class="item">',
+ ' <a id="nestedLinkTrigger" data-bs-toggle="collapse" href="#nestedCollapseOne" aria-expanded="false" aria-controls="nestedCollapseOne"></a>',
+ ' <div id="nestedCollapseOne" data-bs-parent="#nestedAccordion" class="collapse" role="tabpanel"></div>',
+ ' </div>',
+ ' </div>',
+ ' </div>',
+ ' </div>',
+ ' <div class="item">',
+ ' <a id="linkTriggerTwo" data-bs-toggle="collapse" href="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo"></a>',
+ ' <div id="collapseTwo" data-bs-parent="#accordion" class="collapse show" role="tabpanel"></div>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const trigger = fixtureEl.querySelector('#linkTrigger')
+ const triggerTwo = fixtureEl.querySelector('#linkTriggerTwo')
+ const nestedTrigger = fixtureEl.querySelector('#nestedLinkTrigger')
+ const collapseOne = fixtureEl.querySelector('#collapseOne')
+ const collapseTwo = fixtureEl.querySelector('#collapseTwo')
+ const nestedCollapseOne = fixtureEl.querySelector('#nestedCollapseOne')
+
+ function handlerCollapseOne() {
+ expect(collapseOne).toHaveClass('show')
+ expect(collapseTwo).not.toHaveClass('show')
+ expect(nestedCollapseOne).not.toHaveClass('show')
+
+ nestedCollapseOne.addEventListener('shown.bs.collapse', handlerNestedCollapseOne)
+ nestedTrigger.click()
+ collapseOne.removeEventListener('shown.bs.collapse', handlerCollapseOne)
+ }
- const trigger1 = fixtureEl.querySelector('#trigger1')
- const trigger2 = fixtureEl.querySelector('#trigger2')
- const trigger3 = fixtureEl.querySelector('#trigger3')
- const target1 = fixtureEl.querySelector('#test1')
- const target2 = fixtureEl.querySelector('#test2')
+ function handlerNestedCollapseOne() {
+ expect(collapseOne).toHaveClass('show')
+ expect(collapseTwo).not.toHaveClass('show')
+ expect(nestedCollapseOne).toHaveClass('show')
- const target2Shown = () => {
- expect(trigger1.classList.contains('collapsed')).toEqual(false)
- expect(trigger1.getAttribute('aria-expanded')).toEqual('true')
+ collapseTwo.addEventListener('shown.bs.collapse', () => {
+ expect(collapseOne).not.toHaveClass('show')
+ expect(collapseTwo).toHaveClass('show')
+ expect(nestedCollapseOne).toHaveClass('show')
+ resolve()
+ })
- expect(trigger2.classList.contains('collapsed')).toEqual(false)
- expect(trigger2.getAttribute('aria-expanded')).toEqual('true')
+ triggerTwo.click()
+ nestedCollapseOne.removeEventListener('shown.bs.collapse', handlerNestedCollapseOne)
+ }
- expect(trigger3.classList.contains('collapsed')).toEqual(false)
- expect(trigger3.getAttribute('aria-expanded')).toEqual('true')
+ collapseOne.addEventListener('shown.bs.collapse', handlerCollapseOne)
+ trigger.click()
+ })
+ })
- target2.addEventListener('hidden.bs.collapse', () => {
- expect(trigger1.classList.contains('collapsed')).toEqual(false)
+ it('should add "collapsed" class and set aria-expanded to triggers only when all the targeted collapse are hidden', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<a id="trigger1" role="button" data-bs-toggle="collapse" href="#test1"></a>',
+ '<a id="trigger2" role="button" data-bs-toggle="collapse" href="#0/my/id"></a>',
+ '<a id="trigger3" role="button" data-bs-toggle="collapse" href=".multi"></a>',
+ '<div id="test1" class="multi"></div>',
+ '<div id="0/my/id" class="multi"></div>'
+ ].join('')
+
+ const trigger1 = fixtureEl.querySelector('#trigger1')
+ const trigger2 = fixtureEl.querySelector('#trigger2')
+ const trigger3 = fixtureEl.querySelector('#trigger3')
+ const target1 = fixtureEl.querySelector('#test1')
+ const target2 = fixtureEl.querySelector(`#${CSS.escape('0/my/id')}`)
+
+ const target2Shown = () => {
+ expect(trigger1).not.toHaveClass('collapsed')
expect(trigger1.getAttribute('aria-expanded')).toEqual('true')
- expect(trigger2.classList.contains('collapsed')).toEqual(true)
- expect(trigger2.getAttribute('aria-expanded')).toEqual('false')
+ expect(trigger2).not.toHaveClass('collapsed')
+ expect(trigger2.getAttribute('aria-expanded')).toEqual('true')
- expect(trigger3.classList.contains('collapsed')).toEqual(false)
+ expect(trigger3).not.toHaveClass('collapsed')
expect(trigger3.getAttribute('aria-expanded')).toEqual('true')
- target1.addEventListener('hidden.bs.collapse', () => {
- expect(trigger1.classList.contains('collapsed')).toEqual(true)
- expect(trigger1.getAttribute('aria-expanded')).toEqual('false')
+ target2.addEventListener('hidden.bs.collapse', () => {
+ expect(trigger1).not.toHaveClass('collapsed')
+ expect(trigger1.getAttribute('aria-expanded')).toEqual('true')
- expect(trigger2.classList.contains('collapsed')).toEqual(true)
+ expect(trigger2).toHaveClass('collapsed')
expect(trigger2.getAttribute('aria-expanded')).toEqual('false')
- expect(trigger3.classList.contains('collapsed')).toEqual(true)
- expect(trigger3.getAttribute('aria-expanded')).toEqual('false')
- done()
- })
+ expect(trigger3).not.toHaveClass('collapsed')
+ expect(trigger3.getAttribute('aria-expanded')).toEqual('true')
- trigger1.click()
- })
+ target1.addEventListener('hidden.bs.collapse', () => {
+ expect(trigger1).toHaveClass('collapsed')
+ expect(trigger1.getAttribute('aria-expanded')).toEqual('false')
- trigger2.click()
- }
+ expect(trigger2).toHaveClass('collapsed')
+ expect(trigger2.getAttribute('aria-expanded')).toEqual('false')
- target2.addEventListener('shown.bs.collapse', target2Shown)
- trigger3.click()
+ expect(trigger3).toHaveClass('collapsed')
+ expect(trigger3.getAttribute('aria-expanded')).toEqual('false')
+ resolve()
+ })
+
+ trigger1.click()
+ })
+
+ trigger2.click()
+ }
+
+ target2.addEventListener('shown.bs.collapse', target2Shown)
+ trigger3.click()
+ })
})
})
@@ -961,7 +1002,7 @@ describe('Collapse', () => {
const div = fixtureEl.querySelector('div')
- expect(Collapse.getInstance(div)).toEqual(null)
+ expect(Collapse.getInstance(div)).toBeNull()
})
})
@@ -982,7 +1023,7 @@ describe('Collapse', () => {
const div = fixtureEl.querySelector('div')
- expect(Collapse.getInstance(div)).toEqual(null)
+ expect(Collapse.getInstance(div)).toBeNull()
expect(Collapse.getOrCreateInstance(div)).toBeInstanceOf(Collapse)
})
@@ -991,13 +1032,13 @@ describe('Collapse', () => {
const div = fixtureEl.querySelector('div')
- expect(Collapse.getInstance(div)).toEqual(null)
+ expect(Collapse.getInstance(div)).toBeNull()
const collapse = Collapse.getOrCreateInstance(div, {
toggle: false
})
expect(collapse).toBeInstanceOf(Collapse)
- expect(collapse._config.toggle).toEqual(false)
+ expect(collapse._config.toggle).toBeFalse()
})
it('should return the instance when exists without given configuration', () => {
@@ -1015,7 +1056,7 @@ describe('Collapse', () => {
expect(collapse).toBeInstanceOf(Collapse)
expect(collapse2).toEqual(collapse)
- expect(collapse2._config.toggle).toEqual(false)
+ expect(collapse2._config.toggle).toBeFalse()
})
})
})
diff --git a/js/tests/unit/dom/data.spec.js b/js/tests/unit/dom/data.spec.js
index 2560caff7..04e57a8bc 100644
--- a/js/tests/unit/dom/data.spec.js
+++ b/js/tests/unit/dom/data.spec.js
@@ -1,5 +1,5 @@
-import Data from '../../../src/dom/data'
-import { getFixture, clearFixture } from '../../helpers/fixture'
+import Data from '../../../src/dom/data.js'
+import { clearFixture, getFixture } from '../../helpers/fixture.js'
describe('Data', () => {
const TEST_KEY = 'bs.test'
@@ -50,7 +50,7 @@ describe('Data', () => {
Data.set(div, TEST_KEY, data)
- expect(Data.get(div, TEST_KEY)).toBe(data)
+ expect(Data.get(div, TEST_KEY)).toEqual(data)
})
it('should overwrite data if something is already stored', () => {
@@ -60,11 +60,12 @@ describe('Data', () => {
Data.set(div, TEST_KEY, data)
Data.set(div, TEST_KEY, copy)
+ // Using `toBe` since spread creates a shallow copy
expect(Data.get(div, TEST_KEY)).not.toBe(data)
expect(Data.get(div, TEST_KEY)).toBe(copy)
})
- it('should do nothing when an element have nothing stored', () => {
+ it('should do nothing when an element has nothing stored', () => {
Data.remove(div, TEST_KEY)
expect().nothing()
@@ -76,7 +77,7 @@ describe('Data', () => {
Data.set(div, TEST_KEY, data)
Data.remove(div, UNKNOWN_KEY)
- expect(Data.get(div, TEST_KEY)).toBe(data)
+ expect(Data.get(div, TEST_KEY)).toEqual(data)
})
it('should remove data for a given key', () => {
@@ -88,7 +89,6 @@ describe('Data', () => {
expect(Data.get(div, TEST_KEY)).toBeNull()
})
- /* eslint-disable no-console */
it('should console.error a message if called with multiple keys', () => {
console.error = jasmine.createSpy('console.error')
@@ -99,7 +99,6 @@ describe('Data', () => {
Data.set(div, UNKNOWN_KEY, copy)
expect(console.error).toHaveBeenCalled()
- expect(Data.get(div, UNKNOWN_KEY)).toBe(null)
+ expect(Data.get(div, UNKNOWN_KEY)).toBeNull()
})
- /* eslint-enable no-console */
})
diff --git a/js/tests/unit/dom/event-handler.spec.js b/js/tests/unit/dom/event-handler.spec.js
index 19d63492b..7f99c4122 100644
--- a/js/tests/unit/dom/event-handler.spec.js
+++ b/js/tests/unit/dom/event-handler.spec.js
@@ -1,5 +1,6 @@
-import EventHandler from '../../../src/dom/event-handler'
-import { getFixture, clearFixture } from '../../helpers/fixture'
+import EventHandler from '../../../src/dom/event-handler.js'
+import { noop } from '../../../src/util/index.js'
+import { clearFixture, getFixture } from '../../helpers/fixture.js'
describe('EventHandler', () => {
let fixtureEl
@@ -18,176 +19,190 @@ describe('EventHandler', () => {
const div = fixtureEl.querySelector('div')
- EventHandler.on(div, null, () => {})
- EventHandler.on(null, 'click', () => {})
+ EventHandler.on(div, null, noop)
+ EventHandler.on(null, 'click', noop)
expect().nothing()
})
- it('should add event listener', done => {
- fixtureEl.innerHTML = '<div></div>'
+ it('should add event listener', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div></div>'
- const div = fixtureEl.querySelector('div')
+ const div = fixtureEl.querySelector('div')
- EventHandler.on(div, 'click', () => {
- expect().nothing()
- done()
- })
+ EventHandler.on(div, 'click', () => {
+ expect().nothing()
+ resolve()
+ })
- div.click()
+ div.click()
+ })
})
- it('should add namespaced event listener', done => {
- fixtureEl.innerHTML = '<div></div>'
+ it('should add namespaced event listener', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div></div>'
- const div = fixtureEl.querySelector('div')
+ const div = fixtureEl.querySelector('div')
- EventHandler.on(div, 'bs.namespace', () => {
- expect().nothing()
- done()
- })
+ EventHandler.on(div, 'bs.namespace', () => {
+ expect().nothing()
+ resolve()
+ })
- EventHandler.trigger(div, 'bs.namespace')
+ EventHandler.trigger(div, 'bs.namespace')
+ })
})
- it('should add native namespaced event listener', done => {
- fixtureEl.innerHTML = '<div></div>'
+ it('should add native namespaced event listener', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div></div>'
- const div = fixtureEl.querySelector('div')
+ const div = fixtureEl.querySelector('div')
- EventHandler.on(div, 'click.namespace', () => {
- expect().nothing()
- done()
- })
+ EventHandler.on(div, 'click.namespace', () => {
+ expect().nothing()
+ resolve()
+ })
- EventHandler.trigger(div, 'click')
+ EventHandler.trigger(div, 'click')
+ })
})
- it('should handle event delegation', done => {
- EventHandler.on(document, 'click', '.test', () => {
- expect().nothing()
- done()
- })
+ it('should handle event delegation', () => {
+ return new Promise(resolve => {
+ EventHandler.on(document, 'click', '.test', () => {
+ expect().nothing()
+ resolve()
+ })
- fixtureEl.innerHTML = '<div class="test"></div>'
+ fixtureEl.innerHTML = '<div class="test"></div>'
- const div = fixtureEl.querySelector('div')
-
- div.click()
- })
+ const div = fixtureEl.querySelector('div')
- it('should handle mouseenter/mouseleave like the native counterpart', done => {
- fixtureEl.innerHTML = [
- '<div class="outer">',
- '<div class="inner">',
- '<div class="nested">',
- '<div class="deep"></div>',
- '</div>',
- '</div>',
- '<div class="sibling"></div>',
- '</div>'
- ]
-
- const outer = fixtureEl.querySelector('.outer')
- const inner = fixtureEl.querySelector('.inner')
- const nested = fixtureEl.querySelector('.nested')
- const deep = fixtureEl.querySelector('.deep')
- const sibling = fixtureEl.querySelector('.sibling')
-
- const enterSpy = jasmine.createSpy('mouseenter')
- const leaveSpy = jasmine.createSpy('mouseleave')
- const delegateEnterSpy = jasmine.createSpy('mouseenter')
- const delegateLeaveSpy = jasmine.createSpy('mouseleave')
-
- EventHandler.on(inner, 'mouseenter', enterSpy)
- EventHandler.on(inner, 'mouseleave', leaveSpy)
- EventHandler.on(outer, 'mouseenter', '.inner', delegateEnterSpy)
- EventHandler.on(outer, 'mouseleave', '.inner', delegateLeaveSpy)
-
- EventHandler.on(sibling, 'mouseenter', () => {
- expect(enterSpy.calls.count()).toBe(2)
- expect(leaveSpy.calls.count()).toBe(2)
- expect(delegateEnterSpy.calls.count()).toBe(2)
- expect(delegateLeaveSpy.calls.count()).toBe(2)
- done()
+ div.click()
})
+ })
- const moveMouse = (from, to) => {
- from.dispatchEvent(new MouseEvent('mouseout', {
- bubbles: true,
- relatedTarget: to
- }))
-
- to.dispatchEvent(new MouseEvent('mouseover', {
- bubbles: true,
- relatedTarget: from
- }))
- }
+ it('should handle mouseenter/mouseleave like the native counterpart', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="outer">',
+ '<div class="inner">',
+ '<div class="nested">',
+ '<div class="deep"></div>',
+ '</div>',
+ '</div>',
+ '<div class="sibling"></div>',
+ '</div>'
+ ].join('')
+
+ const outer = fixtureEl.querySelector('.outer')
+ const inner = fixtureEl.querySelector('.inner')
+ const nested = fixtureEl.querySelector('.nested')
+ const deep = fixtureEl.querySelector('.deep')
+ const sibling = fixtureEl.querySelector('.sibling')
+
+ const enterSpy = jasmine.createSpy('mouseenter')
+ const leaveSpy = jasmine.createSpy('mouseleave')
+ const delegateEnterSpy = jasmine.createSpy('mouseenter')
+ const delegateLeaveSpy = jasmine.createSpy('mouseleave')
+
+ EventHandler.on(inner, 'mouseenter', enterSpy)
+ EventHandler.on(inner, 'mouseleave', leaveSpy)
+ EventHandler.on(outer, 'mouseenter', '.inner', delegateEnterSpy)
+ EventHandler.on(outer, 'mouseleave', '.inner', delegateLeaveSpy)
+
+ EventHandler.on(sibling, 'mouseenter', () => {
+ expect(enterSpy.calls.count()).toEqual(2)
+ expect(leaveSpy.calls.count()).toEqual(2)
+ expect(delegateEnterSpy.calls.count()).toEqual(2)
+ expect(delegateLeaveSpy.calls.count()).toEqual(2)
+ resolve()
+ })
+
+ const moveMouse = (from, to) => {
+ from.dispatchEvent(new MouseEvent('mouseout', {
+ bubbles: true,
+ relatedTarget: to
+ }))
+
+ to.dispatchEvent(new MouseEvent('mouseover', {
+ bubbles: true,
+ relatedTarget: from
+ }))
+ }
- // from outer to deep and back to outer (nested)
- moveMouse(outer, inner)
- moveMouse(inner, nested)
- moveMouse(nested, deep)
- moveMouse(deep, nested)
- moveMouse(nested, inner)
- moveMouse(inner, outer)
-
- setTimeout(() => {
- expect(enterSpy.calls.count()).toBe(1)
- expect(leaveSpy.calls.count()).toBe(1)
- expect(delegateEnterSpy.calls.count()).toBe(1)
- expect(delegateLeaveSpy.calls.count()).toBe(1)
-
- // from outer to inner to sibling (adjacent)
+ // from outer to deep and back to outer (nested)
moveMouse(outer, inner)
- moveMouse(inner, sibling)
- }, 20)
+ moveMouse(inner, nested)
+ moveMouse(nested, deep)
+ moveMouse(deep, nested)
+ moveMouse(nested, inner)
+ moveMouse(inner, outer)
+
+ setTimeout(() => {
+ expect(enterSpy.calls.count()).toEqual(1)
+ expect(leaveSpy.calls.count()).toEqual(1)
+ expect(delegateEnterSpy.calls.count()).toEqual(1)
+ expect(delegateLeaveSpy.calls.count()).toEqual(1)
+
+ // from outer to inner to sibling (adjacent)
+ moveMouse(outer, inner)
+ moveMouse(inner, sibling)
+ }, 20)
+ })
})
})
describe('one', () => {
- it('should call listener just once', done => {
- fixtureEl.innerHTML = '<div></div>'
-
- let called = 0
- const div = fixtureEl.querySelector('div')
- const obj = {
- oneListener() {
- called++
+ it('should call listener just once', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div></div>'
+
+ let called = 0
+ const div = fixtureEl.querySelector('div')
+ const obj = {
+ oneListener() {
+ called++
+ }
}
- }
- EventHandler.one(div, 'bootstrap', obj.oneListener)
+ EventHandler.one(div, 'bootstrap', obj.oneListener)
- EventHandler.trigger(div, 'bootstrap')
- EventHandler.trigger(div, 'bootstrap')
+ EventHandler.trigger(div, 'bootstrap')
+ EventHandler.trigger(div, 'bootstrap')
- setTimeout(() => {
- expect(called).toEqual(1)
- done()
- }, 20)
+ setTimeout(() => {
+ expect(called).toEqual(1)
+ resolve()
+ }, 20)
+ })
})
- it('should call delegated listener just once', done => {
- fixtureEl.innerHTML = '<div></div>'
+ it('should call delegated listener just once', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div></div>'
- let called = 0
- const div = fixtureEl.querySelector('div')
- const obj = {
- oneListener() {
- called++
+ let called = 0
+ const div = fixtureEl.querySelector('div')
+ const obj = {
+ oneListener() {
+ called++
+ }
}
- }
- EventHandler.one(fixtureEl, 'bootstrap', 'div', obj.oneListener)
+ EventHandler.one(fixtureEl, 'bootstrap', 'div', obj.oneListener)
- EventHandler.trigger(div, 'bootstrap')
- EventHandler.trigger(div, 'bootstrap')
+ EventHandler.trigger(div, 'bootstrap')
+ EventHandler.trigger(div, 'bootstrap')
- setTimeout(() => {
- expect(called).toEqual(1)
- done()
- }, 20)
+ setTimeout(() => {
+ expect(called).toEqual(1)
+ resolve()
+ }, 20)
+ })
})
})
@@ -196,171 +211,185 @@ describe('EventHandler', () => {
fixtureEl.innerHTML = '<div></div>'
const div = fixtureEl.querySelector('div')
- EventHandler.off(div, null, () => {})
- EventHandler.off(null, 'click', () => {})
+ EventHandler.off(div, null, noop)
+ EventHandler.off(null, 'click', noop)
expect().nothing()
})
- it('should remove a listener', done => {
- fixtureEl.innerHTML = '<div></div>'
- const div = fixtureEl.querySelector('div')
+ it('should remove a listener', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div></div>'
+ const div = fixtureEl.querySelector('div')
- let called = 0
- const handler = () => {
- called++
- }
+ let called = 0
+ const handler = () => {
+ called++
+ }
- EventHandler.on(div, 'foobar', handler)
- EventHandler.trigger(div, 'foobar')
+ EventHandler.on(div, 'foobar', handler)
+ EventHandler.trigger(div, 'foobar')
- EventHandler.off(div, 'foobar', handler)
- EventHandler.trigger(div, 'foobar')
+ EventHandler.off(div, 'foobar', handler)
+ EventHandler.trigger(div, 'foobar')
- setTimeout(() => {
- expect(called).toEqual(1)
- done()
- }, 20)
+ setTimeout(() => {
+ expect(called).toEqual(1)
+ resolve()
+ }, 20)
+ })
})
- it('should remove all the events', done => {
- fixtureEl.innerHTML = '<div></div>'
- const div = fixtureEl.querySelector('div')
+ it('should remove all the events', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div></div>'
+ const div = fixtureEl.querySelector('div')
- let called = 0
+ let called = 0
- EventHandler.on(div, 'foobar', () => {
- called++
- })
- EventHandler.on(div, 'foobar', () => {
- called++
- })
- EventHandler.trigger(div, 'foobar')
+ EventHandler.on(div, 'foobar', () => {
+ called++
+ })
+ EventHandler.on(div, 'foobar', () => {
+ called++
+ })
+ EventHandler.trigger(div, 'foobar')
- EventHandler.off(div, 'foobar')
- EventHandler.trigger(div, 'foobar')
+ EventHandler.off(div, 'foobar')
+ EventHandler.trigger(div, 'foobar')
- setTimeout(() => {
- expect(called).toEqual(2)
- done()
- }, 20)
+ setTimeout(() => {
+ expect(called).toEqual(2)
+ resolve()
+ }, 20)
+ })
})
- it('should remove all the namespaced listeners if namespace is passed', done => {
- fixtureEl.innerHTML = '<div></div>'
- const div = fixtureEl.querySelector('div')
+ it('should remove all the namespaced listeners if namespace is passed', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div></div>'
+ const div = fixtureEl.querySelector('div')
- let called = 0
+ let called = 0
- EventHandler.on(div, 'foobar.namespace', () => {
- called++
- })
- EventHandler.on(div, 'foofoo.namespace', () => {
- called++
+ 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)
+ resolve()
+ }, 20)
})
- 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')
+ it('should remove the namespaced listeners', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div></div>'
+ const div = fixtureEl.querySelector('div')
- let calledCallback1 = 0
- let calledCallback2 = 0
+ let calledCallback1 = 0
+ let calledCallback2 = 0
- EventHandler.on(div, 'foobar.namespace', () => {
- calledCallback1++
- })
- EventHandler.on(div, 'foofoo.namespace', () => {
- calledCallback2++
- })
+ 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, 'foobar.namespace')
+ EventHandler.off(div, 'foobar.namespace')
+ EventHandler.trigger(div, 'foobar.namespace')
- EventHandler.trigger(div, 'foofoo.namespace')
+ EventHandler.trigger(div, 'foofoo.namespace')
- setTimeout(() => {
- expect(calledCallback1).toEqual(1)
- expect(calledCallback2).toEqual(1)
- done()
- }, 20)
+ setTimeout(() => {
+ expect(calledCallback1).toEqual(1)
+ expect(calledCallback2).toEqual(1)
+ resolve()
+ }, 20)
+ })
})
- it('should remove the all the namespaced listeners for native events', done => {
- fixtureEl.innerHTML = '<div></div>'
- const div = fixtureEl.querySelector('div')
+ it('should remove the all the namespaced listeners for native events', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div></div>'
+ const div = fixtureEl.querySelector('div')
- let called = 0
+ let called = 0
- EventHandler.on(div, 'click.namespace', () => {
- called++
- })
- EventHandler.on(div, 'click.namespace2', () => {
- called++
- })
+ EventHandler.on(div, 'click.namespace', () => {
+ called++
+ })
+ EventHandler.on(div, 'click.namespace2', () => {
+ called++
+ })
- EventHandler.trigger(div, 'click')
- EventHandler.off(div, 'click')
- EventHandler.trigger(div, 'click')
+ EventHandler.trigger(div, 'click')
+ EventHandler.off(div, 'click')
+ EventHandler.trigger(div, 'click')
- setTimeout(() => {
- expect(called).toEqual(2)
- done()
- }, 20)
+ setTimeout(() => {
+ expect(called).toEqual(2)
+ resolve()
+ }, 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++
+ it('should remove the specified namespaced listeners for native events', () => {
+ return new Promise(resolve => {
+ 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)
+ resolve()
+ }, 20)
})
- 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>'
+ it('should remove a listener registered by .one', () => {
+ return new Promise((resolve, reject) => {
+ fixtureEl.innerHTML = '<div></div>'
- const div = fixtureEl.querySelector('div')
- const handler = () => {
- throw new Error('called')
- }
+ const div = fixtureEl.querySelector('div')
+ const handler = () => {
+ reject(new Error('called'))
+ }
- EventHandler.one(div, 'foobar', handler)
- EventHandler.off(div, 'foobar', handler)
+ EventHandler.one(div, 'foobar', handler)
+ EventHandler.off(div, 'foobar', handler)
- EventHandler.trigger(div, 'foobar')
- setTimeout(() => {
- expect().nothing()
- done()
- }, 20)
+ EventHandler.trigger(div, 'foobar')
+ setTimeout(() => {
+ expect().nothing()
+ resolve()
+ }, 20)
+ })
})
it('should remove the correct delegated event listener', () => {
@@ -412,4 +441,40 @@ describe('EventHandler', () => {
expect(i).toEqual(5)
})
})
+
+ describe('general functionality', () => {
+ it('should hydrate properties, and make them configurable', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div id="div1">',
+ ' <div id="div2"></div>',
+ ' <div id="div3"></div>',
+ '</div>'
+ ].join('')
+
+ const div1 = fixtureEl.querySelector('#div1')
+ const div2 = fixtureEl.querySelector('#div2')
+
+ EventHandler.on(div1, 'click', event => {
+ expect(event.currentTarget).toBe(div2)
+ expect(event.delegateTarget).toBe(div1)
+ expect(event.originalTarget).toBeNull()
+
+ Object.defineProperty(event, 'currentTarget', {
+ configurable: true,
+ get() {
+ return div1
+ }
+ })
+
+ expect(event.currentTarget).toBe(div1)
+ resolve()
+ })
+
+ expect(() => {
+ EventHandler.trigger(div1, 'click', { originalTarget: null, currentTarget: div2 })
+ }).not.toThrowError(TypeError)
+ })
+ })
+ })
})
diff --git a/js/tests/unit/dom/manipulator.spec.js b/js/tests/unit/dom/manipulator.spec.js
index 61ffe7455..9d0be3218 100644
--- a/js/tests/unit/dom/manipulator.spec.js
+++ b/js/tests/unit/dom/manipulator.spec.js
@@ -1,5 +1,5 @@
-import Manipulator from '../../../src/dom/manipulator'
-import { getFixture, clearFixture } from '../../helpers/fixture'
+import Manipulator from '../../../src/dom/manipulator.js'
+import { clearFixture, getFixture } from '../../helpers/fixture.js'
describe('Manipulator', () => {
let fixtureEl
@@ -70,6 +70,17 @@ describe('Manipulator', () => {
target: '#element'
})
})
+
+ it('should omit `bs-config` data attribute', () => {
+ fixtureEl.innerHTML = '<div data-bs-toggle="tabs" data-bs-target="#element" data-bs-config=\'{"testBool":false}\'></div>'
+
+ const div = fixtureEl.querySelector('div')
+
+ expect(Manipulator.getDataAttributes(div)).toEqual({
+ toggle: 'tabs',
+ target: '#element'
+ })
+ })
})
describe('getDataAttribute', () => {
@@ -96,93 +107,29 @@ describe('Manipulator', () => {
const div = fixtureEl.querySelector('div')
- expect(Manipulator.getDataAttribute(div, 'test')).toEqual(false)
+ expect(Manipulator.getDataAttribute(div, 'test')).toBeFalse()
div.setAttribute('data-bs-test', 'true')
- expect(Manipulator.getDataAttribute(div, 'test')).toEqual(true)
+ expect(Manipulator.getDataAttribute(div, 'test')).toBeTrue()
div.setAttribute('data-bs-test', '1')
expect(Manipulator.getDataAttribute(div, 'test')).toEqual(1)
})
- })
-
- describe('offset', () => {
- it('should return an object with two properties top and left, both numbers', () => {
- fixtureEl.innerHTML = '<div></div>'
-
- const div = fixtureEl.querySelector('div')
- const offset = Manipulator.offset(div)
-
- expect(offset).toBeDefined()
- expect(offset.top).toEqual(jasmine.any(Number))
- expect(offset.left).toEqual(jasmine.any(Number))
- })
-
- it('should return offset relative to attached element\'s offset', () => {
- const top = 500
- const left = 1000
-
- fixtureEl.innerHTML = `<div style="position:absolute;top:${top}px;left:${left}px"></div>`
-
- const div = fixtureEl.querySelector('div')
- const offset = Manipulator.offset(div)
- const fixtureOffset = Manipulator.offset(fixtureEl)
-
- expect(offset).toEqual({
- top: fixtureOffset.top + top,
- left: fixtureOffset.left + left
- })
- })
-
- it('should not change offset when viewport is scrolled', done => {
- const top = 500
- const left = 1000
- const scrollY = 200
- const scrollX = 400
- fixtureEl.innerHTML = `<div style="position:absolute;top:${top}px;left:${left}px"></div>`
+ it('should normalize json data', () => {
+ fixtureEl.innerHTML = '<div data-bs-test=\'{"delay":{"show":100,"hide":10}}\'></div>'
const div = fixtureEl.querySelector('div')
- const offset = Manipulator.offset(div)
-
- // append an element that forces scrollbars on the window so we can scroll
- const { defaultView: win, body } = fixtureEl.ownerDocument
- const forceScrollBars = document.createElement('div')
- forceScrollBars.style.cssText = 'position:absolute;top:5000px;left:5000px;width:1px;height:1px'
- body.append(forceScrollBars)
-
- const scrollHandler = () => {
- expect(window.pageYOffset).toBe(scrollY)
- expect(window.pageXOffset).toBe(scrollX)
- const newOffset = Manipulator.offset(div)
+ expect(Manipulator.getDataAttribute(div, 'test')).toEqual({ delay: { show: 100, hide: 10 } })
- expect(newOffset).toEqual({
- top: offset.top,
- left: offset.left
- })
-
- win.removeEventListener('scroll', scrollHandler)
- forceScrollBars.remove()
- win.scrollTo(0, 0)
- done()
- }
-
- win.addEventListener('scroll', scrollHandler)
- win.scrollTo(scrollX, scrollY)
- })
- })
-
- describe('position', () => {
- it('should return an object with two properties top and left, both numbers', () => {
- fixtureEl.innerHTML = '<div></div>'
-
- const div = fixtureEl.querySelector('div')
- const position = Manipulator.position(div)
+ const objectData = { 'Super Hero': ['Iron Man', 'Super Man'], testNum: 90, url: 'http://localhost:8080/test?foo=bar' }
+ const dataStr = JSON.stringify(objectData)
+ div.setAttribute('data-bs-test', encodeURIComponent(dataStr))
+ expect(Manipulator.getDataAttribute(div, 'test')).toEqual(objectData)
- expect(position).toBeDefined()
- expect(position.top).toEqual(jasmine.any(Number))
- expect(position.left).toEqual(jasmine.any(Number))
+ div.setAttribute('data-bs-test', dataStr)
+ expect(Manipulator.getDataAttribute(div, 'test')).toEqual(objectData)
})
})
})
diff --git a/js/tests/unit/dom/selector-engine.spec.js b/js/tests/unit/dom/selector-engine.spec.js
index 09c85a88a..95d9bf8ec 100644
--- a/js/tests/unit/dom/selector-engine.spec.js
+++ b/js/tests/unit/dom/selector-engine.spec.js
@@ -1,5 +1,5 @@
-import SelectorEngine from '../../../src/dom/selector-engine'
-import { getFixture, clearFixture } from '../../helpers/fixture'
+import SelectorEngine from '../../../src/dom/selector-engine.js'
+import { clearFixture, getFixture } from '../../helpers/fixture.js'
describe('SelectorEngine', () => {
let fixtureEl
@@ -21,7 +21,7 @@ describe('SelectorEngine', () => {
expect(SelectorEngine.find('div', fixtureEl)).toEqual([div])
})
- it('should find elements globaly', () => {
+ it('should find elements globally', () => {
fixtureEl.innerHTML = '<div id="test"></div>'
const div = fixtureEl.querySelector('#test')
@@ -30,13 +30,15 @@ describe('SelectorEngine', () => {
})
it('should handle :scope selectors', () => {
- fixtureEl.innerHTML = `<ul>
- <li></li>
- <li>
- <a href="#" class="active">link</a>
- </li>
- <li></li>
- </ul>`
+ fixtureEl.innerHTML = [
+ '<ul>',
+ ' <li></li>',
+ ' <li>',
+ ' <a href="#" class="active">link</a>',
+ ' </li>',
+ ' <li></li>',
+ '</ul>'
+ ].join('')
const listEl = fixtureEl.querySelector('ul')
const aActive = fixtureEl.querySelector('.active')
@@ -57,11 +59,13 @@ describe('SelectorEngine', () => {
describe('children', () => {
it('should find children', () => {
- fixtureEl.innerHTML = `<ul>
- <li></li>
- <li></li>
- <li></li>
- </ul>`
+ fixtureEl.innerHTML = [
+ '<ul>',
+ ' <li></li>',
+ ' <li></li>',
+ ' <li></li>',
+ '</ul>'
+ ].join('')
const list = fixtureEl.querySelector('ul')
const liList = [].concat(...fixtureEl.querySelectorAll('li'))
@@ -73,7 +77,7 @@ describe('SelectorEngine', () => {
describe('parents', () => {
it('should return parents', () => {
- expect(SelectorEngine.parents(fixtureEl, 'body').length).toEqual(1)
+ expect(SelectorEngine.parents(fixtureEl, 'body')).toHaveSize(1)
})
})
@@ -162,7 +166,7 @@ describe('SelectorEngine', () => {
'<span>lorem</span>',
'<a>lorem</a>',
'<button>lorem</button>',
- '<input />',
+ '<input>',
'<textarea></textarea>',
'<select></select>',
'<details>lorem</details>'
@@ -197,9 +201,7 @@ describe('SelectorEngine', () => {
})
it('should return not return elements with negative tab index', () => {
- fixtureEl.innerHTML = [
- '<button tabindex="-1">lorem</button>'
- ].join('')
+ fixtureEl.innerHTML = '<button tabindex="-1">lorem</button>'
const expectedElements = []
@@ -207,9 +209,7 @@ describe('SelectorEngine', () => {
})
it('should return contenteditable elements', () => {
- fixtureEl.innerHTML = [
- '<div contenteditable="true">lorem</div>'
- ].join('')
+ fixtureEl.innerHTML = '<div contenteditable="true">lorem</div>'
const expectedElements = [fixtureEl.querySelector('[contenteditable="true"]')]
@@ -217,9 +217,7 @@ describe('SelectorEngine', () => {
})
it('should not return disabled elements', () => {
- fixtureEl.innerHTML = [
- '<button disabled="true">lorem</button>'
- ].join('')
+ fixtureEl.innerHTML = '<button disabled="true">lorem</button>'
const expectedElements = []
@@ -227,14 +225,190 @@ describe('SelectorEngine', () => {
})
it('should not return invisible elements', () => {
- fixtureEl.innerHTML = [
- '<button style="display:none;">lorem</button>'
- ].join('')
+ fixtureEl.innerHTML = '<button style="display:none;">lorem</button>'
const expectedElements = []
expect(SelectorEngine.focusableChildren(fixtureEl)).toEqual(expectedElements)
})
})
-})
+ describe('getSelectorFromElement', () => {
+ it('should get selector from data-bs-target', () => {
+ fixtureEl.innerHTML = [
+ '<div id="test" data-bs-target=".target"></div>',
+ '<div class="target"></div>'
+ ].join('')
+
+ const testEl = fixtureEl.querySelector('#test')
+
+ expect(SelectorEngine.getSelectorFromElement(testEl)).toEqual('.target')
+ })
+
+ it('should get selector from href if no data-bs-target set', () => {
+ fixtureEl.innerHTML = [
+ '<a id="test" href=".target"></a>',
+ '<div class="target"></div>'
+ ].join('')
+
+ const testEl = fixtureEl.querySelector('#test')
+
+ expect(SelectorEngine.getSelectorFromElement(testEl)).toEqual('.target')
+ })
+
+ it('should get selector from href if data-bs-target equal to #', () => {
+ fixtureEl.innerHTML = [
+ '<a id="test" data-bs-target="#" href=".target"></a>',
+ '<div class="target"></div>'
+ ].join('')
+
+ const testEl = fixtureEl.querySelector('#test')
+
+ expect(SelectorEngine.getSelectorFromElement(testEl)).toEqual('.target')
+ })
+
+ it('should return null if a selector from a href is a url without an anchor', () => {
+ fixtureEl.innerHTML = [
+ '<a id="test" data-bs-target="#" href="foo/bar.html"></a>',
+ '<div class="target"></div>'
+ ].join('')
+
+ const testEl = fixtureEl.querySelector('#test')
+
+ expect(SelectorEngine.getSelectorFromElement(testEl)).toBeNull()
+ })
+
+ it('should return the anchor if a selector from a href is a url', () => {
+ fixtureEl.innerHTML = [
+ '<a id="test" data-bs-target="#" href="foo/bar.html#target"></a>',
+ '<div id="target"></div>'
+ ].join('')
+
+ const testEl = fixtureEl.querySelector('#test')
+
+ expect(SelectorEngine.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(SelectorEngine.getSelectorFromElement(testEl)).toBeNull()
+ })
+
+ it('should return null if no selector', () => {
+ fixtureEl.innerHTML = '<div></div>'
+
+ const testEl = fixtureEl.querySelector('div')
+
+ expect(SelectorEngine.getSelectorFromElement(testEl)).toBeNull()
+ })
+ })
+
+ describe('getElementFromSelector', () => {
+ it('should get element from data-bs-target', () => {
+ fixtureEl.innerHTML = [
+ '<div id="test" data-bs-target=".target"></div>',
+ '<div class="target"></div>'
+ ].join('')
+
+ const testEl = fixtureEl.querySelector('#test')
+
+ expect(SelectorEngine.getElementFromSelector(testEl)).toEqual(fixtureEl.querySelector('.target'))
+ })
+
+ it('should get element from href if no data-bs-target set', () => {
+ fixtureEl.innerHTML = [
+ '<a id="test" href=".target"></a>',
+ '<div class="target"></div>'
+ ].join('')
+
+ const testEl = fixtureEl.querySelector('#test')
+
+ expect(SelectorEngine.getElementFromSelector(testEl)).toEqual(fixtureEl.querySelector('.target'))
+ })
+
+ it('should return null if element not found', () => {
+ fixtureEl.innerHTML = '<a id="test" href=".target"></a>'
+
+ const testEl = fixtureEl.querySelector('#test')
+
+ expect(SelectorEngine.getElementFromSelector(testEl)).toBeNull()
+ })
+
+ it('should return null if no selector', () => {
+ fixtureEl.innerHTML = '<div></div>'
+
+ const testEl = fixtureEl.querySelector('div')
+
+ expect(SelectorEngine.getElementFromSelector(testEl)).toBeNull()
+ })
+ })
+
+ describe('getMultipleElementsFromSelector', () => {
+ it('should get elements from data-bs-target', () => {
+ fixtureEl.innerHTML = [
+ '<div id="test" data-bs-target=".target"></div>',
+ '<div class="target"></div>',
+ '<div class="target"></div>'
+ ].join('')
+
+ const testEl = fixtureEl.querySelector('#test')
+
+ expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toEqual(Array.from(fixtureEl.querySelectorAll('.target')))
+ })
+
+ it('should get elements if several ids are given', () => {
+ fixtureEl.innerHTML = [
+ '<div id="test" data-bs-target="#target1,#target2"></div>',
+ '<div class="target" id="target1"></div>',
+ '<div class="target" id="target2"></div>'
+ ].join('')
+
+ const testEl = fixtureEl.querySelector('#test')
+
+ expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toEqual(Array.from(fixtureEl.querySelectorAll('.target')))
+ })
+
+ it('should get elements if several ids with special chars are given', () => {
+ fixtureEl.innerHTML = [
+ '<div id="test" data-bs-target="#j_id11:exampleModal,#j_id22:exampleModal"></div>',
+ '<div class="target" id="j_id11:exampleModal"></div>',
+ '<div class="target" id="j_id22:exampleModal"></div>'
+ ].join('')
+
+ const testEl = fixtureEl.querySelector('#test')
+
+ expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toEqual(Array.from(fixtureEl.querySelectorAll('.target')))
+ })
+
+ it('should get elements in array, from href if no data-bs-target set', () => {
+ fixtureEl.innerHTML = [
+ '<a id="test" href=".target"></a>',
+ '<div class="target"></div>',
+ '<div class="target"></div>'
+ ].join('')
+
+ const testEl = fixtureEl.querySelector('#test')
+
+ expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toEqual(Array.from(fixtureEl.querySelectorAll('.target')))
+ })
+
+ it('should return empty array if elements not found', () => {
+ fixtureEl.innerHTML = '<a id="test" href=".target"></a>'
+
+ const testEl = fixtureEl.querySelector('#test')
+
+ expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toHaveSize(0)
+ })
+
+ it('should return empty array if no selector', () => {
+ fixtureEl.innerHTML = '<div></div>'
+
+ const testEl = fixtureEl.querySelector('div')
+
+ expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toHaveSize(0)
+ })
+ })
+})
diff --git a/js/tests/unit/dropdown.spec.js b/js/tests/unit/dropdown.spec.js
index f099e9990..63ae4bd10 100644
--- a/js/tests/unit/dropdown.spec.js
+++ b/js/tests/unit/dropdown.spec.js
@@ -1,7 +1,9 @@
-import Dropdown from '../../src/dropdown'
-import EventHandler from '../../src/dom/event-handler'
-import { noop } from '../../src/util/index'
-import { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture'
+import EventHandler from '../../src/dom/event-handler.js'
+import Dropdown from '../../src/dropdown.js'
+import { noop } from '../../src/util/index.js'
+import {
+ clearFixture, createEvent, getFixture, jQueryMock
+} from '../helpers/fixture.js'
describe('Dropdown', () => {
let fixtureEl
@@ -57,36 +59,61 @@ describe('Dropdown', () => {
expect(dropdownByElement._element).toEqual(btnDropdown)
})
- it('should create offset modifier correctly when offset option is a function', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#">Secondary link</a>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should work on invalid markup', () => {
+ return new Promise(resolve => {
+ // TODO: REMOVE in v6
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const getOffset = jasmine.createSpy('getOffset').and.returnValue([10, 20])
- const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const dropdown = new Dropdown(btnDropdown, {
- offset: getOffset,
- popperConfig: {
- onFirstUpdate: state => {
- expect(getOffset).toHaveBeenCalledWith({
- popper: state.rects.popper,
- reference: state.rects.reference,
- placement: state.placement
- }, btnDropdown)
- done()
+ const dropdownElem = fixtureEl.querySelector('.dropdown-menu')
+ const dropdown = new Dropdown(dropdownElem)
+
+ dropdownElem.addEventListener('shown.bs.dropdown', () => {
+ resolve()
+ })
+
+ expect().nothing()
+ dropdown.show()
+ })
+ })
+
+ it('should create offset modifier correctly when offset option is a function', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Secondary link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const getOffset = jasmine.createSpy('getOffset').and.returnValue([10, 20])
+ const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const dropdown = new Dropdown(btnDropdown, {
+ offset: getOffset,
+ popperConfig: {
+ onFirstUpdate(state) {
+ expect(getOffset).toHaveBeenCalledWith({
+ popper: state.rects.popper,
+ reference: state.rects.reference,
+ placement: state.placement
+ }, btnDropdown)
+ resolve()
+ }
}
- }
- })
- const offset = dropdown._getOffset()
+ })
+ const offset = dropdown._getOffset()
- expect(typeof offset).toEqual('function')
+ expect(typeof offset).toEqual('function')
- dropdown.show()
+ dropdown.show()
+ })
})
it('should create offset modifier correctly when offset option is a string into data attribute', () => {
@@ -130,7 +157,7 @@ describe('Dropdown', () => {
it('should allow to pass config to Popper with `popperConfig` as a function', () => {
fixtureEl.innerHTML = [
'<div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" data-bs-placement="right" >Dropdown</button>',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" data-bs-placement="right">Dropdown</button>',
' <div class="dropdown-menu">',
' <a class="dropdown-item" href="#">Secondary link</a>',
' </div>',
@@ -145,767 +172,875 @@ describe('Dropdown', () => {
const popperConfig = dropdown._getPopperConfig()
- expect(getPopperConfig).toHaveBeenCalled()
+ // Ensure that the function was called with the default config.
+ expect(getPopperConfig).toHaveBeenCalledWith(jasmine.objectContaining({
+ placement: jasmine.any(String)
+ }))
expect(popperConfig.placement).toEqual('left')
})
})
describe('toggle', () => {
- it('should toggle a dropdown', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#">Secondary link</a>',
- ' </div>',
- '</div>'
- ].join('')
-
- const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const dropdown = new Dropdown(btnDropdown)
+ it('should toggle a dropdown', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Secondary link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const dropdown = new Dropdown(btnDropdown)
+
+ btnDropdown.addEventListener('shown.bs.dropdown', () => {
+ expect(btnDropdown).toHaveClass('show')
+ expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
+ resolve()
+ })
- btnDropdown.addEventListener('shown.bs.dropdown', () => {
- expect(btnDropdown.classList.contains('show')).toEqual(true)
- expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
- done()
+ dropdown.toggle()
})
-
- dropdown.toggle()
})
- it('should destroy old popper references on toggle', done => {
- fixtureEl.innerHTML = [
- '<div class="first dropdown">',
- ' <button class="firstBtn btn" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#">Secondary link</a>',
- ' </div>',
- '</div>',
- '<div class="second dropdown">',
- ' <button class="secondBtn btn" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#">Secondary link</a>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should destroy old popper references on toggle', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="first dropdown">',
+ ' <button class="firstBtn btn" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Secondary link</a>',
+ ' </div>',
+ '</div>',
+ '<div class="second dropdown">',
+ ' <button class="secondBtn btn" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Secondary link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const btnDropdown1 = fixtureEl.querySelector('.firstBtn')
+ const btnDropdown2 = fixtureEl.querySelector('.secondBtn')
+ const firstDropdownEl = fixtureEl.querySelector('.first')
+ const secondDropdownEl = fixtureEl.querySelector('.second')
+ const dropdown1 = new Dropdown(btnDropdown1)
+
+ firstDropdownEl.addEventListener('shown.bs.dropdown', () => {
+ expect(btnDropdown1).toHaveClass('show')
+ spyOn(dropdown1._popper, 'destroy')
+ btnDropdown2.click()
+ })
- const btnDropdown1 = fixtureEl.querySelector('.firstBtn')
- const btnDropdown2 = fixtureEl.querySelector('.secondBtn')
- const firstDropdownEl = fixtureEl.querySelector('.first')
- const secondDropdownEl = fixtureEl.querySelector('.second')
- const dropdown1 = new Dropdown(btnDropdown1)
+ secondDropdownEl.addEventListener('shown.bs.dropdown', () => setTimeout(() => {
+ expect(dropdown1._popper.destroy).toHaveBeenCalled()
+ resolve()
+ }))
- firstDropdownEl.addEventListener('shown.bs.dropdown', () => {
- expect(btnDropdown1.classList.contains('show')).toEqual(true)
- spyOn(dropdown1._popper, 'destroy')
- btnDropdown2.click()
+ dropdown1.toggle()
})
+ })
- secondDropdownEl.addEventListener('shown.bs.dropdown', () => setTimeout(() => {
- expect(dropdown1._popper.destroy).toHaveBeenCalled()
- done()
- }))
+ it('should toggle a dropdown and add/remove event listener on mobile', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Secondary link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- dropdown1.toggle()
- })
+ const defaultValueOnTouchStart = document.documentElement.ontouchstart
+ const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const dropdown = new Dropdown(btnDropdown)
- it('should toggle a dropdown and add/remove event listener on mobile', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#">Secondary link</a>',
- ' </div>',
- '</div>'
- ].join('')
+ document.documentElement.ontouchstart = noop
+ const spy = spyOn(EventHandler, 'on')
+ const spyOff = spyOn(EventHandler, 'off')
- const defaultValueOnTouchStart = document.documentElement.ontouchstart
- const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const dropdown = new Dropdown(btnDropdown)
+ btnDropdown.addEventListener('shown.bs.dropdown', () => {
+ expect(btnDropdown).toHaveClass('show')
+ expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
+ expect(spy).toHaveBeenCalledWith(jasmine.any(Object), 'mouseover', noop)
- document.documentElement.ontouchstart = noop
- spyOn(EventHandler, 'on')
- spyOn(EventHandler, 'off')
+ dropdown.toggle()
+ })
- btnDropdown.addEventListener('shown.bs.dropdown', () => {
- expect(btnDropdown.classList.contains('show')).toEqual(true)
- expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
- expect(EventHandler.on).toHaveBeenCalledWith(jasmine.any(Object), 'mouseover', noop)
+ btnDropdown.addEventListener('hidden.bs.dropdown', () => {
+ expect(btnDropdown).not.toHaveClass('show')
+ expect(btnDropdown.getAttribute('aria-expanded')).toEqual('false')
+ expect(spyOff).toHaveBeenCalledWith(jasmine.any(Object), 'mouseover', noop)
+
+ document.documentElement.ontouchstart = defaultValueOnTouchStart
+ resolve()
+ })
dropdown.toggle()
})
+ })
- btnDropdown.addEventListener('hidden.bs.dropdown', () => {
- expect(btnDropdown.classList.contains('show')).toEqual(false)
- expect(btnDropdown.getAttribute('aria-expanded')).toEqual('false')
- expect(EventHandler.off).toHaveBeenCalledWith(jasmine.any(Object), 'mouseover', noop)
+ it('should toggle a dropdown at the right', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
+ ' <div class="dropdown-menu dropdown-menu-end">',
+ ' <a class="dropdown-item" href="#">Secondary link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- document.documentElement.ontouchstart = defaultValueOnTouchStart
- done()
- })
+ const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const dropdown = new Dropdown(btnDropdown)
- dropdown.toggle()
+ btnDropdown.addEventListener('shown.bs.dropdown', () => {
+ expect(btnDropdown).toHaveClass('show')
+ expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
+ resolve()
+ })
+
+ dropdown.toggle()
+ })
})
- it('should toggle a dropdown at the right', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
- ' <div class="dropdown-menu dropdown-menu-end">',
- ' <a class="dropdown-item" href="#">Secondary link</a>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should toggle a centered dropdown', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown-center">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Secondary link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const dropdown = new Dropdown(btnDropdown)
+ const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const dropdown = new Dropdown(btnDropdown)
- btnDropdown.addEventListener('shown.bs.dropdown', () => {
- expect(btnDropdown.classList.contains('show')).toEqual(true)
- expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
- done()
- })
+ btnDropdown.addEventListener('shown.bs.dropdown', () => {
+ expect(btnDropdown).toHaveClass('show')
+ expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
+ resolve()
+ })
- dropdown.toggle()
+ dropdown.toggle()
+ })
})
- it('should toggle a dropup', done => {
- fixtureEl.innerHTML = [
- '<div class="dropup">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#">Secondary link</a>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should toggle a dropup', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="dropup">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Secondary link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const dropupEl = fixtureEl.querySelector('.dropup')
- const dropdown = new Dropdown(btnDropdown)
+ const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const dropupEl = fixtureEl.querySelector('.dropup')
+ const dropdown = new Dropdown(btnDropdown)
- dropupEl.addEventListener('shown.bs.dropdown', () => {
- expect(btnDropdown.classList.contains('show')).toEqual(true)
- expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
- done()
- })
+ dropupEl.addEventListener('shown.bs.dropdown', () => {
+ expect(btnDropdown).toHaveClass('show')
+ expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
+ resolve()
+ })
- dropdown.toggle()
+ dropdown.toggle()
+ })
})
- it('should toggle a dropup at the right', done => {
- fixtureEl.innerHTML = [
- '<div class="dropup">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
- ' <div class="dropdown-menu dropdown-menu-end">',
- ' <a class="dropdown-item" href="#">Secondary link</a>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should toggle a dropup centered', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="dropup-center">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Secondary link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const dropupEl = fixtureEl.querySelector('.dropup')
- const dropdown = new Dropdown(btnDropdown)
+ const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const dropupEl = fixtureEl.querySelector('.dropup-center')
+ const dropdown = new Dropdown(btnDropdown)
- dropupEl.addEventListener('shown.bs.dropdown', () => {
- expect(btnDropdown.classList.contains('show')).toEqual(true)
- expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
- done()
- })
+ dropupEl.addEventListener('shown.bs.dropdown', () => {
+ expect(btnDropdown).toHaveClass('show')
+ expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
+ resolve()
+ })
- dropdown.toggle()
+ dropdown.toggle()
+ })
})
- it('should toggle a dropend', done => {
- fixtureEl.innerHTML = [
- '<div class="dropend">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#">Secondary link</a>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should toggle a dropup at the right', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="dropup">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
+ ' <div class="dropdown-menu dropdown-menu-end">',
+ ' <a class="dropdown-item" href="#">Secondary link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const dropendEl = fixtureEl.querySelector('.dropend')
- const dropdown = new Dropdown(btnDropdown)
+ const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const dropupEl = fixtureEl.querySelector('.dropup')
+ const dropdown = new Dropdown(btnDropdown)
- dropendEl.addEventListener('shown.bs.dropdown', () => {
- expect(btnDropdown.classList.contains('show')).toEqual(true)
- expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
- done()
- })
+ dropupEl.addEventListener('shown.bs.dropdown', () => {
+ expect(btnDropdown).toHaveClass('show')
+ expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
+ resolve()
+ })
- dropdown.toggle()
+ dropdown.toggle()
+ })
})
- it('should toggle a dropstart', done => {
- fixtureEl.innerHTML = [
- '<div class="dropstart">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#">Secondary link</a>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should toggle a dropend', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="dropend">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Secondary link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const dropstartEl = fixtureEl.querySelector('.dropstart')
- const dropdown = new Dropdown(btnDropdown)
+ const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const dropendEl = fixtureEl.querySelector('.dropend')
+ const dropdown = new Dropdown(btnDropdown)
- dropstartEl.addEventListener('shown.bs.dropdown', () => {
- expect(btnDropdown.classList.contains('show')).toEqual(true)
- expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
- done()
- })
+ dropendEl.addEventListener('shown.bs.dropdown', () => {
+ expect(btnDropdown).toHaveClass('show')
+ expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
+ resolve()
+ })
- dropdown.toggle()
+ dropdown.toggle()
+ })
})
- it('should toggle a dropdown with parent reference', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#">Secondary link</a>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should toggle a dropstart', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="dropstart">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Secondary link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const dropdown = new Dropdown(btnDropdown, {
- reference: 'parent'
- })
+ const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const dropstartEl = fixtureEl.querySelector('.dropstart')
+ const dropdown = new Dropdown(btnDropdown)
- btnDropdown.addEventListener('shown.bs.dropdown', () => {
- expect(btnDropdown.classList.contains('show')).toEqual(true)
- expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
- done()
- })
+ dropstartEl.addEventListener('shown.bs.dropdown', () => {
+ expect(btnDropdown).toHaveClass('show')
+ expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
+ resolve()
+ })
- dropdown.toggle()
+ dropdown.toggle()
+ })
})
- it('should toggle a dropdown with a dom node reference', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#">Secondary link</a>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should toggle a dropdown with parent reference', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Secondary link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const dropdown = new Dropdown(btnDropdown, {
- reference: fixtureEl
- })
+ const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const dropdown = new Dropdown(btnDropdown, {
+ reference: 'parent'
+ })
- btnDropdown.addEventListener('shown.bs.dropdown', () => {
- expect(btnDropdown.classList.contains('show')).toEqual(true)
- expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
- done()
- })
+ btnDropdown.addEventListener('shown.bs.dropdown', () => {
+ expect(btnDropdown).toHaveClass('show')
+ expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
+ resolve()
+ })
- dropdown.toggle()
+ dropdown.toggle()
+ })
})
- it('should toggle a dropdown with a jquery object reference', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#">Secondary link</a>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should toggle a dropdown with a dom node reference', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Secondary link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const dropdown = new Dropdown(btnDropdown, {
- reference: { 0: fixtureEl, jquery: 'jQuery' }
- })
+ const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const dropdown = new Dropdown(btnDropdown, {
+ reference: fixtureEl
+ })
- btnDropdown.addEventListener('shown.bs.dropdown', () => {
- expect(btnDropdown.classList.contains('show')).toEqual(true)
- expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
- done()
- })
+ btnDropdown.addEventListener('shown.bs.dropdown', () => {
+ expect(btnDropdown).toHaveClass('show')
+ expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
+ resolve()
+ })
- dropdown.toggle()
+ dropdown.toggle()
+ })
})
- it('should toggle a dropdown with a valid virtual element reference', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button class="btn dropdown-toggle visually-hidden" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#">Secondary link</a>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should toggle a dropdown with a jquery object reference', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Secondary link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const virtualElement = {
- nodeType: 1,
- getBoundingClientRect() {
- return {
- width: 0,
- height: 0,
- top: 0,
- right: 0,
- bottom: 0,
- left: 0
- }
- }
- }
+ const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const dropdown = new Dropdown(btnDropdown, {
+ reference: { 0: fixtureEl, jquery: 'jQuery' }
+ })
- expect(() => new Dropdown(btnDropdown, {
- reference: {}
- })).toThrowError(TypeError, 'DROPDOWN: Option "reference" provided type "object" without a required "getBoundingClientRect" method.')
+ btnDropdown.addEventListener('shown.bs.dropdown', () => {
+ expect(btnDropdown).toHaveClass('show')
+ expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
+ resolve()
+ })
- expect(() => new Dropdown(btnDropdown, {
- reference: {
- getBoundingClientRect: 'not-a-function'
- }
- })).toThrowError(TypeError, 'DROPDOWN: Option "reference" provided type "object" without a required "getBoundingClientRect" method.')
+ dropdown.toggle()
+ })
+ })
- // use onFirstUpdate as Poppers internal update is executed async
- const dropdown = new Dropdown(btnDropdown, {
- reference: virtualElement,
- popperConfig: {
- onFirstUpdate() {
- expect(virtualElement.getBoundingClientRect).toHaveBeenCalled()
- expect(btnDropdown.classList.contains('show')).toEqual(true)
- expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
- done()
+ it('should toggle a dropdown with a valid virtual element reference', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle visually-hidden" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Secondary link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const virtualElement = {
+ nodeType: 1,
+ getBoundingClientRect() {
+ return {
+ width: 0,
+ height: 0,
+ top: 0,
+ right: 0,
+ bottom: 0,
+ left: 0
+ }
}
}
- })
- spyOn(virtualElement, 'getBoundingClientRect').and.callThrough()
+ expect(() => new Dropdown(btnDropdown, {
+ reference: {}
+ })).toThrowError(TypeError, 'DROPDOWN: Option "reference" provided type "object" without a required "getBoundingClientRect" method.')
- dropdown.toggle()
+ expect(() => new Dropdown(btnDropdown, {
+ reference: {
+ getBoundingClientRect: 'not-a-function'
+ }
+ })).toThrowError(TypeError, 'DROPDOWN: Option "reference" provided type "object" without a required "getBoundingClientRect" method.')
+
+ // use onFirstUpdate as Poppers internal update is executed async
+ const dropdown = new Dropdown(btnDropdown, {
+ reference: virtualElement,
+ popperConfig: {
+ onFirstUpdate() {
+ expect(spy).toHaveBeenCalled()
+ expect(btnDropdown).toHaveClass('show')
+ expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
+ resolve()
+ }
+ }
+ })
+
+ const spy = spyOn(virtualElement, 'getBoundingClientRect').and.callThrough()
+
+ dropdown.toggle()
+ })
})
- it('should not toggle a dropdown if the element is disabled', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button disabled class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#">Secondary link</a>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should not toggle a dropdown if the element is disabled', () => {
+ return new Promise((resolve, reject) => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button disabled class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Secondary link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const dropdown = new Dropdown(btnDropdown)
+ const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const dropdown = new Dropdown(btnDropdown)
- btnDropdown.addEventListener('shown.bs.dropdown', () => {
- throw new Error('should not throw shown.bs.dropdown event')
- })
+ btnDropdown.addEventListener('shown.bs.dropdown', () => {
+ reject(new Error('should not throw shown.bs.dropdown event'))
+ })
- dropdown.toggle()
+ dropdown.toggle()
- setTimeout(() => {
- expect().nothing()
- done()
+ setTimeout(() => {
+ expect().nothing()
+ resolve()
+ })
})
})
- it('should not toggle a dropdown if the element contains .disabled', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button class="btn dropdown-toggle disabled" data-bs-toggle="dropdown">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#">Secondary link</a>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should not toggle a dropdown if the element contains .disabled', () => {
+ return new Promise((resolve, reject) => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle disabled" data-bs-toggle="dropdown">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Secondary link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const dropdown = new Dropdown(btnDropdown)
+ const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const dropdown = new Dropdown(btnDropdown)
- btnDropdown.addEventListener('shown.bs.dropdown', () => {
- throw new Error('should not throw shown.bs.dropdown event')
- })
+ btnDropdown.addEventListener('shown.bs.dropdown', () => {
+ reject(new Error('should not throw shown.bs.dropdown event'))
+ })
- dropdown.toggle()
+ dropdown.toggle()
- setTimeout(() => {
- expect().nothing()
- done()
+ setTimeout(() => {
+ expect().nothing()
+ resolve()
+ })
})
})
- it('should not toggle a dropdown if the menu is shown', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
- ' <div class="dropdown-menu show">',
- ' <a class="dropdown-item" href="#">Secondary link</a>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should not toggle a dropdown if the menu is shown', () => {
+ return new Promise((resolve, reject) => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
+ ' <div class="dropdown-menu show">',
+ ' <a class="dropdown-item" href="#">Secondary link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const dropdown = new Dropdown(btnDropdown)
+ const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const dropdown = new Dropdown(btnDropdown)
- btnDropdown.addEventListener('shown.bs.dropdown', () => {
- throw new Error('should not throw shown.bs.dropdown event')
- })
+ btnDropdown.addEventListener('shown.bs.dropdown', () => {
+ reject(new Error('should not throw shown.bs.dropdown event'))
+ })
- dropdown.toggle()
+ dropdown.toggle()
- setTimeout(() => {
- expect().nothing()
- done()
+ setTimeout(() => {
+ expect().nothing()
+ resolve()
+ })
})
})
- it('should not toggle a dropdown if show event is prevented', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#">Secondary link</a>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should not toggle a dropdown if show event is prevented', () => {
+ return new Promise((resolve, reject) => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Secondary link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const dropdown = new Dropdown(btnDropdown)
+ const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const dropdown = new Dropdown(btnDropdown)
- btnDropdown.addEventListener('show.bs.dropdown', event => {
- event.preventDefault()
- })
+ btnDropdown.addEventListener('show.bs.dropdown', event => {
+ event.preventDefault()
+ })
- btnDropdown.addEventListener('shown.bs.dropdown', () => {
- throw new Error('should not throw shown.bs.dropdown event')
- })
+ btnDropdown.addEventListener('shown.bs.dropdown', () => {
+ reject(new Error('should not throw shown.bs.dropdown event'))
+ })
- dropdown.toggle()
+ dropdown.toggle()
- setTimeout(() => {
- expect().nothing()
- done()
+ setTimeout(() => {
+ expect().nothing()
+ resolve()
+ })
})
})
})
describe('show', () => {
- it('should show a dropdown', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#">Secondary link</a>',
- ' </div>',
- '</div>'
- ].join('')
-
- const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const dropdown = new Dropdown(btnDropdown)
+ it('should show a dropdown', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Secondary link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const dropdown = new Dropdown(btnDropdown)
+
+ btnDropdown.addEventListener('shown.bs.dropdown', () => {
+ expect(btnDropdown).toHaveClass('show')
+ resolve()
+ })
- btnDropdown.addEventListener('shown.bs.dropdown', () => {
- expect(btnDropdown.classList.contains('show')).toEqual(true)
- done()
+ dropdown.show()
})
-
- dropdown.show()
})
- it('should not show a dropdown if the element is disabled', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button disabled class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#">Secondary link</a>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should not show a dropdown if the element is disabled', () => {
+ return new Promise((resolve, reject) => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button disabled class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Secondary link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const dropdown = new Dropdown(btnDropdown)
+ const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const dropdown = new Dropdown(btnDropdown)
- btnDropdown.addEventListener('shown.bs.dropdown', () => {
- throw new Error('should not throw shown.bs.dropdown event')
- })
+ btnDropdown.addEventListener('shown.bs.dropdown', () => {
+ reject(new Error('should not throw shown.bs.dropdown event'))
+ })
- dropdown.show()
+ dropdown.show()
- setTimeout(() => {
- expect().nothing()
- done()
- }, 10)
+ setTimeout(() => {
+ expect().nothing()
+ resolve()
+ }, 10)
+ })
})
- it('should not show a dropdown if the element contains .disabled', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button class="btn dropdown-toggle disabled" data-bs-toggle="dropdown">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#">Secondary link</a>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should not show a dropdown if the element contains .disabled', () => {
+ return new Promise((resolve, reject) => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle disabled" data-bs-toggle="dropdown">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Secondary link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const dropdown = new Dropdown(btnDropdown)
+ const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const dropdown = new Dropdown(btnDropdown)
- btnDropdown.addEventListener('shown.bs.dropdown', () => {
- throw new Error('should not throw shown.bs.dropdown event')
- })
+ btnDropdown.addEventListener('shown.bs.dropdown', () => {
+ reject(new Error('should not throw shown.bs.dropdown event'))
+ })
- dropdown.show()
+ dropdown.show()
- setTimeout(() => {
- expect().nothing()
- done()
- }, 10)
+ setTimeout(() => {
+ expect().nothing()
+ resolve()
+ }, 10)
+ })
})
- it('should not show a dropdown if the menu is shown', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
- ' <div class="dropdown-menu show">',
- ' <a class="dropdown-item" href="#">Secondary link</a>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should not show a dropdown if the menu is shown', () => {
+ return new Promise((resolve, reject) => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
+ ' <div class="dropdown-menu show">',
+ ' <a class="dropdown-item" href="#">Secondary link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const dropdown = new Dropdown(btnDropdown)
+ const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const dropdown = new Dropdown(btnDropdown)
- btnDropdown.addEventListener('shown.bs.dropdown', () => {
- throw new Error('should not throw shown.bs.dropdown event')
- })
+ btnDropdown.addEventListener('shown.bs.dropdown', () => {
+ reject(new Error('should not throw shown.bs.dropdown event'))
+ })
- dropdown.show()
+ dropdown.show()
- setTimeout(() => {
- expect().nothing()
- done()
- }, 10)
+ setTimeout(() => {
+ expect().nothing()
+ resolve()
+ }, 10)
+ })
})
- it('should not show a dropdown if show event is prevented', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#">Secondary link</a>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should not show a dropdown if show event is prevented', () => {
+ return new Promise((resolve, reject) => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Secondary link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const dropdown = new Dropdown(btnDropdown)
+ const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const dropdown = new Dropdown(btnDropdown)
- btnDropdown.addEventListener('show.bs.dropdown', event => {
- event.preventDefault()
- })
+ btnDropdown.addEventListener('show.bs.dropdown', event => {
+ event.preventDefault()
+ })
- btnDropdown.addEventListener('shown.bs.dropdown', () => {
- throw new Error('should not throw shown.bs.dropdown event')
- })
+ btnDropdown.addEventListener('shown.bs.dropdown', () => {
+ reject(new Error('should not throw shown.bs.dropdown event'))
+ })
- dropdown.show()
+ dropdown.show()
- setTimeout(() => {
- expect().nothing()
- done()
- }, 10)
+ setTimeout(() => {
+ expect().nothing()
+ resolve()
+ }, 10)
+ })
})
})
describe('hide', () => {
- it('should hide a dropdown', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="true">Dropdown</button>',
- ' <div class="dropdown-menu show">',
- ' <a class="dropdown-item" href="#">Secondary link</a>',
- ' </div>',
- '</div>'
- ].join('')
-
- const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
- const dropdown = new Dropdown(btnDropdown)
+ it('should hide a dropdown', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="true">Dropdown</button>',
+ ' <div class="dropdown-menu show">',
+ ' <a class="dropdown-item" href="#">Secondary link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
+ const dropdown = new Dropdown(btnDropdown)
+
+ btnDropdown.addEventListener('hidden.bs.dropdown', () => {
+ expect(dropdownMenu).not.toHaveClass('show')
+ expect(btnDropdown.getAttribute('aria-expanded')).toEqual('false')
+ resolve()
+ })
- btnDropdown.addEventListener('hidden.bs.dropdown', () => {
- expect(dropdownMenu.classList.contains('show')).toEqual(false)
- expect(btnDropdown.getAttribute('aria-expanded')).toEqual('false')
- done()
+ dropdown.hide()
})
-
- dropdown.hide()
})
- it('should hide a dropdown and destroy popper', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#">Secondary link</a>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should hide a dropdown and destroy popper', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Secondary link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const dropdown = new Dropdown(btnDropdown)
+ const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const dropdown = new Dropdown(btnDropdown)
- btnDropdown.addEventListener('shown.bs.dropdown', () => {
- spyOn(dropdown._popper, 'destroy')
- dropdown.hide()
- })
+ btnDropdown.addEventListener('shown.bs.dropdown', () => {
+ spyOn(dropdown._popper, 'destroy')
+ dropdown.hide()
+ })
- btnDropdown.addEventListener('hidden.bs.dropdown', () => {
- expect(dropdown._popper.destroy).toHaveBeenCalled()
- done()
- })
+ btnDropdown.addEventListener('hidden.bs.dropdown', () => {
+ expect(dropdown._popper.destroy).toHaveBeenCalled()
+ resolve()
+ })
- dropdown.show()
+ dropdown.show()
+ })
})
- it('should not hide a dropdown if the element is disabled', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button disabled class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
- ' <div class="dropdown-menu show">',
- ' <a class="dropdown-item" href="#">Secondary link</a>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should not hide a dropdown if the element is disabled', () => {
+ return new Promise((resolve, reject) => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button disabled class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
+ ' <div class="dropdown-menu show">',
+ ' <a class="dropdown-item" href="#">Secondary link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
- const dropdown = new Dropdown(btnDropdown)
+ const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
+ const dropdown = new Dropdown(btnDropdown)
- btnDropdown.addEventListener('hidden.bs.dropdown', () => {
- throw new Error('should not throw hidden.bs.dropdown event')
- })
+ btnDropdown.addEventListener('hidden.bs.dropdown', () => {
+ reject(new Error('should not throw hidden.bs.dropdown event'))
+ })
- dropdown.hide()
+ dropdown.hide()
- setTimeout(() => {
- expect(dropdownMenu.classList.contains('show')).toEqual(true)
- done()
- }, 10)
+ setTimeout(() => {
+ expect(dropdownMenu).toHaveClass('show')
+ resolve()
+ }, 10)
+ })
})
- it('should not hide a dropdown if the element contains .disabled', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button class="btn dropdown-toggle disabled" data-bs-toggle="dropdown">Dropdown</button>',
- ' <div class="dropdown-menu show">',
- ' <a class="dropdown-item" href="#">Secondary link</a>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should not hide a dropdown if the element contains .disabled', () => {
+ return new Promise((resolve, reject) => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle disabled" data-bs-toggle="dropdown">Dropdown</button>',
+ ' <div class="dropdown-menu show">',
+ ' <a class="dropdown-item" href="#">Secondary link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
- const dropdown = new Dropdown(btnDropdown)
+ const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
+ const dropdown = new Dropdown(btnDropdown)
- btnDropdown.addEventListener('hidden.bs.dropdown', () => {
- throw new Error('should not throw hidden.bs.dropdown event')
- })
+ btnDropdown.addEventListener('hidden.bs.dropdown', () => {
+ reject(new Error('should not throw hidden.bs.dropdown event'))
+ })
- dropdown.hide()
+ dropdown.hide()
- setTimeout(() => {
- expect(dropdownMenu.classList.contains('show')).toEqual(true)
- done()
- }, 10)
+ setTimeout(() => {
+ expect(dropdownMenu).toHaveClass('show')
+ resolve()
+ }, 10)
+ })
})
- it('should not hide a dropdown if the menu is not shown', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#">Secondary link</a>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should not hide a dropdown if the menu is not shown', () => {
+ return new Promise((resolve, reject) => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Secondary link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const dropdown = new Dropdown(btnDropdown)
+ const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const dropdown = new Dropdown(btnDropdown)
- btnDropdown.addEventListener('hidden.bs.dropdown', () => {
- throw new Error('should not throw hidden.bs.dropdown event')
- })
+ btnDropdown.addEventListener('hidden.bs.dropdown', () => {
+ reject(new Error('should not throw hidden.bs.dropdown event'))
+ })
- dropdown.hide()
+ dropdown.hide()
- setTimeout(() => {
- expect().nothing()
- done()
- }, 10)
+ setTimeout(() => {
+ expect().nothing()
+ resolve()
+ }, 10)
+ })
})
- it('should not hide a dropdown if hide event is prevented', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
- ' <div class="dropdown-menu show">',
- ' <a class="dropdown-item" href="#">Secondary link</a>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should not hide a dropdown if hide event is prevented', () => {
+ return new Promise((resolve, reject) => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
+ ' <div class="dropdown-menu show">',
+ ' <a class="dropdown-item" href="#">Secondary link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
- const dropdown = new Dropdown(btnDropdown)
+ const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
+ const dropdown = new Dropdown(btnDropdown)
- btnDropdown.addEventListener('hide.bs.dropdown', event => {
- event.preventDefault()
- })
+ btnDropdown.addEventListener('hide.bs.dropdown', event => {
+ event.preventDefault()
+ })
- btnDropdown.addEventListener('hidden.bs.dropdown', () => {
- throw new Error('should not throw hidden.bs.dropdown event')
- })
+ btnDropdown.addEventListener('hidden.bs.dropdown', () => {
+ reject(new Error('should not throw hidden.bs.dropdown event'))
+ })
- dropdown.hide()
+ dropdown.hide()
- setTimeout(() => {
- expect(dropdownMenu.classList.contains('show')).toEqual(true)
- done()
+ setTimeout(() => {
+ expect(dropdownMenu).toHaveClass('show')
+ resolve()
+ })
})
})
- it('should remove event listener on touch-enabled device that was added in show method', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#">Dropdwon item</a>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should remove event listener on touch-enabled device that was added in show method', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Dropdown item</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const defaultValueOnTouchStart = document.documentElement.ontouchstart
- const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const dropdown = new Dropdown(btnDropdown)
+ const defaultValueOnTouchStart = document.documentElement.ontouchstart
+ const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const dropdown = new Dropdown(btnDropdown)
- document.documentElement.ontouchstart = noop
- spyOn(EventHandler, 'off')
+ document.documentElement.ontouchstart = noop
+ const spy = spyOn(EventHandler, 'off')
- btnDropdown.addEventListener('shown.bs.dropdown', () => {
- dropdown.hide()
- })
+ btnDropdown.addEventListener('shown.bs.dropdown', () => {
+ dropdown.hide()
+ })
- btnDropdown.addEventListener('hidden.bs.dropdown', () => {
- expect(btnDropdown.classList.contains('show')).toEqual(false)
- expect(btnDropdown.getAttribute('aria-expanded')).toEqual('false')
- expect(EventHandler.off).toHaveBeenCalled()
+ btnDropdown.addEventListener('hidden.bs.dropdown', () => {
+ expect(btnDropdown).not.toHaveClass('show')
+ expect(btnDropdown.getAttribute('aria-expanded')).toEqual('false')
+ expect(spy).toHaveBeenCalled()
- document.documentElement.ontouchstart = defaultValueOnTouchStart
- done()
- })
+ document.documentElement.ontouchstart = defaultValueOnTouchStart
+ resolve()
+ })
- dropdown.show()
+ dropdown.show()
+ })
})
})
@@ -927,13 +1062,13 @@ describe('Dropdown', () => {
expect(dropdown._popper).toBeNull()
expect(dropdown._menu).not.toBeNull()
expect(dropdown._element).not.toBeNull()
- spyOn(EventHandler, 'off')
+ const spy = spyOn(EventHandler, 'off')
dropdown.dispose()
expect(dropdown._menu).toBeNull()
expect(dropdown._element).toBeNull()
- expect(EventHandler.off).toHaveBeenCalledWith(btnDropdown, Dropdown.EVENT_KEY)
+ expect(spy).toHaveBeenCalledWith(btnDropdown, Dropdown.EVENT_KEY)
})
it('should dispose dropdown with Popper', () => {
@@ -981,13 +1116,13 @@ describe('Dropdown', () => {
expect(dropdown._popper).not.toBeNull()
- spyOn(dropdown._popper, 'update')
- spyOn(dropdown, '_detectNavbar')
+ const spyUpdate = spyOn(dropdown._popper, 'update')
+ const spyDetect = spyOn(dropdown, '_detectNavbar')
dropdown.update()
- expect(dropdown._popper.update).toHaveBeenCalled()
- expect(dropdown._detectNavbar).toHaveBeenCalled()
+ expect(spyUpdate).toHaveBeenCalled()
+ expect(spyDetect).toHaveBeenCalled()
})
it('should just detect navbar on update', () => {
@@ -1003,904 +1138,1083 @@ describe('Dropdown', () => {
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown)
- spyOn(dropdown, '_detectNavbar')
+ const spy = spyOn(dropdown, '_detectNavbar')
dropdown.update()
expect(dropdown._popper).toBeNull()
- expect(dropdown._detectNavbar).toHaveBeenCalled()
+ expect(spy).toHaveBeenCalled()
})
})
describe('data-api', () => {
- it('should show and hide a dropdown', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#">Secondary link</a>',
- ' </div>',
- '</div>'
- ].join('')
-
- const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- let showEventTriggered = false
- let hideEventTriggered = false
+ it('should show and hide a dropdown', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Secondary link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ let showEventTriggered = false
+ let hideEventTriggered = false
+
+ btnDropdown.addEventListener('show.bs.dropdown', () => {
+ showEventTriggered = true
+ })
- btnDropdown.addEventListener('show.bs.dropdown', () => {
- showEventTriggered = true
- })
+ btnDropdown.addEventListener('shown.bs.dropdown', event => setTimeout(() => {
+ expect(btnDropdown).toHaveClass('show')
+ expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
+ expect(showEventTriggered).toBeTrue()
+ expect(event.relatedTarget).toEqual(btnDropdown)
+ document.body.click()
+ }))
- btnDropdown.addEventListener('shown.bs.dropdown', event => setTimeout(() => {
- expect(btnDropdown.classList.contains('show')).toEqual(true)
- expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
- expect(showEventTriggered).toEqual(true)
- expect(event.relatedTarget).toEqual(btnDropdown)
- document.body.click()
- }))
+ btnDropdown.addEventListener('hide.bs.dropdown', () => {
+ hideEventTriggered = true
+ })
- btnDropdown.addEventListener('hide.bs.dropdown', () => {
- hideEventTriggered = true
- })
+ btnDropdown.addEventListener('hidden.bs.dropdown', event => {
+ expect(btnDropdown).not.toHaveClass('show')
+ expect(btnDropdown.getAttribute('aria-expanded')).toEqual('false')
+ expect(hideEventTriggered).toBeTrue()
+ expect(event.relatedTarget).toEqual(btnDropdown)
+ resolve()
+ })
- btnDropdown.addEventListener('hidden.bs.dropdown', event => {
- expect(btnDropdown.classList.contains('show')).toEqual(false)
- expect(btnDropdown.getAttribute('aria-expanded')).toEqual('false')
- expect(hideEventTriggered).toEqual(true)
- expect(event.relatedTarget).toEqual(btnDropdown)
- done()
+ btnDropdown.click()
})
-
- btnDropdown.click()
})
- it('should not use Popper in navbar', done => {
- fixtureEl.innerHTML = [
- '<nav class="navbar navbar-expand-md navbar-light bg-light">',
- ' <div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#">Secondary link</a>',
- ' </div>',
- ' </div>',
- '</nav>'
- ].join('')
+ it('should not use "static" Popper in navbar', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<nav class="navbar navbar-expand-md bg-light">',
+ ' <div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Secondary link</a>',
+ ' </div>',
+ ' </div>',
+ '</nav>'
+ ].join('')
- const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
- const dropdown = new Dropdown(btnDropdown)
+ const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
+ const dropdown = new Dropdown(btnDropdown)
- btnDropdown.addEventListener('shown.bs.dropdown', () => {
- expect(dropdown._popper).toBeNull()
- expect(dropdownMenu.getAttribute('style')).toEqual(null, 'no inline style applied by Popper')
- done()
- })
+ btnDropdown.addEventListener('shown.bs.dropdown', () => {
+ expect(dropdown._popper).not.toBeNull()
+ expect(dropdownMenu.getAttribute('data-bs-popper')).toEqual('static')
+ resolve()
+ })
- dropdown.show()
+ dropdown.show()
+ })
})
- it('should not collapse the dropdown when clicking a select option nested in the dropdown', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <select>',
- ' <option selected>Open this select menu</option>',
- ' <option value="1">One</option>',
- ' </select>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should not collapse the dropdown when clicking a select option nested in the dropdown', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <select>',
+ ' <option selected>Open this select menu</option>',
+ ' <option value="1">One</option>',
+ ' </select>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
- const dropdown = new Dropdown(btnDropdown)
+ const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
+ const dropdown = new Dropdown(btnDropdown)
- const hideSpy = spyOn(dropdown, '_completeHide')
+ const hideSpy = spyOn(dropdown, '_completeHide')
- btnDropdown.addEventListener('shown.bs.dropdown', () => {
- const clickEvent = new MouseEvent('click', {
- bubbles: true
+ btnDropdown.addEventListener('shown.bs.dropdown', () => {
+ const clickEvent = new MouseEvent('click', {
+ bubbles: true
+ })
+
+ dropdownMenu.querySelector('option').dispatchEvent(clickEvent)
})
- dropdownMenu.querySelector('option').dispatchEvent(clickEvent)
- })
+ dropdownMenu.addEventListener('click', event => {
+ expect(event.target.tagName).toMatch(/select|option/i)
- dropdownMenu.addEventListener('click', event => {
- expect(event.target.tagName).toMatch(/select|option/i)
+ Dropdown.clearMenus(event)
- Dropdown.clearMenus(event)
+ setTimeout(() => {
+ expect(hideSpy).not.toHaveBeenCalled()
+ resolve()
+ }, 10)
+ })
- setTimeout(() => {
- expect(hideSpy).not.toHaveBeenCalled()
- done()
- }, 10)
+ dropdown.show()
})
-
- dropdown.show()
})
- it('should manage bs attribute `data-bs-popper`="none" when dropdown is in navbar', done => {
- fixtureEl.innerHTML = [
- '<nav class="navbar navbar-expand-md navbar-light bg-light">',
- ' <div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#">Secondary link</a>',
- ' </div>',
- ' </div>',
- '</nav>'
- ].join('')
+ it('should manage bs attribute `data-bs-popper`="static" when dropdown is in navbar', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<nav class="navbar navbar-expand-md bg-light">',
+ ' <div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Secondary link</a>',
+ ' </div>',
+ ' </div>',
+ '</nav>'
+ ].join('')
- const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
- const dropdown = new Dropdown(btnDropdown)
+ const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
+ const dropdown = new Dropdown(btnDropdown)
- btnDropdown.addEventListener('shown.bs.dropdown', () => {
- expect(dropdownMenu.getAttribute('data-bs-popper')).toEqual('none')
- dropdown.hide()
- })
+ btnDropdown.addEventListener('shown.bs.dropdown', () => {
+ expect(dropdownMenu.getAttribute('data-bs-popper')).toEqual('static')
+ dropdown.hide()
+ })
- btnDropdown.addEventListener('hidden.bs.dropdown', () => {
- expect(dropdownMenu.getAttribute('data-bs-popper')).toBeNull()
- done()
- })
+ btnDropdown.addEventListener('hidden.bs.dropdown', () => {
+ expect(dropdownMenu.getAttribute('data-bs-popper')).toBeNull()
+ resolve()
+ })
- dropdown.show()
+ dropdown.show()
+ })
})
- it('should not use Popper if display set to static', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" data-bs-display="static">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#">Secondary link</a>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should not use Popper if display set to static', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" data-bs-display="static">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Secondary link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
+ const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
- btnDropdown.addEventListener('shown.bs.dropdown', () => {
- // Popper adds this attribute when we use it
- expect(dropdownMenu.getAttribute('data-popper-placement')).toEqual(null)
- done()
- })
+ btnDropdown.addEventListener('shown.bs.dropdown', () => {
+ // Popper adds this attribute when we use it
+ expect(dropdownMenu.getAttribute('data-popper-placement')).toBeNull()
+ resolve()
+ })
- btnDropdown.click()
+ btnDropdown.click()
+ })
})
- it('should manage bs attribute `data-bs-popper`="static" when display set to static', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" data-bs-display="static">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#">Secondary link</a>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should manage bs attribute `data-bs-popper`="static" when display set to static', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" data-bs-display="static">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Secondary link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
- const dropdown = new Dropdown(btnDropdown)
+ const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
+ const dropdown = new Dropdown(btnDropdown)
- btnDropdown.addEventListener('shown.bs.dropdown', () => {
- expect(dropdownMenu.getAttribute('data-bs-popper')).toEqual('static')
- dropdown.hide()
- })
+ btnDropdown.addEventListener('shown.bs.dropdown', () => {
+ expect(dropdownMenu.getAttribute('data-bs-popper')).toEqual('static')
+ dropdown.hide()
+ })
- btnDropdown.addEventListener('hidden.bs.dropdown', () => {
- expect(dropdownMenu.getAttribute('data-bs-popper')).toBeNull()
- done()
- })
+ btnDropdown.addEventListener('hidden.bs.dropdown', () => {
+ expect(dropdownMenu.getAttribute('data-bs-popper')).toBeNull()
+ resolve()
+ })
- dropdown.show()
+ dropdown.show()
+ })
})
- it('should remove "show" class if tabbing outside of menu', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#">Secondary link</a>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should remove "show" class if tabbing outside of menu', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Secondary link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- btnDropdown.addEventListener('shown.bs.dropdown', () => {
- expect(btnDropdown.classList.contains('show')).toEqual(true)
+ btnDropdown.addEventListener('shown.bs.dropdown', () => {
+ expect(btnDropdown).toHaveClass('show')
- const keyup = createEvent('keyup')
+ const keyup = createEvent('keyup')
- keyup.key = 'Tab'
- document.dispatchEvent(keyup)
- })
+ keyup.key = 'Tab'
+ document.dispatchEvent(keyup)
+ })
- btnDropdown.addEventListener('hidden.bs.dropdown', () => {
- expect(btnDropdown.classList.contains('show')).toEqual(false)
- done()
- })
+ btnDropdown.addEventListener('hidden.bs.dropdown', () => {
+ expect(btnDropdown).not.toHaveClass('show')
+ resolve()
+ })
- btnDropdown.click()
+ btnDropdown.click()
+ })
})
- it('should remove "show" class if body is clicked, with multiple dropdowns', done => {
- fixtureEl.innerHTML = [
- '<div class="nav">',
- ' <div class="dropdown" id="testmenu">',
- ' <a class="dropdown-toggle" data-bs-toggle="dropdown" href="#testmenu">Test menu</a>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#sub1">Submenu 1</a>',
- ' </div>',
- ' </div>',
- '</div>',
- '<div class="btn-group">',
- ' <button class="btn">Actions</button>',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown"></button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#">Action 1</a>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should remove "show" class if body is clicked, with multiple dropdowns', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="nav">',
+ ' <div class="dropdown" id="testmenu">',
+ ' <a class="dropdown-toggle" data-bs-toggle="dropdown" href="#testmenu">Test menu</a>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#sub1">Submenu 1</a>',
+ ' </div>',
+ ' </div>',
+ '</div>',
+ '<div class="btn-group">',
+ ' <button class="btn">Actions</button>',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown"></button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Action 1</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const triggerDropdownList = fixtureEl.querySelectorAll('[data-bs-toggle="dropdown"]')
+ const triggerDropdownList = fixtureEl.querySelectorAll('[data-bs-toggle="dropdown"]')
- expect(triggerDropdownList.length).toEqual(2)
+ expect(triggerDropdownList).toHaveSize(2)
- const [triggerDropdownFirst, triggerDropdownLast] = triggerDropdownList
+ const [triggerDropdownFirst, triggerDropdownLast] = triggerDropdownList
- triggerDropdownFirst.addEventListener('shown.bs.dropdown', () => {
- expect(triggerDropdownFirst.classList.contains('show')).toEqual(true)
- expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(1)
- document.body.click()
- })
+ triggerDropdownFirst.addEventListener('shown.bs.dropdown', () => {
+ expect(triggerDropdownFirst).toHaveClass('show')
+ expect(fixtureEl.querySelectorAll('.dropdown-menu.show')).toHaveSize(1)
+ document.body.click()
+ })
- triggerDropdownFirst.addEventListener('hidden.bs.dropdown', () => {
- expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(0)
- triggerDropdownLast.click()
- })
+ triggerDropdownFirst.addEventListener('hidden.bs.dropdown', () => {
+ expect(fixtureEl.querySelectorAll('.dropdown-menu.show')).toHaveSize(0)
+ triggerDropdownLast.click()
+ })
- triggerDropdownLast.addEventListener('shown.bs.dropdown', () => {
- expect(triggerDropdownLast.classList.contains('show')).toEqual(true)
- expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(1)
- document.body.click()
- })
+ triggerDropdownLast.addEventListener('shown.bs.dropdown', () => {
+ expect(triggerDropdownLast).toHaveClass('show')
+ expect(fixtureEl.querySelectorAll('.dropdown-menu.show')).toHaveSize(1)
+ document.body.click()
+ })
- triggerDropdownLast.addEventListener('hidden.bs.dropdown', () => {
- expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(0)
- done()
- })
+ triggerDropdownLast.addEventListener('hidden.bs.dropdown', () => {
+ expect(fixtureEl.querySelectorAll('.dropdown-menu.show')).toHaveSize(0)
+ resolve()
+ })
- triggerDropdownFirst.click()
+ triggerDropdownFirst.click()
+ })
})
- it('should remove "show" class if body if tabbing outside of menu, with multiple dropdowns', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <a class="dropdown-toggle" data-bs-toggle="dropdown" href="#testmenu">Test menu</a>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#sub1">Submenu 1</a>',
- ' </div>',
- '</div>',
- '<div class="btn-group">',
- ' <button class="btn">Actions</button>',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown"></button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#">Action 1</a>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should remove "show" class if body if tabbing outside of menu, with multiple dropdowns', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <a class="dropdown-toggle" data-bs-toggle="dropdown" href="#testmenu">Test menu</a>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#sub1">Submenu 1</a>',
+ ' </div>',
+ '</div>',
+ '<div class="btn-group">',
+ ' <button class="btn">Actions</button>',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown"></button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Action 1</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const triggerDropdownList = fixtureEl.querySelectorAll('[data-bs-toggle="dropdown"]')
+ const triggerDropdownList = fixtureEl.querySelectorAll('[data-bs-toggle="dropdown"]')
- expect(triggerDropdownList.length).toEqual(2)
+ expect(triggerDropdownList).toHaveSize(2)
- const [triggerDropdownFirst, triggerDropdownLast] = triggerDropdownList
+ const [triggerDropdownFirst, triggerDropdownLast] = triggerDropdownList
- triggerDropdownFirst.addEventListener('shown.bs.dropdown', () => {
- expect(triggerDropdownFirst.classList.contains('show')).toEqual(true, '"show" class added on click')
- expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(1, 'only one dropdown is shown')
+ triggerDropdownFirst.addEventListener('shown.bs.dropdown', () => {
+ expect(triggerDropdownFirst).toHaveClass('show')
+ expect(fixtureEl.querySelectorAll('.dropdown-menu.show')).toHaveSize(1)
- const keyup = createEvent('keyup')
- keyup.key = 'Tab'
+ const keyup = createEvent('keyup')
+ keyup.key = 'Tab'
- document.dispatchEvent(keyup)
- })
+ document.dispatchEvent(keyup)
+ })
- triggerDropdownFirst.addEventListener('hidden.bs.dropdown', () => {
- expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(0, '"show" class removed')
- triggerDropdownLast.click()
- })
+ triggerDropdownFirst.addEventListener('hidden.bs.dropdown', () => {
+ expect(fixtureEl.querySelectorAll('.dropdown-menu.show')).toHaveSize(0)
+ triggerDropdownLast.click()
+ })
- triggerDropdownLast.addEventListener('shown.bs.dropdown', () => {
- expect(triggerDropdownLast.classList.contains('show')).toEqual(true, '"show" class added on click')
- expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(1, 'only one dropdown is shown')
+ triggerDropdownLast.addEventListener('shown.bs.dropdown', () => {
+ expect(triggerDropdownLast).toHaveClass('show')
+ expect(fixtureEl.querySelectorAll('.dropdown-menu.show')).toHaveSize(1)
- const keyup = createEvent('keyup')
- keyup.key = 'Tab'
+ const keyup = createEvent('keyup')
+ keyup.key = 'Tab'
- document.dispatchEvent(keyup)
- })
+ document.dispatchEvent(keyup)
+ })
- triggerDropdownLast.addEventListener('hidden.bs.dropdown', () => {
- expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(0, '"show" class removed')
- done()
- })
+ triggerDropdownLast.addEventListener('hidden.bs.dropdown', () => {
+ expect(fixtureEl.querySelectorAll('.dropdown-menu.show')).toHaveSize(0)
+ resolve()
+ })
- triggerDropdownFirst.click()
+ triggerDropdownFirst.click()
+ })
})
- it('should fire hide and hidden event without a clickEvent if event type is not click', done => {
+ it('should be able to identify clicked dropdown, even with multiple dropdowns in the same tag', () => {
fixtureEl.innerHTML = [
'<div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#sub1">Submenu 1</a>',
+ ' <button id="dropdown1" class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown toggle</button>',
+ ' <div id="menu1" class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Dropdown item</a>',
+ ' </div>',
+ ' <button id="dropdown2" class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown toggle</button>',
+ ' <div id="menu2" class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Dropdown item</a>',
' </div>',
'</div>'
].join('')
- const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
-
- triggerDropdown.addEventListener('hide.bs.dropdown', event => {
- expect(event.clickEvent).toBeUndefined()
- })
+ const dropdownToggle1 = fixtureEl.querySelector('#dropdown1')
+ const dropdownToggle2 = fixtureEl.querySelector('#dropdown2')
+ const dropdownMenu1 = fixtureEl.querySelector('#menu1')
+ const dropdownMenu2 = fixtureEl.querySelector('#menu2')
+ const spy = spyOn(Dropdown, 'getOrCreateInstance').and.callThrough()
- triggerDropdown.addEventListener('hidden.bs.dropdown', event => {
- expect(event.clickEvent).toBeUndefined()
- done()
- })
+ dropdownToggle1.click()
+ expect(spy).toHaveBeenCalledWith(dropdownToggle1)
- triggerDropdown.addEventListener('shown.bs.dropdown', () => {
- const keydown = createEvent('keydown')
+ dropdownToggle2.click()
+ expect(spy).toHaveBeenCalledWith(dropdownToggle2)
- keydown.key = 'Escape'
- triggerDropdown.dispatchEvent(keydown)
- })
+ dropdownMenu1.click()
+ expect(spy).toHaveBeenCalledWith(dropdownToggle1)
- triggerDropdown.click()
+ dropdownMenu2.click()
+ expect(spy).toHaveBeenCalledWith(dropdownToggle2)
})
- it('should bubble up the events to the parent elements', done => {
+ it('should be able to show the proper menu, even with multiple dropdowns in the same tag', () => {
fixtureEl.innerHTML = [
'<div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#subMenu">Sub menu</a>',
+ ' <button id="dropdown1" class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown toggle</button>',
+ ' <div id="menu1" class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Dropdown item</a>',
+ ' </div>',
+ ' <button id="dropdown2" class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown toggle</button>',
+ ' <div id="menu2" class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Dropdown item</a>',
' </div>',
'</div>'
].join('')
- const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const dropdownParent = fixtureEl.querySelector('.dropdown')
- const dropdown = new Dropdown(triggerDropdown)
+ const dropdownToggle1 = fixtureEl.querySelector('#dropdown1')
+ const dropdownToggle2 = fixtureEl.querySelector('#dropdown2')
+ const dropdownMenu1 = fixtureEl.querySelector('#menu1')
+ const dropdownMenu2 = fixtureEl.querySelector('#menu2')
- const showFunction = jasmine.createSpy('showFunction')
- dropdownParent.addEventListener('show.bs.dropdown', showFunction)
+ dropdownToggle1.click()
+ expect(dropdownMenu1).toHaveClass('show')
+ expect(dropdownMenu2).not.toHaveClass('show')
- const shownFunction = jasmine.createSpy('shownFunction')
- dropdownParent.addEventListener('shown.bs.dropdown', () => {
- shownFunction()
- dropdown.hide()
- })
+ dropdownToggle2.click()
+ expect(dropdownMenu1).not.toHaveClass('show')
+ expect(dropdownMenu2).toHaveClass('show')
+ })
- const hideFunction = jasmine.createSpy('hideFunction')
- dropdownParent.addEventListener('hide.bs.dropdown', hideFunction)
+ it('should fire hide and hidden event without a clickEvent if event type is not click', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#sub1">Submenu 1</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- dropdownParent.addEventListener('hidden.bs.dropdown', () => {
- expect(showFunction).toHaveBeenCalled()
- expect(shownFunction).toHaveBeenCalled()
- expect(hideFunction).toHaveBeenCalled()
- done()
- })
+ const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+
+ triggerDropdown.addEventListener('hide.bs.dropdown', event => {
+ expect(event.clickEvent).toBeUndefined()
+ })
+
+ triggerDropdown.addEventListener('hidden.bs.dropdown', event => {
+ expect(event.clickEvent).toBeUndefined()
+ resolve()
+ })
+
+ triggerDropdown.addEventListener('shown.bs.dropdown', () => {
+ const keydown = createEvent('keydown')
- dropdown.show()
+ keydown.key = 'Escape'
+ triggerDropdown.dispatchEvent(keydown)
+ })
+
+ triggerDropdown.click()
+ })
})
- it('should ignore keyboard events within <input>s and <textarea>s', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#sub1">Submenu 1</a>',
- ' <input type="text">',
- ' <textarea></textarea>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should bubble up the events to the parent elements', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#subMenu">Sub menu</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const input = fixtureEl.querySelector('input')
- const textarea = fixtureEl.querySelector('textarea')
+ const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const dropdownParent = fixtureEl.querySelector('.dropdown')
+ const dropdown = new Dropdown(triggerDropdown)
- triggerDropdown.addEventListener('shown.bs.dropdown', () => {
- input.focus()
- const keydown = createEvent('keydown')
+ const showFunction = jasmine.createSpy('showFunction')
+ dropdownParent.addEventListener('show.bs.dropdown', showFunction)
- keydown.key = 'ArrowUp'
- input.dispatchEvent(keydown)
+ const shownFunction = jasmine.createSpy('shownFunction')
+ dropdownParent.addEventListener('shown.bs.dropdown', () => {
+ shownFunction()
+ dropdown.hide()
+ })
- expect(document.activeElement).toEqual(input, 'input still focused')
+ const hideFunction = jasmine.createSpy('hideFunction')
+ dropdownParent.addEventListener('hide.bs.dropdown', hideFunction)
- textarea.focus()
- textarea.dispatchEvent(keydown)
+ dropdownParent.addEventListener('hidden.bs.dropdown', () => {
+ expect(showFunction).toHaveBeenCalled()
+ expect(shownFunction).toHaveBeenCalled()
+ expect(hideFunction).toHaveBeenCalled()
+ resolve()
+ })
- expect(document.activeElement).toEqual(textarea, 'textarea still focused')
- done()
+ dropdown.show()
})
-
- triggerDropdown.click()
})
- it('should skip disabled element when using keyboard navigation', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item disabled" href="#sub1">Submenu 1</a>',
- ' <button class="dropdown-item" type="button" disabled>Disabled button</button>',
- ' <a id="item1" class="dropdown-item" href="#">Another link</a>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should ignore keyboard events within <input>s and <textarea>s', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#sub1">Submenu 1</a>',
+ ' <input type="text">',
+ ' <textarea></textarea>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const input = fixtureEl.querySelector('input')
+ const textarea = fixtureEl.querySelector('textarea')
- triggerDropdown.addEventListener('shown.bs.dropdown', () => {
- const keydown = createEvent('keydown')
- keydown.key = 'ArrowDown'
+ triggerDropdown.addEventListener('shown.bs.dropdown', () => {
+ input.focus()
+ const keydown = createEvent('keydown')
- triggerDropdown.dispatchEvent(keydown)
- triggerDropdown.dispatchEvent(keydown)
+ keydown.key = 'ArrowUp'
+ input.dispatchEvent(keydown)
- expect(document.activeElement.classList.contains('disabled')).toEqual(false, '.disabled not focused')
- expect(document.activeElement.hasAttribute('disabled')).toEqual(false, ':disabled not focused')
- done()
- })
+ expect(document.activeElement).toEqual(input, 'input still focused')
+
+ textarea.focus()
+ textarea.dispatchEvent(keydown)
+
+ expect(document.activeElement).toEqual(textarea, 'textarea still focused')
+ resolve()
+ })
- triggerDropdown.click()
+ triggerDropdown.click()
+ })
})
- it('should skip hidden element when using keyboard navigation', done => {
- fixtureEl.innerHTML = [
- '<style>',
- ' .d-none {',
- ' display: none;',
- ' }',
- '</style>',
- '<div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <button class="dropdown-item d-none" type="button">Hidden button by class</button>',
- ' <a class="dropdown-item" href="#sub1" style="display: none">Hidden link</a>',
- ' <a class="dropdown-item" href="#sub1" style="visibility: hidden">Hidden link</a>',
- ' <a id="item1" class="dropdown-item" href="#">Another link</a>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should skip disabled element when using keyboard navigation', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item disabled" href="#sub1">Submenu 1</a>',
+ ' <button class="dropdown-item" type="button" disabled>Disabled button</button>',
+ ' <a id="item1" class="dropdown-item" href="#">Another link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- triggerDropdown.addEventListener('shown.bs.dropdown', () => {
- const keydown = createEvent('keydown')
- keydown.key = 'ArrowDown'
+ triggerDropdown.addEventListener('shown.bs.dropdown', () => {
+ const keydown = createEvent('keydown')
+ keydown.key = 'ArrowDown'
- triggerDropdown.dispatchEvent(keydown)
+ triggerDropdown.dispatchEvent(keydown)
+ triggerDropdown.dispatchEvent(keydown)
- expect(document.activeElement.classList.contains('d-none')).toEqual(false, '.d-none not focused')
- expect(document.activeElement.style.display).not.toBe('none', '"display: none" not focused')
- expect(document.activeElement.style.visibility).not.toBe('hidden', '"visibility: hidden" not focused')
+ expect(document.activeElement).not.toHaveClass('disabled')
+ expect(document.activeElement.hasAttribute('disabled')).toBeFalse()
+ resolve()
+ })
- done()
+ triggerDropdown.click()
})
-
- triggerDropdown.click()
})
- it('should focus next/previous element when using keyboard navigation', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <a id="item1" class="dropdown-item" href="#">A link</a>',
- ' <a id="item2" class="dropdown-item" href="#">Another link</a>',
- ' </div>',
- '</div>'
- ].join('')
-
- const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const item1 = fixtureEl.querySelector('#item1')
- const item2 = fixtureEl.querySelector('#item2')
+ it('should skip hidden element when using keyboard navigation', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<style>',
+ ' .d-none {',
+ ' display: none;',
+ ' }',
+ '</style>',
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <button class="dropdown-item d-none" type="button">Hidden button by class</button>',
+ ' <a class="dropdown-item" href="#sub1" style="display: none">Hidden link</a>',
+ ' <a class="dropdown-item" href="#sub1" style="visibility: hidden">Hidden link</a>',
+ ' <a id="item1" class="dropdown-item" href="#">Another link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- triggerDropdown.addEventListener('shown.bs.dropdown', () => {
- const keydownArrowDown = createEvent('keydown')
- keydownArrowDown.key = 'ArrowDown'
+ const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- triggerDropdown.dispatchEvent(keydownArrowDown)
- expect(document.activeElement).toEqual(item1, 'item1 is focused')
+ triggerDropdown.addEventListener('shown.bs.dropdown', () => {
+ const keydown = createEvent('keydown')
+ keydown.key = 'ArrowDown'
- document.activeElement.dispatchEvent(keydownArrowDown)
- expect(document.activeElement).toEqual(item2, 'item2 is focused')
+ triggerDropdown.dispatchEvent(keydown)
- const keydownArrowUp = createEvent('keydown')
- keydownArrowUp.key = 'ArrowUp'
+ expect(document.activeElement).not.toHaveClass('d-none')
+ expect(document.activeElement.style.display).not.toEqual('none')
+ expect(document.activeElement.style.visibility).not.toEqual('hidden')
- document.activeElement.dispatchEvent(keydownArrowUp)
- expect(document.activeElement).toEqual(item1, 'item1 is focused')
+ resolve()
+ })
- done()
+ triggerDropdown.click()
})
-
- triggerDropdown.click()
})
- it('should open the dropdown and focus on the last item when using ArrowUp for the first time', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <a id="item1" class="dropdown-item" href="#">A link</a>',
- ' <a id="item2" class="dropdown-item" href="#">Another link</a>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should focus next/previous element when using keyboard navigation', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a id="item1" class="dropdown-item" href="#">A link</a>',
+ ' <a id="item2" class="dropdown-item" href="#">Another link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const lastItem = fixtureEl.querySelector('#item2')
+ const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const item1 = fixtureEl.querySelector('#item1')
+ const item2 = fixtureEl.querySelector('#item2')
- triggerDropdown.addEventListener('shown.bs.dropdown', () => {
- setTimeout(() => {
- expect(document.activeElement).toEqual(lastItem, 'item2 is focused')
- done()
+ triggerDropdown.addEventListener('shown.bs.dropdown', () => {
+ const keydownArrowDown = createEvent('keydown')
+ keydownArrowDown.key = 'ArrowDown'
+
+ triggerDropdown.dispatchEvent(keydownArrowDown)
+ expect(document.activeElement).toEqual(item1, 'item1 is focused')
+
+ document.activeElement.dispatchEvent(keydownArrowDown)
+ expect(document.activeElement).toEqual(item2, 'item2 is focused')
+
+ const keydownArrowUp = createEvent('keydown')
+ keydownArrowUp.key = 'ArrowUp'
+
+ document.activeElement.dispatchEvent(keydownArrowUp)
+ expect(document.activeElement).toEqual(item1, 'item1 is focused')
+
+ resolve()
})
- })
- const keydown = createEvent('keydown')
- keydown.key = 'ArrowUp'
- triggerDropdown.dispatchEvent(keydown)
+ triggerDropdown.click()
+ })
})
- it('should open the dropdown and focus on the first item when using ArrowDown for the first time', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <a id="item1" class="dropdown-item" href="#">A link</a>',
- ' <a id="item2" class="dropdown-item" href="#">Another link</a>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should open the dropdown and focus on the last item when using ArrowUp for the first time', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a id="item1" class="dropdown-item" href="#">A link</a>',
+ ' <a id="item2" class="dropdown-item" href="#">Another link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const firstItem = fixtureEl.querySelector('#item1')
+ const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const lastItem = fixtureEl.querySelector('#item2')
- triggerDropdown.addEventListener('shown.bs.dropdown', () => {
- setTimeout(() => {
- expect(document.activeElement).toEqual(firstItem, 'item1 is focused')
- done()
+ triggerDropdown.addEventListener('shown.bs.dropdown', () => {
+ setTimeout(() => {
+ expect(document.activeElement).toEqual(lastItem, 'item2 is focused')
+ resolve()
+ })
})
- })
- const keydown = createEvent('keydown')
- keydown.key = 'ArrowDown'
- triggerDropdown.dispatchEvent(keydown)
+ const keydown = createEvent('keydown')
+ keydown.key = 'ArrowUp'
+ triggerDropdown.dispatchEvent(keydown)
+ })
})
- it('should not close the dropdown if the user clicks on a text field within dropdown-menu', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <input type="text">',
- ' </div>',
- '</div>'
- ].join('')
+ it('should open the dropdown and focus on the first item when using ArrowDown for the first time', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a id="item1" class="dropdown-item" href="#">A link</a>',
+ ' <a id="item2" class="dropdown-item" href="#">Another link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const input = fixtureEl.querySelector('input')
+ const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const firstItem = fixtureEl.querySelector('#item1')
- input.addEventListener('click', () => {
- expect(triggerDropdown.classList.contains('show')).toEqual(true, 'dropdown menu is shown')
- done()
- })
+ triggerDropdown.addEventListener('shown.bs.dropdown', () => {
+ setTimeout(() => {
+ expect(document.activeElement).toEqual(firstItem, 'item1 is focused')
+ resolve()
+ })
+ })
- triggerDropdown.addEventListener('shown.bs.dropdown', () => {
- expect(triggerDropdown.classList.contains('show')).toEqual(true, 'dropdown menu is shown')
- input.dispatchEvent(createEvent('click'))
+ const keydown = createEvent('keydown')
+ keydown.key = 'ArrowDown'
+ triggerDropdown.dispatchEvent(keydown)
})
-
- triggerDropdown.click()
})
- it('should not close the dropdown if the user clicks on a textarea within dropdown-menu', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <textarea></textarea>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should not close the dropdown if the user clicks on a text field within dropdown-menu', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <input type="text">',
+ ' </div>',
+ '</div>'
+ ].join('')
- const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const textarea = fixtureEl.querySelector('textarea')
+ const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const input = fixtureEl.querySelector('input')
- textarea.addEventListener('click', () => {
- expect(triggerDropdown.classList.contains('show')).toEqual(true, 'dropdown menu is shown')
- done()
- })
+ input.addEventListener('click', () => {
+ expect(triggerDropdown).toHaveClass('show')
+ resolve()
+ })
- triggerDropdown.addEventListener('shown.bs.dropdown', () => {
- expect(triggerDropdown.classList.contains('show')).toEqual(true, 'dropdown menu is shown')
- textarea.dispatchEvent(createEvent('click'))
- })
+ triggerDropdown.addEventListener('shown.bs.dropdown', () => {
+ expect(triggerDropdown).toHaveClass('show')
+ input.dispatchEvent(createEvent('click'))
+ })
- triggerDropdown.click()
+ triggerDropdown.click()
+ })
})
- it('should close the dropdown if the user clicks on a text field that is not contained within dropdown-menu', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' </div>',
- '</div>',
- '<input type="text">'
- ]
+ it('should not close the dropdown if the user clicks on a textarea within dropdown-menu', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <textarea></textarea>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const input = fixtureEl.querySelector('input')
+ const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const textarea = fixtureEl.querySelector('textarea')
- triggerDropdown.addEventListener('hidden.bs.dropdown', () => {
- expect().nothing()
- done()
- })
+ textarea.addEventListener('click', () => {
+ expect(triggerDropdown).toHaveClass('show')
+ resolve()
+ })
- triggerDropdown.addEventListener('shown.bs.dropdown', () => {
- input.dispatchEvent(createEvent('click', {
- bubbles: true
- }))
- })
+ triggerDropdown.addEventListener('shown.bs.dropdown', () => {
+ expect(triggerDropdown).toHaveClass('show')
+ textarea.dispatchEvent(createEvent('click'))
+ })
- triggerDropdown.click()
+ triggerDropdown.click()
+ })
})
- it('should ignore keyboard events for <input>s and <textarea>s within dropdown-menu, except for escape key', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#sub1">Submenu 1</a>',
- ' <input type="text">',
- ' <textarea></textarea>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should close the dropdown if the user clicks on a text field that is not contained within dropdown-menu', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' </div>',
+ '</div>',
+ '<input type="text">'
+ ].join('')
- const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const input = fixtureEl.querySelector('input')
- const textarea = fixtureEl.querySelector('textarea')
+ const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const input = fixtureEl.querySelector('input')
- const keydownSpace = createEvent('keydown')
- keydownSpace.key = 'Space'
+ triggerDropdown.addEventListener('hidden.bs.dropdown', () => {
+ expect().nothing()
+ resolve()
+ })
+
+ triggerDropdown.addEventListener('shown.bs.dropdown', () => {
+ input.dispatchEvent(createEvent('click', {
+ bubbles: true
+ }))
+ })
- const keydownArrowUp = createEvent('keydown')
- keydownArrowUp.key = 'ArrowUp'
+ triggerDropdown.click()
+ })
+ })
+
+ it('should ignore keyboard events for <input>s and <textarea>s within dropdown-menu, except for escape key', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#sub1">Submenu 1</a>',
+ ' <input type="text">',
+ ' <textarea></textarea>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const input = fixtureEl.querySelector('input')
+ const textarea = fixtureEl.querySelector('textarea')
+
+ const test = (eventKey, elementToDispatch) => {
+ const event = createEvent('keydown')
+ event.key = eventKey
+ elementToDispatch.focus()
+ elementToDispatch.dispatchEvent(event)
+ expect(document.activeElement).toEqual(elementToDispatch, `${elementToDispatch.tagName} still focused`)
+ }
- const keydownArrowDown = createEvent('keydown')
- keydownArrowDown.key = 'ArrowDown'
+ const keydownEscape = createEvent('keydown')
+ keydownEscape.key = 'Escape'
- const keydownEscape = createEvent('keydown')
- keydownEscape.key = 'Escape'
+ triggerDropdown.addEventListener('shown.bs.dropdown', () => {
+ // Key Space
+ test('Space', input)
- triggerDropdown.addEventListener('shown.bs.dropdown', () => {
- // Key Space
- input.focus()
- input.dispatchEvent(keydownSpace)
+ test('Space', textarea)
- expect(document.activeElement).toEqual(input, 'input still focused')
+ // Key ArrowUp
+ test('ArrowUp', input)
- textarea.focus()
- textarea.dispatchEvent(keydownSpace)
+ test('ArrowUp', textarea)
- expect(document.activeElement).toEqual(textarea, 'textarea still focused')
+ // Key ArrowDown
+ test('ArrowDown', input)
- // Key ArrowUp
- input.focus()
- input.dispatchEvent(keydownArrowUp)
+ test('ArrowDown', textarea)
- expect(document.activeElement).toEqual(input, 'input still focused')
+ // Key Escape
+ input.focus()
+ input.dispatchEvent(keydownEscape)
- textarea.focus()
- textarea.dispatchEvent(keydownArrowUp)
+ expect(triggerDropdown).not.toHaveClass('show')
+ resolve()
+ })
- expect(document.activeElement).toEqual(textarea, 'textarea still focused')
+ triggerDropdown.click()
+ })
+ })
- // Key ArrowDown
- input.focus()
- input.dispatchEvent(keydownArrowDown)
+ it('should not open dropdown if escape key was pressed on the toggle', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="tabs">',
+ ' <div class="dropdown">',
+ ' <button disabled class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Secondary link</a>',
+ ' <a class="dropdown-item" href="#">Something else here</a>',
+ ' <div class="divider"></div>',
+ ' <a class="dropdown-item" href="#">Another link</a>',
+ ' </div>',
+ ' </div>',
+ '</div>'
+ ].join('')
- expect(document.activeElement).toEqual(input, 'input still focused')
+ const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const dropdown = new Dropdown(triggerDropdown)
+ const button = fixtureEl.querySelector('button[data-bs-toggle="dropdown"]')
- textarea.focus()
- textarea.dispatchEvent(keydownArrowDown)
+ const spy = spyOn(dropdown, 'toggle')
- expect(document.activeElement).toEqual(textarea, 'textarea still focused')
+ // Key escape
+ button.focus()
+ // Key escape
+ const keydownEscape = createEvent('keydown')
+ keydownEscape.key = 'Escape'
+ button.dispatchEvent(keydownEscape)
- // Key Escape
- input.focus()
- input.dispatchEvent(keydownEscape)
+ setTimeout(() => {
+ expect(spy).not.toHaveBeenCalled()
+ expect(triggerDropdown).not.toHaveClass('show')
+ resolve()
+ }, 20)
+ })
+ })
+
+ it('should propagate escape key events if dropdown is closed', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="parent">',
+ ' <div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Some Item</a>',
+ ' </div>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const parent = fixtureEl.querySelector('.parent')
+ const toggle = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+
+ const parentKeyHandler = jasmine.createSpy('parentKeyHandler')
+
+ parent.addEventListener('keydown', parentKeyHandler)
+ parent.addEventListener('keyup', () => {
+ expect(parentKeyHandler).toHaveBeenCalled()
+ resolve()
+ })
- expect(triggerDropdown.classList.contains('show')).toEqual(false, 'dropdown menu is not shown')
- done()
- })
+ const keydownEscape = createEvent('keydown', { bubbles: true })
+ keydownEscape.key = 'Escape'
+ const keyupEscape = createEvent('keyup', { bubbles: true })
+ keyupEscape.key = 'Escape'
- triggerDropdown.click()
+ toggle.focus()
+ toggle.dispatchEvent(keydownEscape)
+ toggle.dispatchEvent(keyupEscape)
+ })
})
- it('should not open dropdown if escape key was pressed on the toggle', done => {
- fixtureEl.innerHTML = [
- '<div class="tabs">',
- ' <div class="dropdown">',
- ' <button disabled class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#">Secondary link</a>',
- ' <a class="dropdown-item" href="#">Something else here</a>',
- ' <div class="divider"></div>',
- ' <a class="dropdown-item" href="#">Another link</a>',
- ' </div>',
- ' </div>',
- '</div>'
- ]
+ it('should not propagate escape key events if dropdown is open', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="parent">',
+ ' <div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Some Item</a>',
+ ' </div>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const dropdown = new Dropdown(triggerDropdown)
- const button = fixtureEl.querySelector('button[data-bs-toggle="dropdown"]')
+ const parent = fixtureEl.querySelector('.parent')
+ const toggle = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- spyOn(dropdown, 'toggle')
+ const parentKeyHandler = jasmine.createSpy('parentKeyHandler')
- // Key escape
- button.focus()
- // Key escape
- const keydownEscape = createEvent('keydown')
- keydownEscape.key = 'Escape'
- button.dispatchEvent(keydownEscape)
+ parent.addEventListener('keydown', parentKeyHandler)
+ parent.addEventListener('keyup', () => {
+ expect(parentKeyHandler).not.toHaveBeenCalled()
+ resolve()
+ })
+
+ const keydownEscape = createEvent('keydown', { bubbles: true })
+ keydownEscape.key = 'Escape'
+ const keyupEscape = createEvent('keyup', { bubbles: true })
+ keyupEscape.key = 'Escape'
- setTimeout(() => {
- expect(dropdown.toggle).not.toHaveBeenCalled()
- expect(triggerDropdown.classList.contains('show')).toEqual(false)
- done()
- }, 20)
+ toggle.click()
+ toggle.dispatchEvent(keydownEscape)
+ toggle.dispatchEvent(keyupEscape)
+ })
})
- it('should propagate escape key events if dropdown is closed', done => {
- fixtureEl.innerHTML = [
- '<div class="parent">',
- ' <div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#">Some Item</a>',
- ' </div>',
- ' </div>',
- '</div>'
- ]
+ it('should close dropdown using `escape` button, and return focus to its trigger', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Some Item</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const toggle = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const parent = fixtureEl.querySelector('.parent')
- const toggle = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ toggle.addEventListener('shown.bs.dropdown', () => {
+ const keydownEvent = createEvent('keydown', { bubbles: true })
+ keydownEvent.key = 'ArrowDown'
+ toggle.dispatchEvent(keydownEvent)
+ keydownEvent.key = 'Escape'
+ toggle.dispatchEvent(keydownEvent)
+ })
- const parentKeyHandler = jasmine.createSpy('parentKeyHandler')
+ toggle.addEventListener('hidden.bs.dropdown', () => setTimeout(() => {
+ expect(document.activeElement).toEqual(toggle)
+ resolve()
+ }))
- parent.addEventListener('keydown', parentKeyHandler)
- parent.addEventListener('keyup', () => {
- expect(parentKeyHandler).toHaveBeenCalled()
- done()
+ toggle.click()
})
+ })
- const keydownEscape = createEvent('keydown', { bubbles: true })
- keydownEscape.key = 'Escape'
- const keyupEscape = createEvent('keyup', { bubbles: true })
- keyupEscape.key = 'Escape'
+ it('should close dropdown (only) by clicking inside the dropdown menu when it has data-attribute `data-bs-auto-close="inside"`', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" data-bs-auto-close="inside">Dropdown toggle</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Dropdown item</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- toggle.focus()
- toggle.dispatchEvent(keydownEscape)
- toggle.dispatchEvent(keyupEscape)
- })
+ const dropdownToggle = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
- it('should close dropdown (only) by clicking inside the dropdown menu when it has data-attribute `data-bs-auto-close="inside"`', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" data-bs-auto-close="inside">Dropdown toggle</button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#">Dropdown item</a>',
- ' </div>',
- '</div>'
- ]
+ const expectDropdownToBeOpened = () => setTimeout(() => {
+ expect(dropdownToggle).toHaveClass('show')
+ dropdownMenu.click()
+ }, 150)
- const dropdownToggle = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
+ dropdownToggle.addEventListener('shown.bs.dropdown', () => {
+ document.documentElement.click()
+ expectDropdownToBeOpened()
+ })
- const expectDropdownToBeOpened = () => setTimeout(() => {
- expect(dropdownToggle.classList.contains('show')).toEqual(true)
- dropdownMenu.click()
- }, 150)
+ dropdownToggle.addEventListener('hidden.bs.dropdown', () => setTimeout(() => {
+ expect(dropdownToggle).not.toHaveClass('show')
+ resolve()
+ }))
- dropdownToggle.addEventListener('shown.bs.dropdown', () => {
- document.documentElement.click()
- expectDropdownToBeOpened()
+ dropdownToggle.click()
})
+ })
- dropdownToggle.addEventListener('hidden.bs.dropdown', () => setTimeout(() => {
- expect(dropdownToggle.classList.contains('show')).toEqual(false)
- done()
- }))
+ it('should close dropdown (only) by clicking outside the dropdown menu when it has data-attribute `data-bs-auto-close="outside"`', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" data-bs-auto-close="outside">Dropdown toggle</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Dropdown item</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- dropdownToggle.click()
- })
+ const dropdownToggle = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
- it('should close dropdown (only) by clicking outside the dropdown menu when it has data-attribute `data-bs-auto-close="outside"`', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" data-bs-auto-close="outside">Dropdown toggle</button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#">Dropdown item</a>',
- ' </div>',
- '</div>'
- ]
+ const expectDropdownToBeOpened = () => setTimeout(() => {
+ expect(dropdownToggle).toHaveClass('show')
+ document.documentElement.click()
+ }, 150)
- const dropdownToggle = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
+ dropdownToggle.addEventListener('shown.bs.dropdown', () => {
+ dropdownMenu.click()
+ expectDropdownToBeOpened()
+ })
- const expectDropdownToBeOpened = () => setTimeout(() => {
- expect(dropdownToggle.classList.contains('show')).toEqual(true)
- document.documentElement.click()
- }, 150)
+ dropdownToggle.addEventListener('hidden.bs.dropdown', () => {
+ expect(dropdownToggle).not.toHaveClass('show')
+ resolve()
+ })
- dropdownToggle.addEventListener('shown.bs.dropdown', () => {
- dropdownMenu.click()
- expectDropdownToBeOpened()
+ dropdownToggle.click()
})
+ })
- dropdownToggle.addEventListener('hidden.bs.dropdown', () => {
- expect(dropdownToggle.classList.contains('show')).toEqual(false)
- done()
- })
+ it('should not close dropdown by clicking inside or outside the dropdown menu when it has data-attribute `data-bs-auto-close="false"`', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" data-bs-auto-close="false">Dropdown toggle</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#">Dropdown item</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- dropdownToggle.click()
+ const dropdownToggle = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
+
+ const expectDropdownToBeOpened = (shouldTriggerClick = true) => setTimeout(() => {
+ expect(dropdownToggle).toHaveClass('show')
+ if (shouldTriggerClick) {
+ document.documentElement.click()
+ } else {
+ resolve()
+ }
+
+ expectDropdownToBeOpened(false)
+ }, 150)
+
+ dropdownToggle.addEventListener('shown.bs.dropdown', () => {
+ dropdownMenu.click()
+ expectDropdownToBeOpened()
+ })
+
+ dropdownToggle.click()
+ })
})
- it('should not close dropdown by clicking inside or outside the dropdown menu when it has data-attribute `data-bs-auto-close="false"`', done => {
+ it('should be able to identify clicked dropdown, no matter the markup order', () => {
fixtureEl.innerHTML = [
'<div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" data-bs-auto-close="false">Dropdown toggle</button>',
' <div class="dropdown-menu">',
' <a class="dropdown-item" href="#">Dropdown item</a>',
- ' </div>',
+ ' </div>',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown toggle</button>',
'</div>'
- ]
+ ].join('')
const dropdownToggle = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
-
- const expectDropdownToBeOpened = (shouldTriggerClick = true) => setTimeout(() => {
- expect(dropdownToggle.classList.contains('show')).toEqual(true)
- if (shouldTriggerClick) {
- document.documentElement.click()
- } else {
- done()
- }
-
- expectDropdownToBeOpened(false)
- }, 150)
-
- dropdownToggle.addEventListener('shown.bs.dropdown', () => {
- dropdownMenu.click()
- expectDropdownToBeOpened()
- })
+ const spy = spyOn(Dropdown, 'getOrCreateInstance').and.callThrough()
dropdownToggle.click()
+ expect(spy).toHaveBeenCalledWith(dropdownToggle)
+ dropdownMenu.click()
+ expect(spy).toHaveBeenCalledWith(dropdownToggle)
})
})
@@ -1963,7 +2277,7 @@ describe('Dropdown', () => {
const div = fixtureEl.querySelector('div')
- expect(Dropdown.getInstance(div)).toEqual(null)
+ expect(Dropdown.getInstance(div)).toBeNull()
})
})
@@ -1984,7 +2298,7 @@ describe('Dropdown', () => {
const div = fixtureEl.querySelector('div')
- expect(Dropdown.getInstance(div)).toEqual(null)
+ expect(Dropdown.getInstance(div)).toBeNull()
expect(Dropdown.getOrCreateInstance(div)).toBeInstanceOf(Dropdown)
})
@@ -1993,7 +2307,7 @@ describe('Dropdown', () => {
const div = fixtureEl.querySelector('div')
- expect(Dropdown.getInstance(div)).toEqual(null)
+ expect(Dropdown.getInstance(div)).toBeNull()
const dropdown = Dropdown.getOrCreateInstance(div, {
display: 'dynamic'
})
@@ -2021,52 +2335,54 @@ describe('Dropdown', () => {
})
})
- it('should open dropdown when pressing keydown or keyup', done => {
- fixtureEl.innerHTML = [
- '<div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item disabled" href="#sub1">Submenu 1</a>',
- ' <button class="dropdown-item" type="button" disabled>Disabled button</button>',
- ' <a id="item1" class="dropdown-item" href="#">Another link</a>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should open dropdown when pressing keydown or keyup', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item disabled" href="#sub1">Submenu 1</a>',
+ ' <button class="dropdown-item" type="button" disabled>Disabled button</button>',
+ ' <a id="item1" class="dropdown-item" href="#">Another link</a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const dropdown = fixtureEl.querySelector('.dropdown')
+ const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const dropdown = fixtureEl.querySelector('.dropdown')
- const keydown = createEvent('keydown')
- keydown.key = 'ArrowDown'
+ const keydown = createEvent('keydown')
+ keydown.key = 'ArrowDown'
- const keyup = createEvent('keyup')
- keyup.key = 'ArrowUp'
+ const keyup = createEvent('keyup')
+ keyup.key = 'ArrowUp'
- const handleArrowDown = () => {
- expect(triggerDropdown.classList.contains('show')).toEqual(true)
- expect(triggerDropdown.getAttribute('aria-expanded')).toEqual('true')
- setTimeout(() => {
- dropdown.hide()
- keydown.key = 'ArrowUp'
- triggerDropdown.dispatchEvent(keyup)
- }, 20)
- }
-
- const handleArrowUp = () => {
- expect(triggerDropdown.classList.contains('show')).toEqual(true)
- expect(triggerDropdown.getAttribute('aria-expanded')).toEqual('true')
- done()
- }
-
- dropdown.addEventListener('shown.bs.dropdown', event => {
- if (event.target.key === 'ArrowDown') {
- handleArrowDown()
- } else {
- handleArrowUp()
+ const handleArrowDown = () => {
+ expect(triggerDropdown).toHaveClass('show')
+ expect(triggerDropdown.getAttribute('aria-expanded')).toEqual('true')
+ setTimeout(() => {
+ dropdown.hide()
+ keydown.key = 'ArrowUp'
+ triggerDropdown.dispatchEvent(keyup)
+ }, 20)
}
- })
- triggerDropdown.dispatchEvent(keydown)
+ const handleArrowUp = () => {
+ expect(triggerDropdown).toHaveClass('show')
+ expect(triggerDropdown.getAttribute('aria-expanded')).toEqual('true')
+ resolve()
+ }
+
+ dropdown.addEventListener('shown.bs.dropdown', event => {
+ if (event.target.key === 'ArrowDown') {
+ handleArrowDown()
+ } else {
+ handleArrowUp()
+ }
+ })
+
+ triggerDropdown.dispatchEvent(keydown)
+ })
})
it('should allow `data-bs-toggle="dropdown"` click events to bubble up', () => {
@@ -2092,27 +2408,29 @@ describe('Dropdown', () => {
expect(delegatedClickListener).toHaveBeenCalled()
})
- it('should open the dropdown when clicking the child element inside `data-bs-toggle="dropdown"`', done => {
- fixtureEl.innerHTML = [
- '<div class="container">',
- ' <div class="dropdown">',
- ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown"><span id="childElement">Dropdown</span></button>',
- ' <div class="dropdown-menu">',
- ' <a class="dropdown-item" href="#subMenu">Sub menu</a>',
- ' </div>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should open the dropdown when clicking the child element inside `data-bs-toggle="dropdown"`', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="container">',
+ ' <div class="dropdown">',
+ ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown"><span id="childElement">Dropdown</span></button>',
+ ' <div class="dropdown-menu">',
+ ' <a class="dropdown-item" href="#subMenu">Sub menu</a>',
+ ' </div>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
- const childElement = fixtureEl.querySelector('#childElement')
+ const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+ const childElement = fixtureEl.querySelector('#childElement')
- btnDropdown.addEventListener('shown.bs.dropdown', () => setTimeout(() => {
- expect(btnDropdown.classList.contains('show')).toEqual(true)
- expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
- done()
- }))
+ btnDropdown.addEventListener('shown.bs.dropdown', () => setTimeout(() => {
+ expect(btnDropdown).toHaveClass('show')
+ expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
+ resolve()
+ }))
- childElement.click()
+ childElement.click()
+ })
})
})
diff --git a/js/tests/unit/jquery.spec.js b/js/tests/unit/jquery.spec.js
index 1c9258bd1..7d7f29dc7 100644
--- a/js/tests/unit/jquery.spec.js
+++ b/js/tests/unit/jquery.spec.js
@@ -1,18 +1,18 @@
/* eslint-env jquery */
-import Alert from '../../src/alert'
-import Button from '../../src/button'
-import Carousel from '../../src/carousel'
-import Collapse from '../../src/collapse'
-import Dropdown from '../../src/dropdown'
-import Modal from '../../src/modal'
-import Offcanvas from '../../src/offcanvas'
-import Popover from '../../src/popover'
-import ScrollSpy from '../../src/scrollspy'
-import Tab from '../../src/tab'
-import Toast from '../../src/toast'
-import Tooltip from '../../src/tooltip'
-import { getFixture, clearFixture } from '../helpers/fixture'
+import Alert from '../../src/alert.js'
+import Button from '../../src/button.js'
+import Carousel from '../../src/carousel.js'
+import Collapse from '../../src/collapse.js'
+import Dropdown from '../../src/dropdown.js'
+import Modal from '../../src/modal.js'
+import Offcanvas from '../../src/offcanvas.js'
+import Popover from '../../src/popover.js'
+import ScrollSpy from '../../src/scrollspy.js'
+import Tab from '../../src/tab.js'
+import Toast from '../../src/toast.js'
+import Tooltip from '../../src/tooltip.js'
+import { clearFixture, getFixture } from '../helpers/fixture.js'
describe('jQuery', () => {
let fixtureEl
@@ -40,19 +40,21 @@ describe('jQuery', () => {
expect(Tooltip.jQueryInterface).toEqual(jQuery.fn.tooltip)
})
- it('should use jQuery event system', done => {
- fixtureEl.innerHTML = [
- '<div class="alert">',
- ' <button type="button" data-bs-dismiss="alert">x</button>',
- '</div>'
- ].join('')
-
- $(fixtureEl).find('.alert')
- .one('closed.bs.alert', () => {
- expect($(fixtureEl).find('.alert').length).toEqual(0)
- done()
- })
-
- $(fixtureEl).find('button').trigger('click')
+ it('should use jQuery event system', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="alert">',
+ ' <button type="button" data-bs-dismiss="alert">x</button>',
+ '</div>'
+ ].join('')
+
+ $(fixtureEl).find('.alert')
+ .one('closed.bs.alert', () => {
+ expect($(fixtureEl).find('.alert')).toHaveSize(0)
+ resolve()
+ })
+
+ $(fixtureEl).find('button').trigger('click')
+ })
})
})
diff --git a/js/tests/unit/modal.spec.js b/js/tests/unit/modal.spec.js
index 613b0a0a1..2aa0b7655 100644
--- a/js/tests/unit/modal.spec.js
+++ b/js/tests/unit/modal.spec.js
@@ -1,7 +1,9 @@
-import Modal from '../../src/modal'
-import EventHandler from '../../src/dom/event-handler'
-import ScrollBarHelper from '../../src/util/scrollbar'
-import { clearBodyAndDocument, clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture'
+import EventHandler from '../../src/dom/event-handler.js'
+import Modal from '../../src/modal.js'
+import ScrollBarHelper from '../../src/util/scrollbar.js'
+import {
+ clearBodyAndDocument, clearFixture, createEvent, getFixture, jQueryMock
+} from '../helpers/fixture.js'
describe('Modal', () => {
let fixtureEl
@@ -56,95 +58,101 @@ describe('Modal', () => {
})
describe('toggle', () => {
- it('should call ScrollBarHelper to handle scrollBar on body', done => {
- fixtureEl.innerHTML = [
- '<div class="modal"><div class="modal-dialog"></div></div>'
- ].join('')
+ it('should call ScrollBarHelper to handle scrollBar on body', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog"></div></div>'
+
+ const spyHide = spyOn(ScrollBarHelper.prototype, 'hide').and.callThrough()
+ const spyReset = spyOn(ScrollBarHelper.prototype, 'reset').and.callThrough()
+ const modalEl = fixtureEl.querySelector('.modal')
+ const modal = new Modal(modalEl)
+
+ modalEl.addEventListener('shown.bs.modal', () => {
+ expect(spyHide).toHaveBeenCalled()
+ modal.toggle()
+ })
- spyOn(ScrollBarHelper.prototype, 'hide').and.callThrough()
- spyOn(ScrollBarHelper.prototype, 'reset').and.callThrough()
- const modalEl = fixtureEl.querySelector('.modal')
- const modal = new Modal(modalEl)
+ modalEl.addEventListener('hidden.bs.modal', () => {
+ expect(spyReset).toHaveBeenCalled()
+ resolve()
+ })
- modalEl.addEventListener('shown.bs.modal', () => {
- expect(ScrollBarHelper.prototype.hide).toHaveBeenCalled()
modal.toggle()
})
-
- modalEl.addEventListener('hidden.bs.modal', () => {
- expect(ScrollBarHelper.prototype.reset).toHaveBeenCalled()
- done()
- })
-
- modal.toggle()
})
})
describe('show', () => {
- it('should show a modal', done => {
- fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog"></div></div>'
+ it('should show a modal', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog"></div></div>'
- const modalEl = fixtureEl.querySelector('.modal')
- const modal = new Modal(modalEl)
+ const modalEl = fixtureEl.querySelector('.modal')
+ const modal = new Modal(modalEl)
- modalEl.addEventListener('show.bs.modal', event => {
- expect(event).toBeDefined()
- })
+ modalEl.addEventListener('show.bs.modal', event => {
+ expect(event).toBeDefined()
+ })
- modalEl.addEventListener('shown.bs.modal', () => {
- expect(modalEl.getAttribute('aria-modal')).toEqual('true')
- expect(modalEl.getAttribute('role')).toEqual('dialog')
- expect(modalEl.getAttribute('aria-hidden')).toBeNull()
- expect(modalEl.style.display).toEqual('block')
- expect(document.querySelector('.modal-backdrop')).not.toBeNull()
- done()
- })
+ modalEl.addEventListener('shown.bs.modal', () => {
+ expect(modalEl.getAttribute('aria-modal')).toEqual('true')
+ expect(modalEl.getAttribute('role')).toEqual('dialog')
+ expect(modalEl.getAttribute('aria-hidden')).toBeNull()
+ expect(modalEl.style.display).toEqual('block')
+ expect(document.querySelector('.modal-backdrop')).not.toBeNull()
+ resolve()
+ })
- modal.show()
+ modal.show()
+ })
})
- it('should show a modal without backdrop', done => {
- fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog"></div></div>'
+ it('should show a modal without backdrop', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog"></div></div>'
- const modalEl = fixtureEl.querySelector('.modal')
- const modal = new Modal(modalEl, {
- backdrop: false
- })
+ const modalEl = fixtureEl.querySelector('.modal')
+ const modal = new Modal(modalEl, {
+ backdrop: false
+ })
- modalEl.addEventListener('show.bs.modal', event => {
- expect(event).toBeDefined()
- })
+ modalEl.addEventListener('show.bs.modal', event => {
+ expect(event).toBeDefined()
+ })
- modalEl.addEventListener('shown.bs.modal', () => {
- expect(modalEl.getAttribute('aria-modal')).toEqual('true')
- expect(modalEl.getAttribute('role')).toEqual('dialog')
- expect(modalEl.getAttribute('aria-hidden')).toBeNull()
- expect(modalEl.style.display).toEqual('block')
- expect(document.querySelector('.modal-backdrop')).toBeNull()
- done()
- })
+ modalEl.addEventListener('shown.bs.modal', () => {
+ expect(modalEl.getAttribute('aria-modal')).toEqual('true')
+ expect(modalEl.getAttribute('role')).toEqual('dialog')
+ expect(modalEl.getAttribute('aria-hidden')).toBeNull()
+ expect(modalEl.style.display).toEqual('block')
+ expect(document.querySelector('.modal-backdrop')).toBeNull()
+ resolve()
+ })
- modal.show()
+ modal.show()
+ })
})
- it('should show a modal and append the element', done => {
- const modalEl = document.createElement('div')
- const id = 'dynamicModal'
+ it('should show a modal and append the element', () => {
+ return new Promise(resolve => {
+ const modalEl = document.createElement('div')
+ const id = 'dynamicModal'
- modalEl.setAttribute('id', id)
- modalEl.classList.add('modal')
- modalEl.innerHTML = '<div class="modal-dialog"></div>'
+ modalEl.setAttribute('id', id)
+ modalEl.classList.add('modal')
+ modalEl.innerHTML = '<div class="modal-dialog"></div>'
- const modal = new Modal(modalEl)
+ const modal = new Modal(modalEl)
- modalEl.addEventListener('shown.bs.modal', () => {
- const dynamicModal = document.getElementById(id)
- expect(dynamicModal).not.toBeNull()
- dynamicModal.remove()
- done()
- })
+ modalEl.addEventListener('shown.bs.modal', () => {
+ const dynamicModal = document.getElementById(id)
+ expect(dynamicModal).not.toBeNull()
+ dynamicModal.remove()
+ resolve()
+ })
- modal.show()
+ modal.show()
+ })
})
it('should do nothing if a modal is shown', () => {
@@ -153,12 +161,12 @@ describe('Modal', () => {
const modalEl = fixtureEl.querySelector('.modal')
const modal = new Modal(modalEl)
- spyOn(EventHandler, 'trigger')
+ const spy = spyOn(EventHandler, 'trigger')
modal._isShown = true
modal.show()
- expect(EventHandler.trigger).not.toHaveBeenCalled()
+ expect(spy).not.toHaveBeenCalled()
})
it('should do nothing if a modal is transitioning', () => {
@@ -167,521 +175,595 @@ describe('Modal', () => {
const modalEl = fixtureEl.querySelector('.modal')
const modal = new Modal(modalEl)
- spyOn(EventHandler, 'trigger')
+ const spy = spyOn(EventHandler, 'trigger')
modal._isTransitioning = true
modal.show()
- expect(EventHandler.trigger).not.toHaveBeenCalled()
+ expect(spy).not.toHaveBeenCalled()
})
- it('should not fire shown event when show is prevented', done => {
- fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog"></div></div>'
+ it('should not fire shown event when show is prevented', () => {
+ return new Promise((resolve, reject) => {
+ fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog"></div></div>'
- const modalEl = fixtureEl.querySelector('.modal')
- const modal = new Modal(modalEl)
+ const modalEl = fixtureEl.querySelector('.modal')
+ const modal = new Modal(modalEl)
- modalEl.addEventListener('show.bs.modal', event => {
- event.preventDefault()
+ modalEl.addEventListener('show.bs.modal', event => {
+ event.preventDefault()
- const expectedDone = () => {
- expect().nothing()
- done()
- }
+ const expectedDone = () => {
+ expect().nothing()
+ resolve()
+ }
- setTimeout(expectedDone, 10)
- })
+ setTimeout(expectedDone, 10)
+ })
- modalEl.addEventListener('shown.bs.modal', () => {
- throw new Error('shown event triggered')
- })
+ modalEl.addEventListener('shown.bs.modal', () => {
+ reject(new Error('shown event triggered'))
+ })
- modal.show()
+ modal.show()
+ })
})
- it('should be shown after the first call to show() has been prevented while fading is enabled ', done => {
- fixtureEl.innerHTML = '<div class="modal fade"><div class="modal-dialog"></div></div>'
+ it('should be shown after the first call to show() has been prevented while fading is enabled ', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div class="modal fade"><div class="modal-dialog"></div></div>'
- const modalEl = fixtureEl.querySelector('.modal')
- const modal = new Modal(modalEl)
+ const modalEl = fixtureEl.querySelector('.modal')
+ const modal = new Modal(modalEl)
- let prevented = false
- modalEl.addEventListener('show.bs.modal', event => {
- if (!prevented) {
- event.preventDefault()
- prevented = true
+ let prevented = false
+ modalEl.addEventListener('show.bs.modal', event => {
+ if (!prevented) {
+ event.preventDefault()
+ prevented = true
- setTimeout(() => {
- modal.show()
- })
- }
- })
+ setTimeout(() => {
+ modal.show()
+ })
+ }
+ })
- modalEl.addEventListener('shown.bs.modal', () => {
- expect(prevented).toBeTrue()
- expect(modal._isAnimated()).toBeTrue()
- done()
- })
+ modalEl.addEventListener('shown.bs.modal', () => {
+ expect(prevented).toBeTrue()
+ expect(modal._isAnimated()).toBeTrue()
+ resolve()
+ })
- modal.show()
+ modal.show()
+ })
})
+ it('should set is transitioning if fade class is present', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div class="modal fade"><div class="modal-dialog"></div></div>'
- it('should set is transitioning if fade class is present', done => {
- fixtureEl.innerHTML = '<div class="modal fade"><div class="modal-dialog"></div></div>'
+ const modalEl = fixtureEl.querySelector('.modal')
+ const modal = new Modal(modalEl)
- const modalEl = fixtureEl.querySelector('.modal')
- const modal = new Modal(modalEl)
+ modalEl.addEventListener('show.bs.modal', () => {
+ setTimeout(() => {
+ expect(modal._isTransitioning).toBeTrue()
+ })
+ })
- modalEl.addEventListener('show.bs.modal', () => {
- setTimeout(() => {
- expect(modal._isTransitioning).toEqual(true)
+ modalEl.addEventListener('shown.bs.modal', () => {
+ expect(modal._isTransitioning).toBeFalse()
+ resolve()
})
- })
- modalEl.addEventListener('shown.bs.modal', () => {
- expect(modal._isTransitioning).toEqual(false)
- done()
+ modal.show()
})
-
- modal.show()
})
- it('should close modal when a click occurred on data-bs-dismiss="modal" inside modal', done => {
- fixtureEl.innerHTML = [
- '<div class="modal fade">',
- ' <div class="modal-dialog">',
- ' <div class="modal-header">',
- ' <button type="button" data-bs-dismiss="modal"></button>',
- ' </div>',
- ' </div>',
- '</div>'
- ].join('')
-
- const modalEl = fixtureEl.querySelector('.modal')
- const btnClose = fixtureEl.querySelector('[data-bs-dismiss="modal"]')
- const modal = new Modal(modalEl)
-
- spyOn(modal, 'hide').and.callThrough()
+ it('should close modal when a click occurred on data-bs-dismiss="modal" inside modal', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="modal fade">',
+ ' <div class="modal-dialog">',
+ ' <div class="modal-header">',
+ ' <button type="button" data-bs-dismiss="modal"></button>',
+ ' </div>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const modalEl = fixtureEl.querySelector('.modal')
+ const btnClose = fixtureEl.querySelector('[data-bs-dismiss="modal"]')
+ const modal = new Modal(modalEl)
+
+ const spy = spyOn(modal, 'hide').and.callThrough()
+
+ modalEl.addEventListener('shown.bs.modal', () => {
+ btnClose.click()
+ })
- modalEl.addEventListener('shown.bs.modal', () => {
- btnClose.click()
- })
+ modalEl.addEventListener('hidden.bs.modal', () => {
+ expect(spy).toHaveBeenCalled()
+ resolve()
+ })
- modalEl.addEventListener('hidden.bs.modal', () => {
- expect(modal.hide).toHaveBeenCalled()
- done()
+ modal.show()
})
-
- modal.show()
})
- it('should close modal when a click occurred on a data-bs-dismiss="modal" with "bs-target" outside of modal element', done => {
- fixtureEl.innerHTML = [
- '<button type="button" data-bs-dismiss="modal" data-bs-target="#modal1"></button>',
- '<div id="modal1" class="modal fade">',
- ' <div class="modal-dialog">',
- ' </div>',
- '</div>'
- ].join('')
+ it('should close modal when a click occurred on a data-bs-dismiss="modal" with "bs-target" outside of modal element', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<button type="button" data-bs-dismiss="modal" data-bs-target="#modal1"></button>',
+ '<div id="modal1" class="modal fade">',
+ ' <div class="modal-dialog"></div>',
+ '</div>'
+ ].join('')
- const modalEl = fixtureEl.querySelector('.modal')
- const btnClose = fixtureEl.querySelector('[data-bs-dismiss="modal"]')
- const modal = new Modal(modalEl)
+ const modalEl = fixtureEl.querySelector('.modal')
+ const btnClose = fixtureEl.querySelector('[data-bs-dismiss="modal"]')
+ const modal = new Modal(modalEl)
- spyOn(modal, 'hide').and.callThrough()
+ const spy = spyOn(modal, 'hide').and.callThrough()
- modalEl.addEventListener('shown.bs.modal', () => {
- btnClose.click()
- })
+ modalEl.addEventListener('shown.bs.modal', () => {
+ btnClose.click()
+ })
- modalEl.addEventListener('hidden.bs.modal', () => {
- expect(modal.hide).toHaveBeenCalled()
- done()
- })
+ modalEl.addEventListener('hidden.bs.modal', () => {
+ expect(spy).toHaveBeenCalled()
+ resolve()
+ })
- modal.show()
+ modal.show()
+ })
})
- it('should set .modal\'s scroll top to 0', done => {
- fixtureEl.innerHTML = [
- '<div class="modal fade">',
- ' <div class="modal-dialog">',
- ' </div>',
- '</div>'
- ].join('')
+ it('should set .modal\'s scroll top to 0', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="modal fade">',
+ ' <div class="modal-dialog"></div>',
+ '</div>'
+ ].join('')
- const modalEl = fixtureEl.querySelector('.modal')
- const modal = new Modal(modalEl)
+ const modalEl = fixtureEl.querySelector('.modal')
+ const modal = new Modal(modalEl)
- modalEl.addEventListener('shown.bs.modal', () => {
- expect(modalEl.scrollTop).toEqual(0)
- done()
- })
+ modalEl.addEventListener('shown.bs.modal', () => {
+ expect(modalEl.scrollTop).toEqual(0)
+ resolve()
+ })
- modal.show()
+ modal.show()
+ })
})
- it('should set modal body scroll top to 0 if modal body do not exists', done => {
- fixtureEl.innerHTML = [
- '<div class="modal fade">',
- ' <div class="modal-dialog">',
- ' <div class="modal-body"></div>',
- ' </div>',
- '</div>'
- ].join('')
-
- const modalEl = fixtureEl.querySelector('.modal')
- const modalBody = modalEl.querySelector('.modal-body')
- const modal = new Modal(modalEl)
+ it('should set modal body scroll top to 0 if modal body do not exists', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="modal fade">',
+ ' <div class="modal-dialog">',
+ ' <div class="modal-body"></div>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const modalEl = fixtureEl.querySelector('.modal')
+ const modalBody = modalEl.querySelector('.modal-body')
+ const modal = new Modal(modalEl)
+
+ modalEl.addEventListener('shown.bs.modal', () => {
+ expect(modalBody.scrollTop).toEqual(0)
+ resolve()
+ })
- modalEl.addEventListener('shown.bs.modal', () => {
- expect(modalBody.scrollTop).toEqual(0)
- done()
+ modal.show()
})
-
- modal.show()
})
- it('should not trap focus if focus equal to false', done => {
- fixtureEl.innerHTML = '<div class="modal fade"><div class="modal-dialog"></div></div>'
+ it('should not trap focus if focus equal to false', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div class="modal fade"><div class="modal-dialog"></div></div>'
- const modalEl = fixtureEl.querySelector('.modal')
- const modal = new Modal(modalEl, {
- focus: false
- })
+ const modalEl = fixtureEl.querySelector('.modal')
+ const modal = new Modal(modalEl, {
+ focus: false
+ })
- spyOn(modal._focustrap, 'activate').and.callThrough()
+ const spy = spyOn(modal._focustrap, 'activate').and.callThrough()
- modalEl.addEventListener('shown.bs.modal', () => {
- expect(modal._focustrap.activate).not.toHaveBeenCalled()
- done()
- })
+ modalEl.addEventListener('shown.bs.modal', () => {
+ expect(spy).not.toHaveBeenCalled()
+ resolve()
+ })
- modal.show()
+ modal.show()
+ })
})
- it('should add listener when escape touch is pressed', done => {
- fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog"></div></div>'
+ it('should add listener when escape touch is pressed', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog"></div></div>'
- const modalEl = fixtureEl.querySelector('.modal')
- const modal = new Modal(modalEl)
+ const modalEl = fixtureEl.querySelector('.modal')
+ const modal = new Modal(modalEl)
- spyOn(modal, 'hide').and.callThrough()
+ const spy = spyOn(modal, 'hide').and.callThrough()
- modalEl.addEventListener('shown.bs.modal', () => {
- const keydownEscape = createEvent('keydown')
- keydownEscape.key = 'Escape'
+ modalEl.addEventListener('shown.bs.modal', () => {
+ const keydownEscape = createEvent('keydown')
+ keydownEscape.key = 'Escape'
- modalEl.dispatchEvent(keydownEscape)
- })
+ modalEl.dispatchEvent(keydownEscape)
+ })
- modalEl.addEventListener('hidden.bs.modal', () => {
- expect(modal.hide).toHaveBeenCalled()
- done()
- })
+ modalEl.addEventListener('hidden.bs.modal', () => {
+ expect(spy).toHaveBeenCalled()
+ resolve()
+ })
- modal.show()
+ modal.show()
+ })
})
- it('should do nothing when the pressed key is not escape', done => {
- fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog"></div></div>'
+ it('should do nothing when the pressed key is not escape', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog"></div></div>'
- const modalEl = fixtureEl.querySelector('.modal')
- const modal = new Modal(modalEl)
+ const modalEl = fixtureEl.querySelector('.modal')
+ const modal = new Modal(modalEl)
- spyOn(modal, 'hide')
+ const spy = spyOn(modal, 'hide')
- const expectDone = () => {
- expect(modal.hide).not.toHaveBeenCalled()
+ const expectDone = () => {
+ expect(spy).not.toHaveBeenCalled()
- done()
- }
+ resolve()
+ }
- modalEl.addEventListener('shown.bs.modal', () => {
- const keydownTab = createEvent('keydown')
- keydownTab.key = 'Tab'
+ modalEl.addEventListener('shown.bs.modal', () => {
+ const keydownTab = createEvent('keydown')
+ keydownTab.key = 'Tab'
- modalEl.dispatchEvent(keydownTab)
- setTimeout(expectDone, 30)
- })
+ modalEl.dispatchEvent(keydownTab)
+ setTimeout(expectDone, 30)
+ })
- modal.show()
+ modal.show()
+ })
})
- it('should adjust dialog on resize', done => {
- fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog"></div></div>'
+ it('should adjust dialog on resize', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog"></div></div>'
- const modalEl = fixtureEl.querySelector('.modal')
- const modal = new Modal(modalEl)
+ const modalEl = fixtureEl.querySelector('.modal')
+ const modal = new Modal(modalEl)
- spyOn(modal, '_adjustDialog').and.callThrough()
+ const spy = spyOn(modal, '_adjustDialog').and.callThrough()
- const expectDone = () => {
- expect(modal._adjustDialog).toHaveBeenCalled()
+ const expectDone = () => {
+ expect(spy).toHaveBeenCalled()
- done()
- }
+ resolve()
+ }
- modalEl.addEventListener('shown.bs.modal', () => {
- const resizeEvent = createEvent('resize')
+ modalEl.addEventListener('shown.bs.modal', () => {
+ const resizeEvent = createEvent('resize')
- window.dispatchEvent(resizeEvent)
- setTimeout(expectDone, 10)
- })
+ window.dispatchEvent(resizeEvent)
+ setTimeout(expectDone, 10)
+ })
- modal.show()
+ modal.show()
+ })
})
- it('should not close modal when clicking on modal-content', done => {
- fixtureEl.innerHTML = [
- '<div class="modal">',
- ' <div class="modal-dialog">',
- ' <div class="modal-content"></div>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should not close modal when clicking on modal-content', () => {
+ return new Promise((resolve, reject) => {
+ fixtureEl.innerHTML = [
+ '<div class="modal">',
+ ' <div class="modal-dialog">',
+ ' <div class="modal-content"></div>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const modalEl = fixtureEl.querySelector('.modal')
- const modal = new Modal(modalEl)
+ const modalEl = fixtureEl.querySelector('.modal')
+ const modal = new Modal(modalEl)
- const shownCallback = () => {
- setTimeout(() => {
- expect(modal._isShown).toEqual(true)
- done()
- }, 10)
- }
-
- modalEl.addEventListener('shown.bs.modal', () => {
- fixtureEl.querySelector('.modal-dialog').click()
- fixtureEl.querySelector('.modal-content').click()
- shownCallback()
- })
-
- modalEl.addEventListener('hidden.bs.modal', () => {
- throw new Error('Should not hide a modal')
- })
+ const shownCallback = () => {
+ setTimeout(() => {
+ expect(modal._isShown).toEqual(true)
+ resolve()
+ }, 10)
+ }
- modal.show()
- })
+ modalEl.addEventListener('shown.bs.modal', () => {
+ fixtureEl.querySelector('.modal-dialog').click()
+ fixtureEl.querySelector('.modal-content').click()
+ shownCallback()
+ })
- it('should not close modal when clicking outside of modal-content if backdrop = false', done => {
- fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog"></div></div>'
+ modalEl.addEventListener('hidden.bs.modal', () => {
+ reject(new Error('Should not hide a modal'))
+ })
- const modalEl = fixtureEl.querySelector('.modal')
- const modal = new Modal(modalEl, {
- backdrop: false
+ modal.show()
})
+ })
- const shownCallback = () => {
- setTimeout(() => {
- expect(modal._isShown).toEqual(true)
- done()
- }, 10)
- }
+ it('should not close modal when clicking outside of modal-content if backdrop = false', () => {
+ return new Promise((resolve, reject) => {
+ fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog"></div></div>'
- modalEl.addEventListener('shown.bs.modal', () => {
- modalEl.click()
- shownCallback()
- })
+ const modalEl = fixtureEl.querySelector('.modal')
+ const modal = new Modal(modalEl, {
+ backdrop: false
+ })
- modalEl.addEventListener('hidden.bs.modal', () => {
- throw new Error('Should not hide a modal')
- })
+ const shownCallback = () => {
+ setTimeout(() => {
+ expect(modal._isShown).toBeTrue()
+ resolve()
+ }, 10)
+ }
- modal.show()
- })
+ modalEl.addEventListener('shown.bs.modal', () => {
+ modalEl.click()
+ shownCallback()
+ })
- it('should not close modal when clicking outside of modal-content if backdrop = static', done => {
- fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog"></div></div>'
+ modalEl.addEventListener('hidden.bs.modal', () => {
+ reject(new Error('Should not hide a modal'))
+ })
- const modalEl = fixtureEl.querySelector('.modal')
- const modal = new Modal(modalEl, {
- backdrop: 'static'
+ modal.show()
})
+ })
- const shownCallback = () => {
- setTimeout(() => {
- expect(modal._isShown).toEqual(true)
- done()
- }, 10)
- }
+ it('should not close modal when clicking outside of modal-content if backdrop = static', () => {
+ return new Promise((resolve, reject) => {
+ fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog"></div></div>'
- modalEl.addEventListener('shown.bs.modal', () => {
- modalEl.click()
- shownCallback()
- })
+ const modalEl = fixtureEl.querySelector('.modal')
+ const modal = new Modal(modalEl, {
+ backdrop: 'static'
+ })
- modalEl.addEventListener('hidden.bs.modal', () => {
- throw new Error('Should not hide a modal')
- })
+ const shownCallback = () => {
+ setTimeout(() => {
+ expect(modal._isShown).toBeTrue()
+ resolve()
+ }, 10)
+ }
- modal.show()
- })
+ modalEl.addEventListener('shown.bs.modal', () => {
+ modalEl.click()
+ shownCallback()
+ })
- it('should close modal when escape key is pressed with keyboard = true and backdrop is static', done => {
- fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog"></div></div>'
+ modalEl.addEventListener('hidden.bs.modal', () => {
+ reject(new Error('Should not hide a modal'))
+ })
- const modalEl = fixtureEl.querySelector('.modal')
- const modal = new Modal(modalEl, {
- backdrop: 'static',
- keyboard: true
+ modal.show()
})
+ })
+ it('should close modal when escape key is pressed with keyboard = true and backdrop is static', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog"></div></div>'
+
+ const modalEl = fixtureEl.querySelector('.modal')
+ const modal = new Modal(modalEl, {
+ backdrop: 'static',
+ keyboard: true
+ })
- const shownCallback = () => {
- setTimeout(() => {
- expect(modal._isShown).toEqual(false)
- done()
- }, 10)
- }
+ const shownCallback = () => {
+ setTimeout(() => {
+ expect(modal._isShown).toBeFalse()
+ resolve()
+ }, 10)
+ }
- modalEl.addEventListener('shown.bs.modal', () => {
- const keydownEscape = createEvent('keydown')
- keydownEscape.key = 'Escape'
+ modalEl.addEventListener('shown.bs.modal', () => {
+ const keydownEscape = createEvent('keydown')
+ keydownEscape.key = 'Escape'
- modalEl.dispatchEvent(keydownEscape)
- shownCallback()
- })
+ modalEl.dispatchEvent(keydownEscape)
+ shownCallback()
+ })
- modal.show()
+ modal.show()
+ })
})
- it('should not close modal when escape key is pressed with keyboard = false', done => {
- fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog"></div></div>'
+ it('should not close modal when escape key is pressed with keyboard = false', () => {
+ return new Promise((resolve, reject) => {
+ fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog"></div></div>'
- const modalEl = fixtureEl.querySelector('.modal')
- const modal = new Modal(modalEl, {
- keyboard: false
- })
+ const modalEl = fixtureEl.querySelector('.modal')
+ const modal = new Modal(modalEl, {
+ keyboard: false
+ })
- const shownCallback = () => {
- setTimeout(() => {
- expect(modal._isShown).toEqual(true)
- done()
- }, 10)
- }
+ const shownCallback = () => {
+ setTimeout(() => {
+ expect(modal._isShown).toBeTrue()
+ resolve()
+ }, 10)
+ }
- modalEl.addEventListener('shown.bs.modal', () => {
- const keydownEscape = createEvent('keydown')
- keydownEscape.key = 'Escape'
+ modalEl.addEventListener('shown.bs.modal', () => {
+ const keydownEscape = createEvent('keydown')
+ keydownEscape.key = 'Escape'
- modalEl.dispatchEvent(keydownEscape)
- shownCallback()
- })
+ modalEl.dispatchEvent(keydownEscape)
+ shownCallback()
+ })
- modalEl.addEventListener('hidden.bs.modal', () => {
- throw new Error('Should not hide a modal')
- })
+ modalEl.addEventListener('hidden.bs.modal', () => {
+ reject(new Error('Should not hide a modal'))
+ })
- modal.show()
+ modal.show()
+ })
})
- it('should not overflow when clicking outside of modal-content if backdrop = static', done => {
- fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog" style="transition-duration: 20ms;"></div></div>'
+ it('should not overflow when clicking outside of modal-content if backdrop = static', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog" style="transition-duration: 20ms;"></div></div>'
- const modalEl = fixtureEl.querySelector('.modal')
- const modal = new Modal(modalEl, {
- backdrop: 'static'
- })
+ const modalEl = fixtureEl.querySelector('.modal')
+ const modal = new Modal(modalEl, {
+ backdrop: 'static'
+ })
- modalEl.addEventListener('shown.bs.modal', () => {
- modalEl.click()
- setTimeout(() => {
- expect(modalEl.clientHeight).toEqual(modalEl.scrollHeight)
- done()
- }, 20)
- })
+ modalEl.addEventListener('shown.bs.modal', () => {
+ modalEl.click()
+ setTimeout(() => {
+ expect(modalEl.clientHeight).toEqual(modalEl.scrollHeight)
+ resolve()
+ }, 20)
+ })
- modal.show()
+ modal.show()
+ })
})
- it('should not queue multiple callbacks when clicking outside of modal-content and backdrop = static', done => {
- fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog" style="transition-duration: 50ms;"></div></div>'
+ it('should not queue multiple callbacks when clicking outside of modal-content and backdrop = static', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog" style="transition-duration: 50ms;"></div></div>'
- const modalEl = fixtureEl.querySelector('.modal')
- const modal = new Modal(modalEl, {
- backdrop: 'static'
- })
+ const modalEl = fixtureEl.querySelector('.modal')
+ const modal = new Modal(modalEl, {
+ backdrop: 'static'
+ })
- modalEl.addEventListener('shown.bs.modal', () => {
- const spy = spyOn(modal, '_queueCallback').and.callThrough()
+ modalEl.addEventListener('shown.bs.modal', () => {
+ const spy = spyOn(modal, '_queueCallback').and.callThrough()
+ const mouseDown = createEvent('mousedown')
- modalEl.click()
- modalEl.click()
+ modalEl.dispatchEvent(mouseDown)
+ modalEl.click()
+ modalEl.dispatchEvent(mouseDown)
+ modalEl.click()
- setTimeout(() => {
- expect(spy).toHaveBeenCalledTimes(1)
- done()
- }, 20)
- })
+ setTimeout(() => {
+ expect(spy).toHaveBeenCalledTimes(1)
+ resolve()
+ }, 20)
+ })
- modal.show()
+ modal.show()
+ })
})
- it('should trap focus', done => {
- fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog"></div></div>'
+ it('should trap focus', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog"></div></div>'
- const modalEl = fixtureEl.querySelector('.modal')
- const modal = new Modal(modalEl)
+ const modalEl = fixtureEl.querySelector('.modal')
+ const modal = new Modal(modalEl)
- spyOn(modal._focustrap, 'activate').and.callThrough()
+ const spy = spyOn(modal._focustrap, 'activate').and.callThrough()
- modalEl.addEventListener('shown.bs.modal', () => {
- expect(modal._focustrap.activate).toHaveBeenCalled()
- done()
- })
+ modalEl.addEventListener('shown.bs.modal', () => {
+ expect(spy).toHaveBeenCalled()
+ resolve()
+ })
- modal.show()
+ modal.show()
+ })
})
})
describe('hide', () => {
- it('should hide a modal', done => {
- fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog"></div></div>'
+ it('should hide a modal', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog"></div></div>'
- const modalEl = fixtureEl.querySelector('.modal')
- const modal = new Modal(modalEl)
- const backdropSpy = spyOn(modal._backdrop, 'hide').and.callThrough()
+ const modalEl = fixtureEl.querySelector('.modal')
+ const modal = new Modal(modalEl)
+ const backdropSpy = spyOn(modal._backdrop, 'hide').and.callThrough()
- modalEl.addEventListener('shown.bs.modal', () => {
- modal.hide()
- })
+ modalEl.addEventListener('shown.bs.modal', () => {
+ modal.hide()
+ })
- modalEl.addEventListener('hide.bs.modal', event => {
- expect(event).toBeDefined()
- })
+ modalEl.addEventListener('hide.bs.modal', event => {
+ expect(event).toBeDefined()
+ })
- modalEl.addEventListener('hidden.bs.modal', () => {
- expect(modalEl.getAttribute('aria-modal')).toBeNull()
- expect(modalEl.getAttribute('role')).toBeNull()
- expect(modalEl.getAttribute('aria-hidden')).toEqual('true')
- expect(modalEl.style.display).toEqual('none')
- expect(backdropSpy).toHaveBeenCalled()
- done()
- })
+ modalEl.addEventListener('hidden.bs.modal', () => {
+ expect(modalEl.getAttribute('aria-modal')).toBeNull()
+ expect(modalEl.getAttribute('role')).toBeNull()
+ expect(modalEl.getAttribute('aria-hidden')).toEqual('true')
+ expect(modalEl.style.display).toEqual('none')
+ expect(backdropSpy).toHaveBeenCalled()
+ resolve()
+ })
- modal.show()
+ modal.show()
+ })
})
- it('should close modal when clicking outside of modal-content', done => {
- fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog"></div></div>'
+ it('should close modal when clicking outside of modal-content', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog"></div></div>'
- const modalEl = fixtureEl.querySelector('.modal')
- const modal = new Modal(modalEl)
+ const modalEl = fixtureEl.querySelector('.modal')
+ const dialogEl = modalEl.querySelector('.modal-dialog')
+ const modal = new Modal(modalEl)
- modalEl.addEventListener('shown.bs.modal', () => {
- modalEl.click()
- })
+ const spy = spyOn(modal, 'hide')
+
+ modalEl.addEventListener('shown.bs.modal', () => {
+ const mouseDown = createEvent('mousedown')
+
+ dialogEl.dispatchEvent(mouseDown)
+ modalEl.click()
+ expect(spy).not.toHaveBeenCalled()
+
+ modalEl.dispatchEvent(mouseDown)
+ modalEl.click()
+ expect(spy).toHaveBeenCalled()
+ resolve()
+ })
- modalEl.addEventListener('hidden.bs.modal', () => {
- expect(modalEl.getAttribute('aria-modal')).toBeNull()
- expect(modalEl.getAttribute('role')).toBeNull()
- expect(modalEl.getAttribute('aria-hidden')).toEqual('true')
- expect(modalEl.style.display).toEqual('none')
- expect(document.querySelector('.modal-backdrop')).toBeNull()
- done()
+ modal.show()
})
+ })
- modal.show()
+ it('should not close modal when clicking on an element removed from modal content', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="modal">',
+ ' <div class="modal-dialog">',
+ ' <button class="btn">BTN</button>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const modalEl = fixtureEl.querySelector('.modal')
+ const buttonEl = modalEl.querySelector('.btn')
+ const modal = new Modal(modalEl)
+
+ const spy = spyOn(modal, 'hide')
+ buttonEl.addEventListener('click', () => {
+ buttonEl.remove()
+ })
+
+ modalEl.addEventListener('shown.bs.modal', () => {
+ modalEl.dispatchEvent(createEvent('mousedown'))
+ buttonEl.click()
+ expect(spy).not.toHaveBeenCalled()
+ resolve()
+ })
+
+ modal.show()
+ })
})
it('should do nothing is the modal is not shown', () => {
@@ -707,52 +789,56 @@ describe('Modal', () => {
expect().nothing()
})
- it('should not hide a modal if hide is prevented', done => {
- fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog"></div></div>'
+ it('should not hide a modal if hide is prevented', () => {
+ return new Promise((resolve, reject) => {
+ fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog"></div></div>'
- const modalEl = fixtureEl.querySelector('.modal')
- const modal = new Modal(modalEl)
+ const modalEl = fixtureEl.querySelector('.modal')
+ const modal = new Modal(modalEl)
- modalEl.addEventListener('shown.bs.modal', () => {
- modal.hide()
- })
+ modalEl.addEventListener('shown.bs.modal', () => {
+ modal.hide()
+ })
- const hideCallback = () => {
- setTimeout(() => {
- expect(modal._isShown).toEqual(true)
- done()
- }, 10)
- }
+ const hideCallback = () => {
+ setTimeout(() => {
+ expect(modal._isShown).toBeTrue()
+ resolve()
+ }, 10)
+ }
- modalEl.addEventListener('hide.bs.modal', event => {
- event.preventDefault()
- hideCallback()
- })
+ modalEl.addEventListener('hide.bs.modal', event => {
+ event.preventDefault()
+ hideCallback()
+ })
- modalEl.addEventListener('hidden.bs.modal', () => {
- throw new Error('should not trigger hidden')
- })
+ modalEl.addEventListener('hidden.bs.modal', () => {
+ reject(new Error('should not trigger hidden'))
+ })
- modal.show()
+ modal.show()
+ })
})
- it('should release focus trap', done => {
- fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog"></div></div>'
+ it('should release focus trap', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog"></div></div>'
- const modalEl = fixtureEl.querySelector('.modal')
- const modal = new Modal(modalEl)
- spyOn(modal._focustrap, 'deactivate').and.callThrough()
+ const modalEl = fixtureEl.querySelector('.modal')
+ const modal = new Modal(modalEl)
+ const spy = spyOn(modal._focustrap, 'deactivate').and.callThrough()
- modalEl.addEventListener('shown.bs.modal', () => {
- modal.hide()
- })
+ modalEl.addEventListener('shown.bs.modal', () => {
+ modal.hide()
+ })
- modalEl.addEventListener('hidden.bs.modal', () => {
- expect(modal._focustrap.deactivate).toHaveBeenCalled()
- done()
- })
+ modalEl.addEventListener('hidden.bs.modal', () => {
+ expect(spy).toHaveBeenCalled()
+ resolve()
+ })
- modal.show()
+ modal.show()
+ })
})
})
@@ -763,17 +849,17 @@ describe('Modal', () => {
const modalEl = fixtureEl.querySelector('.modal')
const modal = new Modal(modalEl)
const focustrap = modal._focustrap
- spyOn(focustrap, 'deactivate').and.callThrough()
+ const spyDeactivate = spyOn(focustrap, 'deactivate').and.callThrough()
expect(Modal.getInstance(modalEl)).toEqual(modal)
- spyOn(EventHandler, 'off')
+ const spyOff = spyOn(EventHandler, 'off')
modal.dispose()
expect(Modal.getInstance(modalEl)).toBeNull()
- expect(EventHandler.off).toHaveBeenCalledTimes(3)
- expect(focustrap.deactivate).toHaveBeenCalled()
+ expect(spyOff).toHaveBeenCalledTimes(3)
+ expect(spyDeactivate).toHaveBeenCalled()
})
})
@@ -784,255 +870,298 @@ describe('Modal', () => {
const modalEl = fixtureEl.querySelector('.modal')
const modal = new Modal(modalEl)
- spyOn(modal, '_adjustDialog')
+ const spy = spyOn(modal, '_adjustDialog')
modal.handleUpdate()
- expect(modal._adjustDialog).toHaveBeenCalled()
+ expect(spy).toHaveBeenCalled()
})
})
describe('data-api', () => {
- it('should toggle modal', done => {
- fixtureEl.innerHTML = [
- '<button type="button" data-bs-toggle="modal" data-bs-target="#exampleModal"></button>',
- '<div id="exampleModal" class="modal"><div class="modal-dialog"></div></div>'
- ].join('')
+ it('should toggle modal', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<button type="button" data-bs-toggle="modal" data-bs-target="#exampleModal"></button>',
+ '<div id="exampleModal" class="modal"><div class="modal-dialog"></div></div>'
+ ].join('')
+
+ const modalEl = fixtureEl.querySelector('.modal')
+ const trigger = fixtureEl.querySelector('[data-bs-toggle="modal"]')
+
+ modalEl.addEventListener('shown.bs.modal', () => {
+ expect(modalEl.getAttribute('aria-modal')).toEqual('true')
+ expect(modalEl.getAttribute('role')).toEqual('dialog')
+ expect(modalEl.getAttribute('aria-hidden')).toBeNull()
+ expect(modalEl.style.display).toEqual('block')
+ expect(document.querySelector('.modal-backdrop')).not.toBeNull()
+ setTimeout(() => trigger.click(), 10)
+ })
- const modalEl = fixtureEl.querySelector('.modal')
- const trigger = fixtureEl.querySelector('[data-bs-toggle="modal"]')
-
- modalEl.addEventListener('shown.bs.modal', () => {
- expect(modalEl.getAttribute('aria-modal')).toEqual('true')
- expect(modalEl.getAttribute('role')).toEqual('dialog')
- expect(modalEl.getAttribute('aria-hidden')).toBeNull()
- expect(modalEl.style.display).toEqual('block')
- expect(document.querySelector('.modal-backdrop')).not.toBeNull()
- setTimeout(() => trigger.click(), 10)
- })
+ modalEl.addEventListener('hidden.bs.modal', () => {
+ expect(modalEl.getAttribute('aria-modal')).toBeNull()
+ expect(modalEl.getAttribute('role')).toBeNull()
+ expect(modalEl.getAttribute('aria-hidden')).toEqual('true')
+ expect(modalEl.style.display).toEqual('none')
+ expect(document.querySelector('.modal-backdrop')).toBeNull()
+ resolve()
+ })
- modalEl.addEventListener('hidden.bs.modal', () => {
- expect(modalEl.getAttribute('aria-modal')).toBeNull()
- expect(modalEl.getAttribute('role')).toBeNull()
- expect(modalEl.getAttribute('aria-hidden')).toEqual('true')
- expect(modalEl.style.display).toEqual('none')
- expect(document.querySelector('.modal-backdrop')).toBeNull()
- done()
+ trigger.click()
})
-
- trigger.click()
})
- it('should not recreate a new modal', done => {
- fixtureEl.innerHTML = [
- '<button type="button" data-bs-toggle="modal" data-bs-target="#exampleModal"></button>',
- '<div id="exampleModal" class="modal"><div class="modal-dialog"></div></div>'
- ].join('')
+ it('should not recreate a new modal', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<button type="button" data-bs-toggle="modal" data-bs-target="#exampleModal"></button>',
+ '<div id="exampleModal" class="modal"><div class="modal-dialog"></div></div>'
+ ].join('')
- const modalEl = fixtureEl.querySelector('.modal')
- const modal = new Modal(modalEl)
- const trigger = fixtureEl.querySelector('[data-bs-toggle="modal"]')
+ const modalEl = fixtureEl.querySelector('.modal')
+ const modal = new Modal(modalEl)
+ const trigger = fixtureEl.querySelector('[data-bs-toggle="modal"]')
- spyOn(modal, 'show').and.callThrough()
+ const spy = spyOn(modal, 'show').and.callThrough()
- modalEl.addEventListener('shown.bs.modal', () => {
- expect(modal.show).toHaveBeenCalled()
- done()
- })
+ modalEl.addEventListener('shown.bs.modal', () => {
+ expect(spy).toHaveBeenCalled()
+ resolve()
+ })
- trigger.click()
+ trigger.click()
+ })
})
- it('should prevent default when the trigger is <a> or <area>', done => {
- fixtureEl.innerHTML = [
- '<a data-bs-toggle="modal" href="#" data-bs-target="#exampleModal"></a>',
- '<div id="exampleModal" class="modal"><div class="modal-dialog"></div></div>'
- ].join('')
+ it('should prevent default when the trigger is <a> or <area>', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<a data-bs-toggle="modal" href="#" data-bs-target="#exampleModal"></a>',
+ '<div id="exampleModal" class="modal"><div class="modal-dialog"></div></div>'
+ ].join('')
+
+ const modalEl = fixtureEl.querySelector('.modal')
+ const trigger = fixtureEl.querySelector('[data-bs-toggle="modal"]')
+
+ const spy = spyOn(Event.prototype, 'preventDefault').and.callThrough()
+
+ modalEl.addEventListener('shown.bs.modal', () => {
+ expect(modalEl.getAttribute('aria-modal')).toEqual('true')
+ expect(modalEl.getAttribute('role')).toEqual('dialog')
+ expect(modalEl.getAttribute('aria-hidden')).toBeNull()
+ expect(modalEl.style.display).toEqual('block')
+ expect(document.querySelector('.modal-backdrop')).not.toBeNull()
+ expect(spy).toHaveBeenCalled()
+ resolve()
+ })
- const modalEl = fixtureEl.querySelector('.modal')
- const trigger = fixtureEl.querySelector('[data-bs-toggle="modal"]')
-
- spyOn(Event.prototype, 'preventDefault').and.callThrough()
-
- modalEl.addEventListener('shown.bs.modal', () => {
- expect(modalEl.getAttribute('aria-modal')).toEqual('true')
- expect(modalEl.getAttribute('role')).toEqual('dialog')
- expect(modalEl.getAttribute('aria-hidden')).toBeNull()
- expect(modalEl.style.display).toEqual('block')
- expect(document.querySelector('.modal-backdrop')).not.toBeNull()
- expect(Event.prototype.preventDefault).toHaveBeenCalled()
- done()
+ trigger.click()
})
-
- trigger.click()
})
- it('should focus the trigger on hide', done => {
- fixtureEl.innerHTML = [
- '<a data-bs-toggle="modal" href="#" data-bs-target="#exampleModal"></a>',
- '<div id="exampleModal" class="modal"><div class="modal-dialog"></div></div>'
- ].join('')
+ it('should focus the trigger on hide', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<a data-bs-toggle="modal" href="#" data-bs-target="#exampleModal"></a>',
+ '<div id="exampleModal" class="modal"><div class="modal-dialog"></div></div>'
+ ].join('')
- const modalEl = fixtureEl.querySelector('.modal')
- const trigger = fixtureEl.querySelector('[data-bs-toggle="modal"]')
+ const modalEl = fixtureEl.querySelector('.modal')
+ const trigger = fixtureEl.querySelector('[data-bs-toggle="modal"]')
- spyOn(trigger, 'focus')
+ const spy = spyOn(trigger, 'focus')
- modalEl.addEventListener('shown.bs.modal', () => {
- const modal = Modal.getInstance(modalEl)
+ modalEl.addEventListener('shown.bs.modal', () => {
+ const modal = Modal.getInstance(modalEl)
- modal.hide()
- })
+ modal.hide()
+ })
- const hideListener = () => {
- setTimeout(() => {
- expect(trigger.focus).toHaveBeenCalled()
- done()
- }, 20)
- }
+ const hideListener = () => {
+ setTimeout(() => {
+ expect(spy).toHaveBeenCalled()
+ resolve()
+ }, 20)
+ }
- modalEl.addEventListener('hidden.bs.modal', () => {
- hideListener()
+ modalEl.addEventListener('hidden.bs.modal', () => {
+ hideListener()
+ })
+
+ trigger.click()
})
+ })
+
+ it('should open modal, having special characters in its id', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#j_id22:exampleModal">',
+ ' Launch demo modal',
+ '</button>',
+ '<div class="modal fade" id="j_id22:exampleModal" aria-labelledby="exampleModalLabel" aria-hidden="true">',
+ ' <div class="modal-dialog">',
+ ' <div class="modal-content">',
+ ' <div class="modal-body">',
+ ' <p>modal body</p>',
+ ' </div>',
+ ' </div>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const modalEl = fixtureEl.querySelector('.modal')
+ const trigger = fixtureEl.querySelector('[data-bs-toggle="modal"]')
+
+ modalEl.addEventListener('shown.bs.modal', () => {
+ resolve()
+ })
- trigger.click()
+ trigger.click()
+ })
})
- it('should not prevent default when a click occurred on data-bs-dismiss="modal" where tagName is DIFFERENT than <a> or <area>', done => {
- fixtureEl.innerHTML = [
- '<div class="modal">',
- ' <div class="modal-dialog">',
- ' <button type="button" data-bs-dismiss="modal"></button>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should not prevent default when a click occurred on data-bs-dismiss="modal" where tagName is DIFFERENT than <a> or <area>', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="modal">',
+ ' <div class="modal-dialog">',
+ ' <button type="button" data-bs-dismiss="modal"></button>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const modalEl = fixtureEl.querySelector('.modal')
- const btnClose = fixtureEl.querySelector('button[data-bs-dismiss="modal"]')
- const modal = new Modal(modalEl)
+ const modalEl = fixtureEl.querySelector('.modal')
+ const btnClose = fixtureEl.querySelector('button[data-bs-dismiss="modal"]')
+ const modal = new Modal(modalEl)
- spyOn(Event.prototype, 'preventDefault').and.callThrough()
+ const spy = spyOn(Event.prototype, 'preventDefault').and.callThrough()
- modalEl.addEventListener('shown.bs.modal', () => {
- btnClose.click()
- })
+ modalEl.addEventListener('shown.bs.modal', () => {
+ btnClose.click()
+ })
- modalEl.addEventListener('hidden.bs.modal', () => {
- expect(Event.prototype.preventDefault).not.toHaveBeenCalled()
- done()
- })
+ modalEl.addEventListener('hidden.bs.modal', () => {
+ expect(spy).not.toHaveBeenCalled()
+ resolve()
+ })
- modal.show()
+ modal.show()
+ })
})
- it('should prevent default when a click occurred on data-bs-dismiss="modal" where tagName is <a> or <area>', done => {
- fixtureEl.innerHTML = [
- '<div class="modal">',
- ' <div class="modal-dialog">',
- ' <a type="button" data-bs-dismiss="modal"></a>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should prevent default when a click occurred on data-bs-dismiss="modal" where tagName is <a> or <area>', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="modal">',
+ ' <div class="modal-dialog">',
+ ' <a type="button" data-bs-dismiss="modal"></a>',
+ ' </div>',
+ '</div>'
+ ].join('')
- const modalEl = fixtureEl.querySelector('.modal')
- const btnClose = fixtureEl.querySelector('a[data-bs-dismiss="modal"]')
- const modal = new Modal(modalEl)
+ const modalEl = fixtureEl.querySelector('.modal')
+ const btnClose = fixtureEl.querySelector('a[data-bs-dismiss="modal"]')
+ const modal = new Modal(modalEl)
- spyOn(Event.prototype, 'preventDefault').and.callThrough()
+ const spy = spyOn(Event.prototype, 'preventDefault').and.callThrough()
- modalEl.addEventListener('shown.bs.modal', () => {
- btnClose.click()
- })
+ modalEl.addEventListener('shown.bs.modal', () => {
+ btnClose.click()
+ })
- modalEl.addEventListener('hidden.bs.modal', () => {
- expect(Event.prototype.preventDefault).toHaveBeenCalled()
- done()
- })
+ modalEl.addEventListener('hidden.bs.modal', () => {
+ expect(spy).toHaveBeenCalled()
+ resolve()
+ })
- modal.show()
+ modal.show()
+ })
})
+ it('should not focus the trigger if the modal is not visible', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<a data-bs-toggle="modal" href="#" data-bs-target="#exampleModal" style="display: none;"></a>',
+ '<div id="exampleModal" class="modal" style="display: none;"><div class="modal-dialog"></div></div>'
+ ].join('')
- it('should not focus the trigger if the modal is not visible', done => {
- fixtureEl.innerHTML = [
- '<a data-bs-toggle="modal" href="#" data-bs-target="#exampleModal" style="display: none;"></a>',
- '<div id="exampleModal" class="modal" style="display: none;"><div class="modal-dialog"></div></div>'
- ].join('')
+ const modalEl = fixtureEl.querySelector('.modal')
+ const trigger = fixtureEl.querySelector('[data-bs-toggle="modal"]')
- const modalEl = fixtureEl.querySelector('.modal')
- const trigger = fixtureEl.querySelector('[data-bs-toggle="modal"]')
+ const spy = spyOn(trigger, 'focus')
- spyOn(trigger, 'focus')
+ modalEl.addEventListener('shown.bs.modal', () => {
+ const modal = Modal.getInstance(modalEl)
- modalEl.addEventListener('shown.bs.modal', () => {
- const modal = Modal.getInstance(modalEl)
+ modal.hide()
+ })
- modal.hide()
- })
+ const hideListener = () => {
+ setTimeout(() => {
+ expect(spy).not.toHaveBeenCalled()
+ resolve()
+ }, 20)
+ }
- const hideListener = () => {
- setTimeout(() => {
- expect(trigger.focus).not.toHaveBeenCalled()
- done()
- }, 20)
- }
+ modalEl.addEventListener('hidden.bs.modal', () => {
+ hideListener()
+ })
- modalEl.addEventListener('hidden.bs.modal', () => {
- hideListener()
+ trigger.click()
})
-
- trigger.click()
})
+ it('should not focus the trigger if the modal is not shown', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<a data-bs-toggle="modal" href="#" data-bs-target="#exampleModal"></a>',
+ '<div id="exampleModal" class="modal"><div class="modal-dialog"></div></div>'
+ ].join('')
- it('should not focus the trigger if the modal is not shown', done => {
- fixtureEl.innerHTML = [
- '<a data-bs-toggle="modal" href="#" data-bs-target="#exampleModal"></a>',
- '<div id="exampleModal" class="modal"><div class="modal-dialog"></div></div>'
- ].join('')
+ const modalEl = fixtureEl.querySelector('.modal')
+ const trigger = fixtureEl.querySelector('[data-bs-toggle="modal"]')
- const modalEl = fixtureEl.querySelector('.modal')
- const trigger = fixtureEl.querySelector('[data-bs-toggle="modal"]')
+ const spy = spyOn(trigger, 'focus')
- spyOn(trigger, 'focus')
+ const showListener = () => {
+ setTimeout(() => {
+ expect(spy).not.toHaveBeenCalled()
+ resolve()
+ }, 10)
+ }
- const showListener = () => {
- setTimeout(() => {
- expect(trigger.focus).not.toHaveBeenCalled()
- done()
- }, 10)
- }
+ modalEl.addEventListener('show.bs.modal', event => {
+ event.preventDefault()
+ showListener()
+ })
- modalEl.addEventListener('show.bs.modal', event => {
- event.preventDefault()
- showListener()
+ trigger.click()
})
-
- trigger.click()
})
- it('should call hide first, if another modal is open', done => {
- fixtureEl.innerHTML = [
- '<button data-bs-toggle="modal" data-bs-target="#modal2"></button>',
- '<div id="modal1" class="modal fade"><div class="modal-dialog"></div></div>',
- '<div id="modal2" class="modal"><div class="modal-dialog"></div></div>'
- ].join('')
-
- const trigger2 = fixtureEl.querySelector('button')
- const modalEl1 = document.querySelector('#modal1')
- const modalEl2 = document.querySelector('#modal2')
- const modal1 = new Modal(modalEl1)
-
- modalEl1.addEventListener('shown.bs.modal', () => {
- trigger2.click()
- })
- modalEl1.addEventListener('hidden.bs.modal', () => {
- expect(Modal.getInstance(modalEl2)).not.toBeNull()
- expect(modalEl2.classList.contains('show')).toBeTrue()
- done()
+ it('should call hide first, if another modal is open', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<button data-bs-toggle="modal" data-bs-target="#modal2"></button>',
+ '<div id="modal1" class="modal fade"><div class="modal-dialog"></div></div>',
+ '<div id="modal2" class="modal"><div class="modal-dialog"></div></div>'
+ ].join('')
+
+ const trigger2 = fixtureEl.querySelector('button')
+ const modalEl1 = document.querySelector('#modal1')
+ const modalEl2 = document.querySelector('#modal2')
+ const modal1 = new Modal(modalEl1)
+
+ modalEl1.addEventListener('shown.bs.modal', () => {
+ trigger2.click()
+ })
+ modalEl1.addEventListener('hidden.bs.modal', () => {
+ expect(Modal.getInstance(modalEl2)).not.toBeNull()
+ expect(modalEl2).toHaveClass('show')
+ resolve()
+ })
+ modal1.show()
})
- modal1.show()
})
})
-
describe('jQueryInterface', () => {
it('should create a modal', () => {
fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog"></div></div>'
@@ -1056,12 +1185,12 @@ describe('Modal', () => {
jQueryMock.elements = [div]
jQueryMock.fn.modal.call(jQueryMock, { keyboard: false })
- spyOn(Modal.prototype, 'constructor')
- expect(Modal.prototype.constructor).not.toHaveBeenCalledWith(div, { keyboard: false })
+ const spy = spyOn(Modal.prototype, 'constructor')
+ expect(spy).not.toHaveBeenCalledWith(div, { keyboard: false })
const modal = Modal.getInstance(div)
expect(modal).not.toBeNull()
- expect(modal._config.keyboard).toBe(false)
+ expect(modal._config.keyboard).toBeFalse()
})
it('should not re create a modal', () => {
@@ -1101,11 +1230,11 @@ describe('Modal', () => {
jQueryMock.fn.modal = Modal.jQueryInterface
jQueryMock.elements = [div]
- spyOn(modal, 'show')
+ const spy = spyOn(modal, 'show')
jQueryMock.fn.modal.call(jQueryMock, 'show')
- expect(modal.show).toHaveBeenCalled()
+ expect(spy).toHaveBeenCalled()
})
it('should not call show method', () => {
@@ -1116,11 +1245,11 @@ describe('Modal', () => {
jQueryMock.fn.modal = Modal.jQueryInterface
jQueryMock.elements = [div]
- spyOn(Modal.prototype, 'show')
+ const spy = spyOn(Modal.prototype, 'show')
jQueryMock.fn.modal.call(jQueryMock)
- expect(Modal.prototype.show).not.toHaveBeenCalled()
+ expect(spy).not.toHaveBeenCalled()
})
})
@@ -1161,7 +1290,7 @@ describe('Modal', () => {
const div = fixtureEl.querySelector('div')
- expect(Modal.getInstance(div)).toEqual(null)
+ expect(Modal.getInstance(div)).toBeNull()
expect(Modal.getOrCreateInstance(div)).toBeInstanceOf(Modal)
})
@@ -1170,13 +1299,13 @@ describe('Modal', () => {
const div = fixtureEl.querySelector('div')
- expect(Modal.getInstance(div)).toEqual(null)
+ expect(Modal.getInstance(div)).toBeNull()
const modal = Modal.getOrCreateInstance(div, {
backdrop: true
})
expect(modal).toBeInstanceOf(Modal)
- expect(modal._config.backdrop).toEqual(true)
+ expect(modal._config.backdrop).toBeTrue()
})
it('should return the instance when exists without given configuration', () => {
@@ -1194,7 +1323,7 @@ describe('Modal', () => {
expect(modal).toBeInstanceOf(Modal)
expect(modal2).toEqual(modal)
- expect(modal2._config.backdrop).toEqual(true)
+ expect(modal2._config.backdrop).toBeTrue()
})
})
})
diff --git a/js/tests/unit/offcanvas.spec.js b/js/tests/unit/offcanvas.spec.js
index 3eda50520..3b6c98c10 100644
--- a/js/tests/unit/offcanvas.spec.js
+++ b/js/tests/unit/offcanvas.spec.js
@@ -1,8 +1,10 @@
-import Offcanvas from '../../src/offcanvas'
-import EventHandler from '../../src/dom/event-handler'
-import { clearBodyAndDocument, clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture'
-import { isVisible } from '../../src/util/index'
-import ScrollBarHelper from '../../src/util/scrollbar'
+import EventHandler from '../../src/dom/event-handler.js'
+import Offcanvas from '../../src/offcanvas.js'
+import { isVisible } from '../../src/util/index.js'
+import ScrollBarHelper from '../../src/util/scrollbar.js'
+import {
+ clearBodyAndDocument, clearFixture, createEvent, getFixture, jQueryMock
+} from '../helpers/fixture.js'
describe('Offcanvas', () => {
let fixtureEl
@@ -51,12 +53,12 @@ describe('Offcanvas', () => {
const closeEl = fixtureEl.querySelector('a')
const offCanvas = new Offcanvas(offCanvasEl)
- spyOn(offCanvas, 'hide')
+ const spy = spyOn(offCanvas, 'hide')
closeEl.click()
- expect(offCanvas._config.keyboard).toBe(true)
- expect(offCanvas.hide).toHaveBeenCalled()
+ expect(offCanvas._config.keyboard).toBeTrue()
+ expect(spy).toHaveBeenCalled()
})
it('should hide if esc is pressed', () => {
@@ -67,11 +69,26 @@ describe('Offcanvas', () => {
const keyDownEsc = createEvent('keydown')
keyDownEsc.key = 'Escape'
- spyOn(offCanvas, 'hide')
+ const spy = spyOn(offCanvas, 'hide')
offCanvasEl.dispatchEvent(keyDownEsc)
- expect(offCanvas.hide).toHaveBeenCalled()
+ expect(spy).toHaveBeenCalled()
+ })
+
+ it('should hide if esc is pressed and backdrop is static', () => {
+ fixtureEl.innerHTML = '<div class="offcanvas"></div>'
+
+ const offCanvasEl = fixtureEl.querySelector('.offcanvas')
+ const offCanvas = new Offcanvas(offCanvasEl, { backdrop: 'static' })
+ const keyDownEsc = createEvent('keydown')
+ keyDownEsc.key = 'Escape'
+
+ const spy = spyOn(offCanvas, 'hide')
+
+ offCanvasEl.dispatchEvent(keyDownEsc)
+
+ expect(spy).toHaveBeenCalled()
})
it('should not hide if esc is not pressed', () => {
@@ -82,66 +99,115 @@ describe('Offcanvas', () => {
const keydownTab = createEvent('keydown')
keydownTab.key = 'Tab'
- spyOn(offCanvas, 'hide')
+ const spy = spyOn(offCanvas, 'hide')
- document.dispatchEvent(keydownTab)
+ offCanvasEl.dispatchEvent(keydownTab)
- expect(offCanvas.hide).not.toHaveBeenCalled()
+ expect(spy).not.toHaveBeenCalled()
})
it('should not hide if esc is pressed but with keyboard = false', () => {
- fixtureEl.innerHTML = '<div class="offcanvas"></div>'
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div class="offcanvas"></div>'
- const offCanvasEl = fixtureEl.querySelector('.offcanvas')
- const offCanvas = new Offcanvas(offCanvasEl, { keyboard: false })
- const keyDownEsc = createEvent('keydown')
- keyDownEsc.key = 'Escape'
+ const offCanvasEl = fixtureEl.querySelector('.offcanvas')
+ const offCanvas = new Offcanvas(offCanvasEl, { keyboard: false })
+ const keyDownEsc = createEvent('keydown')
+ keyDownEsc.key = 'Escape'
+
+ const spy = spyOn(offCanvas, 'hide')
+ const hidePreventedSpy = jasmine.createSpy('hidePrevented')
+ offCanvasEl.addEventListener('hidePrevented.bs.offcanvas', hidePreventedSpy)
+
+ offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
+ expect(offCanvas._config.keyboard).toBeFalse()
+ offCanvasEl.dispatchEvent(keyDownEsc)
+
+ expect(hidePreventedSpy).toHaveBeenCalled()
+ expect(spy).not.toHaveBeenCalled()
+ resolve()
+ })
+
+ offCanvas.show()
+ })
+ })
- spyOn(offCanvas, 'hide')
+ it('should not hide if user clicks on static backdrop', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div class="offcanvas"></div>'
- document.dispatchEvent(keyDownEsc)
+ const offCanvasEl = fixtureEl.querySelector('div')
+ const offCanvas = new Offcanvas(offCanvasEl, { backdrop: 'static' })
- expect(offCanvas._config.keyboard).toBe(false)
- expect(offCanvas.hide).not.toHaveBeenCalled()
+ const clickEvent = new Event('mousedown', { bubbles: true, cancelable: true })
+ const spyClick = spyOn(offCanvas._backdrop._config, 'clickCallback').and.callThrough()
+ const spyHide = spyOn(offCanvas._backdrop, 'hide').and.callThrough()
+ const hidePreventedSpy = jasmine.createSpy('hidePrevented')
+ offCanvasEl.addEventListener('hidePrevented.bs.offcanvas', hidePreventedSpy)
+
+ offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
+ expect(spyClick).toEqual(jasmine.any(Function))
+
+ offCanvas._backdrop._getElement().dispatchEvent(clickEvent)
+ expect(hidePreventedSpy).toHaveBeenCalled()
+ expect(spyHide).not.toHaveBeenCalled()
+ resolve()
+ })
+
+ offCanvas.show()
+ })
+ })
+
+ it('should call `hide` on resize, if element\'s position is not fixed any more', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div class="offcanvas-lg"></div>'
+
+ const offCanvasEl = fixtureEl.querySelector('div')
+ const offCanvas = new Offcanvas(offCanvasEl)
+
+ const spy = spyOn(offCanvas, 'hide').and.callThrough()
+
+ offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
+ const resizeEvent = createEvent('resize')
+ offCanvasEl.style.removeProperty('position')
+
+ window.dispatchEvent(resizeEvent)
+ expect(spy).toHaveBeenCalled()
+ resolve()
+ })
+
+ offCanvas.show()
+ })
})
})
describe('config', () => {
it('should have default values', () => {
- fixtureEl.innerHTML = [
- '<div class="offcanvas">',
- '</div>'
- ].join('')
+ fixtureEl.innerHTML = '<div class="offcanvas"></div>'
const offCanvasEl = fixtureEl.querySelector('.offcanvas')
const offCanvas = new Offcanvas(offCanvasEl)
- expect(offCanvas._config.backdrop).toEqual(true)
- expect(offCanvas._backdrop._config.isVisible).toEqual(true)
- expect(offCanvas._config.keyboard).toEqual(true)
- expect(offCanvas._config.scroll).toEqual(false)
+ expect(offCanvas._config.backdrop).toBeTrue()
+ expect(offCanvas._backdrop._config.isVisible).toBeTrue()
+ expect(offCanvas._config.keyboard).toBeTrue()
+ expect(offCanvas._config.scroll).toBeFalse()
})
it('should read data attributes and override default config', () => {
- fixtureEl.innerHTML = [
- '<div class="offcanvas" data-bs-scroll="true" data-bs-backdrop="false" data-bs-keyboard="false">',
- '</div>'
- ].join('')
+ fixtureEl.innerHTML = '<div class="offcanvas" data-bs-scroll="true" data-bs-backdrop="false" data-bs-keyboard="false"></div>'
const offCanvasEl = fixtureEl.querySelector('.offcanvas')
const offCanvas = new Offcanvas(offCanvasEl)
- expect(offCanvas._config.backdrop).toEqual(false)
- expect(offCanvas._backdrop._config.isVisible).toEqual(false)
- expect(offCanvas._config.keyboard).toEqual(false)
- expect(offCanvas._config.scroll).toEqual(true)
+ expect(offCanvas._config.backdrop).toBeFalse()
+ expect(offCanvas._backdrop._config.isVisible).toBeFalse()
+ expect(offCanvas._config.keyboard).toBeFalse()
+ expect(offCanvas._config.scroll).toBeTrue()
})
it('given a config object must override data attributes', () => {
- fixtureEl.innerHTML = [
- '<div class="offcanvas" data-bs-scroll="true" data-bs-backdrop="false" data-bs-keyboard="false">',
- '</div>'
- ].join('')
+ fixtureEl.innerHTML = '<div class="offcanvas" data-bs-scroll="true" data-bs-backdrop="false" data-bs-keyboard="false"></div>'
const offCanvasEl = fixtureEl.querySelector('.offcanvas')
const offCanvas = new Offcanvas(offCanvasEl, {
@@ -149,90 +215,120 @@ describe('Offcanvas', () => {
keyboard: true,
scroll: false
})
- expect(offCanvas._config.backdrop).toEqual(true)
- expect(offCanvas._config.keyboard).toEqual(true)
- expect(offCanvas._config.scroll).toEqual(false)
+ expect(offCanvas._config.backdrop).toBeTrue()
+ expect(offCanvas._config.keyboard).toBeTrue()
+ expect(offCanvas._config.scroll).toBeFalse()
})
})
- describe('options', () => {
- it('if scroll is enabled, should allow body to scroll while offcanvas is open', done => {
- fixtureEl.innerHTML = '<div class="offcanvas"></div>'
-
- spyOn(ScrollBarHelper.prototype, 'hide').and.callThrough()
- spyOn(ScrollBarHelper.prototype, 'reset').and.callThrough()
- const offCanvasEl = fixtureEl.querySelector('.offcanvas')
- const offCanvas = new Offcanvas(offCanvasEl, { scroll: true })
- offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
- expect(ScrollBarHelper.prototype.hide).not.toHaveBeenCalled()
- offCanvas.hide()
+ describe('options', () => {
+ it('if scroll is enabled, should allow body to scroll while offcanvas is open', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div class="offcanvas"></div>'
+
+ const spyHide = spyOn(ScrollBarHelper.prototype, 'hide').and.callThrough()
+ const spyReset = spyOn(ScrollBarHelper.prototype, 'reset').and.callThrough()
+ const offCanvasEl = fixtureEl.querySelector('.offcanvas')
+ const offCanvas = new Offcanvas(offCanvasEl, { scroll: true })
+
+ offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
+ expect(spyHide).not.toHaveBeenCalled()
+ offCanvas.hide()
+ })
+ offCanvasEl.addEventListener('hidden.bs.offcanvas', () => {
+ expect(spyReset).not.toHaveBeenCalled()
+ resolve()
+ })
+ offCanvas.show()
})
- offCanvasEl.addEventListener('hidden.bs.offcanvas', () => {
- expect(ScrollBarHelper.prototype.reset).not.toHaveBeenCalled()
- done()
+ })
+
+ it('if scroll is disabled, should call ScrollBarHelper to handle scrollBar on body', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div class="offcanvas"></div>'
+
+ const spyHide = spyOn(ScrollBarHelper.prototype, 'hide').and.callThrough()
+ const spyReset = spyOn(ScrollBarHelper.prototype, 'reset').and.callThrough()
+ const offCanvasEl = fixtureEl.querySelector('.offcanvas')
+ const offCanvas = new Offcanvas(offCanvasEl, { scroll: false })
+
+ offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
+ expect(spyHide).toHaveBeenCalled()
+ offCanvas.hide()
+ })
+ offCanvasEl.addEventListener('hidden.bs.offcanvas', () => {
+ expect(spyReset).toHaveBeenCalled()
+ resolve()
+ })
+ offCanvas.show()
})
- offCanvas.show()
})
- it('if scroll is disabled, should call ScrollBarHelper to handle scrollBar on body', done => {
- fixtureEl.innerHTML = '<div class="offcanvas"></div>'
+ it('should hide a shown element if user click on backdrop', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div class="offcanvas"></div>'
- spyOn(ScrollBarHelper.prototype, 'hide').and.callThrough()
- spyOn(ScrollBarHelper.prototype, 'reset').and.callThrough()
- const offCanvasEl = fixtureEl.querySelector('.offcanvas')
- const offCanvas = new Offcanvas(offCanvasEl, { scroll: false })
+ const offCanvasEl = fixtureEl.querySelector('div')
+ const offCanvas = new Offcanvas(offCanvasEl, { backdrop: true })
- offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
- expect(ScrollBarHelper.prototype.hide).toHaveBeenCalled()
- offCanvas.hide()
- })
- offCanvasEl.addEventListener('hidden.bs.offcanvas', () => {
- expect(ScrollBarHelper.prototype.reset).toHaveBeenCalled()
- done()
+ const clickEvent = new Event('mousedown', { bubbles: true, cancelable: true })
+ const spy = spyOn(offCanvas._backdrop._config, 'clickCallback').and.callThrough()
+
+ offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
+ expect(offCanvas._backdrop._config.clickCallback).toEqual(jasmine.any(Function))
+
+ offCanvas._backdrop._getElement().dispatchEvent(clickEvent)
+ })
+
+ offCanvasEl.addEventListener('hidden.bs.offcanvas', () => {
+ expect(spy).toHaveBeenCalled()
+ resolve()
+ })
+
+ offCanvas.show()
})
- offCanvas.show()
})
- it('should hide a shown element if user click on backdrop', done => {
- fixtureEl.innerHTML = '<div class="offcanvas"></div>'
-
- const offCanvasEl = fixtureEl.querySelector('div')
- const offCanvas = new Offcanvas(offCanvasEl, { backdrop: true })
+ it('should not trap focus if scroll is allowed', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div class="offcanvas"></div>'
- const clickEvent = document.createEvent('MouseEvents')
- clickEvent.initEvent('mousedown', true, true)
- spyOn(offCanvas._backdrop._config, 'clickCallback').and.callThrough()
+ const offCanvasEl = fixtureEl.querySelector('.offcanvas')
+ const offCanvas = new Offcanvas(offCanvasEl, {
+ scroll: true,
+ backdrop: false
+ })
- offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
- expect(typeof offCanvas._backdrop._config.clickCallback).toBe('function')
+ const spy = spyOn(offCanvas._focustrap, 'activate').and.callThrough()
- offCanvas._backdrop._getElement().dispatchEvent(clickEvent)
- })
+ offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
+ expect(spy).not.toHaveBeenCalled()
+ resolve()
+ })
- offCanvasEl.addEventListener('hidden.bs.offcanvas', () => {
- expect(offCanvas._backdrop._config.clickCallback).toHaveBeenCalled()
- done()
+ offCanvas.show()
})
-
- offCanvas.show()
})
- it('should not trap focus if scroll is allowed', done => {
- fixtureEl.innerHTML = '<div class="offcanvas"></div>'
+ it('should trap focus if scroll is allowed OR backdrop is enabled', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div class="offcanvas"></div>'
- const offCanvasEl = fixtureEl.querySelector('.offcanvas')
- const offCanvas = new Offcanvas(offCanvasEl, {
- scroll: true
- })
+ const offCanvasEl = fixtureEl.querySelector('.offcanvas')
+ const offCanvas = new Offcanvas(offCanvasEl, {
+ scroll: true,
+ backdrop: true
+ })
- spyOn(offCanvas._focustrap, 'activate').and.callThrough()
+ const spy = spyOn(offCanvas._focustrap, 'activate').and.callThrough()
- offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
- expect(offCanvas._focustrap.activate).not.toHaveBeenCalled()
- done()
- })
+ offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
+ expect(spy).toHaveBeenCalled()
+ resolve()
+ })
- offCanvas.show()
+ offCanvas.show()
+ })
})
})
@@ -243,30 +339,57 @@ describe('Offcanvas', () => {
const offCanvasEl = fixtureEl.querySelector('.offcanvas')
const offCanvas = new Offcanvas(offCanvasEl)
- spyOn(offCanvas, 'show')
+ const spy = spyOn(offCanvas, 'show')
offCanvas.toggle()
- expect(offCanvas.show).toHaveBeenCalled()
+ expect(spy).toHaveBeenCalled()
})
it('should call hide method if show class is present', () => {
- fixtureEl.innerHTML = '<div class="offcanvas"></div>'
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div class="offcanvas"></div>'
- const offCanvasEl = fixtureEl.querySelector('.offcanvas')
- const offCanvas = new Offcanvas(offCanvasEl)
- offCanvas.show()
- expect(offCanvasEl.classList.contains('show')).toBe(true)
+ const offCanvasEl = fixtureEl.querySelector('.offcanvas')
+ const offCanvas = new Offcanvas(offCanvasEl)
- spyOn(offCanvas, 'hide')
+ offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
+ expect(offCanvasEl).toHaveClass('show')
+ const spy = spyOn(offCanvas, 'hide')
- offCanvas.toggle()
+ offCanvas.toggle()
+
+ expect(spy).toHaveBeenCalled()
+ resolve()
+ })
- expect(offCanvas.hide).toHaveBeenCalled()
+ offCanvas.show()
+ })
})
})
describe('show', () => {
+ it('should add `showing` class during opening and `show` class on end', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div class="offcanvas"></div>'
+ const offCanvasEl = fixtureEl.querySelector('.offcanvas')
+ const offCanvas = new Offcanvas(offCanvasEl)
+
+ offCanvasEl.addEventListener('show.bs.offcanvas', () => {
+ expect(offCanvasEl).not.toHaveClass('show')
+ })
+
+ offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
+ expect(offCanvasEl).not.toHaveClass('showing')
+ expect(offCanvasEl).toHaveClass('show')
+ resolve()
+ })
+
+ offCanvas.show()
+ expect(offCanvasEl).toHaveClass('showing')
+ })
+ })
+
it('should do nothing if already shown', () => {
fixtureEl.innerHTML = '<div class="offcanvas show"></div>'
@@ -274,166 +397,206 @@ describe('Offcanvas', () => {
const offCanvas = new Offcanvas(offCanvasEl)
offCanvas.show()
- expect(offCanvasEl.classList.contains('show')).toBe(true)
+ expect(offCanvasEl).toHaveClass('show')
- spyOn(offCanvas._backdrop, 'show').and.callThrough()
- spyOn(EventHandler, 'trigger').and.callThrough()
+ const spyShow = spyOn(offCanvas._backdrop, 'show').and.callThrough()
+ const spyTrigger = spyOn(EventHandler, 'trigger').and.callThrough()
offCanvas.show()
- expect(EventHandler.trigger).not.toHaveBeenCalled()
- expect(offCanvas._backdrop.show).not.toHaveBeenCalled()
+ expect(spyTrigger).not.toHaveBeenCalled()
+ expect(spyShow).not.toHaveBeenCalled()
})
- it('should show a hidden element', done => {
- fixtureEl.innerHTML = '<div class="offcanvas"></div>'
+ it('should show a hidden element', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div class="offcanvas"></div>'
- const offCanvasEl = fixtureEl.querySelector('div')
- const offCanvas = new Offcanvas(offCanvasEl)
- spyOn(offCanvas._backdrop, 'show').and.callThrough()
+ const offCanvasEl = fixtureEl.querySelector('div')
+ const offCanvas = new Offcanvas(offCanvasEl)
+ const spy = spyOn(offCanvas._backdrop, 'show').and.callThrough()
- offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
- expect(offCanvasEl.classList.contains('show')).toEqual(true)
- expect(offCanvas._backdrop.show).toHaveBeenCalled()
- done()
- })
+ offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
+ expect(offCanvasEl).toHaveClass('show')
+ expect(spy).toHaveBeenCalled()
+ resolve()
+ })
- offCanvas.show()
+ offCanvas.show()
+ })
})
- it('should not fire shown when show is prevented', done => {
- fixtureEl.innerHTML = '<div class="offcanvas"></div>'
+ it('should not fire shown when show is prevented', () => {
+ return new Promise((resolve, reject) => {
+ fixtureEl.innerHTML = '<div class="offcanvas"></div>'
- const offCanvasEl = fixtureEl.querySelector('div')
- const offCanvas = new Offcanvas(offCanvasEl)
- spyOn(offCanvas._backdrop, 'show').and.callThrough()
-
- const expectEnd = () => {
- setTimeout(() => {
- expect(offCanvas._backdrop.show).not.toHaveBeenCalled()
- done()
- }, 10)
- }
-
- offCanvasEl.addEventListener('show.bs.offcanvas', event => {
- event.preventDefault()
- expectEnd()
- })
+ const offCanvasEl = fixtureEl.querySelector('div')
+ const offCanvas = new Offcanvas(offCanvasEl)
+ const spy = spyOn(offCanvas._backdrop, 'show').and.callThrough()
- offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
- throw new Error('should not fire shown event')
- })
+ const expectEnd = () => {
+ setTimeout(() => {
+ expect(spy).not.toHaveBeenCalled()
+ resolve()
+ }, 10)
+ }
- offCanvas.show()
+ offCanvasEl.addEventListener('show.bs.offcanvas', event => {
+ event.preventDefault()
+ expectEnd()
+ })
+
+ offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
+ reject(new Error('should not fire shown event'))
+ })
+
+ offCanvas.show()
+ })
})
- it('on window load, should make visible an offcanvas element, if its markup contains class "show"', done => {
- fixtureEl.innerHTML = '<div class="offcanvas show"></div>'
+ it('on window load, should make visible an offcanvas element, if its markup contains class "show"', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div class="offcanvas show"></div>'
- const offCanvasEl = fixtureEl.querySelector('div')
- spyOn(Offcanvas.prototype, 'show').and.callThrough()
+ const offCanvasEl = fixtureEl.querySelector('div')
+ const spy = spyOn(Offcanvas.prototype, 'show').and.callThrough()
- offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
- done()
- })
+ offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
+ resolve()
+ })
- window.dispatchEvent(createEvent('load'))
+ window.dispatchEvent(createEvent('load'))
- const instance = Offcanvas.getInstance(offCanvasEl)
- expect(instance).not.toBeNull()
- expect(Offcanvas.prototype.show).toHaveBeenCalled()
+ const instance = Offcanvas.getInstance(offCanvasEl)
+ expect(instance).not.toBeNull()
+ expect(spy).toHaveBeenCalled()
+ })
})
- it('should trap focus', done => {
- fixtureEl.innerHTML = '<div class="offcanvas"></div>'
+ it('should trap focus', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div class="offcanvas"></div>'
- const offCanvasEl = fixtureEl.querySelector('.offcanvas')
- const offCanvas = new Offcanvas(offCanvasEl)
+ const offCanvasEl = fixtureEl.querySelector('.offcanvas')
+ const offCanvas = new Offcanvas(offCanvasEl)
- spyOn(offCanvas._focustrap, 'activate').and.callThrough()
+ const spy = spyOn(offCanvas._focustrap, 'activate').and.callThrough()
- offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
- expect(offCanvas._focustrap.activate).toHaveBeenCalled()
- done()
- })
+ offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
+ expect(spy).toHaveBeenCalled()
+ resolve()
+ })
- offCanvas.show()
+ offCanvas.show()
+ })
})
})
describe('hide', () => {
+ it('should add `hiding` class during closing and remover `show` & `hiding` classes on end', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div class="offcanvas"></div>'
+ const offCanvasEl = fixtureEl.querySelector('.offcanvas')
+ const offCanvas = new Offcanvas(offCanvasEl)
+
+ offCanvasEl.addEventListener('hide.bs.offcanvas', () => {
+ expect(offCanvasEl).not.toHaveClass('showing')
+ expect(offCanvasEl).toHaveClass('show')
+ })
+
+ offCanvasEl.addEventListener('hidden.bs.offcanvas', () => {
+ expect(offCanvasEl).not.toHaveClass('hiding')
+ expect(offCanvasEl).not.toHaveClass('show')
+ resolve()
+ })
+
+ offCanvas.show()
+ offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
+ offCanvas.hide()
+ expect(offCanvasEl).not.toHaveClass('showing')
+ expect(offCanvasEl).toHaveClass('hiding')
+ })
+ })
+ })
+
it('should do nothing if already shown', () => {
fixtureEl.innerHTML = '<div class="offcanvas"></div>'
- spyOn(EventHandler, 'trigger').and.callThrough()
+ const spyTrigger = spyOn(EventHandler, 'trigger').and.callThrough()
const offCanvasEl = fixtureEl.querySelector('div')
const offCanvas = new Offcanvas(offCanvasEl)
- spyOn(offCanvas._backdrop, 'hide').and.callThrough()
+ const spyHide = spyOn(offCanvas._backdrop, 'hide').and.callThrough()
offCanvas.hide()
- expect(offCanvas._backdrop.hide).not.toHaveBeenCalled()
- expect(EventHandler.trigger).not.toHaveBeenCalled()
+ expect(spyHide).not.toHaveBeenCalled()
+ expect(spyTrigger).not.toHaveBeenCalled()
})
- it('should hide a shown element', done => {
- fixtureEl.innerHTML = '<div class="offcanvas"></div>'
+ it('should hide a shown element', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div class="offcanvas"></div>'
- const offCanvasEl = fixtureEl.querySelector('div')
- const offCanvas = new Offcanvas(offCanvasEl)
- spyOn(offCanvas._backdrop, 'hide').and.callThrough()
- offCanvas.show()
+ const offCanvasEl = fixtureEl.querySelector('div')
+ const offCanvas = new Offcanvas(offCanvasEl)
+ const spy = spyOn(offCanvas._backdrop, 'hide').and.callThrough()
+ offCanvas.show()
- offCanvasEl.addEventListener('hidden.bs.offcanvas', () => {
- expect(offCanvasEl.classList.contains('show')).toEqual(false)
- expect(offCanvas._backdrop.hide).toHaveBeenCalled()
- done()
- })
+ offCanvasEl.addEventListener('hidden.bs.offcanvas', () => {
+ expect(offCanvasEl).not.toHaveClass('show')
+ expect(spy).toHaveBeenCalled()
+ resolve()
+ })
- offCanvas.hide()
+ offCanvas.hide()
+ })
})
- it('should not fire hidden when hide is prevented', done => {
- fixtureEl.innerHTML = '<div class="offcanvas"></div>'
+ it('should not fire hidden when hide is prevented', () => {
+ return new Promise((resolve, reject) => {
+ fixtureEl.innerHTML = '<div class="offcanvas"></div>'
- const offCanvasEl = fixtureEl.querySelector('div')
- const offCanvas = new Offcanvas(offCanvasEl)
- spyOn(offCanvas._backdrop, 'hide').and.callThrough()
+ const offCanvasEl = fixtureEl.querySelector('div')
+ const offCanvas = new Offcanvas(offCanvasEl)
+ const spy = spyOn(offCanvas._backdrop, 'hide').and.callThrough()
- offCanvas.show()
+ offCanvas.show()
- const expectEnd = () => {
- setTimeout(() => {
- expect(offCanvas._backdrop.hide).not.toHaveBeenCalled()
- done()
- }, 10)
- }
+ const expectEnd = () => {
+ setTimeout(() => {
+ expect(spy).not.toHaveBeenCalled()
+ resolve()
+ }, 10)
+ }
- offCanvasEl.addEventListener('hide.bs.offcanvas', event => {
- event.preventDefault()
- expectEnd()
- })
+ offCanvasEl.addEventListener('hide.bs.offcanvas', event => {
+ event.preventDefault()
+ expectEnd()
+ })
- offCanvasEl.addEventListener('hidden.bs.offcanvas', () => {
- throw new Error('should not fire hidden event')
- })
+ offCanvasEl.addEventListener('hidden.bs.offcanvas', () => {
+ reject(new Error('should not fire hidden event'))
+ })
- offCanvas.hide()
+ offCanvas.hide()
+ })
})
- it('should release focus trap', done => {
- fixtureEl.innerHTML = '<div class="offcanvas"></div>'
+ it('should release focus trap', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div class="offcanvas"></div>'
- const offCanvasEl = fixtureEl.querySelector('div')
- const offCanvas = new Offcanvas(offCanvasEl)
- spyOn(offCanvas._focustrap, 'deactivate').and.callThrough()
- offCanvas.show()
+ const offCanvasEl = fixtureEl.querySelector('div')
+ const offCanvas = new Offcanvas(offCanvasEl)
+ const spy = spyOn(offCanvas._focustrap, 'deactivate').and.callThrough()
+ offCanvas.show()
- offCanvasEl.addEventListener('hidden.bs.offcanvas', () => {
- expect(offCanvas._focustrap.deactivate).toHaveBeenCalled()
- done()
- })
+ offCanvasEl.addEventListener('hidden.bs.offcanvas', () => {
+ expect(spy).toHaveBeenCalled()
+ resolve()
+ })
- offCanvas.hide()
+ offCanvas.hide()
+ })
})
})
@@ -444,41 +607,41 @@ describe('Offcanvas', () => {
const offCanvasEl = fixtureEl.querySelector('div')
const offCanvas = new Offcanvas(offCanvasEl)
const backdrop = offCanvas._backdrop
- spyOn(backdrop, 'dispose').and.callThrough()
+ const spyDispose = spyOn(backdrop, 'dispose').and.callThrough()
const focustrap = offCanvas._focustrap
- spyOn(focustrap, 'deactivate').and.callThrough()
+ const spyDeactivate = spyOn(focustrap, 'deactivate').and.callThrough()
expect(Offcanvas.getInstance(offCanvasEl)).toEqual(offCanvas)
- spyOn(EventHandler, 'off')
-
offCanvas.dispose()
- expect(backdrop.dispose).toHaveBeenCalled()
+ expect(spyDispose).toHaveBeenCalled()
expect(offCanvas._backdrop).toBeNull()
- expect(focustrap.deactivate).toHaveBeenCalled()
+ expect(spyDeactivate).toHaveBeenCalled()
expect(offCanvas._focustrap).toBeNull()
- expect(Offcanvas.getInstance(offCanvasEl)).toEqual(null)
+ expect(Offcanvas.getInstance(offCanvasEl)).toBeNull()
})
})
describe('data-api', () => {
- it('should not prevent event for input', done => {
- fixtureEl.innerHTML = [
- '<input type="checkbox" data-bs-toggle="offcanvas" data-bs-target="#offcanvasdiv1" />',
- '<div id="offcanvasdiv1" class="offcanvas"></div>'
- ].join('')
-
- const target = fixtureEl.querySelector('input')
- const offCanvasEl = fixtureEl.querySelector('#offcanvasdiv1')
-
- offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
- expect(offCanvasEl.classList.contains('show')).toEqual(true)
- expect(target.checked).toEqual(true)
- done()
+ it('should not prevent event for input', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<input type="checkbox" data-bs-toggle="offcanvas" data-bs-target="#offcanvasdiv1">',
+ '<div id="offcanvasdiv1" class="offcanvas"></div>'
+ ].join('')
+
+ const target = fixtureEl.querySelector('input')
+ const offCanvasEl = fixtureEl.querySelector('#offcanvasdiv1')
+
+ offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
+ expect(offCanvasEl).toHaveClass('show')
+ expect(target.checked).toBeTrue()
+ resolve()
+ })
+
+ target.click()
})
-
- target.click()
})
it('should not call toggle on disabled elements', () => {
@@ -489,83 +652,89 @@ describe('Offcanvas', () => {
const target = fixtureEl.querySelector('a')
- spyOn(Offcanvas.prototype, 'toggle')
+ const spy = spyOn(Offcanvas.prototype, 'toggle')
target.click()
- expect(Offcanvas.prototype.toggle).not.toHaveBeenCalled()
- })
-
- it('should call hide first, if another offcanvas is open', done => {
- fixtureEl.innerHTML = [
- '<button id="btn2" data-bs-toggle="offcanvas" data-bs-target="#offcanvas2" ></button>',
- '<div id="offcanvas1" class="offcanvas"></div>',
- '<div id="offcanvas2" class="offcanvas"></div>'
- ].join('')
-
- const trigger2 = fixtureEl.querySelector('#btn2')
- const offcanvasEl1 = document.querySelector('#offcanvas1')
- const offcanvasEl2 = document.querySelector('#offcanvas2')
- const offcanvas1 = new Offcanvas(offcanvasEl1)
-
- offcanvasEl1.addEventListener('shown.bs.offcanvas', () => {
- trigger2.click()
+ expect(spy).not.toHaveBeenCalled()
+ })
+
+ it('should call hide first, if another offcanvas is open', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<button id="btn2" data-bs-toggle="offcanvas" data-bs-target="#offcanvas2"></button>',
+ '<div id="offcanvas1" class="offcanvas"></div>',
+ '<div id="offcanvas2" class="offcanvas"></div>'
+ ].join('')
+
+ const trigger2 = fixtureEl.querySelector('#btn2')
+ const offcanvasEl1 = document.querySelector('#offcanvas1')
+ const offcanvasEl2 = document.querySelector('#offcanvas2')
+ const offcanvas1 = new Offcanvas(offcanvasEl1)
+
+ offcanvasEl1.addEventListener('shown.bs.offcanvas', () => {
+ trigger2.click()
+ })
+ offcanvasEl1.addEventListener('hidden.bs.offcanvas', () => {
+ expect(Offcanvas.getInstance(offcanvasEl2)).not.toBeNull()
+ resolve()
+ })
+ offcanvas1.show()
})
- offcanvasEl1.addEventListener('hidden.bs.offcanvas', () => {
- expect(Offcanvas.getInstance(offcanvasEl2)).not.toBeNull()
- done()
- })
- offcanvas1.show()
})
- it('should focus on trigger element after closing offcanvas', done => {
- fixtureEl.innerHTML = [
- '<button id="btn" data-bs-toggle="offcanvas" data-bs-target="#offcanvas" ></button>',
- '<div id="offcanvas" class="offcanvas"></div>'
- ].join('')
-
- const trigger = fixtureEl.querySelector('#btn')
- const offcanvasEl = fixtureEl.querySelector('#offcanvas')
- const offcanvas = new Offcanvas(offcanvasEl)
- spyOn(trigger, 'focus')
-
- offcanvasEl.addEventListener('shown.bs.offcanvas', () => {
- offcanvas.hide()
+ it('should focus on trigger element after closing offcanvas', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<button id="btn" data-bs-toggle="offcanvas" data-bs-target="#offcanvas"></button>',
+ '<div id="offcanvas" class="offcanvas"></div>'
+ ].join('')
+
+ const trigger = fixtureEl.querySelector('#btn')
+ const offcanvasEl = fixtureEl.querySelector('#offcanvas')
+ const offcanvas = new Offcanvas(offcanvasEl)
+ const spy = spyOn(trigger, 'focus')
+
+ offcanvasEl.addEventListener('shown.bs.offcanvas', () => {
+ offcanvas.hide()
+ })
+ offcanvasEl.addEventListener('hidden.bs.offcanvas', () => {
+ setTimeout(() => {
+ expect(spy).toHaveBeenCalled()
+ resolve()
+ }, 5)
+ })
+
+ trigger.click()
})
- offcanvasEl.addEventListener('hidden.bs.offcanvas', () => {
- setTimeout(() => {
- expect(trigger.focus).toHaveBeenCalled()
- done()
- }, 5)
- })
-
- trigger.click()
})
- it('should not focus on trigger element after closing offcanvas, if it is not visible', done => {
- fixtureEl.innerHTML = [
- '<button id="btn" data-bs-toggle="offcanvas" data-bs-target="#offcanvas" ></button>',
- '<div id="offcanvas" class="offcanvas"></div>'
- ].join('')
-
- const trigger = fixtureEl.querySelector('#btn')
- const offcanvasEl = fixtureEl.querySelector('#offcanvas')
- const offcanvas = new Offcanvas(offcanvasEl)
- spyOn(trigger, 'focus')
-
- offcanvasEl.addEventListener('shown.bs.offcanvas', () => {
- trigger.style.display = 'none'
- offcanvas.hide()
- })
- offcanvasEl.addEventListener('hidden.bs.offcanvas', () => {
- setTimeout(() => {
- expect(isVisible(trigger)).toBe(false)
- expect(trigger.focus).not.toHaveBeenCalled()
- done()
- }, 5)
+ it('should not focus on trigger element after closing offcanvas, if it is not visible', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<button id="btn" data-bs-toggle="offcanvas" data-bs-target="#offcanvas"></button>',
+ '<div id="offcanvas" class="offcanvas"></div>'
+ ].join('')
+
+ const trigger = fixtureEl.querySelector('#btn')
+ const offcanvasEl = fixtureEl.querySelector('#offcanvas')
+ const offcanvas = new Offcanvas(offcanvasEl)
+ const spy = spyOn(trigger, 'focus')
+
+ offcanvasEl.addEventListener('shown.bs.offcanvas', () => {
+ trigger.style.display = 'none'
+ offcanvas.hide()
+ })
+ offcanvasEl.addEventListener('hidden.bs.offcanvas', () => {
+ setTimeout(() => {
+ expect(isVisible(trigger)).toBeFalse()
+ expect(spy).not.toHaveBeenCalled()
+ resolve()
+ }, 5)
+ })
+
+ trigger.click()
})
-
- trigger.click()
})
})
@@ -644,13 +813,13 @@ describe('Offcanvas', () => {
const div = fixtureEl.querySelector('div')
- spyOn(Offcanvas.prototype, 'show')
+ const spy = spyOn(Offcanvas.prototype, 'show')
jQueryMock.fn.offcanvas = Offcanvas.jQueryInterface
jQueryMock.elements = [div]
jQueryMock.fn.offcanvas.call(jQueryMock, 'show')
- expect(Offcanvas.prototype.show).toHaveBeenCalled()
+ expect(spy).toHaveBeenCalled()
})
it('should create a offcanvas with given config', () => {
@@ -665,7 +834,7 @@ describe('Offcanvas', () => {
const offcanvas = Offcanvas.getInstance(div)
expect(offcanvas).not.toBeNull()
- expect(offcanvas._config.scroll).toBe(true)
+ expect(offcanvas._config.scroll).toBeTrue()
})
})
@@ -706,7 +875,7 @@ describe('Offcanvas', () => {
const div = fixtureEl.querySelector('div')
- expect(Offcanvas.getInstance(div)).toEqual(null)
+ expect(Offcanvas.getInstance(div)).toBeNull()
expect(Offcanvas.getOrCreateInstance(div)).toBeInstanceOf(Offcanvas)
})
@@ -715,13 +884,13 @@ describe('Offcanvas', () => {
const div = fixtureEl.querySelector('div')
- expect(Offcanvas.getInstance(div)).toEqual(null)
+ expect(Offcanvas.getInstance(div)).toBeNull()
const offcanvas = Offcanvas.getOrCreateInstance(div, {
scroll: true
})
expect(offcanvas).toBeInstanceOf(Offcanvas)
- expect(offcanvas._config.scroll).toEqual(true)
+ expect(offcanvas._config.scroll).toBeTrue()
})
it('should return the instance when exists without given configuration', () => {
@@ -739,7 +908,7 @@ describe('Offcanvas', () => {
expect(offcanvas).toBeInstanceOf(Offcanvas)
expect(offcanvas2).toEqual(offcanvas)
- expect(offcanvas2._config.scroll).toEqual(true)
+ expect(offcanvas2._config.scroll).toBeTrue()
})
})
})
diff --git a/js/tests/unit/popover.spec.js b/js/tests/unit/popover.spec.js
index b3bba3180..ba38ebe06 100644
--- a/js/tests/unit/popover.spec.js
+++ b/js/tests/unit/popover.spec.js
@@ -1,5 +1,6 @@
-import Popover from '../../src/popover'
-import { clearFixture, getFixture, jQueryMock } from '../helpers/fixture'
+import EventHandler from '../../src/dom/event-handler.js'
+import Popover from '../../src/popover.js'
+import { clearFixture, getFixture, jQueryMock } from '../helpers/fixture.js'
describe('Popover', () => {
let fixtureEl
@@ -42,12 +43,6 @@ describe('Popover', () => {
})
})
- describe('Event', () => {
- it('should return plugin events', () => {
- expect(Popover.Event).toEqual(jasmine.any(Object))
- })
- })
-
describe('EVENT_KEY', () => {
it('should return plugin event key', () => {
expect(Popover.EVENT_KEY).toEqual('.bs.popover')
@@ -61,166 +56,264 @@ describe('Popover', () => {
})
describe('show', () => {
- it('should show a popover', done => {
- fixtureEl.innerHTML = '<a href="#" title="Popover" data-bs-content="https://twitter.com/getbootstrap">BS twitter</a>'
+ it('should show a popover', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#" title="Popover" data-bs-content="https://twitter.com/getbootstrap">BS twitter</a>'
- const popoverEl = fixtureEl.querySelector('a')
- const popover = new Popover(popoverEl)
+ const popoverEl = fixtureEl.querySelector('a')
+ const popover = new Popover(popoverEl)
- popoverEl.addEventListener('shown.bs.popover', () => {
- expect(document.querySelector('.popover')).not.toBeNull()
- done()
- })
+ popoverEl.addEventListener('shown.bs.popover', () => {
+ expect(document.querySelector('.popover')).not.toBeNull()
+ resolve()
+ })
- popover.show()
+ popover.show()
+ })
})
- it('should set title and content from functions', done => {
- fixtureEl.innerHTML = '<a href="#">BS twitter</a>'
+ it('should set title and content from functions', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#">BS twitter</a>'
- const popoverEl = fixtureEl.querySelector('a')
- const popover = new Popover(popoverEl, {
- title: () => 'Bootstrap',
- content: () => 'loves writing tests (╯°□°)╯︵ ┻━┻'
- })
+ const popoverEl = fixtureEl.querySelector('a')
+ const popover = new Popover(popoverEl, {
+ title: () => 'Bootstrap',
+ content: () => 'loves writing tests (╯°□°)╯︵ ┻━┻'
+ })
- popoverEl.addEventListener('shown.bs.popover', () => {
- const popoverDisplayed = document.querySelector('.popover')
+ popoverEl.addEventListener('shown.bs.popover', () => {
+ const popoverDisplayed = document.querySelector('.popover')
- expect(popoverDisplayed).not.toBeNull()
- expect(popoverDisplayed.querySelector('.popover-header').textContent).toEqual('Bootstrap')
- expect(popoverDisplayed.querySelector('.popover-body').textContent).toEqual('loves writing tests (╯°□°)╯︵ ┻━┻')
- done()
- })
+ expect(popoverDisplayed).not.toBeNull()
+ expect(popoverDisplayed.querySelector('.popover-header').textContent).toEqual('Bootstrap')
+ expect(popoverDisplayed.querySelector('.popover-body').textContent).toEqual('loves writing tests (╯°□°)╯︵ ┻━┻')
+ resolve()
+ })
- popover.show()
+ popover.show()
+ })
})
- it('should show a popover with just content', done => {
- fixtureEl.innerHTML = '<a href="#">BS twitter</a>'
+ it('should call content and title functions with trigger element', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#" data-foo="bar">BS twitter</a>'
+
+ const popoverEl = fixtureEl.querySelector('a')
+ const popover = new Popover(popoverEl, {
+ title(el) {
+ return el.dataset.foo
+ },
+ content(el) {
+ return el.dataset.foo
+ }
+ })
+
+ popoverEl.addEventListener('shown.bs.popover', () => {
+ const popoverDisplayed = document.querySelector('.popover')
+
+ expect(popoverDisplayed).not.toBeNull()
+ expect(popoverDisplayed.querySelector('.popover-header').textContent).toEqual('bar')
+ expect(popoverDisplayed.querySelector('.popover-body').textContent).toEqual('bar')
+ resolve()
+ })
- const popoverEl = fixtureEl.querySelector('a')
- const popover = new Popover(popoverEl, {
- content: 'Popover content'
+ popover.show()
})
+ })
- popoverEl.addEventListener('shown.bs.popover', () => {
- const popoverDisplayed = document.querySelector('.popover')
+ it('should call content and title functions with correct this value', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#" data-foo="bar">BS twitter</a>'
+
+ const popoverEl = fixtureEl.querySelector('a')
+ const popover = new Popover(popoverEl, {
+ title() {
+ return this.dataset.foo
+ },
+ content() {
+ return this.dataset.foo
+ }
+ })
+
+ popoverEl.addEventListener('shown.bs.popover', () => {
+ const popoverDisplayed = document.querySelector('.popover')
+
+ expect(popoverDisplayed).not.toBeNull()
+ expect(popoverDisplayed.querySelector('.popover-header').textContent).toEqual('bar')
+ expect(popoverDisplayed.querySelector('.popover-body').textContent).toEqual('bar')
+ resolve()
+ })
- expect(popoverDisplayed).not.toBeNull()
- expect(popoverDisplayed.querySelector('.popover-body').textContent).toEqual('Popover content')
- done()
+ popover.show()
})
-
- popover.show()
})
- it('should show a popover with just content without having header', done => {
- fixtureEl.innerHTML = '<a href="#">Nice link</a>'
+ it('should show a popover with just content without having header', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#">Nice link</a>'
- const popoverEl = fixtureEl.querySelector('a')
- const popover = new Popover(popoverEl, {
- content: 'Some beautiful content :)'
- })
+ const popoverEl = fixtureEl.querySelector('a')
+ const popover = new Popover(popoverEl, {
+ content: 'Some beautiful content :)'
+ })
- popoverEl.addEventListener('shown.bs.popover', () => {
- const popoverDisplayed = document.querySelector('.popover')
+ popoverEl.addEventListener('shown.bs.popover', () => {
+ const popoverDisplayed = document.querySelector('.popover')
- expect(popoverDisplayed).not.toBeNull()
- expect(popoverDisplayed.querySelector('.popover-header')).toBeNull()
- expect(popoverDisplayed.querySelector('.popover-body').textContent).toEqual('Some beautiful content :)')
- done()
- })
+ expect(popoverDisplayed).not.toBeNull()
+ expect(popoverDisplayed.querySelector('.popover-header')).toBeNull()
+ expect(popoverDisplayed.querySelector('.popover-body').textContent).toEqual('Some beautiful content :)')
+ resolve()
+ })
- popover.show()
+ popover.show()
+ })
})
- it('should show a popover with just title without having body', done => {
- fixtureEl.innerHTML = '<a href="#">Nice link</a>'
+ it('should show a popover with just title without having body', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#">Nice link</a>'
- const popoverEl = fixtureEl.querySelector('a')
- const popover = new Popover(popoverEl, {
- title: 'Title, which does not require content'
- })
+ const popoverEl = fixtureEl.querySelector('a')
+ const popover = new Popover(popoverEl, {
+ title: 'Title which does not require content'
+ })
- popoverEl.addEventListener('shown.bs.popover', () => {
- const popoverDisplayed = document.querySelector('.popover')
+ popoverEl.addEventListener('shown.bs.popover', () => {
+ const popoverDisplayed = document.querySelector('.popover')
- expect(popoverDisplayed).not.toBeNull()
- expect(popoverDisplayed.querySelector('.popover-body')).toBeNull()
- expect(popoverDisplayed.querySelector('.popover-header').textContent).toEqual('Title, which does not require content')
- done()
- })
+ expect(popoverDisplayed).not.toBeNull()
+ expect(popoverDisplayed.querySelector('.popover-body')).toBeNull()
+ expect(popoverDisplayed.querySelector('.popover-header').textContent).toEqual('Title which does not require content')
+ resolve()
+ })
- popover.show()
+ popover.show()
+ })
})
- it('should call setContent once', done => {
- fixtureEl.innerHTML = '<a href="#">BS twitter</a>'
+ it('should show a popover with just title without having body using data-attribute to get config', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#" data-bs-content="" title="Title which does not require content">Nice link</a>'
- const popoverEl = fixtureEl.querySelector('a')
- const popover = new Popover(popoverEl, {
- content: 'Popover content'
- })
- expect(popover._templateFactory).toBeNull()
- let spy = null
- let times = 1
+ const popoverEl = fixtureEl.querySelector('a')
+ const popover = new Popover(popoverEl)
+
+ popoverEl.addEventListener('shown.bs.popover', () => {
+ const popoverDisplayed = document.querySelector('.popover')
+
+ expect(popoverDisplayed).not.toBeNull()
+ expect(popoverDisplayed.querySelector('.popover-body')).toBeNull()
+ expect(popoverDisplayed.querySelector('.popover-header').textContent).toEqual('Title which does not require content')
+ resolve()
+ })
- popoverEl.addEventListener('hidden.bs.popover', () => {
popover.show()
})
+ })
- popoverEl.addEventListener('shown.bs.popover', () => {
- spy = spy || spyOn(popover._templateFactory, 'constructor').and.callThrough()
- const popoverDisplayed = document.querySelector('.popover')
+ it('should NOT show a popover without `title` and `content`', () => {
+ fixtureEl.innerHTML = '<a href="#" data-bs-content="" title="">Nice link</a>'
- expect(popoverDisplayed).not.toBeNull()
- expect(popoverDisplayed.querySelector('.popover-body').textContent).toEqual('Popover content')
- expect(spy).toHaveBeenCalledTimes(0)
- if (times > 1) {
- done()
- }
+ const popoverEl = fixtureEl.querySelector('a')
+ const popover = new Popover(popoverEl, { animation: false })
+ const spy = spyOn(EventHandler, 'trigger').and.callThrough()
- times++
- popover.hide()
- })
popover.show()
+
+ expect(spy).not.toHaveBeenCalledWith(popoverEl, Popover.eventName('show'))
+ expect(document.querySelector('.popover')).toBeNull()
})
- it('should show a popover with provided custom class', done => {
+ it('"setContent" should keep the initial template', () => {
fixtureEl.innerHTML = '<a href="#" title="Popover" data-bs-content="https://twitter.com/getbootstrap" data-bs-custom-class="custom-class">BS twitter</a>'
const popoverEl = fixtureEl.querySelector('a')
const popover = new Popover(popoverEl)
- popoverEl.addEventListener('shown.bs.popover', () => {
- const tip = document.querySelector('.popover')
- expect(tip).not.toBeNull()
- expect(tip.classList.contains('custom-class')).toBeTrue()
- done()
+ popover.setContent({ '.tooltip-inner': 'foo' })
+ const tip = popover._getTipElement()
+
+ expect(tip).toHaveClass('popover')
+ expect(tip).toHaveClass('bs-popover-auto')
+ expect(tip.querySelector('.popover-arrow')).not.toBeNull()
+ expect(tip.querySelector('.popover-header')).not.toBeNull()
+ expect(tip.querySelector('.popover-body')).not.toBeNull()
+ })
+
+ it('should call setContent once', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#">BS twitter</a>'
+
+ const popoverEl = fixtureEl.querySelector('a')
+ const popover = new Popover(popoverEl, {
+ content: 'Popover content'
+ })
+ expect(popover._templateFactory).toBeNull()
+ let spy = null
+ let times = 1
+
+ popoverEl.addEventListener('hidden.bs.popover', () => {
+ popover.show()
+ })
+
+ popoverEl.addEventListener('shown.bs.popover', () => {
+ spy = spy || spyOn(popover._templateFactory, 'constructor').and.callThrough()
+ const popoverDisplayed = document.querySelector('.popover')
+
+ expect(popoverDisplayed).not.toBeNull()
+ expect(popoverDisplayed.querySelector('.popover-body').textContent).toEqual('Popover content')
+ expect(spy).toHaveBeenCalledTimes(0)
+ if (times > 1) {
+ resolve()
+ }
+
+ times++
+ popover.hide()
+ })
+ popover.show()
})
+ })
- popover.show()
+ it('should show a popover with provided custom class', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#" title="Popover" data-bs-content="https://twitter.com/getbootstrap" data-bs-custom-class="custom-class">BS twitter</a>'
+
+ const popoverEl = fixtureEl.querySelector('a')
+ const popover = new Popover(popoverEl)
+
+ popoverEl.addEventListener('shown.bs.popover', () => {
+ const tip = document.querySelector('.popover')
+ expect(tip).not.toBeNull()
+ expect(tip).toHaveClass('custom-class')
+ resolve()
+ })
+
+ popover.show()
+ })
})
})
describe('hide', () => {
- it('should hide a popover', done => {
- fixtureEl.innerHTML = '<a href="#" title="Popover" data-bs-content="https://twitter.com/getbootstrap">BS twitter</a>'
+ it('should hide a popover', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#" title="Popover" data-bs-content="https://twitter.com/getbootstrap">BS twitter</a>'
- const popoverEl = fixtureEl.querySelector('a')
- const popover = new Popover(popoverEl)
+ const popoverEl = fixtureEl.querySelector('a')
+ const popover = new Popover(popoverEl)
- popoverEl.addEventListener('shown.bs.popover', () => {
- popover.hide()
- })
+ popoverEl.addEventListener('shown.bs.popover', () => {
+ popover.hide()
+ })
- popoverEl.addEventListener('hidden.bs.popover', () => {
- expect(document.querySelector('.popover')).toBeNull()
- done()
- })
+ popoverEl.addEventListener('hidden.bs.popover', () => {
+ expect(document.querySelector('.popover')).toBeNull()
+ resolve()
+ })
- popover.show()
+ popover.show()
+ })
})
})
@@ -290,11 +383,11 @@ describe('Popover', () => {
jQueryMock.fn.popover = Popover.jQueryInterface
jQueryMock.elements = [popoverEl]
- spyOn(popover, 'show')
+ const spy = spyOn(popover, 'show')
jQueryMock.fn.popover.call(jQueryMock, 'show')
- expect(popover.show).toHaveBeenCalled()
+ expect(spy).toHaveBeenCalled()
})
})
@@ -314,7 +407,7 @@ describe('Popover', () => {
const popoverEl = fixtureEl.querySelector('a')
- expect(Popover.getInstance(popoverEl)).toEqual(null)
+ expect(Popover.getInstance(popoverEl)).toBeNull()
})
})
@@ -335,7 +428,7 @@ describe('Popover', () => {
const div = fixtureEl.querySelector('div')
- expect(Popover.getInstance(div)).toEqual(null)
+ expect(Popover.getInstance(div)).toBeNull()
expect(Popover.getOrCreateInstance(div)).toBeInstanceOf(Popover)
})
@@ -344,7 +437,7 @@ describe('Popover', () => {
const div = fixtureEl.querySelector('div')
- expect(Popover.getInstance(div)).toEqual(null)
+ expect(Popover.getInstance(div)).toBeNull()
const popover = Popover.getOrCreateInstance(div, {
placement: 'top'
})
diff --git a/js/tests/unit/scrollspy.spec.js b/js/tests/unit/scrollspy.spec.js
index f64b8f1dc..fc44471c4 100644
--- a/js/tests/unit/scrollspy.spec.js
+++ b/js/tests/unit/scrollspy.spec.js
@@ -1,28 +1,71 @@
-import ScrollSpy from '../../src/scrollspy'
-import Manipulator from '../../src/dom/manipulator'
-import { getFixture, clearFixture, createEvent, jQueryMock } from '../helpers/fixture'
+import EventHandler from '../../src/dom/event-handler.js'
+import ScrollSpy from '../../src/scrollspy.js'
+import {
+ clearFixture, createEvent, getFixture, jQueryMock
+} from '../helpers/fixture.js'
describe('ScrollSpy', () => {
let fixtureEl
- const testElementIsActiveAfterScroll = ({ elementSelector, targetSelector, contentEl, scrollSpy, spy, cb }) => {
+ const getElementScrollSpy = element => element.scrollTo ?
+ spyOn(element, 'scrollTo').and.callThrough() :
+ spyOnProperty(element, 'scrollTop', 'set').and.callThrough()
+
+ const scrollTo = (el, height) => {
+ el.scrollTop = height
+ }
+
+ const onScrollStop = (callback, element, timeout = 30) => {
+ let handle = null
+ const onScroll = function () {
+ if (handle) {
+ window.clearTimeout(handle)
+ }
+
+ handle = setTimeout(() => {
+ element.removeEventListener('scroll', onScroll)
+ callback()
+ }, timeout + 1)
+ }
+
+ element.addEventListener('scroll', onScroll)
+ }
+
+ const getDummyFixture = () => {
+ return [
+ '<nav id="navBar" class="navbar">',
+ ' <ul class="nav">',
+ ' <li class="nav-item"><a id="li-jsm-1" class="nav-link" href="#div-jsm-1">div 1</a></li>',
+ ' </ul>',
+ '</nav>',
+ '<div class="content" data-bs-target="#navBar" style="overflow-y: auto">',
+ ' <div id="div-jsm-1">div 1</div>',
+ '</div>'
+ ].join('')
+ }
+
+ const testElementIsActiveAfterScroll = ({ elementSelector, targetSelector, contentEl, scrollSpy, cb }) => {
const element = fixtureEl.querySelector(elementSelector)
const target = fixtureEl.querySelector(targetSelector)
-
// add top padding to fix Chrome on Android failures
- const paddingTop = 5
- const scrollHeight = Math.ceil(contentEl.scrollTop + Manipulator.position(target).top) + paddingTop
-
- function listener() {
- expect(element.classList.contains('active')).toEqual(true)
- contentEl.removeEventListener('scroll', listener)
- expect(scrollSpy._process).toHaveBeenCalled()
- spy.calls.reset()
+ const paddingTop = 0
+ const parentOffset = getComputedStyle(contentEl).getPropertyValue('position') === 'relative' ? 0 : contentEl.offsetTop
+ const scrollHeight = (target.offsetTop - parentOffset) + paddingTop
+
+ contentEl.addEventListener('activate.bs.scrollspy', event => {
+ if (scrollSpy._activeTarget !== element) {
+ return
+ }
+
+ expect(element).toHaveClass('active')
+ expect(scrollSpy._activeTarget).toEqual(element)
+ expect(event.relatedTarget).toEqual(element)
cb()
- }
+ })
- contentEl.addEventListener('scroll', listener)
- contentEl.scrollTop = scrollHeight
+ setTimeout(() => { // in case we scroll something before the test
+ scrollTo(contentEl, scrollHeight)
+ }, 100)
}
beforeAll(() => {
@@ -53,28 +96,25 @@ describe('ScrollSpy', () => {
describe('constructor', () => {
it('should take care of element either passed as a CSS selector or DOM element', () => {
- fixtureEl.innerHTML = '<nav id="navigation"></nav><div class="content"></div>'
+ fixtureEl.innerHTML = getDummyFixture()
- const sSpyEl = fixtureEl.querySelector('#navigation')
- const sSpyBySelector = new ScrollSpy('#navigation')
+ const sSpyEl = fixtureEl.querySelector('.content')
+ const sSpyBySelector = new ScrollSpy('.content')
const sSpyByElement = new ScrollSpy(sSpyEl)
expect(sSpyBySelector._element).toEqual(sSpyEl)
expect(sSpyByElement._element).toEqual(sSpyEl)
})
- it('should not process element without target', () => {
+ it('should null, if element is not scrollable', () => {
fixtureEl.innerHTML = [
'<nav id="navigation" class="navbar">',
- ' <ul class="navbar-nav">',
- ' <li class="nav-item"><a class="nav-link active" id="one-link" href="#">One</a></li>',
- ' <li class="nav-item"><a class="nav-link" id="two-link" href="#two">Two</a></li>',
- ' <li class="nav-item"><a class="nav-link" id="three-link" href="#three">Three</a></li>',
+ ' <ul class="navbar-nav">' +
+ ' <li class="nav-item"><a class="nav-link active" id="one-link" href="#">One</a></li>' +
' </ul>',
'</nav>',
- '<div id="content" style="height: 200px; overflow-y: auto;">',
- ' <div id="two" style="height: 300px;"></div>',
- ' <div id="three" style="height: 10px;"></div>',
+ '<div id="content">',
+ ' <div id="1" style="height: 300px;">test</div>',
'</div>'
].join('')
@@ -82,533 +122,567 @@ describe('ScrollSpy', () => {
target: '#navigation'
})
- expect(scrollSpy._targets.length).toEqual(2)
+ expect(scrollSpy._observer.root).toBeNull()
+ expect(scrollSpy._rootElement).toBeNull()
})
- it('should only switch "active" class on current target', done => {
+ it('should respect threshold option', () => {
fixtureEl.innerHTML = [
- '<div id="root" class="active" style="display: block">',
- ' <div class="topbar">',
- ' <div class="topbar-inner">',
- ' <div class="container" id="ss-target">',
- ' <ul class="nav">',
- ' <li class="nav-item"><a href="#masthead">Overview</a></li>',
- ' <li class="nav-item"><a href="#detail">Detail</a></li>',
- ' </ul>',
- ' </div>',
- ' </div>',
- ' </div>',
- ' <div id="scrollspy-example" style="height: 100px; overflow: auto;">',
- ' <div style="height: 200px;">',
- ' <h4 id="masthead">Overview</h4>',
- ' <p style="height: 200px;"></p>',
- ' </div>',
- ' <div style="height: 200px;">',
- ' <h4 id="detail">Detail</h4>',
- ' <p style="height: 200px;"></p>',
- ' </div>',
- ' </div>',
+ '<ul id="navigation" class="navbar">',
+ ' <a class="nav-link active" id="one-link" href="#">One</a>' +
+ '</ul>',
+ '<div id="content">',
+ ' <div id="one-link">test</div>',
'</div>'
].join('')
- const scrollSpyEl = fixtureEl.querySelector('#scrollspy-example')
- const rootEl = fixtureEl.querySelector('#root')
- const scrollSpy = new ScrollSpy(scrollSpyEl, {
- target: 'ss-target'
- })
-
- spyOn(scrollSpy, '_process').and.callThrough()
-
- scrollSpyEl.addEventListener('scroll', () => {
- expect(rootEl.classList.contains('active')).toEqual(true)
- expect(scrollSpy._process).toHaveBeenCalled()
- done()
+ const scrollSpy = new ScrollSpy('#content', {
+ target: '#navigation',
+ threshold: [1]
})
- scrollSpyEl.scrollTop = 350
+ expect(scrollSpy._observer.thresholds).toEqual([1])
})
- it('should only switch "active" class on current target specified w element', done => {
+ it('should respect threshold option markup', () => {
fixtureEl.innerHTML = [
- '<div id="root" class="active" style="display: block">',
- ' <div class="topbar">',
- ' <div class="topbar-inner">',
- ' <div class="container" id="ss-target">',
- ' <ul class="nav">',
- ' <li class="nav-item"><a href="#masthead">Overview</a></li>',
- ' <li class="nav-item"><a href="#detail">Detail</a></li>',
- ' </ul>',
- ' </div>',
- ' </div>',
- ' </div>',
- ' <div id="scrollspy-example" style="height: 100px; overflow: auto;">',
- ' <div style="height: 200px;">',
- ' <h4 id="masthead">Overview</h4>',
- ' <p style="height: 200px;"></p>',
- ' </div>',
- ' <div style="height: 200px;">',
- ' <h4 id="detail">Detail</h4>',
- ' <p style="height: 200px;"></p>',
- ' </div>',
- ' </div>',
+ '<ul id="navigation" class="navbar">',
+ ' <a class="nav-link active" id="one-link" href="#">One</a>' +
+ '</ul>',
+ '<div id="content" data-bs-threshold="0,0.2,1">',
+ ' <div id="one-link">test</div>',
'</div>'
].join('')
- const scrollSpyEl = fixtureEl.querySelector('#scrollspy-example')
- const rootEl = fixtureEl.querySelector('#root')
- const scrollSpy = new ScrollSpy(scrollSpyEl, {
- target: fixtureEl.querySelector('#ss-target')
+ const scrollSpy = new ScrollSpy('#content', {
+ target: '#navigation'
})
- spyOn(scrollSpy, '_process').and.callThrough()
-
- scrollSpyEl.addEventListener('scroll', () => {
- expect(rootEl.classList.contains('active')).toEqual(true)
- expect(scrollSpy._process).toHaveBeenCalled()
- done()
- })
+ // See https://stackoverflow.com/a/45592926
+ const expectToBeCloseToArray = (actual, expected) => {
+ expect(actual.length).toBe(expected.length)
+ for (const x of actual) {
+ const i = actual.indexOf(x)
+ expect(x).withContext(`[${i}]`).toBeCloseTo(expected[i])
+ }
+ }
- scrollSpyEl.scrollTop = 350
+ expectToBeCloseToArray(scrollSpy._observer.thresholds, [0, 0.2, 1])
})
- it('should correctly select middle navigation option when large offset is used', done => {
+ it('should not take count to not visible sections', () => {
fixtureEl.innerHTML = [
- '<div id="header" style="height: 500px;"></div>',
'<nav id="navigation" class="navbar">',
- ' <ul class="navbar-nav">',
- ' <li class="nav-item"><a class="nav-link active" id="one-link" href="#one">One</a></li>',
- ' <li class="nav-item"><a class="nav-link" id="two-link" href="#two">Two</a></li>',
- ' <li class="nav-item"><a class="nav-link" id="three-link" href="#three">Three</a></li>',
- ' </ul>',
+ ' <ul class="navbar-nav">',
+ ' <li class="nav-item"><a class="nav-link active" id="one-link" href="#one">One</a></li>',
+ ' <li class="nav-item"><a class="nav-link" id="two-link" href="#two">Two</a></li>',
+ ' <li class="nav-item"><a class="nav-link" id="three-link" href="#three">Three</a></li>',
+ ' </ul>',
'</nav>',
'<div id="content" style="height: 200px; overflow-y: auto;">',
- ' <div id="one" style="height: 500px;"></div>',
- ' <div id="two" style="height: 300px;"></div>',
- ' <div id="three" style="height: 10px;"></div>',
+ ' <div id="one" style="height: 300px;">test</div>',
+ ' <div id="two" hidden style="height: 300px;">test</div>',
+ ' <div id="three" style="display: none;">test</div>',
'</div>'
].join('')
- const contentEl = fixtureEl.querySelector('#content')
- const scrollSpy = new ScrollSpy(contentEl, {
- target: '#navigation',
- offset: Manipulator.position(contentEl).top
- })
-
- spyOn(scrollSpy, '_process').and.callThrough()
-
- contentEl.addEventListener('scroll', () => {
- expect(fixtureEl.querySelector('#one-link').classList.contains('active')).toEqual(false)
- expect(fixtureEl.querySelector('#two-link').classList.contains('active')).toEqual(true)
- expect(fixtureEl.querySelector('#three-link').classList.contains('active')).toEqual(false)
- expect(scrollSpy._process).toHaveBeenCalled()
- done()
+ const scrollSpy = new ScrollSpy(fixtureEl.querySelector('#content'), {
+ target: '#navigation'
})
- contentEl.scrollTop = 550
+ expect(scrollSpy._observableSections.size).toBe(1)
+ expect(scrollSpy._targetLinks.size).toBe(1)
})
- it('should add the active class to the correct element', done => {
+ it('should not process element without target', () => {
fixtureEl.innerHTML = [
- '<nav class="navbar">',
- ' <ul class="nav">',
- ' <li class="nav-item"><a class="nav-link" id="a-1" href="#div-1">div 1</a></li>',
- ' <li class="nav-item"><a class="nav-link" id="a-2" href="#div-2">div 2</a></li>',
+ '<nav id="navigation" class="navbar">',
+ ' <ul class="navbar-nav">',
+ ' <li class="nav-item"><a class="nav-link active" id="one-link" href="#">One</a></li>',
+ ' <li class="nav-item"><a class="nav-link" id="two-link" href="#two">Two</a></li>',
+ ' <li class="nav-item"><a class="nav-link" id="three-link" href="#three">Three</a></li>',
' </ul>',
'</nav>',
- '<div class="content" style="overflow: auto; height: 50px">',
- ' <div id="div-1" style="height: 100px; padding: 0; margin: 0">div 1</div>',
- ' <div id="div-2" style="height: 200px; padding: 0; margin: 0">div 2</div>',
+ '<div id="content" style="height: 200px; overflow-y: auto;">',
+ ' <div id="two" style="height: 300px;">test</div>',
+ ' <div id="three" style="height: 10px;">test2</div>',
'</div>'
].join('')
- const contentEl = fixtureEl.querySelector('.content')
- const scrollSpy = new ScrollSpy(contentEl, {
- offset: 0,
- target: '.navbar'
- })
- const spy = spyOn(scrollSpy, '_process').and.callThrough()
-
- testElementIsActiveAfterScroll({
- elementSelector: '#a-1',
- targetSelector: '#div-1',
- contentEl,
- scrollSpy,
- spy,
- cb: () => {
- testElementIsActiveAfterScroll({
- elementSelector: '#a-2',
- targetSelector: '#div-2',
- contentEl,
- scrollSpy,
- spy,
- cb: () => done()
- })
- }
+ const scrollSpy = new ScrollSpy(fixtureEl.querySelector('#content'), {
+ target: '#navigation'
})
- })
- it('should add the active class to the correct element (nav markup)', done => {
- fixtureEl.innerHTML = [
- '<nav class="navbar">',
- ' <nav class="nav">',
- ' <a class="nav-link" id="a-1" href="#div-1">div 1</a>',
- ' <a class="nav-link" id="a-2" href="#div-2">div 2</a>',
- ' </nav>',
- '</nav>',
- '<div class="content" style="overflow: auto; height: 50px">',
- ' <div id="div-1" style="height: 100px; padding: 0; margin: 0">div 1</div>',
- ' <div id="div-2" style="height: 200px; padding: 0; margin: 0">div 2</div>',
- '</div>'
- ].join('')
+ expect(scrollSpy._targetLinks).toHaveSize(2)
+ })
- const contentEl = fixtureEl.querySelector('.content')
- const scrollSpy = new ScrollSpy(contentEl, {
- offset: 0,
- target: '.navbar'
- })
- const spy = spyOn(scrollSpy, '_process').and.callThrough()
-
- testElementIsActiveAfterScroll({
- elementSelector: '#a-1',
- targetSelector: '#div-1',
- contentEl,
- scrollSpy,
- spy,
- cb: () => {
- testElementIsActiveAfterScroll({
- elementSelector: '#a-2',
- targetSelector: '#div-2',
- contentEl,
- scrollSpy,
- spy,
- cb: () => done()
- })
- }
+ it('should only switch "active" class on current target', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div id="root" class="active" style="display: block">',
+ ' <div class="topbar">',
+ ' <div class="topbar-inner">',
+ ' <div class="container" id="ss-target">',
+ ' <ul class="nav">',
+ ' <li class="nav-item"><a href="#masthead">Overview</a></li>',
+ ' <li class="nav-item"><a href="#detail">Detail</a></li>',
+ ' </ul>',
+ ' </div>',
+ ' </div>',
+ ' </div>',
+ ' <div id="scrollspy-example" style="height: 100px; overflow: auto;">',
+ ' <div style="height: 200px;" id="masthead">Overview</div>',
+ ' <div style="height: 200px;" id="detail">Detail</div>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const scrollSpyEl = fixtureEl.querySelector('#scrollspy-example')
+ const rootEl = fixtureEl.querySelector('#root')
+ const scrollSpy = new ScrollSpy(scrollSpyEl, {
+ target: 'ss-target'
+ })
+
+ const spy = spyOn(scrollSpy, '_process').and.callThrough()
+
+ onScrollStop(() => {
+ expect(rootEl).toHaveClass('active')
+ expect(spy).toHaveBeenCalled()
+ resolve()
+ }, scrollSpyEl)
+
+ scrollTo(scrollSpyEl, 350)
})
})
- it('should add the active class to the correct element (list-group markup)', done => {
- fixtureEl.innerHTML = [
- '<nav class="navbar">',
- ' <div class="list-group">',
- ' <a class="list-group-item" id="a-1" href="#div-1">div 1</a>',
- ' <a class="list-group-item" id="a-2" href="#div-2">div 2</a>',
- ' </div>',
- '</nav>',
- '<div class="content" style="overflow: auto; height: 50px">',
- ' <div id="div-1" style="height: 100px; padding: 0; margin: 0">div 1</div>',
- ' <div id="div-2" style="height: 200px; padding: 0; margin: 0">div 2</div>',
- '</div>'
- ].join('')
-
- const contentEl = fixtureEl.querySelector('.content')
- const scrollSpy = new ScrollSpy(contentEl, {
- offset: 0,
- target: '.navbar'
- })
- const spy = spyOn(scrollSpy, '_process').and.callThrough()
-
- testElementIsActiveAfterScroll({
- elementSelector: '#a-1',
- targetSelector: '#div-1',
- contentEl,
- scrollSpy,
- spy,
- cb: () => {
- testElementIsActiveAfterScroll({
- elementSelector: '#a-2',
- targetSelector: '#div-2',
- contentEl,
- scrollSpy,
- spy,
- cb: () => done()
- })
- }
+ it('should not process data if `activeTarget` is same as given target', () => {
+ return new Promise((resolve, reject) => {
+ fixtureEl.innerHTML = [
+ '<nav class="navbar">',
+ ' <ul class="nav">',
+ ' <li class="nav-item"><a class="nav-link" id="a-1" href="#div-1">div 1</a></li>',
+ ' <li class="nav-item"><a class="nav-link" id="a-2" href="#div-2">div 2</a></li>',
+ ' </ul>',
+ '</nav>',
+ '<div class="content" style="overflow: auto; height: 50px">',
+ ' <div id="div-1" style="height: 100px; padding: 0; margin: 0">div 1</div>',
+ ' <div id="div-2" style="height: 200px; padding: 0; margin: 0">div 2</div>',
+ '</div>'
+ ].join('')
+
+ const contentEl = fixtureEl.querySelector('.content')
+ const scrollSpy = new ScrollSpy(contentEl, {
+ offset: 0,
+ target: '.navbar'
+ })
+
+ const triggerSpy = spyOn(EventHandler, 'trigger').and.callThrough()
+
+ scrollSpy._activeTarget = fixtureEl.querySelector('#a-1')
+ testElementIsActiveAfterScroll({
+ elementSelector: '#a-1',
+ targetSelector: '#div-1',
+ contentEl,
+ scrollSpy,
+ cb: reject
+ })
+
+ setTimeout(() => {
+ expect(triggerSpy).not.toHaveBeenCalled()
+ resolve()
+ }, 100)
})
})
- it('should clear selection if above the first section', done => {
- fixtureEl.innerHTML = [
- '<div id="header" style="height: 500px;"></div>',
- '<nav id="navigation" class="navbar">',
- ' <ul class="navbar-nav">',
- ' <li class="nav-item"><a id="one-link" class="nav-link active" href="#one">One</a></li>',
- ' <li class="nav-item"><a id="two-link" class="nav-link" href="#two">Two</a></li>',
- ' <li class="nav-item"><a id="three-link" class="nav-link" href="#three">Three</a></li>',
- ' </ul>',
- '</nav>',
- '<div id="content" style="height: 200px; overflow-y: auto;">',
- ' <div id="spacer" style="height: 100px;"></div>',
- ' <div id="one" style="height: 100px;"></div>',
- ' <div id="two" style="height: 100px;"></div>',
- ' <div id="three" style="height: 100px;"></div>',
- ' <div id="spacer" style="height: 100px;"></div>',
- '</div>'
- ].join('')
-
- const contentEl = fixtureEl.querySelector('#content')
- const scrollSpy = new ScrollSpy(contentEl, {
- target: '#navigation',
- offset: Manipulator.position(contentEl).top
+ it('should only switch "active" class on current target specified w element', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div id="root" class="active" style="display: block">',
+ ' <div class="topbar">',
+ ' <div class="topbar-inner">',
+ ' <div class="container" id="ss-target">',
+ ' <ul class="nav">',
+ ' <li class="nav-item"><a href="#masthead">Overview</a></li>',
+ ' <li class="nav-item"><a href="#detail">Detail</a></li>',
+ ' </ul>',
+ ' </div>',
+ ' </div>',
+ ' </div>',
+ ' <div id="scrollspy-example" style="height: 100px; overflow: auto;">',
+ ' <div style="height: 200px;" id="masthead">Overview</div>',
+ ' <div style="height: 200px;" id="detail">Detail</div>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const scrollSpyEl = fixtureEl.querySelector('#scrollspy-example')
+ const rootEl = fixtureEl.querySelector('#root')
+ const scrollSpy = new ScrollSpy(scrollSpyEl, {
+ target: fixtureEl.querySelector('#ss-target')
+ })
+
+ const spy = spyOn(scrollSpy, '_process').and.callThrough()
+
+ onScrollStop(() => {
+ expect(rootEl).toHaveClass('active')
+ expect(scrollSpy._activeTarget).toEqual(fixtureEl.querySelector('[href="#detail"]'))
+ expect(spy).toHaveBeenCalled()
+ resolve()
+ }, scrollSpyEl)
+
+ scrollTo(scrollSpyEl, 350)
})
- const spy = spyOn(scrollSpy, '_process').and.callThrough()
-
- let firstTime = true
-
- contentEl.addEventListener('scroll', () => {
- const active = fixtureEl.querySelector('.active')
+ })
- expect(spy).toHaveBeenCalled()
- spy.calls.reset()
- if (firstTime) {
- expect(fixtureEl.querySelectorAll('.active').length).toEqual(1)
- expect(active.getAttribute('id')).toEqual('two-link')
- firstTime = false
- contentEl.scrollTop = 0
- } else {
- expect(active).toBeNull()
- done()
- }
+ it('should add the active class to the correct element', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<nav class="navbar">',
+ ' <ul class="nav">',
+ ' <li class="nav-item"><a class="nav-link" id="a-1" href="#div-1">div 1</a></li>',
+ ' <li class="nav-item"><a class="nav-link" id="a-2" href="#div-2">div 2</a></li>',
+ ' </ul>',
+ '</nav>',
+ '<div class="content" style="overflow: auto; height: 50px">',
+ ' <div id="div-1" style="height: 100px; padding: 0; margin: 0">div 1</div>',
+ ' <div id="div-2" style="height: 200px; padding: 0; margin: 0">div 2</div>',
+ '</div>'
+ ].join('')
+
+ const contentEl = fixtureEl.querySelector('.content')
+ const scrollSpy = new ScrollSpy(contentEl, {
+ offset: 0,
+ target: '.navbar'
+ })
+
+ testElementIsActiveAfterScroll({
+ elementSelector: '#a-1',
+ targetSelector: '#div-1',
+ contentEl,
+ scrollSpy,
+ cb() {
+ testElementIsActiveAfterScroll({
+ elementSelector: '#a-2',
+ targetSelector: '#div-2',
+ contentEl,
+ scrollSpy,
+ cb: resolve
+ })
+ }
+ })
})
-
- contentEl.scrollTop = 201
})
- it('should not clear selection if above the first section and first section is at the top', done => {
- fixtureEl.innerHTML = [
- '<div id="header" style="height: 500px;"></div>',
- '<nav id="navigation" class="navbar">',
- ' <ul class="navbar-nav">',
- ' <li class="nav-item"><a id="one-link" class="nav-link active" href="#one">One</a></li>',
- ' <li class="nav-item"><a id="two-link" class="nav-link" href="#two">Two</a></li>',
- ' <li class="nav-item"><a id="three-link" class="nav-link" href="#three">Three</a></li>',
- ' </ul>',
- '</nav>',
- '<div id="content" style="height: 200px; overflow-y: auto;">',
- ' <div id="one" style="height: 100px;"></div>',
- ' <div id="two" style="height: 100px;"></div>',
- ' <div id="three" style="height: 100px;"></div>',
- ' <div id="spacer" style="height: 100px;"></div>',
- '</div>'
- ].join('')
-
- const negativeHeight = -10
- const startOfSectionTwo = 101
- const contentEl = fixtureEl.querySelector('#content')
- const scrollSpy = new ScrollSpy(contentEl, {
- target: '#navigation',
- offset: contentEl.offsetTop
+ it('should add to nav the active class to the correct element (nav markup)', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<nav class="navbar">',
+ ' <nav class="nav">',
+ ' <a class="nav-link" id="a-1" href="#div-1">div 1</a>',
+ ' <a class="nav-link" id="a-2" href="#div-2">div 2</a>',
+ ' </nav>',
+ '</nav>',
+ '<div class="content" style="overflow: auto; height: 50px">',
+ ' <div id="div-1" style="height: 100px; padding: 0; margin: 0">div 1</div>',
+ ' <div id="div-2" style="height: 200px; padding: 0; margin: 0">div 2</div>',
+ '</div>'
+ ].join('')
+
+ const contentEl = fixtureEl.querySelector('.content')
+ const scrollSpy = new ScrollSpy(contentEl, {
+ offset: 0,
+ target: '.navbar'
+ })
+
+ testElementIsActiveAfterScroll({
+ elementSelector: '#a-1',
+ targetSelector: '#div-1',
+ contentEl,
+ scrollSpy,
+ cb() {
+ testElementIsActiveAfterScroll({
+ elementSelector: '#a-2',
+ targetSelector: '#div-2',
+ contentEl,
+ scrollSpy,
+ cb: resolve
+ })
+ }
+ })
})
- const spy = spyOn(scrollSpy, '_process').and.callThrough()
-
- let firstTime = true
-
- contentEl.addEventListener('scroll', () => {
- const active = fixtureEl.querySelector('.active')
+ })
- expect(spy).toHaveBeenCalled()
- spy.calls.reset()
- if (firstTime) {
- expect(fixtureEl.querySelectorAll('.active').length).toEqual(1)
- expect(active.getAttribute('id')).toEqual('two-link')
- firstTime = false
- contentEl.scrollTop = negativeHeight
- } else {
- expect(fixtureEl.querySelectorAll('.active').length).toEqual(1)
- expect(active.getAttribute('id')).toEqual('one-link')
- done()
- }
+ it('should add to list-group, the active class to the correct element (list-group markup)', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<nav class="navbar">',
+ ' <div class="list-group">',
+ ' <a class="list-group-item" id="a-1" href="#div-1">div 1</a>',
+ ' <a class="list-group-item" id="a-2" href="#div-2">div 2</a>',
+ ' </div>',
+ '</nav>',
+ '<div class="content" style="overflow: auto; height: 50px">',
+ ' <div id="div-1" style="height: 100px; padding: 0; margin: 0">div 1</div>',
+ ' <div id="div-2" style="height: 200px; padding: 0; margin: 0">div 2</div>',
+ '</div>'
+ ].join('')
+
+ const contentEl = fixtureEl.querySelector('.content')
+ const scrollSpy = new ScrollSpy(contentEl, {
+ offset: 0,
+ target: '.navbar'
+ })
+
+ testElementIsActiveAfterScroll({
+ elementSelector: '#a-1',
+ targetSelector: '#div-1',
+ contentEl,
+ scrollSpy,
+ cb() {
+ testElementIsActiveAfterScroll({
+ elementSelector: '#a-2',
+ targetSelector: '#div-2',
+ contentEl,
+ scrollSpy,
+ cb: resolve
+ })
+ }
+ })
})
-
- contentEl.scrollTop = startOfSectionTwo
})
- it('should correctly select navigation element on backward scrolling when each target section height is 100%', done => {
- fixtureEl.innerHTML = [
- '<nav class="navbar">',
- ' <ul class="nav">',
- ' <li class="nav-item"><a id="li-100-1" class="nav-link" href="#div-100-1">div 1</a></li>',
- ' <li class="nav-item"><a id="li-100-2" class="nav-link" href="#div-100-2">div 2</a></li>',
- ' <li class="nav-item"><a id="li-100-3" class="nav-link" href="#div-100-3">div 3</a></li>',
- ' <li class="nav-item"><a id="li-100-4" class="nav-link" href="#div-100-4">div 4</a></li>',
- ' <li class="nav-item"><a id="li-100-5" class="nav-link" href="#div-100-5">div 5</a></li>',
- ' </ul>',
- '</nav>',
- '<div class="content" style="position: relative; overflow: auto; height: 100px">',
- ' <div id="div-100-1" style="position: relative; height: 100%; padding: 0; margin: 0">div 1</div>',
- ' <div id="div-100-2" style="position: relative; height: 100%; padding: 0; margin: 0">div 2</div>',
- ' <div id="div-100-3" style="position: relative; height: 100%; padding: 0; margin: 0">div 3</div>',
- ' <div id="div-100-4" style="position: relative; height: 100%; padding: 0; margin: 0">div 4</div>',
- ' <div id="div-100-5" style="position: relative; height: 100%; padding: 0; margin: 0">div 5</div>',
- '</div>'
- ].join('')
-
- const contentEl = fixtureEl.querySelector('.content')
- const scrollSpy = new ScrollSpy(contentEl, {
- offset: 0,
- target: '.navbar'
- })
- const spy = spyOn(scrollSpy, '_process').and.callThrough()
-
- testElementIsActiveAfterScroll({
- elementSelector: '#li-100-5',
- targetSelector: '#div-100-5',
- scrollSpy,
- spy,
- contentEl,
- cb() {
- contentEl.scrollTop = 0
- testElementIsActiveAfterScroll({
- elementSelector: '#li-100-4',
- targetSelector: '#div-100-4',
- scrollSpy,
- spy,
- contentEl,
- cb() {
- contentEl.scrollTop = 0
- testElementIsActiveAfterScroll({
- elementSelector: '#li-100-3',
- targetSelector: '#div-100-3',
- scrollSpy,
- spy,
- contentEl,
- cb() {
- contentEl.scrollTop = 0
- testElementIsActiveAfterScroll({
- elementSelector: '#li-100-2',
- targetSelector: '#div-100-2',
- scrollSpy,
- spy,
- contentEl,
- cb() {
- contentEl.scrollTop = 0
- testElementIsActiveAfterScroll({
- elementSelector: '#li-100-1',
- targetSelector: '#div-100-1',
- scrollSpy,
- spy,
- contentEl,
- cb: done
- })
- }
- })
- }
- })
- }
- })
- }
+ it('should clear selection if above the first section', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div id="header" style="height: 500px;"></div>',
+ '<nav id="navigation" class="navbar">',
+ ' <ul class="navbar-nav">',
+ ' <li class="nav-item"><a id="one-link" class="nav-link active" href="#one">One</a></li>',
+ ' <li class="nav-item"><a id="two-link" class="nav-link" href="#two">Two</a></li>',
+ ' <li class="nav-item"><a id="three-link" class="nav-link" href="#three">Three</a></li>',
+ ' </ul>',
+ '</nav>',
+ '<div id="content" style="height: 200px; overflow-y: auto;">',
+ ' <div id="spacer" style="height: 200px;"></div>',
+ ' <div id="one" style="height: 100px;">text</div>',
+ ' <div id="two" style="height: 100px;">text</div>',
+ ' <div id="three" style="height: 100px;">text</div>',
+ ' <div id="spacer" style="height: 100px;"></div>',
+ '</div>'
+ ].join('')
+
+ const contentEl = fixtureEl.querySelector('#content')
+ const scrollSpy = new ScrollSpy(contentEl, {
+ target: '#navigation',
+ offset: contentEl.offsetTop
+ })
+ const spy = spyOn(scrollSpy, '_process').and.callThrough()
+
+ onScrollStop(() => {
+ const active = () => fixtureEl.querySelector('.active')
+ expect(spy).toHaveBeenCalled()
+
+ expect(fixtureEl.querySelectorAll('.active')).toHaveSize(1)
+ expect(active().getAttribute('id')).toEqual('two-link')
+ onScrollStop(() => {
+ expect(active()).toBeNull()
+ resolve()
+ }, contentEl)
+ scrollTo(contentEl, 0)
+ }, contentEl)
+
+ scrollTo(contentEl, 200)
})
})
- it('should allow passed in option offset method: offset', () => {
- fixtureEl.innerHTML = [
- '<nav class="navbar">',
- ' <ul class="nav">',
- ' <li class="nav-item"><a id="li-jsm-1" class="nav-link" href="#div-jsm-1">div 1</a></li>',
- ' <li class="nav-item"><a id="li-jsm-2" class="nav-link" href="#div-jsm-2">div 2</a></li>',
- ' <li class="nav-item"><a id="li-jsm-3" class="nav-link" href="#div-jsm-3">div 3</a></li>',
- ' </ul>',
- '</nav>',
- '<div class="content" style="position: relative; overflow: auto; height: 100px">',
- ' <div id="div-jsm-1" style="position: relative; height: 200px; padding: 0; margin: 0">div 1</div>',
- ' <div id="div-jsm-2" style="position: relative; height: 150px; padding: 0; margin: 0">div 2</div>',
- ' <div id="div-jsm-3" style="position: relative; height: 250px; padding: 0; margin: 0">div 3</div>',
- '</div>'
- ].join('')
-
- const contentEl = fixtureEl.querySelector('.content')
- const targetEl = fixtureEl.querySelector('#div-jsm-2')
- const scrollSpy = new ScrollSpy(contentEl, {
- target: '.navbar',
- offset: 0,
- method: 'offset'
+ it('should not clear selection if above the first section and first section is at the top', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div id="header" style="height: 500px;"></div>',
+ '<nav id="navigation" class="navbar">',
+ ' <ul class="navbar-nav">',
+ ' <li class="nav-item"><a id="one-link" class="nav-link active" href="#one">One</a></li>',
+ ' <li class="nav-item"><a id="two-link" class="nav-link" href="#two">Two</a></li>',
+ ' <li class="nav-item"><a id="three-link" class="nav-link" href="#three">Three</a></li>',
+ ' </ul>',
+ '</nav>',
+ '<div id="content" style="height: 150px; overflow-y: auto;">',
+ ' <div id="one" style="height: 100px;">test</div>',
+ ' <div id="two" style="height: 100px;">test</div>',
+ ' <div id="three" style="height: 100px;">test</div>',
+ ' <div id="spacer" style="height: 100px;">test</div>',
+ '</div>'
+ ].join('')
+
+ const negativeHeight = 0
+ const startOfSectionTwo = 101
+ const contentEl = fixtureEl.querySelector('#content')
+ // eslint-disable-next-line no-unused-vars
+ const scrollSpy = new ScrollSpy(contentEl, {
+ target: '#navigation',
+ rootMargin: '0px 0px -50%'
+ })
+
+ onScrollStop(() => {
+ const activeId = () => fixtureEl.querySelector('.active').getAttribute('id')
+
+ expect(fixtureEl.querySelectorAll('.active')).toHaveSize(1)
+ expect(activeId()).toEqual('two-link')
+ scrollTo(contentEl, negativeHeight)
+
+ onScrollStop(() => {
+ expect(fixtureEl.querySelectorAll('.active')).toHaveSize(1)
+ expect(activeId()).toEqual('one-link')
+ resolve()
+ }, contentEl)
+
+ scrollTo(contentEl, 0)
+ }, contentEl)
+
+ scrollTo(contentEl, startOfSectionTwo)
})
+ })
- expect(scrollSpy._offsets[1]).toEqual(Manipulator.offset(targetEl).top)
- expect(scrollSpy._offsets[1]).not.toEqual(Manipulator.position(targetEl).top)
+ it('should correctly select navigation element on backward scrolling when each target section height is 100%', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<nav class="navbar">',
+ ' <ul class="nav">',
+ ' <li class="nav-item"><a id="li-100-1" class="nav-link" href="#div-100-1">div 1</a></li>',
+ ' <li class="nav-item"><a id="li-100-2" class="nav-link" href="#div-100-2">div 2</a></li>',
+ ' <li class="nav-item"><a id="li-100-3" class="nav-link" href="#div-100-3">div 3</a></li>',
+ ' <li class="nav-item"><a id="li-100-4" class="nav-link" href="#div-100-4">div 4</a></li>',
+ ' <li class="nav-item"><a id="li-100-5" class="nav-link" href="#div-100-5">div 5</a></li>',
+ ' </ul>',
+ '</nav>',
+ '<div class="content" style="position: relative; overflow: auto; height: 100px">',
+ ' <div id="div-100-1" style="position: relative; height: 100%; padding: 0; margin: 0">div 1</div>',
+ ' <div id="div-100-2" style="position: relative; height: 100%; padding: 0; margin: 0">div 2</div>',
+ ' <div id="div-100-3" style="position: relative; height: 100%; padding: 0; margin: 0">div 3</div>',
+ ' <div id="div-100-4" style="position: relative; height: 100%; padding: 0; margin: 0">div 4</div>',
+ ' <div id="div-100-5" style="position: relative; height: 100%; padding: 0; margin: 0">div 5</div>',
+ '</div>'
+ ].join('')
+
+ const contentEl = fixtureEl.querySelector('.content')
+ const scrollSpy = new ScrollSpy(contentEl, {
+ offset: 0,
+ target: '.navbar'
+ })
+
+ scrollTo(contentEl, 0)
+ testElementIsActiveAfterScroll({
+ elementSelector: '#li-100-5',
+ targetSelector: '#div-100-5',
+ contentEl,
+ scrollSpy,
+ cb() {
+ scrollTo(contentEl, 0)
+ testElementIsActiveAfterScroll({
+ elementSelector: '#li-100-2',
+ targetSelector: '#div-100-2',
+ contentEl,
+ scrollSpy,
+ cb() {
+ scrollTo(contentEl, 0)
+ testElementIsActiveAfterScroll({
+ elementSelector: '#li-100-3',
+ targetSelector: '#div-100-3',
+ contentEl,
+ scrollSpy,
+ cb() {
+ scrollTo(contentEl, 0)
+ testElementIsActiveAfterScroll({
+ elementSelector: '#li-100-2',
+ targetSelector: '#div-100-2',
+ contentEl,
+ scrollSpy,
+ cb() {
+ scrollTo(contentEl, 0)
+ testElementIsActiveAfterScroll({
+ elementSelector: '#li-100-1',
+ targetSelector: '#div-100-1',
+ contentEl,
+ scrollSpy,
+ cb: resolve
+ })
+ }
+ })
+ }
+ })
+ }
+ })
+ }
+ })
+ })
})
+ })
- it('should allow passed in option offset method: position', () => {
- fixtureEl.innerHTML = [
- '<nav class="navbar">',
- ' <ul class="nav">',
- ' <li class="nav-item"><a id="li-jsm-1" class="nav-link" href="#div-jsm-1">div 1</a></li>',
- ' <li class="nav-item"><a id="li-jsm-2" class="nav-link" href="#div-jsm-2">div 2</a></li>',
- ' <li class="nav-item"><a id="li-jsm-3" class="nav-link" href="#div-jsm-3">div 3</a></li>',
- ' </ul>',
- '</nav>',
- '<div class="content" style="position: relative; overflow: auto; height: 100px">',
- ' <div id="div-jsm-1" style="position: relative; height: 200px; padding: 0; margin: 0">div 1</div>',
- ' <div id="div-jsm-2" style="position: relative; height: 150px; padding: 0; margin: 0">div 2</div>',
- ' <div id="div-jsm-3" style="position: relative; height: 250px; padding: 0; margin: 0">div 3</div>',
- '</div>'
- ].join('')
+ describe('refresh', () => {
+ it('should disconnect existing observer', () => {
+ fixtureEl.innerHTML = getDummyFixture()
- const contentEl = fixtureEl.querySelector('.content')
- const targetEl = fixtureEl.querySelector('#div-jsm-2')
- const scrollSpy = new ScrollSpy(contentEl, {
- target: '.navbar',
- offset: 0,
- method: 'position'
- })
+ const el = fixtureEl.querySelector('.content')
+ const scrollSpy = new ScrollSpy(el)
+
+ const spy = spyOn(scrollSpy._observer, 'disconnect')
+
+ scrollSpy.refresh()
- expect(scrollSpy._offsets[1]).not.toEqual(Manipulator.offset(targetEl).top)
- expect(scrollSpy._offsets[1]).toEqual(Manipulator.position(targetEl).top)
+ expect(spy).toHaveBeenCalled()
})
})
describe('dispose', () => {
it('should dispose a scrollspy', () => {
- fixtureEl.innerHTML = '<div style="display: none;"></div>'
+ fixtureEl.innerHTML = getDummyFixture()
- const divEl = fixtureEl.querySelector('div')
- spyOn(divEl, 'addEventListener').and.callThrough()
- spyOn(divEl, 'removeEventListener').and.callThrough()
+ const el = fixtureEl.querySelector('.content')
+ const scrollSpy = new ScrollSpy(el)
- const scrollSpy = new ScrollSpy(divEl)
- expect(divEl.addEventListener).toHaveBeenCalledWith('scroll', jasmine.any(Function), jasmine.any(Boolean))
+ expect(ScrollSpy.getInstance(el)).not.toBeNull()
scrollSpy.dispose()
- expect(divEl.removeEventListener).toHaveBeenCalledWith('scroll', jasmine.any(Function), jasmine.any(Boolean))
+ expect(ScrollSpy.getInstance(el)).toBeNull()
})
})
describe('jQueryInterface', () => {
it('should create a scrollspy', () => {
- fixtureEl.innerHTML = '<div></div>'
+ fixtureEl.innerHTML = getDummyFixture()
- const div = fixtureEl.querySelector('div')
+ const div = fixtureEl.querySelector('.content')
jQueryMock.fn.scrollspy = ScrollSpy.jQueryInterface
jQueryMock.elements = [div]
- jQueryMock.fn.scrollspy.call(jQueryMock)
+ jQueryMock.fn.scrollspy.call(jQueryMock, { target: '#navBar' })
expect(ScrollSpy.getInstance(div)).not.toBeNull()
})
it('should create a scrollspy with given config', () => {
- fixtureEl.innerHTML = '<div></div>'
+ fixtureEl.innerHTML = getDummyFixture()
- const div = fixtureEl.querySelector('div')
+ const div = fixtureEl.querySelector('.content')
jQueryMock.fn.scrollspy = ScrollSpy.jQueryInterface
jQueryMock.elements = [div]
- jQueryMock.fn.scrollspy.call(jQueryMock, { offset: 15 })
- spyOn(ScrollSpy.prototype, 'constructor')
- expect(ScrollSpy.prototype.constructor).not.toHaveBeenCalledWith(div, { offset: 15 })
+ jQueryMock.fn.scrollspy.call(jQueryMock, { rootMargin: '100px' })
+ const spy = spyOn(ScrollSpy.prototype, 'constructor')
+ expect(spy).not.toHaveBeenCalledWith(div, { rootMargin: '100px' })
const scrollspy = ScrollSpy.getInstance(div)
expect(scrollspy).not.toBeNull()
- expect(scrollspy._config.offset).toBe(15)
+ expect(scrollspy._config.rootMargin).toEqual('100px')
})
it('should not re create a scrollspy', () => {
- fixtureEl.innerHTML = '<div></div>'
+ fixtureEl.innerHTML = getDummyFixture()
- const div = fixtureEl.querySelector('div')
+ const div = fixtureEl.querySelector('.content')
const scrollSpy = new ScrollSpy(div)
jQueryMock.fn.scrollspy = ScrollSpy.jQueryInterface
@@ -620,12 +694,12 @@ describe('ScrollSpy', () => {
})
it('should call a scrollspy method', () => {
- fixtureEl.innerHTML = '<div></div>'
+ fixtureEl.innerHTML = getDummyFixture()
- const div = fixtureEl.querySelector('div')
+ const div = fixtureEl.querySelector('.content')
const scrollSpy = new ScrollSpy(div)
- spyOn(scrollSpy, 'refresh')
+ const spy = spyOn(scrollSpy, 'refresh')
jQueryMock.fn.scrollspy = ScrollSpy.jQueryInterface
jQueryMock.elements = [div]
@@ -633,13 +707,13 @@ describe('ScrollSpy', () => {
jQueryMock.fn.scrollspy.call(jQueryMock, 'refresh')
expect(ScrollSpy.getInstance(div)).toEqual(scrollSpy)
- expect(scrollSpy.refresh).toHaveBeenCalled()
+ expect(spy).toHaveBeenCalled()
})
it('should throw error on undefined method', () => {
- fixtureEl.innerHTML = '<div></div>'
+ fixtureEl.innerHTML = getDummyFixture()
- const div = fixtureEl.querySelector('div')
+ const div = fixtureEl.querySelector('.content')
const action = 'undefinedMethod'
jQueryMock.fn.scrollspy = ScrollSpy.jQueryInterface
@@ -649,29 +723,60 @@ describe('ScrollSpy', () => {
jQueryMock.fn.scrollspy.call(jQueryMock, action)
}).toThrowError(TypeError, `No method named "${action}"`)
})
+
+ it('should throw error on protected method', () => {
+ fixtureEl.innerHTML = getDummyFixture()
+
+ const div = fixtureEl.querySelector('.content')
+ const action = '_getConfig'
+
+ jQueryMock.fn.scrollspy = ScrollSpy.jQueryInterface
+ jQueryMock.elements = [div]
+
+ expect(() => {
+ jQueryMock.fn.scrollspy.call(jQueryMock, action)
+ }).toThrowError(TypeError, `No method named "${action}"`)
+ })
+
+ it('should throw error if method "constructor" is being called', () => {
+ fixtureEl.innerHTML = getDummyFixture()
+
+ const div = fixtureEl.querySelector('.content')
+ const action = 'constructor'
+
+ jQueryMock.fn.scrollspy = ScrollSpy.jQueryInterface
+ jQueryMock.elements = [div]
+
+ expect(() => {
+ jQueryMock.fn.scrollspy.call(jQueryMock, action)
+ }).toThrowError(TypeError, `No method named "${action}"`)
+ })
})
describe('getInstance', () => {
it('should return scrollspy instance', () => {
- fixtureEl.innerHTML = '<div></div>'
+ fixtureEl.innerHTML = getDummyFixture()
- const div = fixtureEl.querySelector('div')
- const scrollSpy = new ScrollSpy(div)
+ const div = fixtureEl.querySelector('.content')
+ const scrollSpy = new ScrollSpy(div, { target: fixtureEl.querySelector('#navBar') })
expect(ScrollSpy.getInstance(div)).toEqual(scrollSpy)
expect(ScrollSpy.getInstance(div)).toBeInstanceOf(ScrollSpy)
})
it('should return null if there is no instance', () => {
- expect(ScrollSpy.getInstance(fixtureEl)).toEqual(null)
+ fixtureEl.innerHTML = getDummyFixture()
+
+ const div = fixtureEl.querySelector('.content')
+ expect(ScrollSpy.getInstance(div)).toBeNull()
})
})
describe('getOrCreateInstance', () => {
it('should return scrollspy instance', () => {
- fixtureEl.innerHTML = '<div></div>'
+ fixtureEl.innerHTML = getDummyFixture()
- const div = fixtureEl.querySelector('div')
+ const div = fixtureEl.querySelector('.content')
const scrollspy = new ScrollSpy(div)
expect(ScrollSpy.getOrCreateInstance(div)).toEqual(scrollspy)
@@ -680,20 +785,20 @@ describe('ScrollSpy', () => {
})
it('should return new instance when there is no scrollspy instance', () => {
- fixtureEl.innerHTML = '<div></div>'
+ fixtureEl.innerHTML = getDummyFixture()
- const div = fixtureEl.querySelector('div')
+ const div = fixtureEl.querySelector('.content')
- expect(ScrollSpy.getInstance(div)).toEqual(null)
+ expect(ScrollSpy.getInstance(div)).toBeNull()
expect(ScrollSpy.getOrCreateInstance(div)).toBeInstanceOf(ScrollSpy)
})
it('should return new instance when there is no scrollspy instance with given configuration', () => {
- fixtureEl.innerHTML = '<div></div>'
+ fixtureEl.innerHTML = getDummyFixture()
- const div = fixtureEl.querySelector('div')
+ const div = fixtureEl.querySelector('.content')
- expect(ScrollSpy.getInstance(div)).toEqual(null)
+ expect(ScrollSpy.getInstance(div)).toBeNull()
const scrollspy = ScrollSpy.getOrCreateInstance(div, {
offset: 1
})
@@ -703,9 +808,9 @@ describe('ScrollSpy', () => {
})
it('should return the instance when exists without given configuration', () => {
- fixtureEl.innerHTML = '<div></div>'
+ fixtureEl.innerHTML = getDummyFixture()
- const div = fixtureEl.querySelector('div')
+ const div = fixtureEl.querySelector('.content')
const scrollspy = new ScrollSpy(div, {
offset: 1
})
@@ -723,13 +828,153 @@ describe('ScrollSpy', () => {
describe('event handler', () => {
it('should create scrollspy on window load event', () => {
- fixtureEl.innerHTML = '<div data-bs-spy="scroll"></div>'
+ fixtureEl.innerHTML = [
+ '<div id="nav"></div>' +
+ '<div id="wrapper" data-bs-spy="scroll" data-bs-target="#nav" style="overflow-y: auto"></div>'
+ ].join('')
- const scrollSpyEl = fixtureEl.querySelector('div')
+ const scrollSpyEl = fixtureEl.querySelector('#wrapper')
window.dispatchEvent(createEvent('load'))
expect(ScrollSpy.getInstance(scrollSpyEl)).not.toBeNull()
})
})
+
+ describe('SmoothScroll', () => {
+ it('should not enable smoothScroll', () => {
+ fixtureEl.innerHTML = getDummyFixture()
+ const offSpy = spyOn(EventHandler, 'off').and.callThrough()
+ const onSpy = spyOn(EventHandler, 'on').and.callThrough()
+
+ const div = fixtureEl.querySelector('.content')
+ const target = fixtureEl.querySelector('#navBar')
+ // eslint-disable-next-line no-new
+ new ScrollSpy(div, {
+ offset: 1
+ })
+
+ expect(offSpy).not.toHaveBeenCalledWith(target, 'click.bs.scrollspy')
+ expect(onSpy).not.toHaveBeenCalledWith(target, 'click.bs.scrollspy')
+ })
+
+ it('should enable smoothScroll', () => {
+ fixtureEl.innerHTML = getDummyFixture()
+ const offSpy = spyOn(EventHandler, 'off').and.callThrough()
+ const onSpy = spyOn(EventHandler, 'on').and.callThrough()
+
+ const div = fixtureEl.querySelector('.content')
+ const target = fixtureEl.querySelector('#navBar')
+ // eslint-disable-next-line no-new
+ new ScrollSpy(div, {
+ offset: 1,
+ smoothScroll: true
+ })
+
+ expect(offSpy).toHaveBeenCalledWith(target, 'click.bs.scrollspy')
+ expect(onSpy).toHaveBeenCalledWith(target, 'click.bs.scrollspy', '[href]', jasmine.any(Function))
+ })
+
+ it('should not smoothScroll to element if it not handles a scrollspy section', () => {
+ fixtureEl.innerHTML = [
+ '<nav id="navBar" class="navbar">',
+ ' <ul class="nav">',
+ ' <a id="anchor-1" href="#div-jsm-1">div 1</a></li>',
+ ' <a id="anchor-2" href="#foo">div 2</a></li>',
+ ' </ul>',
+ '</nav>',
+ '<div class="content" data-bs-target="#navBar" style="overflow-y: auto">',
+ ' <div id="div-jsm-1">div 1</div>',
+ '</div>'
+ ].join('')
+
+ const div = fixtureEl.querySelector('.content')
+ // eslint-disable-next-line no-new
+ new ScrollSpy(div, {
+ offset: 1,
+ smoothScroll: true
+ })
+
+ const clickSpy = getElementScrollSpy(div)
+
+ fixtureEl.querySelector('#anchor-2').click()
+ expect(clickSpy).not.toHaveBeenCalled()
+ })
+
+ it('should call `scrollTop` if element doesn\'t not support `scrollTo`', () => {
+ fixtureEl.innerHTML = getDummyFixture()
+
+ const div = fixtureEl.querySelector('.content')
+ const link = fixtureEl.querySelector('[href="#div-jsm-1"]')
+ delete div.scrollTo
+ const clickSpy = getElementScrollSpy(div)
+ // eslint-disable-next-line no-new
+ new ScrollSpy(div, {
+ offset: 1,
+ smoothScroll: true
+ })
+
+ link.click()
+ expect(clickSpy).toHaveBeenCalled()
+ })
+
+ it('should smoothScroll to the proper observable element on anchor click', done => {
+ fixtureEl.innerHTML = getDummyFixture()
+
+ const div = fixtureEl.querySelector('.content')
+ const link = fixtureEl.querySelector('[href="#div-jsm-1"]')
+ const observable = fixtureEl.querySelector('#div-jsm-1')
+ const clickSpy = getElementScrollSpy(div)
+ // eslint-disable-next-line no-new
+ new ScrollSpy(div, {
+ offset: 1,
+ smoothScroll: true
+ })
+
+ setTimeout(() => {
+ if (div.scrollTo) {
+ expect(clickSpy).toHaveBeenCalledWith({ top: observable.offsetTop - div.offsetTop, behavior: 'smooth' })
+ } else {
+ expect(clickSpy).toHaveBeenCalledWith(observable.offsetTop - div.offsetTop)
+ }
+
+ done()
+ }, 100)
+ link.click()
+ })
+
+ it('should smoothscroll to observable with anchor link that contains a french word as id', done => {
+ fixtureEl.innerHTML = [
+ '<nav id="navBar" class="navbar">',
+ ' <ul class="nav">',
+ ' <li class="nav-item"><a id="li-jsm-1" class="nav-link" href="#présentation">div 1</a></li>',
+ ' </ul>',
+ '</nav>',
+ '<div class="content" data-bs-target="#navBar" style="overflow-y: auto">',
+ ' <div id="présentation">div 1</div>',
+ '</div>'
+ ].join('')
+
+ const div = fixtureEl.querySelector('.content')
+ const link = fixtureEl.querySelector('[href="#présentation"]')
+ const observable = fixtureEl.querySelector('#présentation')
+ const clickSpy = getElementScrollSpy(div)
+ // eslint-disable-next-line no-new
+ new ScrollSpy(div, {
+ offset: 1,
+ smoothScroll: true
+ })
+
+ setTimeout(() => {
+ if (div.scrollTo) {
+ expect(clickSpy).toHaveBeenCalledWith({ top: observable.offsetTop - div.offsetTop, behavior: 'smooth' })
+ } else {
+ expect(clickSpy).toHaveBeenCalledWith(observable.offsetTop - div.offsetTop)
+ }
+
+ done()
+ }, 100)
+ link.click()
+ })
+ })
})
diff --git a/js/tests/unit/tab.spec.js b/js/tests/unit/tab.spec.js
index 05f9db2ec..4fcf8ee0f 100644
--- a/js/tests/unit/tab.spec.js
+++ b/js/tests/unit/tab.spec.js
@@ -1,5 +1,7 @@
-import Tab from '../../src/tab'
-import { getFixture, clearFixture, jQueryMock } from '../helpers/fixture'
+import Tab from '../../src/tab.js'
+import {
+ clearFixture, createEvent, getFixture, jQueryMock
+} from '../helpers/fixture.js'
describe('Tab', () => {
let fixtureEl
@@ -21,8 +23,12 @@ describe('Tab', () => {
describe('constructor', () => {
it('should take care of element either passed as a CSS selector or DOM element', () => {
fixtureEl.innerHTML = [
- '<ul class="nav"><li><a href="#home" role="tab">Home</a></li></ul>',
- '<ul><li id="home"></li></ul>'
+ '<ul class="nav">',
+ ' <li><a href="#home" role="tab">Home</a></li>',
+ '</ul>',
+ '<ul>',
+ ' <li id="home"></li>',
+ '</ul>'
].join('')
const tabEl = fixtureEl.querySelector('[href="#home"]')
@@ -32,334 +38,800 @@ describe('Tab', () => {
expect(tabBySelector._element).toEqual(tabEl)
expect(tabByElement._element).toEqual(tabEl)
})
- })
- describe('show', () => {
- it('should activate element by tab id (using buttons, the preferred semantic way)', done => {
+ it('Do not Throw exception if not parent', () => {
fixtureEl.innerHTML = [
- '<ul class="nav" role="tablist">',
- ' <li><button type="button" data-bs-target="#home" role="tab">Home</button></li>',
- ' <li><button type="button" id="triggerProfile" data-bs-target="#profile" role="tab">Profile</button></li>',
- '</ul>',
- '<ul>',
- ' <li id="home" role="tabpanel"></li>',
- ' <li id="profile" role="tabpanel"></li>',
- '</ul>'
+ fixtureEl.innerHTML = '<div class=""><div class="nav-link"></div></div>'
].join('')
+ const navEl = fixtureEl.querySelector('.nav-link')
- const profileTriggerEl = fixtureEl.querySelector('#triggerProfile')
- const tab = new Tab(profileTriggerEl)
+ expect(() => {
+ new Tab(navEl) // eslint-disable-line no-new
+ }).not.toThrowError(TypeError)
+ })
+ })
- profileTriggerEl.addEventListener('shown.bs.tab', () => {
- expect(fixtureEl.querySelector('#profile').classList.contains('active')).toEqual(true)
- expect(profileTriggerEl.getAttribute('aria-selected')).toEqual('true')
- done()
+ describe('show', () => {
+ it('should activate element by tab id (using buttons, the preferred semantic way)', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<ul class="nav" role="tablist">',
+ ' <li><button type="button" data-bs-target="#home" role="tab">Home</button></li>',
+ ' <li><button type="button" id="triggerProfile" data-bs-target="#profile" role="tab">Profile</button></li>',
+ '</ul>',
+ '<ul>',
+ ' <li id="home" role="tabpanel"></li>',
+ ' <li id="profile" role="tabpanel"></li>',
+ '</ul>'
+ ].join('')
+
+ const profileTriggerEl = fixtureEl.querySelector('#triggerProfile')
+ const tab = new Tab(profileTriggerEl)
+
+ profileTriggerEl.addEventListener('shown.bs.tab', () => {
+ expect(fixtureEl.querySelector('#profile')).toHaveClass('active')
+ expect(profileTriggerEl.getAttribute('aria-selected')).toEqual('true')
+ resolve()
+ })
+
+ tab.show()
})
+ })
- tab.show()
+ it('should activate element by tab id (using links for tabs - not ideal, but still supported)', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<ul class="nav" role="tablist">',
+ ' <li><a href="#home" role="tab">Home</a></li>',
+ ' <li><a id="triggerProfile" href="#profile" role="tab">Profile</a></li>',
+ '</ul>',
+ '<ul>',
+ ' <li id="home" role="tabpanel"></li>',
+ ' <li id="profile" role="tabpanel"></li>',
+ '</ul>'
+ ].join('')
+
+ const profileTriggerEl = fixtureEl.querySelector('#triggerProfile')
+ const tab = new Tab(profileTriggerEl)
+
+ profileTriggerEl.addEventListener('shown.bs.tab', () => {
+ expect(fixtureEl.querySelector('#profile')).toHaveClass('active')
+ expect(profileTriggerEl.getAttribute('aria-selected')).toEqual('true')
+ resolve()
+ })
+
+ tab.show()
+ })
})
- it('should activate element by tab id (using links for tabs - not ideal, but still supported)', done => {
- fixtureEl.innerHTML = [
- '<ul class="nav" role="tablist">',
- ' <li><a href="#home" role="tab">Home</a></li>',
- ' <li><a id="triggerProfile" href="#profile" role="tab">Profile</a></li>',
- '</ul>',
- '<ul>',
- ' <li id="home" role="tabpanel"></li>',
- ' <li id="profile" role="tabpanel"></li>',
- '</ul>'
- ].join('')
+ it('should activate element by tab id in ordered list', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<ol class="nav nav-pills">',
+ ' <li><button type="button" data-bs-target="#home" role="tab">Home</button></li>',
+ ' <li><button type="button" id="triggerProfile" href="#profile" role="tab">Profile</button></li>',
+ '</ol>',
+ '<ol>',
+ ' <li id="home" role="tabpanel"></li>',
+ ' <li id="profile" role="tabpanel"></li>',
+ '</ol>'
+ ].join('')
+
+ const profileTriggerEl = fixtureEl.querySelector('#triggerProfile')
+ const tab = new Tab(profileTriggerEl)
+
+ profileTriggerEl.addEventListener('shown.bs.tab', () => {
+ expect(fixtureEl.querySelector('#profile')).toHaveClass('active')
+ resolve()
+ })
- const profileTriggerEl = fixtureEl.querySelector('#triggerProfile')
- const tab = new Tab(profileTriggerEl)
+ tab.show()
+ })
+ })
- profileTriggerEl.addEventListener('shown.bs.tab', () => {
- expect(fixtureEl.querySelector('#profile').classList.contains('active')).toEqual(true)
- expect(profileTriggerEl.getAttribute('aria-selected')).toEqual('true')
- done()
+ it('should activate element by tab id in nav list', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<nav class="nav">',
+ ' <button type="button" data-bs-target="#home" role="tab">Home</button>',
+ ' <button type="button" id="triggerProfile" data-bs-target="#profile" role="tab">Profile</button>',
+ '</nav>',
+ '<div>',
+ ' <div id="home" role="tabpanel"></div>',
+ ' <div id="profile" role="tabpanel"></div>',
+ '</div>'
+ ].join('')
+
+ const profileTriggerEl = fixtureEl.querySelector('#triggerProfile')
+ const tab = new Tab(profileTriggerEl)
+
+ profileTriggerEl.addEventListener('shown.bs.tab', () => {
+ expect(fixtureEl.querySelector('#profile')).toHaveClass('active')
+ resolve()
+ })
+
+ tab.show()
})
+ })
- tab.show()
+ it('should activate element by tab id in list group', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="list-group" role="tablist">',
+ ' <button type="button" data-bs-target="#home" role="tab">Home</button>',
+ ' <button type="button" id="triggerProfile" data-bs-target="#profile" role="tab">Profile</button>',
+ '</div>',
+ '<div>',
+ ' <div id="home" role="tabpanel"></div>',
+ ' <div id="profile" role="tabpanel"></div>',
+ '</div>'
+ ].join('')
+
+ const profileTriggerEl = fixtureEl.querySelector('#triggerProfile')
+ const tab = new Tab(profileTriggerEl)
+
+ profileTriggerEl.addEventListener('shown.bs.tab', () => {
+ expect(fixtureEl.querySelector('#profile')).toHaveClass('active')
+ resolve()
+ })
+
+ tab.show()
+ })
})
- it('should activate element by tab id in ordered list', done => {
+ it('should work with tab id being an int', done => {
fixtureEl.innerHTML = [
- '<ol class="nav nav-pills">',
- ' <li><button type="button" data-bs-target="#home" role="tab">Home</button></li>',
- ' <li><button type="button" id="triggerProfile" href="#profile" role="tab">Profile</button></li>',
- '</ol>',
- '<ol>',
- ' <li id="home" role="tabpanel"></li>',
- ' <li id="profile" role="tabpanel"></li>',
- '</ol>'
+ '<div class="card-header d-block d-inline-block">',
+ ' <ul class="nav nav-tabs card-header-tabs" id="page_tabs">',
+ ' <li class="nav-item">',
+ ' <a class="nav-link" draggable="false" data-toggle="tab" href="#tab1">',
+ ' Working Tab 1 (#tab1)',
+ ' </a>',
+ ' </li>',
+ ' <li class="nav-item">',
+ ' <a id="trigger2" class="nav-link" draggable="false" data-toggle="tab" href="#2">',
+ ' Tab with numeric ID should work (#2)',
+ ' </a>',
+ ' </li>',
+ ' </ul>',
+ '</div>',
+ '<div class="card-body">',
+ ' <div class="tab-content" id="page_content">',
+ ' <div class="tab-pane fade" id="tab1">',
+ ' Working Tab 1 (#tab1) Content Here',
+ ' </div>',
+ ' <div class="tab-pane fade" id="2">',
+ ' Working Tab 2 (#2) with numeric ID',
+ ' </div>',
+ '</div>'
].join('')
-
- const profileTriggerEl = fixtureEl.querySelector('#triggerProfile')
+ const profileTriggerEl = fixtureEl.querySelector('#trigger2')
const tab = new Tab(profileTriggerEl)
profileTriggerEl.addEventListener('shown.bs.tab', () => {
- expect(fixtureEl.querySelector('#profile').classList.contains('active')).toEqual(true)
+ expect(fixtureEl.querySelector(`#${CSS.escape('2')}`)).toHaveClass('active')
done()
})
tab.show()
})
- it('should activate element by tab id in nav list', done => {
- fixtureEl.innerHTML = [
- '<nav class="nav">',
- ' <button type="button" data-bs-target="#home" role="tab">Home</button>',
- ' <button type="button" id="triggerProfile" data-bs-target="#profile" role="tab">Profile</a>',
- '</nav>',
- '<div><div id="home" role="tabpanel"></div><div id="profile" role="tabpanel"></div></div>'
- ].join('')
+ it('should not fire shown when show is prevented', () => {
+ return new Promise((resolve, reject) => {
+ fixtureEl.innerHTML = '<div class="nav"><div class="nav-link"></div></div>'
+
+ const navEl = fixtureEl.querySelector('.nav > div')
+ const tab = new Tab(navEl)
+ const expectDone = () => {
+ setTimeout(() => {
+ expect().nothing()
+ resolve()
+ }, 30)
+ }
+
+ navEl.addEventListener('show.bs.tab', ev => {
+ ev.preventDefault()
+ expectDone()
+ })
- const profileTriggerEl = fixtureEl.querySelector('#triggerProfile')
- const tab = new Tab(profileTriggerEl)
+ navEl.addEventListener('shown.bs.tab', () => {
+ reject(new Error('should not trigger shown event'))
+ })
- profileTriggerEl.addEventListener('shown.bs.tab', () => {
- expect(fixtureEl.querySelector('#profile').classList.contains('active')).toEqual(true)
- done()
+ tab.show()
})
+ })
- tab.show()
+ it('should not fire shown when tab is already active', () => {
+ return new Promise((resolve, reject) => {
+ fixtureEl.innerHTML = [
+ '<ul class="nav nav-tabs" role="tablist">',
+ ' <li class="nav-item" role="presentation"><button type="button" data-bs-target="#home" class="nav-link active" role="tab" aria-selected="true">Home</button></li>',
+ ' <li class="nav-item" role="presentation"><button type="button" data-bs-target="#profile" class="nav-link" role="tab">Profile</button></li>',
+ '</ul>',
+ '<div class="tab-content">',
+ ' <div class="tab-pane active" id="home" role="tabpanel"></div>',
+ ' <div class="tab-pane" id="profile" role="tabpanel"></div>',
+ '</div>'
+ ].join('')
+
+ const triggerActive = fixtureEl.querySelector('button.active')
+ const tab = new Tab(triggerActive)
+
+ triggerActive.addEventListener('shown.bs.tab', () => {
+ reject(new Error('should not trigger shown event'))
+ })
+
+ tab.show()
+ setTimeout(() => {
+ expect().nothing()
+ resolve()
+ }, 30)
+ })
})
- it('should activate element by tab id in list group', done => {
- fixtureEl.innerHTML = [
- '<div class="list-group" role="tablist">',
- ' <button type="button" data-bs-target="#home" role="tab">Home</button>',
- ' <button type="button" id="triggerProfile" data-bs-target="#profile" role="tab">Profile</button>',
- '</div>',
- '<div><div id="home" role="tabpanel"></div><div id="profile" role="tabpanel"></div></div>'
- ].join('')
+ it('show and shown events should reference correct relatedTarget', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<ul class="nav nav-tabs" role="tablist">',
+ ' <li class="nav-item" role="presentation"><button type="button" data-bs-target="#home" class="nav-link active" role="tab" aria-selected="true">Home</button></li>',
+ ' <li class="nav-item" role="presentation"><button type="button" id="triggerProfile" data-bs-target="#profile" class="nav-link" role="tab">Profile</button></li>',
+ '</ul>',
+ '<div class="tab-content">',
+ ' <div class="tab-pane active" id="home" role="tabpanel"></div>',
+ ' <div class="tab-pane" id="profile" role="tabpanel"></div>',
+ '</div>'
+ ].join('')
+
+ const secondTabTrigger = fixtureEl.querySelector('#triggerProfile')
+ const secondTab = new Tab(secondTabTrigger)
+
+ secondTabTrigger.addEventListener('show.bs.tab', ev => {
+ expect(ev.relatedTarget.getAttribute('data-bs-target')).toEqual('#home')
+ })
- const profileTriggerEl = fixtureEl.querySelector('#triggerProfile')
- const tab = new Tab(profileTriggerEl)
+ secondTabTrigger.addEventListener('shown.bs.tab', ev => {
+ expect(ev.relatedTarget.getAttribute('data-bs-target')).toEqual('#home')
+ expect(secondTabTrigger.getAttribute('aria-selected')).toEqual('true')
+ expect(fixtureEl.querySelector('button:not(.active)').getAttribute('aria-selected')).toEqual('false')
+ resolve()
+ })
- profileTriggerEl.addEventListener('shown.bs.tab', () => {
- expect(fixtureEl.querySelector('#profile').classList.contains('active')).toEqual(true)
- done()
+ secondTab.show()
})
+ })
- tab.show()
+ it('should fire hide and hidden events', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<ul class="nav" role="tablist">',
+ ' <li><button type="button" data-bs-target="#home" role="tab">Home</button></li>',
+ ' <li><button type="button" data-bs-target="#profile" role="tab">Profile</button></li>',
+ '</ul>'
+ ].join('')
+
+ const triggerList = fixtureEl.querySelectorAll('button')
+ const firstTab = new Tab(triggerList[0])
+ const secondTab = new Tab(triggerList[1])
+
+ let hideCalled = false
+ triggerList[0].addEventListener('shown.bs.tab', () => {
+ secondTab.show()
+ })
+
+ triggerList[0].addEventListener('hide.bs.tab', ev => {
+ hideCalled = true
+ expect(ev.relatedTarget.getAttribute('data-bs-target')).toEqual('#profile')
+ })
+
+ triggerList[0].addEventListener('hidden.bs.tab', ev => {
+ expect(hideCalled).toBeTrue()
+ expect(ev.relatedTarget.getAttribute('data-bs-target')).toEqual('#profile')
+ resolve()
+ })
+
+ firstTab.show()
+ })
})
- it('should not fire shown when show is prevented', done => {
- fixtureEl.innerHTML = '<div class="nav"></div>'
+ it('should not fire hidden when hide is prevented', () => {
+ return new Promise((resolve, reject) => {
+ fixtureEl.innerHTML = [
+ '<ul class="nav" role="tablist">',
+ ' <li><button type="button" data-bs-target="#home" role="tab">Home</button></li>',
+ ' <li><button type="button" data-bs-target="#profile" role="tab">Profile</button></li>',
+ '</ul>'
+ ].join('')
+
+ const triggerList = fixtureEl.querySelectorAll('button')
+ const firstTab = new Tab(triggerList[0])
+ const secondTab = new Tab(triggerList[1])
+ const expectDone = () => {
+ setTimeout(() => {
+ expect().nothing()
+ resolve()
+ }, 30)
+ }
+
+ triggerList[0].addEventListener('shown.bs.tab', () => {
+ secondTab.show()
+ })
- const navEl = fixtureEl.querySelector('div')
- const tab = new Tab(navEl)
- const expectDone = () => {
- setTimeout(() => {
- expect().nothing()
- done()
- }, 30)
- }
+ triggerList[0].addEventListener('hide.bs.tab', ev => {
+ ev.preventDefault()
+ expectDone()
+ })
+
+ triggerList[0].addEventListener('hidden.bs.tab', () => {
+ reject(new Error('should not trigger hidden'))
+ })
- navEl.addEventListener('show.bs.tab', ev => {
- ev.preventDefault()
- expectDone()
+ firstTab.show()
})
+ })
+
+ it('should handle removed tabs', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<ul class="nav nav-tabs" role="tablist">',
+ ' <li class="nav-item" role="presentation">',
+ ' <a class="nav-link nav-tab" href="#profile" role="tab" data-bs-toggle="tab">',
+ ' <button class="btn-close" aria-label="Close"></button>',
+ ' </a>',
+ ' </li>',
+ ' <li class="nav-item" role="presentation">',
+ ' <a id="secondNav" class="nav-link nav-tab" href="#buzz" role="tab" data-bs-toggle="tab">',
+ ' <button class="btn-close" aria-label="Close"></button>',
+ ' </a>',
+ ' </li>',
+ ' <li class="nav-item" role="presentation">',
+ ' <a class="nav-link nav-tab" href="#references" role="tab" data-bs-toggle="tab">',
+ ' <button id="btnClose" class="btn-close" aria-label="Close"></button>',
+ ' </a>',
+ ' </li>',
+ '</ul>',
+ '<div class="tab-content">',
+ ' <div role="tabpanel" class="tab-pane fade show active" id="profile">test 1</div>',
+ ' <div role="tabpanel" class="tab-pane fade" id="buzz">test 2</div>',
+ ' <div role="tabpanel" class="tab-pane fade" id="references">test 3</div>',
+ '</div>'
+ ].join('')
+
+ const secondNavEl = fixtureEl.querySelector('#secondNav')
+ const btnCloseEl = fixtureEl.querySelector('#btnClose')
+ const secondNavTab = new Tab(secondNavEl)
+
+ secondNavEl.addEventListener('shown.bs.tab', () => {
+ expect(fixtureEl.querySelectorAll('.nav-tab')).toHaveSize(2)
+ resolve()
+ })
- navEl.addEventListener('shown.bs.tab', () => {
- throw new Error('should not trigger shown event')
+ btnCloseEl.addEventListener('click', () => {
+ const linkEl = btnCloseEl.parentNode
+ const liEl = linkEl.parentNode
+ const tabId = linkEl.getAttribute('href')
+ const tabIdEl = fixtureEl.querySelector(tabId)
+
+ liEl.remove()
+ tabIdEl.remove()
+ secondNavTab.show()
+ })
+
+ btnCloseEl.click()
})
+ })
- tab.show()
+ it('should not focus on opened tab', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<ul class="nav" role="tablist">',
+ ' <li><button type="button" id="home" data-bs-target="#home" role="tab">Home</button></li>',
+ ' <li><button type="button" id="triggerProfile" data-bs-target="#profile" role="tab">Profile</button></li>',
+ '</ul>',
+ '<ul>',
+ ' <li id="home" role="tabpanel"></li>',
+ ' <li id="profile" role="tabpanel"></li>',
+ '</ul>'
+ ].join('')
+
+ const firstTab = fixtureEl.querySelector('#home')
+ firstTab.focus()
+
+ const profileTriggerEl = fixtureEl.querySelector('#triggerProfile')
+ const tab = new Tab(profileTriggerEl)
+
+ profileTriggerEl.addEventListener('shown.bs.tab', () => {
+ expect(document.activeElement).toBe(firstTab)
+ expect(document.activeElement).not.toBe(profileTriggerEl)
+ resolve()
+ })
+
+ tab.show()
+ })
})
+ })
+
+ describe('dispose', () => {
+ it('should dispose a tab', () => {
+ fixtureEl.innerHTML = '<div class="nav"><div class="nav-link"></div></div>'
+
+ const el = fixtureEl.querySelector('.nav > div')
+ const tab = new Tab(fixtureEl.querySelector('.nav > div'))
+
+ expect(Tab.getInstance(el)).not.toBeNull()
- it('should not fire shown when tab is already active', done => {
+ tab.dispose()
+
+ expect(Tab.getInstance(el)).toBeNull()
+ })
+ })
+
+ describe('_activate', () => {
+ it('should not be called if element argument is null', () => {
+ fixtureEl.innerHTML = [
+ '<ul class="nav" role="tablist">',
+ ' <li class="nav-link"></li>',
+ '</ul>'
+ ].join('')
+
+ const tabEl = fixtureEl.querySelector('.nav-link')
+ const tab = new Tab(tabEl)
+ const spy = jasmine.createSpy('spy')
+
+ const spyQueue = spyOn(tab, '_queueCallback')
+ tab._activate(null, spy)
+ expect(spyQueue).not.toHaveBeenCalled()
+ expect(spy).not.toHaveBeenCalled()
+ })
+ })
+
+ describe('_setInitialAttributes', () => {
+ it('should put aria attributes', () => {
fixtureEl.innerHTML = [
- '<ul class="nav nav-tabs" role="tablist">',
- ' <li class="nav-item" role="presentation"><button type="button" data-bs-target="#home" class="nav-link active" role="tab" aria-selected="true">Home</button></li>',
- ' <li class="nav-item" role="presentation"><button type="button" data-bs-target="#profile" class="nav-link" role="tab">Profile</button></li>',
+ '<ul class="nav">',
+ ' <li class="nav-link" id="foo" data-bs-target="#panel"></li>',
+ ' <li class="nav-link" data-bs-target="#panel2"></li>',
'</ul>',
- '<div class="tab-content">',
- ' <div class="tab-pane active" id="home" role="tabpanel"></div>',
- ' <div class="tab-pane" id="profile" role="tabpanel"></div>',
- '</div>'
+ '<div id="panel"></div>',
+ '<div id="panel2"></div>'
].join('')
- const triggerActive = fixtureEl.querySelector('button.active')
- const tab = new Tab(triggerActive)
+ const tabEl = fixtureEl.querySelector('.nav-link')
+ const parent = fixtureEl.querySelector('.nav')
+ const children = fixtureEl.querySelectorAll('.nav-link')
+ const tabPanel = fixtureEl.querySelector('#panel')
+ const tabPanel2 = fixtureEl.querySelector('#panel2')
- triggerActive.addEventListener('shown.bs.tab', () => {
- throw new Error('should not trigger shown event')
- })
+ expect(parent.getAttribute('role')).toEqual(null)
+ expect(tabEl.getAttribute('role')).toEqual(null)
+ expect(tabPanel.getAttribute('role')).toEqual(null)
+ const tab = new Tab(tabEl)
+ tab._setInitialAttributes(parent, children)
- tab.show()
- setTimeout(() => {
- expect().nothing()
- done()
- }, 30)
+ expect(parent.getAttribute('role')).toEqual('tablist')
+ expect(tabEl.getAttribute('role')).toEqual('tab')
+
+ expect(tabPanel.getAttribute('role')).toEqual('tabpanel')
+ expect(tabPanel2.getAttribute('role')).toEqual('tabpanel')
+ expect(tabPanel.hasAttribute('tabindex')).toBeFalse()
+ expect(tabPanel.hasAttribute('tabindex2')).toBeFalse()
+
+ expect(tabPanel.getAttribute('aria-labelledby')).toEqual('foo')
+ expect(tabPanel2.hasAttribute('aria-labelledby')).toBeFalse()
})
+ })
- it('show and shown events should reference correct relatedTarget', done => {
+ describe('_keydown', () => {
+ it('if event is not one of left/right/up/down arrow, ignore it', () => {
fixtureEl.innerHTML = [
- '<ul class="nav nav-tabs" role="tablist">',
- ' <li class="nav-item" role="presentation"><button type="button" data-bs-target="#home" class="nav-link active" role="tab" aria-selected="true">Home</button></li>',
- ' <li class="nav-item" role="presentation"><button type="button" id="triggerProfile" data-bs-target="#profile" class="nav-link" role="tab">Profile</button></li>',
- '</ul>',
- '<div class="tab-content">',
- ' <div class="tab-pane active" id="home" role="tabpanel"></div>',
- ' <div class="tab-pane" id="profile" role="tabpanel"></div>',
+ '<ul class="nav">',
+ ' <li class="nav-link" data-bs-toggle="tab"></li>',
+ '</ul>'
+ ].join('')
+
+ const tabEl = fixtureEl.querySelector('.nav-link')
+ const tab = new Tab(tabEl)
+
+ const keydown = createEvent('keydown')
+ keydown.key = 'Enter'
+ const spyStop = spyOn(Event.prototype, 'stopPropagation').and.callThrough()
+ const spyPrevent = spyOn(Event.prototype, 'preventDefault').and.callThrough()
+ const spyKeydown = spyOn(tab, '_keydown')
+ const spyGet = spyOn(tab, '_getChildren')
+
+ tabEl.dispatchEvent(keydown)
+ expect(spyKeydown).toHaveBeenCalled()
+ expect(spyGet).not.toHaveBeenCalled()
+
+ expect(spyStop).not.toHaveBeenCalled()
+ expect(spyPrevent).not.toHaveBeenCalled()
+ })
+
+ it('if keydown event is right/down arrow, handle it', () => {
+ fixtureEl.innerHTML = [
+ '<div class="nav">',
+ ' <span id="tab1" class="nav-link" data-bs-toggle="tab"></span>',
+ ' <span id="tab2" class="nav-link" data-bs-toggle="tab"></span>',
+ ' <span id="tab3" class="nav-link" data-bs-toggle="tab"></span>',
'</div>'
].join('')
- const secondTabTrigger = fixtureEl.querySelector('#triggerProfile')
- const secondTab = new Tab(secondTabTrigger)
+ const tabEl1 = fixtureEl.querySelector('#tab1')
+ const tabEl2 = fixtureEl.querySelector('#tab2')
+ const tabEl3 = fixtureEl.querySelector('#tab3')
+ const tab1 = new Tab(tabEl1)
+ const tab2 = new Tab(tabEl2)
+ const tab3 = new Tab(tabEl3)
+ const spyShow1 = spyOn(tab1, 'show').and.callThrough()
+ const spyShow2 = spyOn(tab2, 'show').and.callThrough()
+ const spyShow3 = spyOn(tab3, 'show').and.callThrough()
+ const spyFocus1 = spyOn(tabEl1, 'focus').and.callThrough()
+ const spyFocus2 = spyOn(tabEl2, 'focus').and.callThrough()
+ const spyFocus3 = spyOn(tabEl3, 'focus').and.callThrough()
+
+ const spyStop = spyOn(Event.prototype, 'stopPropagation').and.callThrough()
+ const spyPrevent = spyOn(Event.prototype, 'preventDefault').and.callThrough()
+
+ let keydown = createEvent('keydown')
+ keydown.key = 'ArrowRight'
+
+ tabEl1.dispatchEvent(keydown)
+ expect(spyShow2).toHaveBeenCalled()
+ expect(spyFocus2).toHaveBeenCalled()
+
+ keydown = createEvent('keydown')
+ keydown.key = 'ArrowDown'
+
+ tabEl2.dispatchEvent(keydown)
+ expect(spyShow3).toHaveBeenCalled()
+ expect(spyFocus3).toHaveBeenCalled()
+
+ tabEl3.dispatchEvent(keydown)
+ expect(spyShow1).toHaveBeenCalled()
+ expect(spyFocus1).toHaveBeenCalled()
+
+ expect(spyStop).toHaveBeenCalledTimes(3)
+ expect(spyPrevent).toHaveBeenCalledTimes(3)
+ })
- secondTabTrigger.addEventListener('show.bs.tab', ev => {
- expect(ev.relatedTarget.getAttribute('data-bs-target')).toEqual('#home')
- })
+ it('if keydown event is left arrow, handle it', () => {
+ fixtureEl.innerHTML = [
+ '<div class="nav">',
+ ' <span id="tab1" class="nav-link" data-bs-toggle="tab"></span>',
+ ' <span id="tab2" class="nav-link" data-bs-toggle="tab"></span>',
+ '</div>'
+ ].join('')
- secondTabTrigger.addEventListener('shown.bs.tab', ev => {
- expect(ev.relatedTarget.getAttribute('data-bs-target')).toEqual('#home')
- expect(secondTabTrigger.getAttribute('aria-selected')).toEqual('true')
- expect(fixtureEl.querySelector('button:not(.active)').getAttribute('aria-selected')).toEqual('false')
- done()
- })
+ const tabEl1 = fixtureEl.querySelector('#tab1')
+ const tabEl2 = fixtureEl.querySelector('#tab2')
+ const tab1 = new Tab(tabEl1)
+ const tab2 = new Tab(tabEl2)
+ const spyShow1 = spyOn(tab1, 'show').and.callThrough()
+ const spyShow2 = spyOn(tab2, 'show').and.callThrough()
+ const spyFocus1 = spyOn(tabEl1, 'focus').and.callThrough()
+ const spyFocus2 = spyOn(tabEl2, 'focus').and.callThrough()
+
+ const spyStop = spyOn(Event.prototype, 'stopPropagation').and.callThrough()
+ const spyPrevent = spyOn(Event.prototype, 'preventDefault').and.callThrough()
+
+ let keydown = createEvent('keydown')
+ keydown.key = 'ArrowLeft'
+
+ tabEl2.dispatchEvent(keydown)
+ expect(spyShow1).toHaveBeenCalled()
+ expect(spyFocus1).toHaveBeenCalled()
+
+ keydown = createEvent('keydown')
+ keydown.key = 'ArrowUp'
+
+ tabEl1.dispatchEvent(keydown)
+ expect(spyShow2).toHaveBeenCalled()
+ expect(spyFocus2).toHaveBeenCalled()
- secondTab.show()
+ expect(spyStop).toHaveBeenCalledTimes(2)
+ expect(spyPrevent).toHaveBeenCalledTimes(2)
})
- it('should fire hide and hidden events', done => {
+ it('if keydown event is Home, handle it', () => {
fixtureEl.innerHTML = [
- '<ul class="nav" role="tablist">',
- ' <li><button type="button" data-bs-target="#home" role="tab">Home</button></li>',
- ' <li><button type="button" data-bs-target="#profile">Profile</button></li>',
- '</ul>'
+ '<div class="nav">',
+ ' <span id="tab1" class="nav-link" data-bs-toggle="tab"></span>',
+ ' <span id="tab2" class="nav-link" data-bs-toggle="tab"></span>',
+ ' <span id="tab3" class="nav-link" data-bs-toggle="tab"></span>',
+ '</div>'
].join('')
- const triggerList = fixtureEl.querySelectorAll('button')
- const firstTab = new Tab(triggerList[0])
- const secondTab = new Tab(triggerList[1])
+ const tabEl1 = fixtureEl.querySelector('#tab1')
+ const tabEl3 = fixtureEl.querySelector('#tab3')
- let hideCalled = false
- triggerList[0].addEventListener('shown.bs.tab', () => {
- secondTab.show()
- })
+ const tab3 = new Tab(tabEl3)
+ tab3.show()
- triggerList[0].addEventListener('hide.bs.tab', ev => {
- hideCalled = true
- expect(ev.relatedTarget.getAttribute('data-bs-target')).toEqual('#profile')
- })
+ const spyShown = jasmine.createSpy()
+ tabEl1.addEventListener('shown.bs.tab', spyShown)
- triggerList[0].addEventListener('hidden.bs.tab', ev => {
- expect(hideCalled).toEqual(true)
- expect(ev.relatedTarget.getAttribute('data-bs-target')).toEqual('#profile')
- done()
- })
+ const keydown = createEvent('keydown')
+ keydown.key = 'Home'
+
+ tabEl3.dispatchEvent(keydown)
- firstTab.show()
+ expect(spyShown).toHaveBeenCalled()
})
- it('should not fire hidden when hide is prevented', done => {
+ it('if keydown event is End, handle it', () => {
fixtureEl.innerHTML = [
- '<ul class="nav" role="tablist">',
- ' <li><button type="button" data-bs-target="#home" role="tab">Home</button></li>',
- ' <li><button type="button" data-bs-target="#profile" role="tab">Profile</button></li>',
- '</ul>'
+ '<div class="nav">',
+ ' <span id="tab1" class="nav-link" data-bs-toggle="tab"></span>',
+ ' <span id="tab2" class="nav-link" data-bs-toggle="tab"></span>',
+ ' <span id="tab3" class="nav-link" data-bs-toggle="tab"></span>',
+ '</div>'
].join('')
- const triggerList = fixtureEl.querySelectorAll('button')
- const firstTab = new Tab(triggerList[0])
- const secondTab = new Tab(triggerList[1])
- const expectDone = () => {
- setTimeout(() => {
- expect().nothing()
- done()
- }, 30)
- }
+ const tabEl1 = fixtureEl.querySelector('#tab1')
+ const tabEl3 = fixtureEl.querySelector('#tab3')
- triggerList[0].addEventListener('shown.bs.tab', () => {
- secondTab.show()
- })
+ const tab1 = new Tab(tabEl1)
+ tab1.show()
- triggerList[0].addEventListener('hide.bs.tab', ev => {
- ev.preventDefault()
- expectDone()
- })
+ const spyShown = jasmine.createSpy()
+ tabEl3.addEventListener('shown.bs.tab', spyShown)
- triggerList[0].addEventListener('hidden.bs.tab', () => {
- throw new Error('should not trigger hidden')
- })
+ const keydown = createEvent('keydown')
+ keydown.key = 'End'
+
+ tabEl1.dispatchEvent(keydown)
- firstTab.show()
+ expect(spyShown).toHaveBeenCalled()
})
- it('should handle removed tabs', done => {
+ it('if keydown event is right arrow and next element is disabled', () => {
fixtureEl.innerHTML = [
- '<ul class="nav nav-tabs" role="tablist">',
- ' <li class="nav-item" role="presentation">',
- ' <a class="nav-link nav-tab" href="#profile" role="tab" data-bs-toggle="tab">',
- ' <button class="btn-close" aria-label="Close"></button>',
- ' </a>',
- ' </li>',
- ' <li class="nav-item" role="presentation">',
- ' <a id="secondNav" class="nav-link nav-tab" href="#buzz" role="tab" data-bs-toggle="tab">',
- ' <button class="btn-close" aria-label="Close"></button>',
- ' </a>',
- ' </li>',
- ' <li class="nav-item" role="presentation">',
- ' <a class="nav-link nav-tab" href="#references" role="tab" data-bs-toggle="tab">',
- ' <button id="btnClose" class="btn-close" aria-label="Close"></button>',
- ' </a>',
- ' </li>',
- '</ul>',
- '<div class="tab-content">',
- ' <div role="tabpanel" class="tab-pane fade show active" id="profile">test 1</div>',
- ' <div role="tabpanel" class="tab-pane fade" id="buzz">test 2</div>',
- ' <div role="tabpanel" class="tab-pane fade" id="references">test 3</div>',
+ '<div class="nav">',
+ ' <span id="tab1" class="nav-link" data-bs-toggle="tab"></span>',
+ ' <span id="tab2" class="nav-link" data-bs-toggle="tab" disabled></span>',
+ ' <span id="tab3" class="nav-link disabled" data-bs-toggle="tab"></span>',
+ ' <span id="tab4" class="nav-link" data-bs-toggle="tab"></span>',
'</div>'
].join('')
- const secondNavEl = fixtureEl.querySelector('#secondNav')
- const btnCloseEl = fixtureEl.querySelector('#btnClose')
- const secondNavTab = new Tab(secondNavEl)
+ const tabEl1 = fixtureEl.querySelector('#tab1')
+ const tabEl2 = fixtureEl.querySelector('#tab2')
+ const tabEl3 = fixtureEl.querySelector('#tab3')
+ const tabEl4 = fixtureEl.querySelector('#tab4')
+ const tab1 = new Tab(tabEl1)
+ const tab2 = new Tab(tabEl2)
+ const tab3 = new Tab(tabEl3)
+ const tab4 = new Tab(tabEl4)
+ const spy1 = spyOn(tab1, 'show').and.callThrough()
+ const spy2 = spyOn(tab2, 'show').and.callThrough()
+ const spy3 = spyOn(tab3, 'show').and.callThrough()
+ const spy4 = spyOn(tab4, 'show').and.callThrough()
+ const spyFocus1 = spyOn(tabEl1, 'focus').and.callThrough()
+ const spyFocus2 = spyOn(tabEl2, 'focus').and.callThrough()
+ const spyFocus3 = spyOn(tabEl3, 'focus').and.callThrough()
+ const spyFocus4 = spyOn(tabEl4, 'focus').and.callThrough()
+
+ const keydown = createEvent('keydown')
+ keydown.key = 'ArrowRight'
+
+ tabEl1.dispatchEvent(keydown)
+ expect(spy1).not.toHaveBeenCalled()
+ expect(spy2).not.toHaveBeenCalled()
+ expect(spy3).not.toHaveBeenCalled()
+ expect(spy4).toHaveBeenCalledTimes(1)
+ expect(spyFocus1).not.toHaveBeenCalled()
+ expect(spyFocus2).not.toHaveBeenCalled()
+ expect(spyFocus3).not.toHaveBeenCalled()
+ expect(spyFocus4).toHaveBeenCalledTimes(1)
+ })
- secondNavEl.addEventListener('shown.bs.tab', () => {
- expect(fixtureEl.querySelectorAll('.nav-tab').length).toEqual(2)
- done()
- })
+ it('if keydown event is left arrow and next element is disabled', () => {
+ fixtureEl.innerHTML = [
+ '<div class="nav">',
+ ' <span id="tab1" class="nav-link" data-bs-toggle="tab"></span>',
+ ' <span id="tab2" class="nav-link" data-bs-toggle="tab" disabled></span>',
+ ' <span id="tab3" class="nav-link disabled" data-bs-toggle="tab"></span>',
+ ' <span id="tab4" class="nav-link" data-bs-toggle="tab"></span>',
+ '</div>'
+ ].join('')
- btnCloseEl.addEventListener('click', () => {
- const linkEl = btnCloseEl.parentNode
- const liEl = linkEl.parentNode
- const tabId = linkEl.getAttribute('href')
- const tabIdEl = fixtureEl.querySelector(tabId)
+ const tabEl1 = fixtureEl.querySelector('#tab1')
+ const tabEl2 = fixtureEl.querySelector('#tab2')
+ const tabEl3 = fixtureEl.querySelector('#tab3')
+ const tabEl4 = fixtureEl.querySelector('#tab4')
+ const tab1 = new Tab(tabEl1)
+ const tab2 = new Tab(tabEl2)
+ const tab3 = new Tab(tabEl3)
+ const tab4 = new Tab(tabEl4)
+ const spy1 = spyOn(tab1, 'show').and.callThrough()
+ const spy2 = spyOn(tab2, 'show').and.callThrough()
+ const spy3 = spyOn(tab3, 'show').and.callThrough()
+ const spy4 = spyOn(tab4, 'show').and.callThrough()
+ const spyFocus1 = spyOn(tabEl1, 'focus').and.callThrough()
+ const spyFocus2 = spyOn(tabEl2, 'focus').and.callThrough()
+ const spyFocus3 = spyOn(tabEl3, 'focus').and.callThrough()
+ const spyFocus4 = spyOn(tabEl4, 'focus').and.callThrough()
+
+ const keydown = createEvent('keydown')
+ keydown.key = 'ArrowLeft'
+
+ tabEl4.dispatchEvent(keydown)
+ expect(spy4).not.toHaveBeenCalled()
+ expect(spy3).not.toHaveBeenCalled()
+ expect(spy2).not.toHaveBeenCalled()
+ expect(spy1).toHaveBeenCalledTimes(1)
+ expect(spyFocus4).not.toHaveBeenCalled()
+ expect(spyFocus3).not.toHaveBeenCalled()
+ expect(spyFocus2).not.toHaveBeenCalled()
+ expect(spyFocus1).toHaveBeenCalledTimes(1)
+ })
- liEl.remove()
- tabIdEl.remove()
- secondNavTab.show()
- })
+ it('if keydown event is Home and first element is disabled', () => {
+ fixtureEl.innerHTML = [
+ '<div class="nav">',
+ ' <span id="tab1" class="nav-link disabled" data-bs-toggle="tab" disabled></span>',
+ ' <span id="tab2" class="nav-link" data-bs-toggle="tab"></span>',
+ ' <span id="tab3" class="nav-link" data-bs-toggle="tab"></span>',
+ '</div>'
+ ].join('')
+
+ const tabEl1 = fixtureEl.querySelector('#tab1')
+ const tabEl2 = fixtureEl.querySelector('#tab2')
+ const tabEl3 = fixtureEl.querySelector('#tab3')
+ const tab3 = new Tab(tabEl3)
- btnCloseEl.click()
+ tab3.show()
+
+ const spyShown1 = jasmine.createSpy()
+ const spyShown2 = jasmine.createSpy()
+ tabEl1.addEventListener('shown.bs.tab', spyShown1)
+ tabEl2.addEventListener('shown.bs.tab', spyShown2)
+
+ const keydown = createEvent('keydown')
+ keydown.key = 'Home'
+
+ tabEl3.dispatchEvent(keydown)
+
+ expect(spyShown1).not.toHaveBeenCalled()
+ expect(spyShown2).toHaveBeenCalled()
})
- })
- describe('dispose', () => {
- it('should dispose a tab', () => {
- fixtureEl.innerHTML = '<div></div>'
+ it('if keydown event is End and last element is disabled', () => {
+ fixtureEl.innerHTML = [
+ '<div class="nav">',
+ ' <span id="tab1" class="nav-link" data-bs-toggle="tab"></span>',
+ ' <span id="tab2" class="nav-link" data-bs-toggle="tab"></span>',
+ ' <span id="tab3" class="nav-link" data-bs-toggle="tab" disabled></span>',
+ '</div>'
+ ].join('')
- const el = fixtureEl.querySelector('div')
- const tab = new Tab(fixtureEl.querySelector('div'))
+ const tabEl1 = fixtureEl.querySelector('#tab1')
+ const tabEl2 = fixtureEl.querySelector('#tab2')
+ const tabEl3 = fixtureEl.querySelector('#tab3')
+ const tab1 = new Tab(tabEl1)
- expect(Tab.getInstance(el)).not.toBeNull()
+ tab1.show()
- tab.dispose()
+ const spyShown2 = jasmine.createSpy()
+ const spyShown3 = jasmine.createSpy()
+ tabEl2.addEventListener('shown.bs.tab', spyShown2)
+ tabEl3.addEventListener('shown.bs.tab', spyShown3)
- expect(Tab.getInstance(el)).toBeNull()
+ const keydown = createEvent('keydown')
+ keydown.key = 'End'
+
+ tabEl1.dispatchEvent(keydown)
+
+ expect(spyShown3).not.toHaveBeenCalled()
+ expect(spyShown2).toHaveBeenCalled()
})
})
describe('jQueryInterface', () => {
it('should create a tab', () => {
- fixtureEl.innerHTML = '<div></div>'
+ fixtureEl.innerHTML = '<div class="nav"><div class="nav-link"></div></div>'
- const div = fixtureEl.querySelector('div')
+ const div = fixtureEl.querySelector('.nav > div')
jQueryMock.fn.tab = Tab.jQueryInterface
jQueryMock.elements = [div]
@@ -370,9 +842,9 @@ describe('Tab', () => {
})
it('should not re create a tab', () => {
- fixtureEl.innerHTML = '<div></div>'
+ fixtureEl.innerHTML = '<div class="nav"><div class="nav-link"></div></div>'
- const div = fixtureEl.querySelector('div')
+ const div = fixtureEl.querySelector('.nav > div')
const tab = new Tab(div)
jQueryMock.fn.tab = Tab.jQueryInterface
@@ -384,12 +856,12 @@ describe('Tab', () => {
})
it('should call a tab method', () => {
- fixtureEl.innerHTML = '<div></div>'
+ fixtureEl.innerHTML = '<div class="nav"><div class="nav-link"></div></div>'
- const div = fixtureEl.querySelector('div')
+ const div = fixtureEl.querySelector('.nav > div')
const tab = new Tab(div)
- spyOn(tab, 'show')
+ const spy = spyOn(tab, 'show')
jQueryMock.fn.tab = Tab.jQueryInterface
jQueryMock.elements = [div]
@@ -397,13 +869,13 @@ describe('Tab', () => {
jQueryMock.fn.tab.call(jQueryMock, 'show')
expect(Tab.getInstance(div)).toEqual(tab)
- expect(tab.show).toHaveBeenCalled()
+ expect(spy).toHaveBeenCalled()
})
it('should throw error on undefined method', () => {
- fixtureEl.innerHTML = '<div></div>'
+ fixtureEl.innerHTML = '<div class="nav"><div class="nav-link"></div></div>'
- const div = fixtureEl.querySelector('div')
+ const div = fixtureEl.querySelector('.nav > div')
const action = 'undefinedMethod'
jQueryMock.fn.tab = Tab.jQueryInterface
@@ -417,13 +889,13 @@ describe('Tab', () => {
describe('getInstance', () => {
it('should return null if there is no instance', () => {
- expect(Tab.getInstance(fixtureEl)).toEqual(null)
+ expect(Tab.getInstance(fixtureEl)).toBeNull()
})
it('should return this instance', () => {
- fixtureEl.innerHTML = '<div></div>'
+ fixtureEl.innerHTML = '<div class="nav"><div class="nav-link"></div></div>'
- const divEl = fixtureEl.querySelector('div')
+ const divEl = fixtureEl.querySelector('.nav > div')
const tab = new Tab(divEl)
expect(Tab.getInstance(divEl)).toEqual(tab)
@@ -433,7 +905,7 @@ describe('Tab', () => {
describe('getOrCreateInstance', () => {
it('should return tab instance', () => {
- fixtureEl.innerHTML = '<div></div>'
+ fixtureEl.innerHTML = '<div class="nav"><div class="nav-link"></div></div>'
const div = fixtureEl.querySelector('div')
const tab = new Tab(div)
@@ -444,37 +916,39 @@ describe('Tab', () => {
})
it('should return new instance when there is no tab instance', () => {
- fixtureEl.innerHTML = '<div></div>'
+ fixtureEl.innerHTML = '<div class="nav"><div class="nav-link"></div></div>'
const div = fixtureEl.querySelector('div')
- expect(Tab.getInstance(div)).toEqual(null)
+ expect(Tab.getInstance(div)).toBeNull()
expect(Tab.getOrCreateInstance(div)).toBeInstanceOf(Tab)
})
})
describe('data-api', () => {
- it('should create dynamically a tab', done => {
- fixtureEl.innerHTML = [
- '<ul class="nav nav-tabs" role="tablist">',
- ' <li class="nav-item" role="presentation"><button type="button" data-bs-target="#home" class="nav-link active" role="tab" aria-selected="true">Home</button></li>',
- ' <li class="nav-item" role="presentation"><button type="button" id="triggerProfile" data-bs-toggle="tab" data-bs-target="#profile" class="nav-link" role="tab">Profile</button></li>',
- '</ul>',
- '<div class="tab-content">',
- ' <div class="tab-pane active" id="home" role="tabpanel"></div>',
- ' <div class="tab-pane" id="profile" role="tabpanel"></div>',
- '</div>'
- ].join('')
-
- const secondTabTrigger = fixtureEl.querySelector('#triggerProfile')
+ it('should create dynamically a tab', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<ul class="nav nav-tabs" role="tablist">',
+ ' <li class="nav-item" role="presentation"><button type="button" data-bs-target="#home" class="nav-link active" role="tab" aria-selected="true">Home</button></li>',
+ ' <li class="nav-item" role="presentation"><button type="button" id="triggerProfile" data-bs-toggle="tab" data-bs-target="#profile" class="nav-link" role="tab">Profile</button></li>',
+ '</ul>',
+ '<div class="tab-content">',
+ ' <div class="tab-pane active" id="home" role="tabpanel"></div>',
+ ' <div class="tab-pane" id="profile" role="tabpanel"></div>',
+ '</div>'
+ ].join('')
+
+ const secondTabTrigger = fixtureEl.querySelector('#triggerProfile')
+
+ secondTabTrigger.addEventListener('shown.bs.tab', () => {
+ expect(secondTabTrigger).toHaveClass('active')
+ expect(fixtureEl.querySelector('#profile')).toHaveClass('active')
+ resolve()
+ })
- secondTabTrigger.addEventListener('shown.bs.tab', () => {
- expect(secondTabTrigger.classList.contains('active')).toEqual(true)
- expect(fixtureEl.querySelector('#profile').classList.contains('active')).toEqual(true)
- done()
+ secondTabTrigger.click()
})
-
- secondTabTrigger.click()
})
it('selected tab should deactivate previous selected link in dropdown', () => {
@@ -495,9 +969,9 @@ describe('Tab', () => {
const firstLiLinkEl = fixtureEl.querySelector('li:first-child a')
firstLiLinkEl.click()
- expect(firstLiLinkEl.classList.contains('active')).toEqual(true)
- expect(fixtureEl.querySelector('li:last-child a').classList.contains('active')).toEqual(false)
- expect(fixtureEl.querySelector('li:last-child .dropdown-menu a:first-child').classList.contains('active')).toEqual(false)
+ expect(firstLiLinkEl).toHaveClass('active')
+ expect(fixtureEl.querySelector('li:last-child a')).not.toHaveClass('active')
+ expect(fixtureEl.querySelector('li:last-child .dropdown-menu a:first-child')).not.toHaveClass('active')
})
it('selecting a dropdown tab does not activate another', () => {
@@ -529,10 +1003,10 @@ describe('Tab', () => {
const firstDropItem = fixtureEl.querySelector('#nav1 .dropdown-item')
firstDropItem.click()
- expect(firstDropItem.classList.contains('active')).toEqual(true)
- expect(fixtureEl.querySelector('#nav1 .dropdown-toggle').classList.contains('active')).toEqual(true)
- expect(fixtureEl.querySelector('#nav2 .dropdown-toggle').classList.contains('active')).toEqual(false)
- expect(fixtureEl.querySelector('#nav2 .dropdown-item').classList.contains('active')).toEqual(false)
+ expect(firstDropItem).toHaveClass('active')
+ expect(fixtureEl.querySelector('#nav1 .dropdown-toggle')).toHaveClass('active')
+ expect(fixtureEl.querySelector('#nav2 .dropdown-toggle')).not.toHaveClass('active')
+ expect(fixtureEl.querySelector('#nav2 .dropdown-item')).not.toHaveClass('active')
})
it('should support li > .dropdown-item', () => {
@@ -550,209 +1024,229 @@ describe('Tab', () => {
'</ul>'
].join('')
- const firstDropItem = fixtureEl.querySelector('.dropdown-item')
+ const dropItems = fixtureEl.querySelectorAll('.dropdown-item')
- firstDropItem.click()
- expect(firstDropItem.classList.contains('active')).toEqual(true)
- expect(fixtureEl.querySelector('.nav-link').classList.contains('active')).toEqual(false)
+ dropItems[1].click()
+ expect(dropItems[0]).not.toHaveClass('active')
+ expect(dropItems[1]).toHaveClass('active')
+ expect(fixtureEl.querySelector('.nav-link')).not.toHaveClass('active')
})
- it('should handle nested tabs', done => {
- fixtureEl.innerHTML = [
- '<nav class="nav nav-tabs" role="tablist">',
- ' <button type="button" id="tab1" data-bs-target="#x-tab1" class="nav-link" data-bs-toggle="tab" role="tab" aria-controls="x-tab1">Tab 1</button>',
- ' <button type="button" data-bs-target="#x-tab2" class="nav-link active" data-bs-toggle="tab" role="tab" aria-controls="x-tab2" aria-selected="true">Tab 2</button>',
- ' <button type="button" data-bs-target="#x-tab3" class="nav-link" data-bs-toggle="tab" role="tab" aria-controls="x-tab3">Tab 3</button>',
- '</nav>',
- '<div class="tab-content">',
- ' <div class="tab-pane" id="x-tab1" role="tabpanel">',
- ' <nav class="nav nav-tabs" role="tablist">',
- ' <button type="button" data-bs-target="#nested-tab1" class="nav-link active" data-bs-toggle="tab" role="tab" aria-controls="x-tab1" aria-selected="true">Nested Tab 1</button>',
- ' <button type="button" id="tabNested2" data-bs-target="#nested-tab2" class="nav-link" data-bs-toggle="tab" role="tab" aria-controls="x-profile">Nested Tab2</button>',
- ' </nav>',
- ' <div class="tab-content">',
- ' <div class="tab-pane active" id="nested-tab1" role="tabpanel">Nested Tab1 Content</div>',
- ' <div class="tab-pane" id="nested-tab2" role="tabpanel">Nested Tab2 Content</div>',
- ' </div>',
- ' </div>',
- ' <div class="tab-pane active" id="x-tab2" role="tabpanel">Tab2 Content</div>',
- ' <div class="tab-pane" id="x-tab3" role="tabpanel">Tab3 Content</div>',
- '</div>'
- ].join('')
-
- const tab1El = fixtureEl.querySelector('#tab1')
- const tabNested2El = fixtureEl.querySelector('#tabNested2')
- const xTab1El = fixtureEl.querySelector('#x-tab1')
+ it('should handle nested tabs', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<nav class="nav nav-tabs" role="tablist">',
+ ' <button type="button" id="tab1" data-bs-target="#x-tab1" class="nav-link" data-bs-toggle="tab" role="tab" aria-controls="x-tab1">Tab 1</button>',
+ ' <button type="button" data-bs-target="#x-tab2" class="nav-link active" data-bs-toggle="tab" role="tab" aria-controls="x-tab2" aria-selected="true">Tab 2</button>',
+ ' <button type="button" data-bs-target="#x-tab3" class="nav-link" data-bs-toggle="tab" role="tab" aria-controls="x-tab3">Tab 3</button>',
+ '</nav>',
+ '<div class="tab-content">',
+ ' <div class="tab-pane" id="x-tab1" role="tabpanel">',
+ ' <nav class="nav nav-tabs" role="tablist">',
+ ' <button type="button" data-bs-target="#nested-tab1" class="nav-link active" data-bs-toggle="tab" role="tab" aria-controls="x-tab1" aria-selected="true">Nested Tab 1</button>',
+ ' <button type="button" id="tabNested2" data-bs-target="#nested-tab2" class="nav-link" data-bs-toggle="tab" role="tab" aria-controls="x-profile">Nested Tab2</button>',
+ ' </nav>',
+ ' <div class="tab-content">',
+ ' <div class="tab-pane active" id="nested-tab1" role="tabpanel">Nested Tab1 Content</div>',
+ ' <div class="tab-pane" id="nested-tab2" role="tabpanel">Nested Tab2 Content</div>',
+ ' </div>',
+ ' </div>',
+ ' <div class="tab-pane active" id="x-tab2" role="tabpanel">Tab2 Content</div>',
+ ' <div class="tab-pane" id="x-tab3" role="tabpanel">Tab3 Content</div>',
+ '</div>'
+ ].join('')
+
+ const tab1El = fixtureEl.querySelector('#tab1')
+ const tabNested2El = fixtureEl.querySelector('#tabNested2')
+ const xTab1El = fixtureEl.querySelector('#x-tab1')
+
+ tabNested2El.addEventListener('shown.bs.tab', () => {
+ expect(xTab1El).toHaveClass('active')
+ resolve()
+ })
- tabNested2El.addEventListener('shown.bs.tab', () => {
- expect(xTab1El.classList.contains('active')).toEqual(true)
- done()
- })
+ tab1El.addEventListener('shown.bs.tab', () => {
+ expect(xTab1El).toHaveClass('active')
+ tabNested2El.click()
+ })
- tab1El.addEventListener('shown.bs.tab', () => {
- expect(xTab1El.classList.contains('active')).toEqual(true)
- tabNested2El.click()
+ tab1El.click()
})
-
- tab1El.click()
})
- it('should not remove fade class if no active pane is present', done => {
- fixtureEl.innerHTML = [
- '<ul class="nav nav-tabs" role="tablist">',
- ' <li class="nav-item" role="presentation"><button type="button" id="tab-home" data-bs-target="#home" class="nav-link" data-bs-toggle="tab" role="tab">Home</button></li>',
- ' <li class="nav-item" role="presentation"><button type="button" id="tab-profile" data-bs-target="#profile" class="nav-link" data-bs-toggle="tab" role="tab">Profile</button></li>',
- '</ul>',
- '<div class="tab-content">',
- ' <div class="tab-pane fade" id="home" role="tabpanel"></div>',
- ' <div class="tab-pane fade" id="profile" role="tabpanel"></div>',
- '</div>'
- ].join('')
-
- const triggerTabProfileEl = fixtureEl.querySelector('#tab-profile')
- const triggerTabHomeEl = fixtureEl.querySelector('#tab-home')
- const tabProfileEl = fixtureEl.querySelector('#profile')
- const tabHomeEl = fixtureEl.querySelector('#home')
-
- triggerTabProfileEl.addEventListener('shown.bs.tab', () => {
- expect(tabProfileEl.classList.contains('fade')).toEqual(true)
- expect(tabProfileEl.classList.contains('show')).toEqual(true)
+ it('should not remove fade class if no active pane is present', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<ul class="nav nav-tabs" role="tablist">',
+ ' <li class="nav-item" role="presentation"><button type="button" id="tab-home" data-bs-target="#home" class="nav-link" data-bs-toggle="tab" role="tab">Home</button></li>',
+ ' <li class="nav-item" role="presentation"><button type="button" id="tab-profile" data-bs-target="#profile" class="nav-link" data-bs-toggle="tab" role="tab">Profile</button></li>',
+ '</ul>',
+ '<div class="tab-content">',
+ ' <div class="tab-pane fade" id="home" role="tabpanel"></div>',
+ ' <div class="tab-pane fade" id="profile" role="tabpanel"></div>',
+ '</div>'
+ ].join('')
+
+ const triggerTabProfileEl = fixtureEl.querySelector('#tab-profile')
+ const triggerTabHomeEl = fixtureEl.querySelector('#tab-home')
+ const tabProfileEl = fixtureEl.querySelector('#profile')
+ const tabHomeEl = fixtureEl.querySelector('#home')
triggerTabHomeEl.addEventListener('shown.bs.tab', () => {
- expect(tabProfileEl.classList.contains('fade')).toEqual(true)
- expect(tabProfileEl.classList.contains('show')).toEqual(false)
+ setTimeout(() => {
+ expect(tabProfileEl).toHaveClass('fade')
+ expect(tabProfileEl).not.toHaveClass('show')
- expect(tabHomeEl.classList.contains('fade')).toEqual(true)
- expect(tabHomeEl.classList.contains('show')).toEqual(true)
+ expect(tabHomeEl).toHaveClass('fade')
+ expect(tabHomeEl).toHaveClass('show')
- done()
+ resolve()
+ }, 10)
})
- triggerTabHomeEl.click()
- })
+ triggerTabProfileEl.addEventListener('shown.bs.tab', () => {
+ setTimeout(() => {
+ expect(tabProfileEl).toHaveClass('fade')
+ expect(tabProfileEl).toHaveClass('show')
+ triggerTabHomeEl.click()
+ }, 10)
+ })
- triggerTabProfileEl.click()
+ triggerTabProfileEl.click()
+ })
})
- it('should not add show class to tab panes if there is no `.fade` class', done => {
- fixtureEl.innerHTML = [
- '<ul class="nav nav-tabs" role="tablist">',
- ' <li class="nav-item" role="presentation">',
- ' <button type="button" class="nav-link nav-tab" data-bs-target="#home" role="tab" data-bs-toggle="tab">Home</button>',
- ' </li>',
- ' <li class="nav-item" role="presentation">',
- ' <button type="button" id="secondNav" class="nav-link nav-tab" data-bs-target="#profile" role="tab" data-bs-toggle="tab">Profile</button>',
- ' </li>',
- '</ul>',
- '<div class="tab-content">',
- ' <div role="tabpanel" class="tab-pane" id="home">test 1</div>',
- ' <div role="tabpanel" class="tab-pane" id="profile">test 2</div>',
- '</div>'
- ].join('')
-
- const secondNavEl = fixtureEl.querySelector('#secondNav')
+ it('should add `show` class to tab panes if there is no `.fade` class', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<ul class="nav nav-tabs" role="tablist">',
+ ' <li class="nav-item" role="presentation">',
+ ' <button type="button" class="nav-link nav-tab" data-bs-target="#home" role="tab" data-bs-toggle="tab">Home</button>',
+ ' </li>',
+ ' <li class="nav-item" role="presentation">',
+ ' <button type="button" id="secondNav" class="nav-link nav-tab" data-bs-target="#profile" role="tab" data-bs-toggle="tab">Profile</button>',
+ ' </li>',
+ '</ul>',
+ '<div class="tab-content">',
+ ' <div role="tabpanel" class="tab-pane" id="home">test 1</div>',
+ ' <div role="tabpanel" class="tab-pane" id="profile">test 2</div>',
+ '</div>'
+ ].join('')
+
+ const secondNavEl = fixtureEl.querySelector('#secondNav')
+
+ secondNavEl.addEventListener('shown.bs.tab', () => {
+ expect(fixtureEl.querySelectorAll('.tab-content .show')).toHaveSize(1)
+ resolve()
+ })
- secondNavEl.addEventListener('shown.bs.tab', () => {
- expect(fixtureEl.querySelectorAll('.show').length).toEqual(0)
- done()
+ secondNavEl.click()
})
-
- secondNavEl.click()
})
- it('should add show class to tab panes if there is a `.fade` class', done => {
- fixtureEl.innerHTML = [
- '<ul class="nav nav-tabs" role="tablist">',
- ' <li class="nav-item" role="presentation">',
- ' <button type="button" class="nav-link nav-tab" data-bs-target="#home" role="tab" data-bs-toggle="tab">Home</button>',
- ' </li>',
- ' <li class="nav-item" role="presentation">',
- ' <button type="button" id="secondNav" class="nav-link nav-tab" data-bs-target="#profile" role="tab" data-bs-toggle="tab">Profile</button>',
- ' </li>',
- '</ul>',
- '<div class="tab-content">',
- ' <div role="tabpanel" class="tab-pane fade" id="home">test 1</div>',
- ' <div role="tabpanel" class="tab-pane fade" id="profile">test 2</div>',
- '</div>'
- ].join('')
-
- const secondNavEl = fixtureEl.querySelector('#secondNav')
+ it('should add show class to tab panes if there is a `.fade` class', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<ul class="nav nav-tabs" role="tablist">',
+ ' <li class="nav-item" role="presentation">',
+ ' <button type="button" class="nav-link nav-tab" data-bs-target="#home" role="tab" data-bs-toggle="tab">Home</button>',
+ ' </li>',
+ ' <li class="nav-item" role="presentation">',
+ ' <button type="button" id="secondNav" class="nav-link nav-tab" data-bs-target="#profile" role="tab" data-bs-toggle="tab">Profile</button>',
+ ' </li>',
+ '</ul>',
+ '<div class="tab-content">',
+ ' <div role="tabpanel" class="tab-pane fade" id="home">test 1</div>',
+ ' <div role="tabpanel" class="tab-pane fade" id="profile">test 2</div>',
+ '</div>'
+ ].join('')
+
+ const secondNavEl = fixtureEl.querySelector('#secondNav')
+
+ secondNavEl.addEventListener('shown.bs.tab', () => {
+ setTimeout(() => {
+ expect(fixtureEl.querySelectorAll('.show')).toHaveSize(1)
+ resolve()
+ }, 10)
+ })
- secondNavEl.addEventListener('shown.bs.tab', () => {
- expect(fixtureEl.querySelectorAll('.show').length).toEqual(1)
- done()
+ secondNavEl.click()
})
-
- secondNavEl.click()
})
- it('should prevent default when the trigger is <a> or <area>', done => {
- fixtureEl.innerHTML = [
- '<ul class="nav" role="tablist">',
- ' <li><a type="button" href="#test" class="active" role="tab" data-bs-toggle="tab">Home</a></li>',
- ' <li><a type="button" href="#test2" role="tab" data-bs-toggle="tab">Home</a></li>',
- '</ul>'
- ].join('')
-
- const tabEl = fixtureEl.querySelector('[href="#test2"]')
- spyOn(Event.prototype, 'preventDefault').and.callThrough()
+ it('should prevent default when the trigger is <a> or <area>', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<ul class="nav" role="tablist">',
+ ' <li><a type="button" href="#test" class="active" role="tab" data-bs-toggle="tab">Home</a></li>',
+ ' <li><a type="button" href="#test2" role="tab" data-bs-toggle="tab">Home</a></li>',
+ '</ul>'
+ ].join('')
+
+ const tabEl = fixtureEl.querySelector('[href="#test2"]')
+ const spy = spyOn(Event.prototype, 'preventDefault').and.callThrough()
+
+ tabEl.addEventListener('shown.bs.tab', () => {
+ expect(tabEl).toHaveClass('active')
+ expect(spy).toHaveBeenCalled()
+ resolve()
+ })
- tabEl.addEventListener('shown.bs.tab', () => {
- expect(tabEl.classList.contains('active')).toEqual(true)
- expect(Event.prototype.preventDefault).toHaveBeenCalled()
- done()
+ tabEl.click()
})
-
- tabEl.click()
})
- it('should not fire shown when tab has disabled attribute', done => {
- fixtureEl.innerHTML = [
- '<ul class="nav nav-tabs" role="tablist">',
- ' <li class="nav-item" role="presentation"><button type="button" data-bs-target="#home" class="nav-link active" role="tab" aria-selected="true">Home</button></li>',
- ' <li class="nav-item" role="presentation"><button type="button" data-bs-target="#profile" class="nav-link" disabled role="tab">Profile</button></li>',
- '</ul>',
- '<div class="tab-content">',
- ' <div class="tab-pane active" id="home" role="tabpanel"></div>',
- ' <div class="tab-pane" id="profile" role="tabpanel"></div>',
- '</div>'
- ].join('')
+ it('should not fire shown when tab has disabled attribute', () => {
+ return new Promise((resolve, reject) => {
+ fixtureEl.innerHTML = [
+ '<ul class="nav nav-tabs" role="tablist">',
+ ' <li class="nav-item" role="presentation"><button type="button" data-bs-target="#home" class="nav-link active" role="tab" aria-selected="true">Home</button></li>',
+ ' <li class="nav-item" role="presentation"><button type="button" data-bs-target="#profile" class="nav-link" disabled role="tab">Profile</button></li>',
+ '</ul>',
+ '<div class="tab-content">',
+ ' <div class="tab-pane active" id="home" role="tabpanel"></div>',
+ ' <div class="tab-pane" id="profile" role="tabpanel"></div>',
+ '</div>'
+ ].join('')
+
+ const triggerDisabled = fixtureEl.querySelector('button[disabled]')
+ triggerDisabled.addEventListener('shown.bs.tab', () => {
+ reject(new Error('should not trigger shown event'))
+ })
- const triggerDisabled = fixtureEl.querySelector('button[disabled]')
- triggerDisabled.addEventListener('shown.bs.tab', () => {
- throw new Error('should not trigger shown event')
+ triggerDisabled.click()
+ setTimeout(() => {
+ expect().nothing()
+ resolve()
+ }, 30)
})
-
- triggerDisabled.click()
- setTimeout(() => {
- expect().nothing()
- done()
- }, 30)
})
- it('should not fire shown when tab has disabled class', done => {
- fixtureEl.innerHTML = [
- '<ul class="nav nav-tabs" role="tablist">',
- ' <li class="nav-item" role="presentation"><a href="#home" class="nav-link active" role="tab" aria-selected="true">Home</a></li>',
- ' <li class="nav-item" role="presentation"><a href="#profile" class="nav-link disabled" role="tab">Profile</a></li>',
- '</ul>',
- '<div class="tab-content">',
- ' <div class="tab-pane active" id="home" role="tabpanel"></div>',
- ' <div class="tab-pane" id="profile" role="tabpanel"></div>',
- '</div>'
- ].join('')
-
- const triggerDisabled = fixtureEl.querySelector('a.disabled')
+ it('should not fire shown when tab has disabled class', () => {
+ return new Promise((resolve, reject) => {
+ fixtureEl.innerHTML = [
+ '<ul class="nav nav-tabs" role="tablist">',
+ ' <li class="nav-item" role="presentation"><a href="#home" class="nav-link active" role="tab" aria-selected="true">Home</a></li>',
+ ' <li class="nav-item" role="presentation"><a href="#profile" class="nav-link disabled" role="tab">Profile</a></li>',
+ '</ul>',
+ '<div class="tab-content">',
+ ' <div class="tab-pane active" id="home" role="tabpanel"></div>',
+ ' <div class="tab-pane" id="profile" role="tabpanel"></div>',
+ '</div>'
+ ].join('')
+
+ const triggerDisabled = fixtureEl.querySelector('a.disabled')
+
+ triggerDisabled.addEventListener('shown.bs.tab', () => {
+ reject(new Error('should not trigger shown event'))
+ })
- triggerDisabled.addEventListener('shown.bs.tab', () => {
- throw new Error('should not trigger shown event')
+ triggerDisabled.click()
+ setTimeout(() => {
+ expect().nothing()
+ resolve()
+ }, 30)
})
-
- triggerDisabled.click()
- setTimeout(() => {
- expect().nothing()
- done()
- }, 30)
})
})
})
diff --git a/js/tests/unit/toast.spec.js b/js/tests/unit/toast.spec.js
index 4b84bf2c5..200fe3e40 100644
--- a/js/tests/unit/toast.spec.js
+++ b/js/tests/unit/toast.spec.js
@@ -1,5 +1,7 @@
-import Toast from '../../src/toast'
-import { getFixture, clearFixture, createEvent, jQueryMock } from '../helpers/fixture'
+import Toast from '../../src/toast.js'
+import {
+ clearFixture, createEvent, getFixture, jQueryMock
+} from '../helpers/fixture.js'
describe('Toast', () => {
let fixtureEl
@@ -36,52 +38,56 @@ describe('Toast', () => {
expect(toastByElement._element).toEqual(toastEl)
})
- it('should allow to config in js', done => {
- fixtureEl.innerHTML = [
- '<div class="toast">',
- ' <div class="toast-body">',
- ' a simple toast',
- ' </div>',
- '</div>'
- ].join('')
+ it('should allow to config in js', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="toast">',
+ ' <div class="toast-body">',
+ ' a simple toast',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const toastEl = fixtureEl.querySelector('div')
+ const toast = new Toast(toastEl, {
+ delay: 1
+ })
- const toastEl = fixtureEl.querySelector('div')
- const toast = new Toast(toastEl, {
- delay: 1
- })
+ toastEl.addEventListener('shown.bs.toast', () => {
+ expect(toastEl).toHaveClass('show')
+ resolve()
+ })
- toastEl.addEventListener('shown.bs.toast', () => {
- expect(toastEl.classList.contains('show')).toEqual(true)
- done()
+ toast.show()
})
-
- toast.show()
})
- it('should close toast when close element with data-bs-dismiss attribute is set', done => {
- fixtureEl.innerHTML = [
- '<div class="toast" data-bs-delay="1" data-bs-autohide="false" data-bs-animation="false">',
- ' <button type="button" class="ms-2 mb-1 btn-close" data-bs-dismiss="toast" aria-label="Close"></button>',
- '</div>'
- ].join('')
+ it('should close toast when close element with data-bs-dismiss attribute is set', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="toast" data-bs-delay="1" data-bs-autohide="false" data-bs-animation="false">',
+ ' <button type="button" class="ms-2 mb-1 btn-close" data-bs-dismiss="toast" aria-label="Close"></button>',
+ '</div>'
+ ].join('')
- const toastEl = fixtureEl.querySelector('div')
- const toast = new Toast(toastEl)
+ const toastEl = fixtureEl.querySelector('div')
+ const toast = new Toast(toastEl)
- toastEl.addEventListener('shown.bs.toast', () => {
- expect(toastEl.classList.contains('show')).toEqual(true)
+ toastEl.addEventListener('shown.bs.toast', () => {
+ expect(toastEl).toHaveClass('show')
- const button = toastEl.querySelector('.btn-close')
+ const button = toastEl.querySelector('.btn-close')
- button.click()
- })
+ button.click()
+ })
- toastEl.addEventListener('hidden.bs.toast', () => {
- expect(toastEl.classList.contains('show')).toEqual(false)
- done()
- })
+ toastEl.addEventListener('hidden.bs.toast', () => {
+ expect(toastEl).not.toHaveClass('show')
+ resolve()
+ })
- toast.show()
+ toast.show()
+ })
})
})
@@ -111,304 +117,324 @@ describe('Toast', () => {
})
describe('show', () => {
- it('should auto hide', done => {
- fixtureEl.innerHTML = [
- '<div class="toast" data-bs-delay="1">',
- ' <div class="toast-body">',
- ' a simple toast',
- ' </div>',
- '</div>'
- ].join('')
-
- const toastEl = fixtureEl.querySelector('.toast')
- const toast = new Toast(toastEl)
-
- toastEl.addEventListener('hidden.bs.toast', () => {
- expect(toastEl.classList.contains('show')).toEqual(false)
- done()
- })
-
- toast.show()
- })
-
- it('should not add fade class', done => {
- fixtureEl.innerHTML = [
- '<div class="toast" data-bs-delay="1" data-bs-animation="false">',
- ' <div class="toast-body">',
- ' a simple toast',
- ' </div>',
- '</div>'
- ].join('')
-
- const toastEl = fixtureEl.querySelector('.toast')
- const toast = new Toast(toastEl)
+ it('should auto hide', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="toast" data-bs-delay="1">',
+ ' <div class="toast-body">',
+ ' a simple toast',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const toastEl = fixtureEl.querySelector('.toast')
+ const toast = new Toast(toastEl)
+
+ toastEl.addEventListener('hidden.bs.toast', () => {
+ expect(toastEl).not.toHaveClass('show')
+ resolve()
+ })
- toastEl.addEventListener('shown.bs.toast', () => {
- expect(toastEl.classList.contains('fade')).toEqual(false)
- done()
+ toast.show()
})
-
- toast.show()
})
- it('should not trigger shown if show is prevented', done => {
- fixtureEl.innerHTML = [
- '<div class="toast" data-bs-delay="1" data-bs-animation="false">',
- ' <div class="toast-body">',
- ' a simple toast',
- ' </div>',
- '</div>'
- ].join('')
+ it('should not add fade class', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="toast" data-bs-delay="1" data-bs-animation="false">',
+ ' <div class="toast-body">',
+ ' a simple toast',
+ ' </div>',
+ '</div>'
+ ].join('')
- const toastEl = fixtureEl.querySelector('.toast')
- const toast = new Toast(toastEl)
+ const toastEl = fixtureEl.querySelector('.toast')
+ const toast = new Toast(toastEl)
- const assertDone = () => {
- setTimeout(() => {
- expect(toastEl.classList.contains('show')).toEqual(false)
- done()
- }, 20)
- }
-
- toastEl.addEventListener('show.bs.toast', event => {
- event.preventDefault()
- assertDone()
- })
+ toastEl.addEventListener('shown.bs.toast', () => {
+ expect(toastEl).not.toHaveClass('fade')
+ resolve()
+ })
- toastEl.addEventListener('shown.bs.toast', () => {
- throw new Error('shown event should not be triggered if show is prevented')
+ toast.show()
})
-
- toast.show()
})
- it('should clear timeout if toast is shown again before it is hidden', done => {
- fixtureEl.innerHTML = [
- '<div class="toast">',
- ' <div class="toast-body">',
- ' a simple toast',
- ' </div>',
- '</div>'
- ].join('')
-
- const toastEl = fixtureEl.querySelector('.toast')
- const toast = new Toast(toastEl)
+ it('should not trigger shown if show is prevented', () => {
+ return new Promise((resolve, reject) => {
+ fixtureEl.innerHTML = [
+ '<div class="toast" data-bs-delay="1" data-bs-animation="false">',
+ ' <div class="toast-body">',
+ ' a simple toast',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const toastEl = fixtureEl.querySelector('.toast')
+ const toast = new Toast(toastEl)
+
+ const assertDone = () => {
+ setTimeout(() => {
+ expect(toastEl).not.toHaveClass('show')
+ resolve()
+ }, 20)
+ }
+
+ toastEl.addEventListener('show.bs.toast', event => {
+ event.preventDefault()
+ assertDone()
+ })
- setTimeout(() => {
- toast._config.autohide = false
toastEl.addEventListener('shown.bs.toast', () => {
- expect(toast._clearTimeout).toHaveBeenCalled()
- expect(toast._timeout).toBeNull()
- done()
+ reject(new Error('shown event should not be triggered if show is prevented'))
})
- toast.show()
- }, toast._config.delay / 2)
- spyOn(toast, '_clearTimeout').and.callThrough()
-
- toast.show()
+ toast.show()
+ })
})
- it('should clear timeout if toast is interacted with mouse', done => {
- fixtureEl.innerHTML = [
- '<div class="toast">',
- ' <div class="toast-body">',
- ' a simple toast',
- ' </div>',
- '</div>'
- ].join('')
-
- const toastEl = fixtureEl.querySelector('.toast')
- const toast = new Toast(toastEl)
- const spy = spyOn(toast, '_clearTimeout').and.callThrough()
+ it('should clear timeout if toast is shown again before it is hidden', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="toast">',
+ ' <div class="toast-body">',
+ ' a simple toast',
+ ' </div>',
+ '</div>'
+ ].join('')
- setTimeout(() => {
- spy.calls.reset()
+ const toastEl = fixtureEl.querySelector('.toast')
+ const toast = new Toast(toastEl)
- toastEl.addEventListener('mouseover', () => {
- expect(toast._clearTimeout).toHaveBeenCalledTimes(1)
- expect(toast._timeout).toBeNull()
- done()
- })
+ setTimeout(() => {
+ toast._config.autohide = false
+ toastEl.addEventListener('shown.bs.toast', () => {
+ expect(spy).toHaveBeenCalled()
+ expect(toast._timeout).toBeNull()
+ resolve()
+ })
+ toast.show()
+ }, toast._config.delay / 2)
- const mouseOverEvent = createEvent('mouseover')
- toastEl.dispatchEvent(mouseOverEvent)
- }, toast._config.delay / 2)
+ const spy = spyOn(toast, '_clearTimeout').and.callThrough()
- toast.show()
+ toast.show()
+ })
})
- it('should clear timeout if toast is interacted with keyboard', done => {
- fixtureEl.innerHTML = [
- '<button id="outside-focusable">outside focusable</button>',
- '<div class="toast">',
- ' <div class="toast-body">',
- ' a simple toast',
- ' <button>with a button</button>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should clear timeout if toast is interacted with mouse', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="toast">',
+ ' <div class="toast-body">',
+ ' a simple toast',
+ ' </div>',
+ '</div>'
+ ].join('')
- const toastEl = fixtureEl.querySelector('.toast')
- const toast = new Toast(toastEl)
- const spy = spyOn(toast, '_clearTimeout').and.callThrough()
+ const toastEl = fixtureEl.querySelector('.toast')
+ const toast = new Toast(toastEl)
+ const spy = spyOn(toast, '_clearTimeout').and.callThrough()
- setTimeout(() => {
- spy.calls.reset()
+ setTimeout(() => {
+ spy.calls.reset()
- toastEl.addEventListener('focusin', () => {
- expect(toast._clearTimeout).toHaveBeenCalledTimes(1)
- expect(toast._timeout).toBeNull()
- done()
- })
+ toastEl.addEventListener('mouseover', () => {
+ expect(toast._clearTimeout).toHaveBeenCalledTimes(1)
+ expect(toast._timeout).toBeNull()
+ resolve()
+ })
- const insideFocusable = toastEl.querySelector('button')
- insideFocusable.focus()
- }, toast._config.delay / 2)
+ const mouseOverEvent = createEvent('mouseover')
+ toastEl.dispatchEvent(mouseOverEvent)
+ }, toast._config.delay / 2)
- toast.show()
+ toast.show()
+ })
})
- it('should still auto hide after being interacted with mouse and keyboard', done => {
- fixtureEl.innerHTML = [
- '<button id="outside-focusable">outside focusable</button>',
- '<div class="toast">',
- ' <div class="toast-body">',
- ' a simple toast',
- ' <button>with a button</button>',
- ' </div>',
- '</div>'
- ].join('')
+ it('should clear timeout if toast is interacted with keyboard', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<button id="outside-focusable">outside focusable</button>',
+ '<div class="toast">',
+ ' <div class="toast-body">',
+ ' a simple toast',
+ ' <button>with a button</button>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const toastEl = fixtureEl.querySelector('.toast')
+ const toast = new Toast(toastEl)
+ const spy = spyOn(toast, '_clearTimeout').and.callThrough()
- const toastEl = fixtureEl.querySelector('.toast')
- const toast = new Toast(toastEl)
+ setTimeout(() => {
+ spy.calls.reset()
+
+ toastEl.addEventListener('focusin', () => {
+ expect(toast._clearTimeout).toHaveBeenCalledTimes(1)
+ expect(toast._timeout).toBeNull()
+ resolve()
+ })
- setTimeout(() => {
- toastEl.addEventListener('mouseover', () => {
const insideFocusable = toastEl.querySelector('button')
insideFocusable.focus()
- })
+ }, toast._config.delay / 2)
- toastEl.addEventListener('focusin', () => {
- const mouseOutEvent = createEvent('mouseout')
- toastEl.dispatchEvent(mouseOutEvent)
- })
-
- toastEl.addEventListener('mouseout', () => {
- const outsideFocusable = document.getElementById('outside-focusable')
- outsideFocusable.focus()
- })
+ toast.show()
+ })
+ })
- toastEl.addEventListener('focusout', () => {
- expect(toast._timeout).not.toBeNull()
- done()
- })
+ it('should still auto hide after being interacted with mouse and keyboard', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<button id="outside-focusable">outside focusable</button>',
+ '<div class="toast">',
+ ' <div class="toast-body">',
+ ' a simple toast',
+ ' <button>with a button</button>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const toastEl = fixtureEl.querySelector('.toast')
+ const toast = new Toast(toastEl)
- const mouseOverEvent = createEvent('mouseover')
- toastEl.dispatchEvent(mouseOverEvent)
- }, toast._config.delay / 2)
+ setTimeout(() => {
+ toastEl.addEventListener('mouseover', () => {
+ const insideFocusable = toastEl.querySelector('button')
+ insideFocusable.focus()
+ })
+
+ toastEl.addEventListener('focusin', () => {
+ const mouseOutEvent = createEvent('mouseout')
+ toastEl.dispatchEvent(mouseOutEvent)
+ })
+
+ toastEl.addEventListener('mouseout', () => {
+ const outsideFocusable = document.getElementById('outside-focusable')
+ outsideFocusable.focus()
+ })
+
+ toastEl.addEventListener('focusout', () => {
+ expect(toast._timeout).not.toBeNull()
+ resolve()
+ })
+
+ const mouseOverEvent = createEvent('mouseover')
+ toastEl.dispatchEvent(mouseOverEvent)
+ }, toast._config.delay / 2)
- toast.show()
+ toast.show()
+ })
})
- it('should not auto hide if focus leaves but mouse pointer remains inside', done => {
- fixtureEl.innerHTML = [
- '<button id="outside-focusable">outside focusable</button>',
- '<div class="toast">',
- ' <div class="toast-body">',
- ' a simple toast',
- ' <button>with a button</button>',
- ' </div>',
- '</div>'
- ].join('')
-
- const toastEl = fixtureEl.querySelector('.toast')
- const toast = new Toast(toastEl)
+ it('should not auto hide if focus leaves but mouse pointer remains inside', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<button id="outside-focusable">outside focusable</button>',
+ '<div class="toast">',
+ ' <div class="toast-body">',
+ ' a simple toast',
+ ' <button>with a button</button>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const toastEl = fixtureEl.querySelector('.toast')
+ const toast = new Toast(toastEl)
- setTimeout(() => {
- toastEl.addEventListener('mouseover', () => {
- const insideFocusable = toastEl.querySelector('button')
- insideFocusable.focus()
- })
+ setTimeout(() => {
+ toastEl.addEventListener('mouseover', () => {
+ const insideFocusable = toastEl.querySelector('button')
+ insideFocusable.focus()
+ })
- toastEl.addEventListener('focusin', () => {
- const outsideFocusable = document.getElementById('outside-focusable')
- outsideFocusable.focus()
- })
+ toastEl.addEventListener('focusin', () => {
+ const outsideFocusable = document.getElementById('outside-focusable')
+ outsideFocusable.focus()
+ })
- toastEl.addEventListener('focusout', () => {
- expect(toast._timeout).toBeNull()
- done()
- })
+ toastEl.addEventListener('focusout', () => {
+ expect(toast._timeout).toBeNull()
+ resolve()
+ })
- const mouseOverEvent = createEvent('mouseover')
- toastEl.dispatchEvent(mouseOverEvent)
- }, toast._config.delay / 2)
+ const mouseOverEvent = createEvent('mouseover')
+ toastEl.dispatchEvent(mouseOverEvent)
+ }, toast._config.delay / 2)
- toast.show()
+ toast.show()
+ })
})
- it('should not auto hide if mouse pointer leaves but focus remains inside', done => {
- fixtureEl.innerHTML = [
- '<button id="outside-focusable">outside focusable</button>',
- '<div class="toast">',
- ' <div class="toast-body">',
- ' a simple toast',
- ' <button>with a button</button>',
- ' </div>',
- '</div>'
- ].join('')
-
- const toastEl = fixtureEl.querySelector('.toast')
- const toast = new Toast(toastEl)
+ it('should not auto hide if mouse pointer leaves but focus remains inside', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<button id="outside-focusable">outside focusable</button>',
+ '<div class="toast">',
+ ' <div class="toast-body">',
+ ' a simple toast',
+ ' <button>with a button</button>',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const toastEl = fixtureEl.querySelector('.toast')
+ const toast = new Toast(toastEl)
- setTimeout(() => {
- toastEl.addEventListener('mouseover', () => {
- const insideFocusable = toastEl.querySelector('button')
- insideFocusable.focus()
- })
+ setTimeout(() => {
+ toastEl.addEventListener('mouseover', () => {
+ const insideFocusable = toastEl.querySelector('button')
+ insideFocusable.focus()
+ })
- toastEl.addEventListener('focusin', () => {
- const mouseOutEvent = createEvent('mouseout')
- toastEl.dispatchEvent(mouseOutEvent)
- })
+ toastEl.addEventListener('focusin', () => {
+ const mouseOutEvent = createEvent('mouseout')
+ toastEl.dispatchEvent(mouseOutEvent)
+ })
- toastEl.addEventListener('mouseout', () => {
- expect(toast._timeout).toBeNull()
- done()
- })
+ toastEl.addEventListener('mouseout', () => {
+ expect(toast._timeout).toBeNull()
+ resolve()
+ })
- const mouseOverEvent = createEvent('mouseover')
- toastEl.dispatchEvent(mouseOverEvent)
- }, toast._config.delay / 2)
+ const mouseOverEvent = createEvent('mouseover')
+ toastEl.dispatchEvent(mouseOverEvent)
+ }, toast._config.delay / 2)
- toast.show()
+ toast.show()
+ })
})
})
describe('hide', () => {
- it('should allow to hide toast manually', done => {
- fixtureEl.innerHTML = [
- '<div class="toast" data-bs-delay="1" data-bs-autohide="false">',
- ' <div class="toast-body">',
- ' a simple toast',
- ' </div>',
- ' </div>'
- ].join('')
+ it('should allow to hide toast manually', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="toast" data-bs-delay="1" data-bs-autohide="false">',
+ ' <div class="toast-body">',
+ ' a simple toast',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const toastEl = fixtureEl.querySelector('.toast')
+ const toast = new Toast(toastEl)
- const toastEl = fixtureEl.querySelector('.toast')
- const toast = new Toast(toastEl)
+ toastEl.addEventListener('shown.bs.toast', () => {
+ toast.hide()
+ })
- toastEl.addEventListener('shown.bs.toast', () => {
- toast.hide()
- })
+ toastEl.addEventListener('hidden.bs.toast', () => {
+ expect(toastEl).not.toHaveClass('show')
+ resolve()
+ })
- toastEl.addEventListener('hidden.bs.toast', () => {
- expect(toastEl.classList.contains('show')).toEqual(false)
- done()
+ toast.show()
})
-
- toast.show()
})
it('should do nothing when we call hide on a non shown toast', () => {
@@ -417,46 +443,48 @@ describe('Toast', () => {
const toastEl = fixtureEl.querySelector('div')
const toast = new Toast(toastEl)
- spyOn(toastEl.classList, 'contains')
+ const spy = spyOn(toastEl.classList, 'contains')
toast.hide()
- expect(toastEl.classList.contains).toHaveBeenCalled()
+ expect(spy).toHaveBeenCalled()
})
- it('should not trigger hidden if hide is prevented', done => {
- fixtureEl.innerHTML = [
- '<div class="toast" data-bs-delay="1" data-bs-animation="false">',
- ' <div class="toast-body">',
- ' a simple toast',
- ' </div>',
- '</div>'
- ].join('')
+ it('should not trigger hidden if hide is prevented', () => {
+ return new Promise((resolve, reject) => {
+ fixtureEl.innerHTML = [
+ '<div class="toast" data-bs-delay="1" data-bs-animation="false">',
+ ' <div class="toast-body">',
+ ' a simple toast',
+ ' </div>',
+ '</div>'
+ ].join('')
+
+ const toastEl = fixtureEl.querySelector('.toast')
+ const toast = new Toast(toastEl)
+
+ const assertDone = () => {
+ setTimeout(() => {
+ expect(toastEl).toHaveClass('show')
+ resolve()
+ }, 20)
+ }
- const toastEl = fixtureEl.querySelector('.toast')
- const toast = new Toast(toastEl)
-
- const assertDone = () => {
- setTimeout(() => {
- expect(toastEl.classList.contains('show')).toEqual(true)
- done()
- }, 20)
- }
+ toastEl.addEventListener('shown.bs.toast', () => {
+ toast.hide()
+ })
- toastEl.addEventListener('shown.bs.toast', () => {
- toast.hide()
- })
+ toastEl.addEventListener('hide.bs.toast', event => {
+ event.preventDefault()
+ assertDone()
+ })
- toastEl.addEventListener('hide.bs.toast', event => {
- event.preventDefault()
- assertDone()
- })
+ toastEl.addEventListener('hidden.bs.toast', () => {
+ reject(new Error('hidden event should not be triggered if hide is prevented'))
+ })
- toastEl.addEventListener('hidden.bs.toast', () => {
- throw new Error('hidden event should not be triggered if hide is prevented')
+ toast.show()
})
-
- toast.show()
})
})
@@ -475,34 +503,36 @@ describe('Toast', () => {
expect(Toast.getInstance(toastEl)).toBeNull()
})
- it('should allow to destroy toast and hide it before that', done => {
- fixtureEl.innerHTML = [
- '<div class="toast" data-bs-delay="0" data-bs-autohide="false">',
- ' <div class="toast-body">',
- ' a simple toast',
- ' </div>',
- '</div>'
- ].join('')
+ it('should allow to destroy toast and hide it before that', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="toast" data-bs-delay="0" data-bs-autohide="false">',
+ ' <div class="toast-body">',
+ ' a simple toast',
+ ' </div>',
+ '</div>'
+ ].join('')
- const toastEl = fixtureEl.querySelector('div')
- const toast = new Toast(toastEl)
- const expected = () => {
- expect(toastEl.classList.contains('show')).toEqual(true)
- expect(Toast.getInstance(toastEl)).not.toBeNull()
+ const toastEl = fixtureEl.querySelector('div')
+ const toast = new Toast(toastEl)
+ const expected = () => {
+ expect(toastEl).toHaveClass('show')
+ expect(Toast.getInstance(toastEl)).not.toBeNull()
- toast.dispose()
+ toast.dispose()
- expect(Toast.getInstance(toastEl)).toBeNull()
- expect(toastEl.classList.contains('show')).toEqual(false)
+ expect(Toast.getInstance(toastEl)).toBeNull()
+ expect(toastEl).not.toHaveClass('show')
- done()
- }
+ resolve()
+ }
- toastEl.addEventListener('shown.bs.toast', () => {
- setTimeout(expected, 1)
- })
+ toastEl.addEventListener('shown.bs.toast', () => {
+ setTimeout(expected, 1)
+ })
- toast.show()
+ toast.show()
+ })
})
})
@@ -540,7 +570,7 @@ describe('Toast', () => {
const div = fixtureEl.querySelector('div')
const toast = new Toast(div)
- spyOn(toast, 'show')
+ const spy = spyOn(toast, 'show')
jQueryMock.fn.toast = Toast.jQueryInterface
jQueryMock.elements = [div]
@@ -548,7 +578,7 @@ describe('Toast', () => {
jQueryMock.fn.toast.call(jQueryMock, 'show')
expect(Toast.getInstance(div)).toEqual(toast)
- expect(toast.show).toHaveBeenCalled()
+ expect(spy).toHaveBeenCalled()
})
it('should throw error on undefined method', () => {
@@ -582,7 +612,7 @@ describe('Toast', () => {
const div = fixtureEl.querySelector('div')
- expect(Toast.getInstance(div)).toEqual(null)
+ expect(Toast.getInstance(div)).toBeNull()
})
})
@@ -603,7 +633,7 @@ describe('Toast', () => {
const div = fixtureEl.querySelector('div')
- expect(Toast.getInstance(div)).toEqual(null)
+ expect(Toast.getInstance(div)).toBeNull()
expect(Toast.getOrCreateInstance(div)).toBeInstanceOf(Toast)
})
@@ -612,7 +642,7 @@ describe('Toast', () => {
const div = fixtureEl.querySelector('div')
- expect(Toast.getInstance(div)).toEqual(null)
+ expect(Toast.getInstance(div)).toBeNull()
const toast = Toast.getOrCreateInstance(div, {
delay: 1
})
diff --git a/js/tests/unit/tooltip.spec.js b/js/tests/unit/tooltip.spec.js
index 4a7022234..37f2c230d 100644
--- a/js/tests/unit/tooltip.spec.js
+++ b/js/tests/unit/tooltip.spec.js
@@ -1,7 +1,9 @@
-import Tooltip from '../../src/tooltip'
-import EventHandler from '../../src/dom/event-handler'
-import { noop } from '../../src/util/index'
-import { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture'
+import EventHandler from '../../src/dom/event-handler.js'
+import Tooltip from '../../src/tooltip.js'
+import { noop } from '../../src/util/index.js'
+import {
+ clearFixture, createEvent, getFixture, jQueryMock
+} from '../helpers/fixture.js'
describe('Tooltip', () => {
let fixtureEl
@@ -42,12 +44,6 @@ describe('Tooltip', () => {
})
})
- describe('Event', () => {
- it('should return plugin events', () => {
- expect(Tooltip.Event).toEqual(jasmine.any(Object))
- })
- })
-
describe('EVENT_KEY', () => {
it('should return plugin event key', () => {
expect(Tooltip.EVENT_KEY).toEqual('.bs.tooltip')
@@ -62,7 +58,7 @@ describe('Tooltip', () => {
describe('constructor', () => {
it('should take care of element either passed as a CSS selector or DOM element', () => {
- fixtureEl.innerHTML = '<a href="#" id="tooltipEl" rel="tooltip" title="Nice and short title">'
+ fixtureEl.innerHTML = '<a href="#" id="tooltipEl" rel="tooltip" title="Nice and short title"></a>'
const tooltipEl = fixtureEl.querySelector('#tooltipEl')
const tooltipBySelector = new Tooltip('#tooltipEl')
@@ -73,16 +69,16 @@ describe('Tooltip', () => {
})
it('should not take care of disallowed data attributes', () => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" data-bs-sanitize="false" title="Another tooltip">'
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" data-bs-sanitize="false" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl)
- expect(tooltip._config.sanitize).toEqual(true)
+ expect(tooltip._config.sanitize).toBeTrue()
})
it('should convert title and content to string if numbers', () => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip">'
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl, {
@@ -94,56 +90,60 @@ describe('Tooltip', () => {
expect(tooltip._config.content).toEqual('7')
})
- it('should enable selector delegation', done => {
- fixtureEl.innerHTML = '<div></div>'
+ it('should enable selector delegation', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div></div>'
- const containerEl = fixtureEl.querySelector('div')
- const tooltipContainer = new Tooltip(containerEl, {
- selector: 'a[rel="tooltip"]',
- trigger: 'click'
- })
+ const containerEl = fixtureEl.querySelector('div')
+ const tooltipContainer = new Tooltip(containerEl, {
+ selector: 'a[rel="tooltip"]',
+ trigger: 'click'
+ })
- containerEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ containerEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
- const tooltipInContainerEl = containerEl.querySelector('a')
+ const tooltipInContainerEl = containerEl.querySelector('a')
- tooltipInContainerEl.addEventListener('shown.bs.tooltip', () => {
- expect(document.querySelector('.tooltip')).not.toBeNull()
- tooltipContainer.dispose()
- done()
- })
+ tooltipInContainerEl.addEventListener('shown.bs.tooltip', () => {
+ expect(document.querySelector('.tooltip')).not.toBeNull()
+ tooltipContainer.dispose()
+ resolve()
+ })
- tooltipInContainerEl.click()
+ tooltipInContainerEl.click()
+ })
})
- it('should create offset modifier when offset is passed as a function', done => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Offset from function">'
+ it('should create offset modifier when offset is passed as a function', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Offset from function"></a>'
- const getOffset = jasmine.createSpy('getOffset').and.returnValue([10, 20])
- const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl, {
- offset: getOffset,
- popperConfig: {
- onFirstUpdate: state => {
- expect(getOffset).toHaveBeenCalledWith({
- popper: state.rects.popper,
- reference: state.rects.reference,
- placement: state.placement
- }, tooltipEl)
- done()
+ const getOffset = jasmine.createSpy('getOffset').and.returnValue([10, 20])
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl, {
+ offset: getOffset,
+ popperConfig: {
+ onFirstUpdate(state) {
+ expect(getOffset).toHaveBeenCalledWith({
+ popper: state.rects.popper,
+ reference: state.rects.reference,
+ placement: state.placement
+ }, tooltipEl)
+ resolve()
+ }
}
- }
- })
+ })
- const offset = tooltip._getOffset()
+ const offset = tooltip._getOffset()
- expect(typeof offset).toEqual('function')
+ expect(offset).toEqual(jasmine.any(Function))
- tooltip.show()
+ tooltip.show()
+ })
})
it('should create offset modifier when offset option is passed in data attribute', () => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" data-bs-offset="10,20" title="Another tooltip">'
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" data-bs-offset="10,20" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl)
@@ -152,7 +152,7 @@ describe('Tooltip', () => {
})
it('should allow to pass config to Popper with `popperConfig`', () => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip">'
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl, {
@@ -167,7 +167,7 @@ describe('Tooltip', () => {
})
it('should allow to pass config to Popper with `popperConfig` as a function', () => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip">'
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a')
const getPopperConfig = jasmine.createSpy('getPopperConfig').and.returnValue({ placement: 'left' })
@@ -177,163 +177,189 @@ describe('Tooltip', () => {
const popperConfig = tooltip._getPopperConfig('top')
- expect(getPopperConfig).toHaveBeenCalled()
+ // Ensure that the function was called with the default config.
+ expect(getPopperConfig).toHaveBeenCalledWith(jasmine.objectContaining({
+ placement: jasmine.any(String)
+ }))
expect(popperConfig.placement).toEqual('left')
})
- })
- describe('enable', () => {
- it('should enable a tooltip', done => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ it('should use original title, if not "data-bs-title" is given', () => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl)
- tooltip.enable()
+ expect(tooltip._getTitle()).toEqual('Another tooltip')
+ })
+ })
- tooltipEl.addEventListener('shown.bs.tooltip', () => {
- expect(document.querySelector('.tooltip')).not.toBeNull()
- done()
- })
+ describe('enable', () => {
+ it('should enable a tooltip', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
- tooltip.show()
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
+
+ tooltip.enable()
+
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ expect(document.querySelector('.tooltip')).not.toBeNull()
+ resolve()
+ })
+
+ tooltip.show()
+ })
})
})
describe('disable', () => {
- it('should disable tooltip', done => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ it('should disable tooltip', () => {
+ return new Promise((resolve, reject) => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
- const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl)
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
- tooltip.disable()
+ tooltip.disable()
- tooltipEl.addEventListener('show.bs.tooltip', () => {
- throw new Error('should not show a disabled tooltip')
- })
+ tooltipEl.addEventListener('show.bs.tooltip', () => {
+ reject(new Error('should not show a disabled tooltip'))
+ })
- tooltip.show()
+ tooltip.show()
- setTimeout(() => {
- expect().nothing()
- done()
- }, 10)
+ setTimeout(() => {
+ expect().nothing()
+ resolve()
+ }, 10)
+ })
})
})
describe('toggleEnabled', () => {
it('should toggle enabled', () => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl)
- expect(tooltip._isEnabled).toEqual(true)
+ expect(tooltip._isEnabled).toBeTrue()
tooltip.toggleEnabled()
- expect(tooltip._isEnabled).toEqual(false)
+ expect(tooltip._isEnabled).toBeFalse()
})
})
describe('toggle', () => {
- it('should do nothing if disabled', done => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ it('should do nothing if disabled', () => {
+ return new Promise((resolve, reject) => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
- const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl)
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
- tooltip.disable()
+ tooltip.disable()
- tooltipEl.addEventListener('show.bs.tooltip', () => {
- throw new Error('should not show a disabled tooltip')
- })
+ tooltipEl.addEventListener('show.bs.tooltip', () => {
+ reject(new Error('should not show a disabled tooltip'))
+ })
- tooltip.toggle()
+ tooltip.toggle()
- setTimeout(() => {
- expect().nothing()
- done()
- }, 10)
+ setTimeout(() => {
+ expect().nothing()
+ resolve()
+ }, 10)
+ })
})
- it('should show a tooltip', done => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ it('should show a tooltip', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
- const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl)
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
- tooltipEl.addEventListener('shown.bs.tooltip', () => {
- expect(document.querySelector('.tooltip')).not.toBeNull()
- done()
- })
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ expect(document.querySelector('.tooltip')).not.toBeNull()
+ resolve()
+ })
- tooltip.toggle()
+ tooltip.toggle()
+ })
})
- it('should call toggle and show the tooltip when trigger is "click"', done => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ it('should call toggle and show the tooltip when trigger is "click"', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
- const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl, {
- trigger: 'click'
- })
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl, {
+ trigger: 'click'
+ })
- spyOn(tooltip, 'toggle').and.callThrough()
+ const spy = spyOn(tooltip, 'toggle').and.callThrough()
- tooltipEl.addEventListener('shown.bs.tooltip', () => {
- expect(tooltip.toggle).toHaveBeenCalled()
- done()
- })
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ expect(spy).toHaveBeenCalled()
+ resolve()
+ })
- tooltipEl.click()
+ tooltipEl.click()
+ })
})
- it('should hide a tooltip', done => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ it('should hide a tooltip', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
- const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl)
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
+
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ tooltip.toggle()
+ })
+
+ tooltipEl.addEventListener('hidden.bs.tooltip', () => {
+ expect(document.querySelector('.tooltip')).toBeNull()
+ resolve()
+ })
- tooltipEl.addEventListener('shown.bs.tooltip', () => {
tooltip.toggle()
})
+ })
- tooltipEl.addEventListener('hidden.bs.tooltip', () => {
- expect(document.querySelector('.tooltip')).toBeNull()
- done()
- })
+ it('should call toggle and hide the tooltip when trigger is "click"', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
- tooltip.toggle()
- })
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl, {
+ trigger: 'click'
+ })
- it('should call toggle and hide the tooltip when trigger is "click"', done => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ const spy = spyOn(tooltip, 'toggle').and.callThrough()
- const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl, {
- trigger: 'click'
- })
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ tooltipEl.click()
+ })
- spyOn(tooltip, 'toggle').and.callThrough()
+ tooltipEl.addEventListener('hidden.bs.tooltip', () => {
+ expect(spy).toHaveBeenCalled()
+ resolve()
+ })
- tooltipEl.addEventListener('shown.bs.tooltip', () => {
tooltipEl.click()
})
-
- tooltipEl.addEventListener('hidden.bs.tooltip', () => {
- expect(tooltip.toggle).toHaveBeenCalled()
- done()
- })
-
- tooltipEl.click()
})
})
describe('dispose', () => {
it('should destroy a tooltip', () => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a')
const addEventSpy = spyOn(tooltipEl, 'addEventListener').and.callThrough()
@@ -354,246 +380,290 @@ describe('Tooltip', () => {
tooltip.dispose()
- expect(Tooltip.getInstance(tooltipEl)).toEqual(null)
+ expect(Tooltip.getInstance(tooltipEl)).toBeNull()
expect(removeEventSpy.calls.allArgs()).toEqual(expectedArgs)
})
- it('should destroy a tooltip after it is shown and hidden', done => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ it('should destroy a tooltip after it is shown and hidden', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
- const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl)
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
- tooltipEl.addEventListener('shown.bs.tooltip', () => {
- tooltip.hide()
- })
- tooltipEl.addEventListener('hidden.bs.tooltip', () => {
- tooltip.dispose()
- expect(tooltip.tip).toEqual(null)
- expect(Tooltip.getInstance(tooltipEl)).toEqual(null)
- done()
- })
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ tooltip.hide()
+ })
+ tooltipEl.addEventListener('hidden.bs.tooltip', () => {
+ tooltip.dispose()
+ expect(tooltip.tip).toBeNull()
+ expect(Tooltip.getInstance(tooltipEl)).toBeNull()
+ resolve()
+ })
- tooltip.show()
+ tooltip.show()
+ })
})
- it('should destroy a tooltip and remove it from the dom', done => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ it('should destroy a tooltip and remove it from the dom', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
- const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl)
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
+
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ expect(document.querySelector('.tooltip')).not.toBeNull()
- tooltipEl.addEventListener('shown.bs.tooltip', () => {
- expect(document.querySelector('.tooltip')).not.toBeNull()
+ tooltip.dispose()
- tooltip.dispose()
+ expect(document.querySelector('.tooltip')).toBeNull()
+ resolve()
+ })
- expect(document.querySelector('.tooltip')).toBeNull()
- done()
+ tooltip.show()
})
+ })
- tooltip.show()
+ it('should destroy a tooltip and reset it\'s initial title', () => {
+ fixtureEl.innerHTML = [
+ '<span id="tooltipWithTitle" rel="tooltip" title="tooltipTitle"></span>',
+ '<span id="tooltipWithoutTitle" rel="tooltip" data-bs-title="tooltipTitle"></span>'
+ ].join('')
+
+ const tooltipWithTitleEl = fixtureEl.querySelector('#tooltipWithTitle')
+ const tooltip = new Tooltip('#tooltipWithTitle')
+ expect(tooltipWithTitleEl.getAttribute('title')).toBeNull()
+ tooltip.dispose()
+ expect(tooltipWithTitleEl.getAttribute('title')).toBe('tooltipTitle')
+
+ const tooltipWithoutTitleEl = fixtureEl.querySelector('#tooltipWithoutTitle')
+ const tooltip2 = new Tooltip('#tooltipWithTitle')
+ expect(tooltipWithoutTitleEl.getAttribute('title')).toBeNull()
+ tooltip2.dispose()
+ expect(tooltipWithoutTitleEl.getAttribute('title')).toBeNull()
})
})
describe('show', () => {
- it('should show a tooltip', done => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ it('should show a tooltip', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
- const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl)
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
- tooltipEl.addEventListener('shown.bs.tooltip', () => {
- const tooltipShown = document.querySelector('.tooltip')
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ const tooltipShown = document.querySelector('.tooltip')
- expect(tooltipShown).not.toBeNull()
- expect(tooltipEl.getAttribute('aria-describedby')).toEqual(tooltipShown.getAttribute('id'))
- expect(tooltipShown.getAttribute('id')).toContain('tooltip')
- done()
- })
+ expect(tooltipShown).not.toBeNull()
+ expect(tooltipEl.getAttribute('aria-describedby')).toEqual(tooltipShown.getAttribute('id'))
+ expect(tooltipShown.getAttribute('id')).toContain('tooltip')
+ resolve()
+ })
- tooltip.show()
+ tooltip.show()
+ })
})
- it('should show a tooltip when hovering a children element', done => {
- fixtureEl.innerHTML =
- '<a href="#" rel="tooltip" title="Another tooltip">' +
- '<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 100 100">' +
- '<rect width="100%" fill="#563d7c"/>' +
- '<circle cx="50" cy="50" r="30" fill="#fff"/>' +
- '</svg>' +
- '</a>'
+ it('should show a tooltip when hovering a child element', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<a href="#" rel="tooltip" title="Another tooltip">',
+ ' <svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 100 100">',
+ ' <rect width="100%" fill="#563d7c"/>',
+ ' <circle cx="50" cy="50" r="30" fill="#fff"/>',
+ ' </svg>',
+ '</a>'
+ ].join('')
- const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl)
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
- spyOn(tooltip, 'show')
+ const spy = spyOn(tooltip, 'show')
- tooltipEl.querySelector('rect').dispatchEvent(createEvent('mouseover', { bubbles: true }))
+ tooltipEl.querySelector('rect').dispatchEvent(createEvent('mouseover', { bubbles: true }))
- setTimeout(() => {
- expect(tooltip.show).toHaveBeenCalled()
- done()
- }, 0)
+ setTimeout(() => {
+ expect(spy).toHaveBeenCalled()
+ resolve()
+ }, 0)
+ })
})
- it('should show a tooltip on mobile', done => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ it('should show a tooltip on mobile', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
- const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl)
- document.documentElement.ontouchstart = noop
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
+ document.documentElement.ontouchstart = noop
- spyOn(EventHandler, 'on').and.callThrough()
+ const spy = spyOn(EventHandler, 'on').and.callThrough()
- tooltipEl.addEventListener('shown.bs.tooltip', () => {
- expect(document.querySelector('.tooltip')).not.toBeNull()
- expect(EventHandler.on).toHaveBeenCalledWith(jasmine.any(Object), 'mouseover', noop)
- document.documentElement.ontouchstart = undefined
- done()
- })
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ expect(document.querySelector('.tooltip')).not.toBeNull()
+ expect(spy).toHaveBeenCalledWith(jasmine.any(Object), 'mouseover', noop)
+ document.documentElement.ontouchstart = undefined
+ resolve()
+ })
- tooltip.show()
+ tooltip.show()
+ })
})
- it('should show a tooltip relative to placement option', done => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ it('should show a tooltip relative to placement option', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
- const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl, {
- placement: 'bottom'
- })
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl, {
+ placement: 'bottom'
+ })
- tooltipEl.addEventListener('inserted.bs.tooltip', () => {
- expect(tooltip.getTipElement().classList.contains('bs-tooltip-auto')).toEqual(true)
- })
+ tooltipEl.addEventListener('inserted.bs.tooltip', () => {
+ expect(tooltip._getTipElement()).toHaveClass('bs-tooltip-auto')
+ })
- tooltipEl.addEventListener('shown.bs.tooltip', () => {
- expect(tooltip.getTipElement().classList.contains('bs-tooltip-auto')).toEqual(true)
- expect(tooltip.getTipElement().getAttribute('data-popper-placement')).toEqual('bottom')
- done()
- })
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ expect(tooltip._getTipElement()).toHaveClass('bs-tooltip-auto')
+ expect(tooltip._getTipElement().getAttribute('data-popper-placement')).toEqual('bottom')
+ resolve()
+ })
- tooltip.show()
+ tooltip.show()
+ })
})
- it('should not error when trying to show a tooltip that has been removed from the dom', done => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ it('should not error when trying to show a tooltip that has been removed from the dom', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
- const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl)
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
- const firstCallback = () => {
- tooltipEl.removeEventListener('shown.bs.tooltip', firstCallback)
- let tooltipShown = document.querySelector('.tooltip')
+ const firstCallback = () => {
+ tooltipEl.removeEventListener('shown.bs.tooltip', firstCallback)
+ let tooltipShown = document.querySelector('.tooltip')
- tooltipShown.remove()
+ tooltipShown.remove()
- tooltipEl.addEventListener('shown.bs.tooltip', () => {
- tooltipShown = document.querySelector('.tooltip')
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ tooltipShown = document.querySelector('.tooltip')
- expect(tooltipShown).not.toBeNull()
- done()
- })
+ expect(tooltipShown).not.toBeNull()
+ resolve()
+ })
- tooltip.show()
- }
+ tooltip.show()
+ }
- tooltipEl.addEventListener('shown.bs.tooltip', firstCallback)
+ tooltipEl.addEventListener('shown.bs.tooltip', firstCallback)
- tooltip.show()
+ tooltip.show()
+ })
})
- it('should show a tooltip with a dom element container', done => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ it('should show a tooltip with a dom element container', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
- const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl, {
- container: fixtureEl
- })
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl, {
+ container: fixtureEl
+ })
- tooltipEl.addEventListener('shown.bs.tooltip', () => {
- expect(fixtureEl.querySelector('.tooltip')).not.toBeNull()
- done()
- })
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ expect(fixtureEl.querySelector('.tooltip')).not.toBeNull()
+ resolve()
+ })
- tooltip.show()
+ tooltip.show()
+ })
})
- it('should show a tooltip with a jquery element container', done => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ it('should show a tooltip with a jquery element container', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
- const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl, {
- container: {
- 0: fixtureEl,
- jquery: 'jQuery'
- }
- })
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl, {
+ container: {
+ 0: fixtureEl,
+ jquery: 'jQuery'
+ }
+ })
- tooltipEl.addEventListener('shown.bs.tooltip', () => {
- expect(fixtureEl.querySelector('.tooltip')).not.toBeNull()
- done()
- })
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ expect(fixtureEl.querySelector('.tooltip')).not.toBeNull()
+ resolve()
+ })
- tooltip.show()
+ tooltip.show()
+ })
})
- it('should show a tooltip with a selector in container', done => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ it('should show a tooltip with a selector in container', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
- const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl, {
- container: '#fixture'
- })
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl, {
+ container: '#fixture'
+ })
- tooltipEl.addEventListener('shown.bs.tooltip', () => {
- expect(fixtureEl.querySelector('.tooltip')).not.toBeNull()
- done()
- })
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ expect(fixtureEl.querySelector('.tooltip')).not.toBeNull()
+ resolve()
+ })
- tooltip.show()
+ tooltip.show()
+ })
})
- it('should show a tooltip with placement as a function', done => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ it('should show a tooltip with placement as a function', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
- const spy = jasmine.createSpy('placement').and.returnValue('top')
- const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl, {
- placement: spy
- })
+ const spy = jasmine.createSpy('placement').and.returnValue('top')
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl, {
+ placement: spy
+ })
- tooltipEl.addEventListener('shown.bs.tooltip', () => {
- expect(document.querySelector('.tooltip')).not.toBeNull()
- expect(spy).toHaveBeenCalled()
- done()
- })
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ expect(document.querySelector('.tooltip')).not.toBeNull()
+ expect(spy).toHaveBeenCalled()
+ resolve()
+ })
- tooltip.show()
+ tooltip.show()
+ })
})
- it('should show a tooltip without the animation', done => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ it('should show a tooltip without the animation', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
- const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl, {
- animation: false
- })
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl, {
+ animation: false
+ })
- tooltipEl.addEventListener('shown.bs.tooltip', () => {
- const tip = document.querySelector('.tooltip')
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ const tip = document.querySelector('.tooltip')
- expect(tip).not.toBeNull()
- expect(tip.classList.contains('fade')).toEqual(false)
- done()
- })
+ expect(tip).not.toBeNull()
+ expect(tip).not.toHaveClass('fade')
+ resolve()
+ })
- tooltip.show()
+ tooltip.show()
+ })
})
it('should throw an error the element is not visible', () => {
- fixtureEl.innerHTML = '<a href="#" style="display: none" rel="tooltip" title="Another tooltip">'
+ fixtureEl.innerHTML = '<a href="#" style="display: none" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl)
@@ -605,339 +675,382 @@ describe('Tooltip', () => {
}
})
- it('should not show a tooltip if show.bs.tooltip is prevented', done => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
-
- const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl)
-
- const expectedDone = () => {
- setTimeout(() => {
- expect(document.querySelector('.tooltip')).toBeNull()
- done()
- }, 10)
- }
+ it('should not show a tooltip if show.bs.tooltip is prevented', () => {
+ return new Promise((resolve, reject) => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
- tooltipEl.addEventListener('show.bs.tooltip', ev => {
- ev.preventDefault()
- expectedDone()
- })
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
- tooltipEl.addEventListener('shown.bs.tooltip', () => {
- throw new Error('Tooltip should not be shown')
- })
+ const expectedDone = () => {
+ setTimeout(() => {
+ expect(document.querySelector('.tooltip')).toBeNull()
+ resolve()
+ }, 10)
+ }
- tooltip.show()
- })
+ tooltipEl.addEventListener('show.bs.tooltip', ev => {
+ ev.preventDefault()
+ expectedDone()
+ })
- it('should show tooltip if leave event hasn\'t occurred before delay expires', done => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ reject(new Error('Tooltip should not be shown'))
+ })
- const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl, {
- delay: 150
+ tooltip.show()
})
-
- spyOn(tooltip, 'show')
-
- setTimeout(() => {
- expect(tooltip.show).not.toHaveBeenCalled()
- }, 100)
-
- setTimeout(() => {
- expect(tooltip.show).toHaveBeenCalled()
- done()
- }, 200)
-
- tooltipEl.dispatchEvent(createEvent('mouseover'))
})
- it('should not show tooltip if leave event occurs before delay expires', done => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ it('should show tooltip if leave event hasn\'t occurred before delay expires', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
- const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl, {
- delay: 150
- })
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl, {
+ delay: 150
+ })
- spyOn(tooltip, 'show')
+ const spy = spyOn(tooltip, 'show')
- setTimeout(() => {
- expect(tooltip.show).not.toHaveBeenCalled()
- tooltipEl.dispatchEvent(createEvent('mouseover'))
- }, 100)
+ setTimeout(() => {
+ expect(spy).not.toHaveBeenCalled()
+ }, 100)
- setTimeout(() => {
- expect(tooltip.show).toHaveBeenCalled()
- expect(document.querySelectorAll('.tooltip').length).toEqual(0)
- done()
- }, 200)
+ setTimeout(() => {
+ expect(spy).toHaveBeenCalled()
+ resolve()
+ }, 200)
- tooltipEl.dispatchEvent(createEvent('mouseover'))
+ tooltipEl.dispatchEvent(createEvent('mouseover'))
+ })
})
- it('should not hide tooltip if leave event occurs and enter event occurs within the hide delay', done => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ it('should not show tooltip if leave event occurs before delay expires', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
- const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl, {
- delay: {
- show: 0,
- hide: 150
- }
- })
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl, {
+ delay: 150
+ })
- setTimeout(() => {
- expect(tooltip.getTipElement().classList.contains('show')).toEqual(true)
- tooltipEl.dispatchEvent(createEvent('mouseout'))
+ const spy = spyOn(tooltip, 'show')
setTimeout(() => {
- expect(tooltip.getTipElement().classList.contains('show')).toEqual(true)
+ expect(spy).not.toHaveBeenCalled()
tooltipEl.dispatchEvent(createEvent('mouseover'))
}, 100)
setTimeout(() => {
- expect(tooltip.getTipElement().classList.contains('show')).toEqual(true)
- expect(document.querySelectorAll('.tooltip').length).toEqual(1)
- done()
+ expect(spy).toHaveBeenCalled()
+ expect(document.querySelectorAll('.tooltip')).toHaveSize(0)
+ resolve()
}, 200)
- }, 0)
- tooltipEl.dispatchEvent(createEvent('mouseover'))
+ tooltipEl.dispatchEvent(createEvent('mouseover'))
+ })
})
- it('should not hide tooltip if leave event occurs and interaction remains inside trigger', done => {
- fixtureEl.innerHTML = [
- '<a href="#" rel="tooltip" title="Another tooltip">',
- '<b>Trigger</b>',
- 'the tooltip',
- '</a>'
- ]
+ it('should not hide tooltip if leave event occurs and enter event occurs within the hide delay', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip" data-bs-delay=\'{"show":0,"hide":150}\'>'
- const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl)
- const triggerChild = tooltipEl.querySelector('b')
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
- spyOn(tooltip, 'hide').and.callThrough()
+ expect(tooltip._config.delay).toEqual({ show: 0, hide: 150 })
- tooltipEl.addEventListener('mouseover', () => {
- const moveMouseToChildEvent = createEvent('mouseout')
- Object.defineProperty(moveMouseToChildEvent, 'relatedTarget', {
- value: triggerChild
- })
+ setTimeout(() => {
+ expect(tooltip._getTipElement()).toHaveClass('show')
+ tooltipEl.dispatchEvent(createEvent('mouseout'))
+
+ setTimeout(() => {
+ expect(tooltip._getTipElement()).toHaveClass('show')
+ tooltipEl.dispatchEvent(createEvent('mouseover'))
+ }, 100)
+
+ setTimeout(() => {
+ expect(tooltip._getTipElement()).toHaveClass('show')
+ expect(document.querySelectorAll('.tooltip')).toHaveSize(1)
+ resolve()
+ }, 200)
+ }, 10)
- tooltipEl.dispatchEvent(moveMouseToChildEvent)
+ tooltipEl.dispatchEvent(createEvent('mouseover'))
})
+ })
- tooltipEl.addEventListener('mouseout', () => {
- expect(tooltip.hide).not.toHaveBeenCalled()
- done()
- })
+ it('should not hide tooltip if leave event occurs and interaction remains inside trigger', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<a href="#" rel="tooltip" title="Another tooltip">',
+ '<b>Trigger</b>',
+ 'the tooltip',
+ '</a>'
+ ].join('')
- tooltipEl.dispatchEvent(createEvent('mouseover'))
- })
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
+ const triggerChild = tooltipEl.querySelector('b')
- it('should properly maintain tooltip state if leave event occurs and enter event occurs during hide transition', done => {
- // Style this tooltip to give it plenty of room for popper to do what it wants
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip" data-bs-placement="top" style="position:fixed;left:50%;top:50%;">Trigger</a>'
+ const spy = spyOn(tooltip, 'hide').and.callThrough()
- const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl)
+ tooltipEl.addEventListener('mouseover', () => {
+ const moveMouseToChildEvent = createEvent('mouseout')
+ Object.defineProperty(moveMouseToChildEvent, 'relatedTarget', {
+ value: triggerChild
+ })
+
+ tooltipEl.dispatchEvent(moveMouseToChildEvent)
+ })
+
+ tooltipEl.addEventListener('mouseout', () => {
+ expect(spy).not.toHaveBeenCalled()
+ resolve()
+ })
- spyOn(window, 'getComputedStyle').and.returnValue({
- transitionDuration: '0.15s',
- transitionDelay: '0s'
+ tooltipEl.dispatchEvent(createEvent('mouseover'))
})
+ })
- setTimeout(() => {
- expect(tooltip._popper).not.toBeNull()
- expect(tooltip.getTipElement().getAttribute('data-popper-placement')).toBe('top')
- tooltipEl.dispatchEvent(createEvent('mouseout'))
+ it('should properly maintain tooltip state if leave event occurs and enter event occurs during hide transition', () => {
+ return new Promise(resolve => {
+ // Style this tooltip to give it plenty of room for popper to do what it wants
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip" data-bs-placement="top" style="position:fixed;left:50%;top:50%;">Trigger</a>'
- setTimeout(() => {
- expect(tooltip.getTipElement().classList.contains('show')).toEqual(false)
- tooltipEl.dispatchEvent(createEvent('mouseover'))
- }, 100)
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
+
+ spyOn(window, 'getComputedStyle').and.returnValue({
+ transitionDuration: '0.15s',
+ transitionDelay: '0s'
+ })
setTimeout(() => {
expect(tooltip._popper).not.toBeNull()
- expect(tooltip.getTipElement().getAttribute('data-popper-placement')).toBe('top')
- done()
- }, 200)
- }, 0)
+ expect(tooltip._getTipElement().getAttribute('data-popper-placement')).toEqual('top')
+ tooltipEl.dispatchEvent(createEvent('mouseout'))
+
+ setTimeout(() => {
+ expect(tooltip._getTipElement()).not.toHaveClass('show')
+ tooltipEl.dispatchEvent(createEvent('mouseover'))
+ }, 100)
+
+ setTimeout(() => {
+ expect(tooltip._popper).not.toBeNull()
+ expect(tooltip._getTipElement().getAttribute('data-popper-placement')).toEqual('top')
+ resolve()
+ }, 200)
+ }, 10)
- tooltipEl.dispatchEvent(createEvent('mouseover'))
+ tooltipEl.dispatchEvent(createEvent('mouseover'))
+ })
})
- it('should only trigger inserted event if a new tooltip element was created', done => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ it('should only trigger inserted event if a new tooltip element was created', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
- const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl)
-
- spyOn(window, 'getComputedStyle').and.returnValue({
- transitionDuration: '0.15s',
- transitionDelay: '0s'
- })
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
- const insertedFunc = jasmine.createSpy()
- tooltipEl.addEventListener('inserted.bs.tooltip', insertedFunc)
+ spyOn(window, 'getComputedStyle').and.returnValue({
+ transitionDuration: '0.15s',
+ transitionDelay: '0s'
+ })
- setTimeout(() => {
- expect(insertedFunc).toHaveBeenCalledTimes(1)
- tooltip.hide()
-
- setTimeout(() => {
- tooltip.show()
- }, 100)
+ const insertedFunc = jasmine.createSpy()
+ tooltipEl.addEventListener('inserted.bs.tooltip', insertedFunc)
setTimeout(() => {
expect(insertedFunc).toHaveBeenCalledTimes(1)
- done()
- }, 200)
- }, 0)
+ tooltip.hide()
- tooltip.show()
+ setTimeout(() => {
+ tooltip.show()
+ }, 100)
+
+ setTimeout(() => {
+ expect(insertedFunc).toHaveBeenCalledTimes(2)
+ resolve()
+ }, 200)
+ }, 0)
+
+ tooltip.show()
+ })
})
- it('should show a tooltip with custom class provided in data attributes', done => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip" data-bs-custom-class="custom-class">'
+ it('should show a tooltip with custom class provided in data attributes', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip" data-bs-custom-class="custom-class"></a>'
- const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl)
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
- tooltipEl.addEventListener('shown.bs.tooltip', () => {
- const tip = document.querySelector('.tooltip')
- expect(tip).not.toBeNull()
- expect(tip.classList.contains('custom-class')).toBeTrue()
- done()
- })
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ const tip = document.querySelector('.tooltip')
+ expect(tip).not.toBeNull()
+ expect(tip).toHaveClass('custom-class')
+ resolve()
+ })
- tooltip.show()
+ tooltip.show()
+ })
})
- it('should show a tooltip with custom class provided as a string in config', done => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ it('should show a tooltip with custom class provided as a string in config', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
- const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl, {
- customClass: 'custom-class custom-class-2'
- })
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl, {
+ customClass: 'custom-class custom-class-2'
+ })
- tooltipEl.addEventListener('shown.bs.tooltip', () => {
- const tip = document.querySelector('.tooltip')
- expect(tip).not.toBeNull()
- expect(tip.classList.contains('custom-class')).toBeTrue()
- expect(tip.classList.contains('custom-class-2')).toBeTrue()
- done()
- })
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ const tip = document.querySelector('.tooltip')
+ expect(tip).not.toBeNull()
+ expect(tip).toHaveClass('custom-class')
+ expect(tip).toHaveClass('custom-class-2')
+ resolve()
+ })
- tooltip.show()
+ tooltip.show()
+ })
})
- it('should show a tooltip with custom class provided as a function in config', done => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ it('should show a tooltip with custom class provided as a function in config', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip" data-class-a="custom-class-a" data-class-b="custom-class-b"></a>'
- const spy = jasmine.createSpy('customClass').and.returnValue('custom-class')
- const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl, {
- customClass: spy
- })
+ const tooltipEl = fixtureEl.querySelector('a')
+ const spy = jasmine.createSpy('customClass').and.callFake(function (el) {
+ return `${el.dataset.classA} ${this.dataset.classB}`
+ })
+ const tooltip = new Tooltip(tooltipEl, {
+ customClass: spy
+ })
+
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ const tip = document.querySelector('.tooltip')
+ expect(tip).not.toBeNull()
+ expect(spy).toHaveBeenCalled()
+ expect(tip).toHaveClass('custom-class-a')
+ expect(tip).toHaveClass('custom-class-b')
+ resolve()
+ })
- tooltipEl.addEventListener('shown.bs.tooltip', () => {
- const tip = document.querySelector('.tooltip')
- expect(tip).not.toBeNull()
- expect(spy).toHaveBeenCalled()
- expect(tip.classList.contains('custom-class')).toBeTrue()
- done()
+ tooltip.show()
})
+ })
- tooltip.show()
+ it('should remove `title` attribute if exists', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
+
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ expect(tooltipEl.getAttribute('title')).toBeNull()
+ resolve()
+ })
+ tooltip.show()
+ })
})
})
describe('hide', () => {
- it('should hide a tooltip', done => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ it('should hide a tooltip', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
- const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl)
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
- tooltipEl.addEventListener('shown.bs.tooltip', () => tooltip.hide())
- tooltipEl.addEventListener('hidden.bs.tooltip', () => {
- expect(document.querySelector('.tooltip')).toBeNull()
- expect(tooltipEl.getAttribute('aria-describedby')).toBeNull()
- done()
- })
+ tooltipEl.addEventListener('shown.bs.tooltip', () => tooltip.hide())
+ tooltipEl.addEventListener('hidden.bs.tooltip', () => {
+ expect(document.querySelector('.tooltip')).toBeNull()
+ expect(tooltipEl.getAttribute('aria-describedby')).toBeNull()
+ resolve()
+ })
- tooltip.show()
+ tooltip.show()
+ })
})
- it('should hide a tooltip on mobile', done => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ it('should hide a tooltip on mobile', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
- const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl)
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
+ const spy = spyOn(EventHandler, 'off')
- tooltipEl.addEventListener('shown.bs.tooltip', () => {
- document.documentElement.ontouchstart = noop
- spyOn(EventHandler, 'off')
- tooltip.hide()
- })
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ document.documentElement.ontouchstart = noop
+ tooltip.hide()
+ })
- tooltipEl.addEventListener('hidden.bs.tooltip', () => {
- expect(document.querySelector('.tooltip')).toBeNull()
- expect(EventHandler.off).toHaveBeenCalledWith(jasmine.any(Object), 'mouseover', noop)
- document.documentElement.ontouchstart = undefined
- done()
- })
+ tooltipEl.addEventListener('hidden.bs.tooltip', () => {
+ expect(document.querySelector('.tooltip')).toBeNull()
+ expect(spy).toHaveBeenCalledWith(jasmine.any(Object), 'mouseover', noop)
+ document.documentElement.ontouchstart = undefined
+ resolve()
+ })
- tooltip.show()
+ tooltip.show()
+ })
})
- it('should hide a tooltip without animation', done => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ it('should hide a tooltip without animation', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
- const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl, {
- animation: false
- })
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl, {
+ animation: false
+ })
- tooltipEl.addEventListener('shown.bs.tooltip', () => tooltip.hide())
- tooltipEl.addEventListener('hidden.bs.tooltip', () => {
- expect(document.querySelector('.tooltip')).toBeNull()
- expect(tooltipEl.getAttribute('aria-describedby')).toBeNull()
- done()
- })
+ tooltipEl.addEventListener('shown.bs.tooltip', () => tooltip.hide())
+ tooltipEl.addEventListener('hidden.bs.tooltip', () => {
+ expect(document.querySelector('.tooltip')).toBeNull()
+ expect(tooltipEl.getAttribute('aria-describedby')).toBeNull()
+ resolve()
+ })
- tooltip.show()
+ tooltip.show()
+ })
})
- it('should not hide a tooltip if hide event is prevented', done => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ it('should not hide a tooltip if hide event is prevented', () => {
+ return new Promise((resolve, reject) => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
- const assertDone = () => {
- setTimeout(() => {
- expect(document.querySelector('.tooltip')).not.toBeNull()
- done()
- }, 20)
- }
+ const assertDone = () => {
+ setTimeout(() => {
+ expect(document.querySelector('.tooltip')).not.toBeNull()
+ resolve()
+ }, 20)
+ }
- const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl, {
- animation: false
- })
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl, {
+ animation: false
+ })
- tooltipEl.addEventListener('shown.bs.tooltip', () => tooltip.hide())
- tooltipEl.addEventListener('hide.bs.tooltip', event => {
- event.preventDefault()
- assertDone()
- })
- tooltipEl.addEventListener('hidden.bs.tooltip', () => {
- throw new Error('should not trigger hidden event')
- })
+ tooltipEl.addEventListener('shown.bs.tooltip', () => tooltip.hide())
+ tooltipEl.addEventListener('hide.bs.tooltip', event => {
+ event.preventDefault()
+ assertDone()
+ })
+ tooltipEl.addEventListener('hidden.bs.tooltip', () => {
+ reject(new Error('should not trigger hidden event'))
+ })
- tooltip.show()
+ tooltip.show()
+ })
})
it('should not throw error running hide if popper hasn\'t been shown', () => {
@@ -956,26 +1069,28 @@ describe('Tooltip', () => {
})
describe('update', () => {
- it('should call popper update', done => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ it('should call popper update', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
- const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl)
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
- tooltipEl.addEventListener('shown.bs.tooltip', () => {
- spyOn(tooltip._popper, 'update')
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ const spy = spyOn(tooltip._popper, 'update')
- tooltip.update()
+ tooltip.update()
- expect(tooltip._popper.update).toHaveBeenCalled()
- done()
- })
+ expect(spy).toHaveBeenCalled()
+ resolve()
+ })
- tooltip.show()
+ tooltip.show()
+ })
})
it('should do nothing if the tooltip is not shown', () => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl)
@@ -985,140 +1100,122 @@ describe('Tooltip', () => {
})
})
- describe('isWithContent', () => {
+ describe('_isWithContent', () => {
it('should return true if there is content', () => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl)
- expect(tooltip.isWithContent()).toEqual(true)
+ expect(tooltip._isWithContent()).toBeTrue()
})
it('should return false if there is no content', () => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="">'
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title=""></a>'
const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl)
- expect(tooltip.isWithContent()).toEqual(false)
+ expect(tooltip._isWithContent()).toBeFalse()
})
})
- describe('getTipElement', () => {
+ describe('_getTipElement', () => {
it('should create the tip element and return it', () => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl)
- spyOn(document, 'createElement').and.callThrough()
+ const spy = spyOn(document, 'createElement').and.callThrough()
- expect(tooltip.getTipElement()).toBeDefined()
- expect(document.createElement).toHaveBeenCalled()
+ expect(tooltip._getTipElement()).toBeDefined()
+ expect(spy).toHaveBeenCalled()
})
it('should return the created tip element', () => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl)
const spy = spyOn(document, 'createElement').and.callThrough()
- expect(tooltip.getTipElement()).toBeDefined()
+ expect(tooltip._getTipElement()).toBeDefined()
expect(spy).toHaveBeenCalled()
spy.calls.reset()
- expect(tooltip.getTipElement()).toBeDefined()
+ expect(tooltip._getTipElement()).toBeDefined()
expect(spy).not.toHaveBeenCalled()
})
})
describe('setContent', () => {
it('should set tip content', () => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl, { animation: false })
- const tip = tooltip.getTipElement()
+ const tip = tooltip._getTipElement()
tooltip.setContent(tip)
- expect(tip.classList.contains('show')).toEqual(false)
- expect(tip.classList.contains('fade')).toEqual(false)
+ expect(tip).not.toHaveClass('show')
+ expect(tip).not.toHaveClass('fade')
expect(tip.querySelector('.tooltip-inner').textContent).toEqual('Another tooltip')
})
it('should re-show tip if it was already shown', () => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" data-bs-title="Another tooltip">'
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" data-bs-title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl)
tooltip.show()
- const tip = () => tooltip.getTipElement()
+ const tip = () => tooltip._getTipElement()
- expect(tip().classList.contains('show')).toEqual(true)
+ expect(tip()).toHaveClass('show')
tooltip.setContent({ '.tooltip-inner': 'foo' })
- expect(tip().classList.contains('show')).toEqual(true)
+ expect(tip()).toHaveClass('show')
expect(tip().querySelector('.tooltip-inner').textContent).toEqual('foo')
})
it('should keep tip hidden, if it was already hidden before', () => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" data-bs-title="Another tooltip">'
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" data-bs-title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl)
- const tip = () => tooltip.getTipElement()
+ const tip = () => tooltip._getTipElement()
- expect(tip().classList.contains('show')).toEqual(false)
+ expect(tip()).not.toHaveClass('show')
tooltip.setContent({ '.tooltip-inner': 'foo' })
- expect(tip().classList.contains('show')).toEqual(false)
- expect(tip().querySelector('.tooltip-inner').textContent).toEqual('foo')
- })
- })
-
- describe('updateAttachment', () => {
- it('should use end class name when right placement specified', done => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
-
- const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl, {
- placement: 'right'
- })
-
- tooltipEl.addEventListener('inserted.bs.tooltip', () => {
- expect(tooltip.getTipElement().classList.contains('bs-tooltip-auto')).toEqual(true)
- done()
- })
-
+ expect(tip()).not.toHaveClass('show')
tooltip.show()
+ expect(tip().querySelector('.tooltip-inner').textContent).toEqual('foo')
})
- it('should use start class name when left placement specified', done => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ it('"setContent" should keep the initial template', () => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl, {
- placement: 'left'
- })
+ const tooltip = new Tooltip(tooltipEl)
- tooltipEl.addEventListener('inserted.bs.tooltip', () => {
- expect(tooltip.getTipElement().classList.contains('bs-tooltip-auto')).toEqual(true)
- done()
- })
+ tooltip.setContent({ '.tooltip-inner': 'foo' })
+ const tip = tooltip._getTipElement()
- tooltip.show()
+ expect(tip).toHaveClass('tooltip')
+ expect(tip).toHaveClass('bs-tooltip-auto')
+ expect(tip.querySelector('.tooltip-arrow')).not.toBeNull()
+ expect(tip.querySelector('.tooltip-inner')).not.toBeNull()
})
})
describe('setContent', () => {
it('should do nothing if the element is null', () => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl)
@@ -1130,7 +1227,8 @@ describe('Tooltip', () => {
it('should do nothing if the content is a child of the element', () => {
fixtureEl.innerHTML = [
'<a href="#" rel="tooltip" title="Another tooltip">',
- '<div id="childContent"></div>'
+ ' <div id="childContent"></div>',
+ '</a>'
].join('')
const tooltipEl = fixtureEl.querySelector('a')
@@ -1139,7 +1237,7 @@ describe('Tooltip', () => {
html: true
})
- tooltip.getTipElement().append(childContent)
+ tooltip._getTipElement().append(childContent)
tooltip.setContent({ '.tooltip': childContent })
expect().nothing()
@@ -1148,7 +1246,8 @@ describe('Tooltip', () => {
it('should add the content as a child of the element for jQuery elements', () => {
fixtureEl.innerHTML = [
'<a href="#" rel="tooltip" title="Another tooltip">',
- '<div id="childContent"></div>'
+ ' <div id="childContent"></div>',
+ '</a>'
].join('')
const tooltipEl = fixtureEl.querySelector('a')
@@ -1158,14 +1257,16 @@ describe('Tooltip', () => {
})
tooltip.setContent({ '.tooltip': { 0: childContent, jquery: 'jQuery' } })
+ tooltip.show()
- expect(childContent.parentNode).toEqual(tooltip.getTipElement())
+ expect(childContent.parentNode).toEqual(tooltip._getTipElement())
})
it('should add the child text content in the element', () => {
fixtureEl.innerHTML = [
'<a href="#" rel="tooltip" title="Another tooltip">',
- '<div id="childContent">Tooltip</div>'
+ ' <div id="childContent">Tooltip</div>',
+ '</a>'
].join('')
const tooltipEl = fixtureEl.querySelector('a')
@@ -1174,11 +1275,11 @@ describe('Tooltip', () => {
tooltip.setContent({ '.tooltip': childContent })
- expect(childContent.textContent).toEqual(tooltip.getTipElement().textContent)
+ expect(childContent.textContent).toEqual(tooltip._getTipElement().textContent)
})
it('should add html without sanitize it', () => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl, {
@@ -1188,11 +1289,11 @@ describe('Tooltip', () => {
tooltip.setContent({ '.tooltip': '<div id="childContent">Tooltip</div>' })
- expect(tooltip.getTipElement().querySelector('div').id).toEqual('childContent')
+ expect(tooltip._getTipElement().querySelector('div').id).toEqual('childContent')
})
it('should add html sanitized', () => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl, {
@@ -1201,35 +1302,35 @@ describe('Tooltip', () => {
const content = [
'<div id="childContent">',
- ' <button type="button">test btn</button>',
+ ' <button type="button">test btn</button>',
'</div>'
].join('')
tooltip.setContent({ '.tooltip': content })
- expect(tooltip.getTipElement().querySelector('div').id).toEqual('childContent')
- expect(tooltip.getTipElement().querySelector('button')).toEqual(null)
+ expect(tooltip._getTipElement().querySelector('div').id).toEqual('childContent')
+ expect(tooltip._getTipElement().querySelector('button')).toBeNull()
})
it('should add text content', () => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl)
tooltip.setContent({ '.tooltip': 'test' })
- expect(tooltip.getTipElement().textContent).toEqual('test')
+ expect(tooltip._getTipElement().textContent).toEqual('test')
})
})
- describe('getTitle', () => {
+ describe('_getTitle', () => {
it('should return the title', () => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl)
- expect(tooltip.getTitle()).toEqual('Another tooltip')
+ expect(tooltip._getTitle()).toEqual('Another tooltip')
})
it('should call title function', () => {
@@ -1240,7 +1341,33 @@ describe('Tooltip', () => {
title: () => 'test'
})
- expect(tooltip.getTitle()).toEqual('test')
+ expect(tooltip._getTitle()).toEqual('test')
+ })
+
+ it('should call title function with trigger element', () => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" data-foo="bar"></a>'
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl, {
+ title(el) {
+ return el.dataset.foo
+ }
+ })
+
+ expect(tooltip._getTitle()).toEqual('bar')
+ })
+
+ it('should call title function with correct this value', () => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" data-foo="bar"></a>'
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl, {
+ title() {
+ return this.dataset.foo
+ }
+ })
+
+ expect(tooltip._getTitle()).toEqual('bar')
})
})
@@ -1260,60 +1387,85 @@ describe('Tooltip', () => {
const div = fixtureEl.querySelector('div')
- expect(Tooltip.getInstance(div)).toEqual(null)
+ expect(Tooltip.getInstance(div)).toBeNull()
})
})
describe('aria-label', () => {
- it('should add the aria-label attribute for referencing original title', done => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
+ it('should add the aria-label attribute for referencing original title', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
- const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl)
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
- tooltipEl.addEventListener('shown.bs.tooltip', () => {
- const tooltipShown = document.querySelector('.tooltip')
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ const tooltipShown = document.querySelector('.tooltip')
- expect(tooltipShown).not.toBeNull()
- expect(tooltipEl.getAttribute('aria-label')).toEqual('Another tooltip')
- done()
- })
+ expect(tooltipShown).not.toBeNull()
+ expect(tooltipEl.getAttribute('aria-label')).toEqual('Another tooltip')
+ resolve()
+ })
- tooltip.show()
+ tooltip.show()
+ })
})
- it('should not add the aria-label attribute if the attribute already exists', done => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" aria-label="Different label" title="Another tooltip"></a>'
+ it('should add the aria-label attribute when element text content is a whitespace string', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="A tooltip"><span> </span></a>'
- const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl)
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
- tooltipEl.addEventListener('shown.bs.tooltip', () => {
- const tooltipShown = document.querySelector('.tooltip')
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ const tooltipShown = document.querySelector('.tooltip')
- expect(tooltipShown).not.toBeNull()
- expect(tooltipEl.getAttribute('aria-label')).toEqual('Different label')
- done()
- })
+ expect(tooltipShown).not.toBeNull()
+ expect(tooltipEl.getAttribute('aria-label')).toEqual('A tooltip')
+ resolve()
+ })
- tooltip.show()
+ tooltip.show()
+ })
})
- it('should not add the aria-label attribute if the element has text content', done => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">text content</a>'
+ it('should not add the aria-label attribute if the attribute already exists', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" aria-label="Different label" title="Another tooltip"></a>'
- const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl)
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
+
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ const tooltipShown = document.querySelector('.tooltip')
- tooltipEl.addEventListener('shown.bs.tooltip', () => {
- const tooltipShown = document.querySelector('.tooltip')
+ expect(tooltipShown).not.toBeNull()
+ expect(tooltipEl.getAttribute('aria-label')).toEqual('Different label')
+ resolve()
+ })
- expect(tooltipShown).not.toBeNull()
- expect(tooltipEl.getAttribute('aria-label')).toBeNull()
- done()
+ tooltip.show()
})
+ })
- tooltip.show()
+ it('should not add the aria-label attribute if the element has text content', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">text content</a>'
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
+
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ const tooltipShown = document.querySelector('.tooltip')
+
+ expect(tooltipShown).not.toBeNull()
+ expect(tooltipEl.getAttribute('aria-label')).toBeNull()
+ resolve()
+ })
+
+ tooltip.show()
+ })
})
})
@@ -1334,7 +1486,7 @@ describe('Tooltip', () => {
const div = fixtureEl.querySelector('div')
- expect(Tooltip.getInstance(div)).toEqual(null)
+ expect(Tooltip.getInstance(div)).toBeNull()
expect(Tooltip.getOrCreateInstance(div)).toBeInstanceOf(Tooltip)
})
@@ -1343,13 +1495,13 @@ describe('Tooltip', () => {
const div = fixtureEl.querySelector('div')
- expect(Tooltip.getInstance(div)).toEqual(null)
+ expect(Tooltip.getInstance(div)).toBeNull()
const tooltip = Tooltip.getOrCreateInstance(div, {
title: () => 'test'
})
expect(tooltip).toBeInstanceOf(Tooltip)
- expect(tooltip.getTitle()).toEqual('test')
+ expect(tooltip._getTitle()).toEqual('test')
})
it('should return the instance when exists without given configuration', () => {
@@ -1367,7 +1519,7 @@ describe('Tooltip', () => {
expect(tooltip).toBeInstanceOf(Tooltip)
expect(tooltip2).toEqual(tooltip)
- expect(tooltip2.getTitle()).toEqual('nothing')
+ expect(tooltip2._getTitle()).toEqual('nothing')
})
})
@@ -1405,7 +1557,7 @@ describe('Tooltip', () => {
const div = fixtureEl.querySelector('div')
const tooltip = new Tooltip(div)
- spyOn(tooltip, 'show')
+ const spy = spyOn(tooltip, 'show')
jQueryMock.fn.tooltip = Tooltip.jQueryInterface
jQueryMock.elements = [div]
@@ -1413,7 +1565,7 @@ describe('Tooltip', () => {
jQueryMock.fn.tooltip.call(jQueryMock, 'show')
expect(Tooltip.getInstance(div)).toEqual(tooltip)
- expect(tooltip.show).toHaveBeenCalled()
+ expect(spy).toHaveBeenCalled()
})
it('should throw error on undefined method', () => {
diff --git a/js/tests/unit/util/backdrop.spec.js b/js/tests/unit/util/backdrop.spec.js
index 818ddf221..0faaac6a5 100644
--- a/js/tests/unit/util/backdrop.spec.js
+++ b/js/tests/unit/util/backdrop.spec.js
@@ -1,6 +1,6 @@
-import Backdrop from '../../../src/util/backdrop'
-import { getTransitionDurationFromElement } from '../../../src/util/index'
-import { clearFixture, getFixture } from '../../helpers/fixture'
+import Backdrop from '../../../src/util/backdrop.js'
+import { getTransitionDurationFromElement } from '../../../src/util/index.js'
+import { clearFixture, getFixture } from '../../helpers/fixture.js'
const CLASS_BACKDROP = '.modal-backdrop'
const CLASS_NAME_FADE = 'fade'
@@ -23,270 +23,297 @@ describe('Backdrop', () => {
})
describe('show', () => {
- it('if it is "shown", should append the backdrop html once, on show, and contain "show" class', done => {
- const instance = new Backdrop({
- isVisible: true,
- isAnimated: false
- })
- const getElements = () => document.querySelectorAll(CLASS_BACKDROP)
+ it('should append the backdrop html once on show and include the "show" class if it is "shown"', () => {
+ return new Promise(resolve => {
+ const instance = new Backdrop({
+ isVisible: true,
+ isAnimated: false
+ })
+ const getElements = () => document.querySelectorAll(CLASS_BACKDROP)
- expect(getElements().length).toEqual(0)
+ expect(getElements()).toHaveSize(0)
- instance.show()
- instance.show(() => {
- expect(getElements().length).toEqual(1)
- for (const el of getElements()) {
- expect(el.classList.contains(CLASS_NAME_SHOW)).toEqual(true)
- }
+ instance.show()
+ instance.show(() => {
+ expect(getElements()).toHaveSize(1)
+ for (const el of getElements()) {
+ expect(el).toHaveClass(CLASS_NAME_SHOW)
+ }
- done()
+ resolve()
+ })
})
})
- it('if it is not "shown", should not append the backdrop html', done => {
- const instance = new Backdrop({
- isVisible: false,
- isAnimated: true
- })
- const getElements = () => document.querySelectorAll(CLASS_BACKDROP)
+ it('should not append the backdrop html if it is not "shown"', () => {
+ return new Promise(resolve => {
+ const instance = new Backdrop({
+ isVisible: false,
+ isAnimated: true
+ })
+ const getElements = () => document.querySelectorAll(CLASS_BACKDROP)
- expect(getElements().length).toEqual(0)
- instance.show(() => {
- expect(getElements().length).toEqual(0)
- done()
+ expect(getElements()).toHaveSize(0)
+ instance.show(() => {
+ expect(getElements()).toHaveSize(0)
+ resolve()
+ })
})
})
- it('if it is "shown" and "animated", should append the backdrop html once, and contain "fade" class', done => {
- const instance = new Backdrop({
- isVisible: true,
- isAnimated: true
- })
- const getElements = () => document.querySelectorAll(CLASS_BACKDROP)
+ it('should append the backdrop html once and include the "fade" class if it is "shown" and "animated"', () => {
+ return new Promise(resolve => {
+ const instance = new Backdrop({
+ isVisible: true,
+ isAnimated: true
+ })
+ const getElements = () => document.querySelectorAll(CLASS_BACKDROP)
- expect(getElements().length).toEqual(0)
+ expect(getElements()).toHaveSize(0)
- instance.show(() => {
- expect(getElements().length).toEqual(1)
- for (const el of getElements()) {
- expect(el.classList.contains(CLASS_NAME_FADE)).toEqual(true)
- }
+ instance.show(() => {
+ expect(getElements()).toHaveSize(1)
+ for (const el of getElements()) {
+ expect(el).toHaveClass(CLASS_NAME_FADE)
+ }
- done()
+ resolve()
+ })
})
})
})
describe('hide', () => {
- it('should remove the backdrop html', done => {
- const instance = new Backdrop({
- isVisible: true,
- isAnimated: true
- })
+ it('should remove the backdrop html', () => {
+ return new Promise(resolve => {
+ const instance = new Backdrop({
+ isVisible: true,
+ isAnimated: true
+ })
- const getElements = () => document.body.querySelectorAll(CLASS_BACKDROP)
+ const getElements = () => document.body.querySelectorAll(CLASS_BACKDROP)
- expect(getElements().length).toEqual(0)
- instance.show(() => {
- expect(getElements().length).toEqual(1)
- instance.hide(() => {
- expect(getElements().length).toEqual(0)
- done()
+ expect(getElements()).toHaveSize(0)
+ instance.show(() => {
+ expect(getElements()).toHaveSize(1)
+ instance.hide(() => {
+ expect(getElements()).toHaveSize(0)
+ resolve()
+ })
})
})
})
- it('should remove "show" class', done => {
- const instance = new Backdrop({
- isVisible: true,
- isAnimated: true
- })
- const elem = instance._getElement()
+ it('should remove the "show" class', () => {
+ return new Promise(resolve => {
+ const instance = new Backdrop({
+ isVisible: true,
+ isAnimated: true
+ })
+ const elem = instance._getElement()
- instance.show()
- instance.hide(() => {
- expect(elem.classList.contains(CLASS_NAME_SHOW)).toEqual(false)
- done()
+ instance.show()
+ instance.hide(() => {
+ expect(elem).not.toHaveClass(CLASS_NAME_SHOW)
+ resolve()
+ })
})
})
- it('if it is not "shown", should not try to remove Node on remove method', done => {
- const instance = new Backdrop({
- isVisible: false,
- isAnimated: true
- })
- const getElements = () => document.querySelectorAll(CLASS_BACKDROP)
- const spy = spyOn(instance, 'dispose').and.callThrough()
+ it('should not try to remove Node on remove method if it is not "shown"', () => {
+ return new Promise(resolve => {
+ const instance = new Backdrop({
+ isVisible: false,
+ isAnimated: true
+ })
+ const getElements = () => document.querySelectorAll(CLASS_BACKDROP)
+ const spy = spyOn(instance, 'dispose').and.callThrough()
- expect(getElements().length).toEqual(0)
- expect(instance._isAppended).toEqual(false)
- instance.show(() => {
- instance.hide(() => {
- expect(getElements().length).toEqual(0)
- expect(spy).not.toHaveBeenCalled()
- expect(instance._isAppended).toEqual(false)
- done()
+ expect(getElements()).toHaveSize(0)
+ expect(instance._isAppended).toBeFalse()
+ instance.show(() => {
+ instance.hide(() => {
+ expect(getElements()).toHaveSize(0)
+ expect(spy).not.toHaveBeenCalled()
+ expect(instance._isAppended).toBeFalse()
+ resolve()
+ })
})
})
})
- it('should not error if the backdrop no longer has a parent', done => {
- fixtureEl.innerHTML = '<div id="wrapper"></div>'
+ it('should not error if the backdrop no longer has a parent', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div id="wrapper"></div>'
- const wrapper = fixtureEl.querySelector('#wrapper')
- const instance = new Backdrop({
- isVisible: true,
- isAnimated: true,
- rootElement: wrapper
- })
+ const wrapper = fixtureEl.querySelector('#wrapper')
+ const instance = new Backdrop({
+ isVisible: true,
+ isAnimated: true,
+ rootElement: wrapper
+ })
- const getElements = () => document.querySelectorAll(CLASS_BACKDROP)
+ const getElements = () => document.querySelectorAll(CLASS_BACKDROP)
- instance.show(() => {
- wrapper.remove()
- instance.hide(() => {
- expect(getElements().length).toEqual(0)
- done()
+ instance.show(() => {
+ wrapper.remove()
+ instance.hide(() => {
+ expect(getElements()).toHaveSize(0)
+ resolve()
+ })
})
})
})
})
describe('click callback', () => {
- it('it should execute callback on click', done => {
- const spy = jasmine.createSpy('spy')
+ it('should execute callback on click', () => {
+ return new Promise(resolve => {
+ const spy = jasmine.createSpy('spy')
- const instance = new Backdrop({
- isVisible: true,
- isAnimated: false,
- clickCallback: () => spy()
- })
- const endTest = () => {
- setTimeout(() => {
- expect(spy).toHaveBeenCalled()
- done()
- }, 10)
- }
-
- instance.show(() => {
- const clickEvent = document.createEvent('MouseEvents')
- clickEvent.initEvent('mousedown', true, true)
- document.querySelector(CLASS_BACKDROP).dispatchEvent(clickEvent)
- endTest()
- })
- })
- })
-
- describe('animation callbacks', () => {
- it('if it is animated, should show and hide backdrop after counting transition duration', done => {
- const instance = new Backdrop({
- isVisible: true,
- isAnimated: true
- })
- const spy2 = jasmine.createSpy('spy2')
-
- const execDone = () => {
- setTimeout(() => {
- expect(spy2).toHaveBeenCalledTimes(2)
- done()
- }, 10)
- }
-
- instance.show(spy2)
- instance.hide(() => {
- spy2()
- execDone()
- })
- expect(spy2).not.toHaveBeenCalled()
- })
+ const instance = new Backdrop({
+ isVisible: true,
+ isAnimated: false,
+ clickCallback: () => spy()
+ })
+ const endTest = () => {
+ setTimeout(() => {
+ expect(spy).toHaveBeenCalled()
+ resolve()
+ }, 10)
+ }
- it('if it is not animated, should show and hide backdrop without delay', done => {
- const spy = jasmine.createSpy('spy', getTransitionDurationFromElement)
- const instance = new Backdrop({
- isVisible: true,
- isAnimated: false
+ instance.show(() => {
+ const clickEvent = new Event('mousedown', { bubbles: true, cancelable: true })
+ document.querySelector(CLASS_BACKDROP).dispatchEvent(clickEvent)
+ endTest()
+ })
})
- const spy2 = jasmine.createSpy('spy2')
-
- instance.show(spy2)
- instance.hide(spy2)
-
- setTimeout(() => {
- expect(spy2).toHaveBeenCalled()
- expect(spy).not.toHaveBeenCalled()
- done()
- }, 10)
})
- it('if it is not "shown", should not call delay callbacks', done => {
- const instance = new Backdrop({
- isVisible: false,
- isAnimated: true
+ describe('animation callbacks', () => {
+ it('should show and hide backdrop after counting transition duration if it is animated', () => {
+ return new Promise(resolve => {
+ const instance = new Backdrop({
+ isVisible: true,
+ isAnimated: true
+ })
+ const spy2 = jasmine.createSpy('spy2')
+
+ const execDone = () => {
+ setTimeout(() => {
+ expect(spy2).toHaveBeenCalledTimes(2)
+ resolve()
+ }, 10)
+ }
+
+ instance.show(spy2)
+ instance.hide(() => {
+ spy2()
+ execDone()
+ })
+ expect(spy2).not.toHaveBeenCalled()
+ })
})
- const spy = jasmine.createSpy('spy', getTransitionDurationFromElement)
- instance.show()
- instance.hide(() => {
- expect(spy).not.toHaveBeenCalled()
- done()
- })
- })
- })
- describe('Config', () => {
- describe('rootElement initialization', () => {
- it('Should be appended on "document.body" by default', done => {
- const instance = new Backdrop({
- isVisible: true
- })
- const getElement = () => document.querySelector(CLASS_BACKDROP)
- instance.show(() => {
- expect(getElement().parentElement).toEqual(document.body)
- done()
+ it('should show and hide backdrop without a delay if it is not animated', () => {
+ return new Promise(resolve => {
+ const spy = jasmine.createSpy('spy', getTransitionDurationFromElement)
+ const instance = new Backdrop({
+ isVisible: true,
+ isAnimated: false
+ })
+ const spy2 = jasmine.createSpy('spy2')
+
+ instance.show(spy2)
+ instance.hide(spy2)
+
+ setTimeout(() => {
+ expect(spy2).toHaveBeenCalled()
+ expect(spy).not.toHaveBeenCalled()
+ resolve()
+ }, 10)
})
})
- it('Should find the rootElement if passed as a string', done => {
- const instance = new Backdrop({
- isVisible: true,
- rootElement: 'body'
- })
- const getElement = () => document.querySelector(CLASS_BACKDROP)
- instance.show(() => {
- expect(getElement().parentElement).toEqual(document.body)
- done()
+ it('should not call delay callbacks if it is not "shown"', () => {
+ return new Promise(resolve => {
+ const instance = new Backdrop({
+ isVisible: false,
+ isAnimated: true
+ })
+ const spy = jasmine.createSpy('spy', getTransitionDurationFromElement)
+
+ instance.show()
+ instance.hide(() => {
+ expect(spy).not.toHaveBeenCalled()
+ resolve()
+ })
})
})
+ })
- it('Should appended on any element given by the proper config', done => {
- fixtureEl.innerHTML = [
- '<div id="wrapper">',
- '</div>'
- ].join('')
+ describe('Config', () => {
+ describe('rootElement initialization', () => {
+ it('should be appended on "document.body" by default', () => {
+ return new Promise(resolve => {
+ const instance = new Backdrop({
+ isVisible: true
+ })
+ const getElement = () => document.querySelector(CLASS_BACKDROP)
+ instance.show(() => {
+ expect(getElement().parentElement).toEqual(document.body)
+ resolve()
+ })
+ })
+ })
- const wrapper = fixtureEl.querySelector('#wrapper')
- const instance = new Backdrop({
- isVisible: true,
- rootElement: wrapper
+ it('should find the rootElement if passed as a string', () => {
+ return new Promise(resolve => {
+ const instance = new Backdrop({
+ isVisible: true,
+ rootElement: 'body'
+ })
+ const getElement = () => document.querySelector(CLASS_BACKDROP)
+ instance.show(() => {
+ expect(getElement().parentElement).toEqual(document.body)
+ resolve()
+ })
+ })
})
- const getElement = () => document.querySelector(CLASS_BACKDROP)
- instance.show(() => {
- expect(getElement().parentElement).toEqual(wrapper)
- done()
+
+ it('should be appended on any element given by the proper config', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div id="wrapper"></div>'
+
+ const wrapper = fixtureEl.querySelector('#wrapper')
+ const instance = new Backdrop({
+ isVisible: true,
+ rootElement: wrapper
+ })
+ const getElement = () => document.querySelector(CLASS_BACKDROP)
+ instance.show(() => {
+ expect(getElement().parentElement).toEqual(wrapper)
+ resolve()
+ })
+ })
})
})
- })
- describe('ClassName', () => {
- it('Should be able to have different classNames than default', done => {
- const instance = new Backdrop({
- isVisible: true,
- className: 'foo'
- })
- const getElement = () => document.querySelector('.foo')
- instance.show(() => {
- expect(getElement()).toEqual(instance._getElement())
- instance.dispose()
- done()
+ describe('ClassName', () => {
+ it('should allow configuring className', () => {
+ return new Promise(resolve => {
+ const instance = new Backdrop({
+ isVisible: true,
+ className: 'foo'
+ })
+ const getElement = () => document.querySelector('.foo')
+ instance.show(() => {
+ expect(getElement()).toEqual(instance._getElement())
+ instance.dispose()
+ resolve()
+ })
+ })
})
})
})
diff --git a/js/tests/unit/util/component-functions.spec.js b/js/tests/unit/util/component-functions.spec.js
index edaedd32e..ce83785e2 100644
--- a/js/tests/unit/util/component-functions.spec.js
+++ b/js/tests/unit/util/component-functions.spec.js
@@ -1,8 +1,6 @@
-/* Test helpers */
-
-import { clearFixture, createEvent, getFixture } from '../../helpers/fixture'
-import { enableDismissTrigger } from '../../../src/util/component-functions'
-import BaseComponent from '../../../src/base-component'
+import BaseComponent from '../../../src/base-component.js'
+import { enableDismissTrigger } from '../../../src/util/component-functions.js'
+import { clearFixture, createEvent, getFixture } from '../../helpers/fixture.js'
class DummyClass2 extends BaseComponent {
static get NAME() {
@@ -33,12 +31,12 @@ describe('Plugin functions', () => {
it('should get Plugin and execute the given method, when a click occurred on data-bs-dismiss="PluginName"', () => {
fixtureEl.innerHTML = [
'<div id="foo" class="test">',
- ' <button type="button" data-bs-dismiss="test" data-bs-target="#foo"></button>',
+ ' <button type="button" data-bs-dismiss="test" data-bs-target="#foo"></button>',
'</div>'
].join('')
- spyOn(DummyClass2, 'getOrCreateInstance').and.callThrough()
- spyOn(DummyClass2.prototype, 'testMethod')
+ const spyGet = spyOn(DummyClass2, 'getOrCreateInstance').and.callThrough()
+ const spyTest = spyOn(DummyClass2.prototype, 'testMethod')
const componentWrapper = fixtureEl.querySelector('#foo')
const btnClose = fixtureEl.querySelector('[data-bs-dismiss="test"]')
const event = createEvent('click')
@@ -46,19 +44,19 @@ describe('Plugin functions', () => {
enableDismissTrigger(DummyClass2, 'testMethod')
btnClose.dispatchEvent(event)
- expect(DummyClass2.getOrCreateInstance).toHaveBeenCalledWith(componentWrapper)
- expect(DummyClass2.prototype.testMethod).toHaveBeenCalled()
+ expect(spyGet).toHaveBeenCalledWith(componentWrapper)
+ expect(spyTest).toHaveBeenCalled()
})
it('if data-bs-dismiss="PluginName" hasn\'t got "data-bs-target", "getOrCreateInstance" has to be initialized by closest "plugin.Name" class', () => {
fixtureEl.innerHTML = [
'<div id="foo" class="test">',
- ' <button type="button" data-bs-dismiss="test"></button>',
+ ' <button type="button" data-bs-dismiss="test"></button>',
'</div>'
].join('')
- spyOn(DummyClass2, 'getOrCreateInstance').and.callThrough()
- spyOn(DummyClass2.prototype, 'hide')
+ const spyGet = spyOn(DummyClass2, 'getOrCreateInstance').and.callThrough()
+ const spyHide = spyOn(DummyClass2.prototype, 'hide')
const componentWrapper = fixtureEl.querySelector('#foo')
const btnClose = fixtureEl.querySelector('[data-bs-dismiss="test"]')
const event = createEvent('click')
@@ -66,31 +64,31 @@ describe('Plugin functions', () => {
enableDismissTrigger(DummyClass2)
btnClose.dispatchEvent(event)
- expect(DummyClass2.getOrCreateInstance).toHaveBeenCalledWith(componentWrapper)
- expect(DummyClass2.prototype.hide).toHaveBeenCalled()
+ expect(spyGet).toHaveBeenCalledWith(componentWrapper)
+ expect(spyHide).toHaveBeenCalled()
})
it('if data-bs-dismiss="PluginName" is disabled, must not trigger function', () => {
fixtureEl.innerHTML = [
'<div id="foo" class="test">',
- ' <button type="button" disabled data-bs-dismiss="test"></button>',
+ ' <button type="button" disabled data-bs-dismiss="test"></button>',
'</div>'
].join('')
- spyOn(DummyClass2, 'getOrCreateInstance').and.callThrough()
+ const spy = spyOn(DummyClass2, 'getOrCreateInstance').and.callThrough()
const btnClose = fixtureEl.querySelector('[data-bs-dismiss="test"]')
const event = createEvent('click')
enableDismissTrigger(DummyClass2)
btnClose.dispatchEvent(event)
- expect(DummyClass2.getOrCreateInstance).not.toHaveBeenCalled()
+ expect(spy).not.toHaveBeenCalled()
})
it('should prevent default when the trigger is <a> or <area>', () => {
fixtureEl.innerHTML = [
'<div id="foo" class="test">',
- ' <a type="button" data-bs-dismiss="test"></a>',
+ ' <a type="button" data-bs-dismiss="test"></a>',
'</div>'
].join('')
@@ -98,11 +96,11 @@ describe('Plugin functions', () => {
const event = createEvent('click')
enableDismissTrigger(DummyClass2)
- spyOn(Event.prototype, 'preventDefault').and.callThrough()
+ const spy = spyOn(Event.prototype, 'preventDefault').and.callThrough()
btnClose.dispatchEvent(event)
- expect(Event.prototype.preventDefault).toHaveBeenCalled()
+ expect(spy).toHaveBeenCalled()
})
})
})
diff --git a/js/tests/unit/util/config.spec.js b/js/tests/unit/util/config.spec.js
new file mode 100644
index 000000000..93987a74a
--- /dev/null
+++ b/js/tests/unit/util/config.spec.js
@@ -0,0 +1,166 @@
+import Config from '../../../src/util/config.js'
+import { clearFixture, getFixture } from '../../helpers/fixture.js'
+
+class DummyConfigClass extends Config {
+ static get NAME() {
+ return 'dummy'
+ }
+}
+
+describe('Config', () => {
+ let fixtureEl
+ const name = 'dummy'
+
+ beforeAll(() => {
+ fixtureEl = getFixture()
+ })
+
+ afterEach(() => {
+ clearFixture()
+ })
+
+ describe('NAME', () => {
+ it('should return plugin NAME', () => {
+ expect(DummyConfigClass.NAME).toEqual(name)
+ })
+ })
+
+ describe('DefaultType', () => {
+ it('should return plugin default type', () => {
+ expect(DummyConfigClass.DefaultType).toEqual(jasmine.any(Object))
+ })
+ })
+
+ describe('Default', () => {
+ it('should return plugin defaults', () => {
+ expect(DummyConfigClass.Default).toEqual(jasmine.any(Object))
+ })
+ })
+
+ describe('mergeConfigObj', () => {
+ it('should parse element\'s data attributes and merge it with default config. Element\'s data attributes must excel Defaults', () => {
+ fixtureEl.innerHTML = '<div id="test" data-bs-test-bool="false" data-bs-test-int="8" data-bs-test-string1="bar"></div>'
+
+ spyOnProperty(DummyConfigClass, 'Default', 'get').and.returnValue({
+ testBool: true,
+ testString: 'foo',
+ testString1: 'foo',
+ testInt: 7
+ })
+ const instance = new DummyConfigClass()
+ const configResult = instance._mergeConfigObj({}, fixtureEl.querySelector('#test'))
+
+ expect(configResult.testBool).toEqual(false)
+ expect(configResult.testString).toEqual('foo')
+ expect(configResult.testString1).toEqual('bar')
+ expect(configResult.testInt).toEqual(8)
+ })
+
+ it('should parse element\'s data attributes and merge it with default config, plug these given during method call. The programmatically given should excel all', () => {
+ fixtureEl.innerHTML = '<div id="test" data-bs-test-bool="false" data-bs-test-int="8" data-bs-test-string-1="bar"></div>'
+
+ spyOnProperty(DummyConfigClass, 'Default', 'get').and.returnValue({
+ testBool: true,
+ testString: 'foo',
+ testString1: 'foo',
+ testInt: 7
+ })
+ const instance = new DummyConfigClass()
+ const configResult = instance._mergeConfigObj({
+ testString1: 'test',
+ testInt: 3
+ }, fixtureEl.querySelector('#test'))
+
+ expect(configResult.testBool).toEqual(false)
+ expect(configResult.testString).toEqual('foo')
+ expect(configResult.testString1).toEqual('test')
+ expect(configResult.testInt).toEqual(3)
+ })
+
+ it('should parse element\'s data attribute `config` and any rest attributes. The programmatically given should excel all. Data attribute `config` should excel only Defaults', () => {
+ fixtureEl.innerHTML = '<div id="test" data-bs-config=\'{"testBool":false,"testInt":50,"testInt2":100}\' data-bs-test-int="8" data-bs-test-string-1="bar"></div>'
+
+ spyOnProperty(DummyConfigClass, 'Default', 'get').and.returnValue({
+ testBool: true,
+ testString: 'foo',
+ testString1: 'foo',
+ testInt: 7,
+ testInt2: 600
+ })
+ const instance = new DummyConfigClass()
+ const configResult = instance._mergeConfigObj({
+ testString1: 'test'
+ }, fixtureEl.querySelector('#test'))
+
+ expect(configResult.testBool).toEqual(false)
+ expect(configResult.testString).toEqual('foo')
+ expect(configResult.testString1).toEqual('test')
+ expect(configResult.testInt).toEqual(8)
+ expect(configResult.testInt2).toEqual(100)
+ })
+
+ it('should omit element\'s data attribute `config` if is not an object', () => {
+ fixtureEl.innerHTML = '<div id="test" data-bs-config="foo" data-bs-test-int="8"></div>'
+
+ spyOnProperty(DummyConfigClass, 'Default', 'get').and.returnValue({
+ testInt: 7,
+ testInt2: 79
+ })
+ const instance = new DummyConfigClass()
+ const configResult = instance._mergeConfigObj({}, fixtureEl.querySelector('#test'))
+
+ expect(configResult.testInt).toEqual(8)
+ expect(configResult.testInt2).toEqual(79)
+ })
+ })
+
+ describe('typeCheckConfig', () => {
+ it('should check type of the config object', () => {
+ spyOnProperty(DummyConfigClass, 'DefaultType', 'get').and.returnValue({
+ toggle: 'boolean',
+ parent: '(string|element)'
+ })
+ const config = {
+ toggle: true,
+ parent: 777
+ }
+
+ const obj = new DummyConfigClass()
+ expect(() => {
+ obj._typeCheckConfig(config)
+ }).toThrowError(TypeError, `${obj.constructor.NAME.toUpperCase()}: Option "parent" provided type "number" but expected type "(string|element)".`)
+ })
+
+ it('should return null stringified when null is passed', () => {
+ spyOnProperty(DummyConfigClass, 'DefaultType', 'get').and.returnValue({
+ toggle: 'boolean',
+ parent: '(null|element)'
+ })
+
+ const obj = new DummyConfigClass()
+ const config = {
+ toggle: true,
+ parent: null
+ }
+
+ obj._typeCheckConfig(config)
+ expect().nothing()
+ })
+
+ it('should return undefined stringified when undefined is passed', () => {
+ spyOnProperty(DummyConfigClass, 'DefaultType', 'get').and.returnValue({
+ toggle: 'boolean',
+ parent: '(undefined|element)'
+ })
+
+ const obj = new DummyConfigClass()
+ const config = {
+ toggle: true,
+ parent: undefined
+ }
+
+ obj._typeCheckConfig(config)
+ expect().nothing()
+ })
+ })
+})
diff --git a/js/tests/unit/util/focustrap.spec.js b/js/tests/unit/util/focustrap.spec.js
index 99bc95fca..0a20017d5 100644
--- a/js/tests/unit/util/focustrap.spec.js
+++ b/js/tests/unit/util/focustrap.spec.js
@@ -1,7 +1,7 @@
-import FocusTrap from '../../../src/util/focustrap'
-import EventHandler from '../../../src/dom/event-handler'
-import SelectorEngine from '../../../src/dom/selector-engine'
-import { clearFixture, getFixture, createEvent } from '../../helpers/fixture'
+import EventHandler from '../../../src/dom/event-handler.js'
+import SelectorEngine from '../../../src/dom/selector-engine.js'
+import FocusTrap from '../../../src/util/focustrap.js'
+import { clearFixture, createEvent, getFixture } from '../../helpers/fixture.js'
describe('FocusTrap', () => {
let fixtureEl
@@ -20,12 +20,12 @@ describe('FocusTrap', () => {
const trapElement = fixtureEl.querySelector('div')
- spyOn(trapElement, 'focus')
+ const spy = spyOn(trapElement, 'focus')
const focustrap = new FocusTrap({ trapElement })
focustrap.activate()
- expect(trapElement.focus).toHaveBeenCalled()
+ expect(spy).toHaveBeenCalled()
})
it('if configured not to autofocus, should not autofocus itself', () => {
@@ -33,148 +33,156 @@ describe('FocusTrap', () => {
const trapElement = fixtureEl.querySelector('div')
- spyOn(trapElement, 'focus')
+ const spy = spyOn(trapElement, 'focus')
const focustrap = new FocusTrap({ trapElement, autofocus: false })
focustrap.activate()
- expect(trapElement.focus).not.toHaveBeenCalled()
+ expect(spy).not.toHaveBeenCalled()
})
- it('should force focus inside focus trap if it can', done => {
- fixtureEl.innerHTML = [
- '<a href="#" id="outside">outside</a>',
- '<div id="focustrap" tabindex="-1">',
- ' <a href="#" id="inside">inside</a>',
- '</div>'
- ].join('')
+ it('should force focus inside focus trap if it can', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<a href="#" id="outside">outside</a>',
+ '<div id="focustrap" tabindex="-1">',
+ ' <a href="#" id="inside">inside</a>',
+ '</div>'
+ ].join('')
- const trapElement = fixtureEl.querySelector('div')
- const focustrap = new FocusTrap({ trapElement })
- focustrap.activate()
+ const trapElement = fixtureEl.querySelector('div')
+ const focustrap = new FocusTrap({ trapElement })
+ focustrap.activate()
- const inside = document.getElementById('inside')
+ const inside = document.getElementById('inside')
- const focusInListener = () => {
- expect(inside.focus).toHaveBeenCalled()
- document.removeEventListener('focusin', focusInListener)
- done()
- }
+ const focusInListener = () => {
+ expect(spy).toHaveBeenCalled()
+ document.removeEventListener('focusin', focusInListener)
+ resolve()
+ }
- spyOn(inside, 'focus')
- spyOn(SelectorEngine, 'focusableChildren').and.callFake(() => [inside])
+ const spy = spyOn(inside, 'focus')
+ spyOn(SelectorEngine, 'focusableChildren').and.callFake(() => [inside])
- document.addEventListener('focusin', focusInListener)
+ document.addEventListener('focusin', focusInListener)
- const focusInEvent = createEvent('focusin', { bubbles: true })
- Object.defineProperty(focusInEvent, 'target', {
- value: document.getElementById('outside')
- })
+ const focusInEvent = createEvent('focusin', { bubbles: true })
+ Object.defineProperty(focusInEvent, 'target', {
+ value: document.getElementById('outside')
+ })
- document.dispatchEvent(focusInEvent)
+ document.dispatchEvent(focusInEvent)
+ })
})
- it('should wrap focus around forward on tab', done => {
- fixtureEl.innerHTML = [
- '<a href="#" id="outside">outside</a>',
- '<div id="focustrap" tabindex="-1">',
- ' <a href="#" id="first">first</a>',
- ' <a href="#" id="inside">inside</a>',
- ' <a href="#" id="last">last</a>',
- '</div>'
- ].join('')
-
- const trapElement = fixtureEl.querySelector('div')
- const focustrap = new FocusTrap({ trapElement })
- focustrap.activate()
-
- const first = document.getElementById('first')
- const inside = document.getElementById('inside')
- const last = document.getElementById('last')
- const outside = document.getElementById('outside')
-
- spyOn(SelectorEngine, 'focusableChildren').and.callFake(() => [first, inside, last])
- spyOn(first, 'focus').and.callThrough()
-
- const focusInListener = () => {
- expect(first.focus).toHaveBeenCalled()
- first.removeEventListener('focusin', focusInListener)
- done()
- }
-
- first.addEventListener('focusin', focusInListener)
-
- const keydown = createEvent('keydown')
- keydown.key = 'Tab'
-
- document.dispatchEvent(keydown)
- outside.focus()
+ it('should wrap focus around forward on tab', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<a href="#" id="outside">outside</a>',
+ '<div id="focustrap" tabindex="-1">',
+ ' <a href="#" id="first">first</a>',
+ ' <a href="#" id="inside">inside</a>',
+ ' <a href="#" id="last">last</a>',
+ '</div>'
+ ].join('')
+
+ const trapElement = fixtureEl.querySelector('div')
+ const focustrap = new FocusTrap({ trapElement })
+ focustrap.activate()
+
+ const first = document.getElementById('first')
+ const inside = document.getElementById('inside')
+ const last = document.getElementById('last')
+ const outside = document.getElementById('outside')
+
+ spyOn(SelectorEngine, 'focusableChildren').and.callFake(() => [first, inside, last])
+ const spy = spyOn(first, 'focus').and.callThrough()
+
+ const focusInListener = () => {
+ expect(spy).toHaveBeenCalled()
+ first.removeEventListener('focusin', focusInListener)
+ resolve()
+ }
+
+ first.addEventListener('focusin', focusInListener)
+
+ const keydown = createEvent('keydown')
+ keydown.key = 'Tab'
+
+ document.dispatchEvent(keydown)
+ outside.focus()
+ })
})
- it('should wrap focus around backwards on shift-tab', done => {
- fixtureEl.innerHTML = [
- '<a href="#" id="outside">outside</a>',
- '<div id="focustrap" tabindex="-1">',
- ' <a href="#" id="first">first</a>',
- ' <a href="#" id="inside">inside</a>',
- ' <a href="#" id="last">last</a>',
- '</div>'
- ].join('')
-
- const trapElement = fixtureEl.querySelector('div')
- const focustrap = new FocusTrap({ trapElement })
- focustrap.activate()
-
- const first = document.getElementById('first')
- const inside = document.getElementById('inside')
- const last = document.getElementById('last')
- const outside = document.getElementById('outside')
-
- spyOn(SelectorEngine, 'focusableChildren').and.callFake(() => [first, inside, last])
- spyOn(last, 'focus').and.callThrough()
-
- const focusInListener = () => {
- expect(last.focus).toHaveBeenCalled()
- last.removeEventListener('focusin', focusInListener)
- done()
- }
-
- last.addEventListener('focusin', focusInListener)
-
- const keydown = createEvent('keydown')
- keydown.key = 'Tab'
- keydown.shiftKey = true
-
- document.dispatchEvent(keydown)
- outside.focus()
+ it('should wrap focus around backwards on shift-tab', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<a href="#" id="outside">outside</a>',
+ '<div id="focustrap" tabindex="-1">',
+ ' <a href="#" id="first">first</a>',
+ ' <a href="#" id="inside">inside</a>',
+ ' <a href="#" id="last">last</a>',
+ '</div>'
+ ].join('')
+
+ const trapElement = fixtureEl.querySelector('div')
+ const focustrap = new FocusTrap({ trapElement })
+ focustrap.activate()
+
+ const first = document.getElementById('first')
+ const inside = document.getElementById('inside')
+ const last = document.getElementById('last')
+ const outside = document.getElementById('outside')
+
+ spyOn(SelectorEngine, 'focusableChildren').and.callFake(() => [first, inside, last])
+ const spy = spyOn(last, 'focus').and.callThrough()
+
+ const focusInListener = () => {
+ expect(spy).toHaveBeenCalled()
+ last.removeEventListener('focusin', focusInListener)
+ resolve()
+ }
+
+ last.addEventListener('focusin', focusInListener)
+
+ const keydown = createEvent('keydown')
+ keydown.key = 'Tab'
+ keydown.shiftKey = true
+
+ document.dispatchEvent(keydown)
+ outside.focus()
+ })
})
- it('should force focus on itself if there is no focusable content', done => {
- fixtureEl.innerHTML = [
- '<a href="#" id="outside">outside</a>',
- '<div id="focustrap" tabindex="-1"></div>'
- ].join('')
+ it('should force focus on itself if there is no focusable content', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<a href="#" id="outside">outside</a>',
+ '<div id="focustrap" tabindex="-1"></div>'
+ ].join('')
- const trapElement = fixtureEl.querySelector('div')
- const focustrap = new FocusTrap({ trapElement })
- focustrap.activate()
+ const trapElement = fixtureEl.querySelector('div')
+ const focustrap = new FocusTrap({ trapElement })
+ focustrap.activate()
- const focusInListener = () => {
- expect(focustrap._config.trapElement.focus).toHaveBeenCalled()
- document.removeEventListener('focusin', focusInListener)
- done()
- }
+ const focusInListener = () => {
+ expect(spy).toHaveBeenCalled()
+ document.removeEventListener('focusin', focusInListener)
+ resolve()
+ }
- spyOn(focustrap._config.trapElement, 'focus')
+ const spy = spyOn(focustrap._config.trapElement, 'focus')
- document.addEventListener('focusin', focusInListener)
+ document.addEventListener('focusin', focusInListener)
- const focusInEvent = createEvent('focusin', { bubbles: true })
- Object.defineProperty(focusInEvent, 'target', {
- value: document.getElementById('outside')
- })
+ const focusInEvent = createEvent('focusin', { bubbles: true })
+ Object.defineProperty(focusInEvent, 'target', {
+ value: document.getElementById('outside')
+ })
- document.dispatchEvent(focusInEvent)
+ document.dispatchEvent(focusInEvent)
+ })
})
})
@@ -182,29 +190,29 @@ describe('FocusTrap', () => {
it('should flag itself as no longer active', () => {
const focustrap = new FocusTrap({ trapElement: fixtureEl })
focustrap.activate()
- expect(focustrap._isActive).toBe(true)
+ expect(focustrap._isActive).toBeTrue()
focustrap.deactivate()
- expect(focustrap._isActive).toBe(false)
+ expect(focustrap._isActive).toBeFalse()
})
it('should remove all event listeners', () => {
const focustrap = new FocusTrap({ trapElement: fixtureEl })
focustrap.activate()
- spyOn(EventHandler, 'off')
+ const spy = spyOn(EventHandler, 'off')
focustrap.deactivate()
- expect(EventHandler.off).toHaveBeenCalled()
+ expect(spy).toHaveBeenCalled()
})
it('doesn\'t try removing event listeners unless it needs to (in case it hasn\'t been activated)', () => {
const focustrap = new FocusTrap({ trapElement: fixtureEl })
- spyOn(EventHandler, 'off')
+ const spy = spyOn(EventHandler, 'off')
focustrap.deactivate()
- expect(EventHandler.off).not.toHaveBeenCalled()
+ expect(spy).not.toHaveBeenCalled()
})
})
})
diff --git a/js/tests/unit/util/index.spec.js b/js/tests/unit/util/index.spec.js
index ccfe5e2c2..9e154818f 100644
--- a/js/tests/unit/util/index.spec.js
+++ b/js/tests/unit/util/index.spec.js
@@ -1,5 +1,6 @@
-import * as Util from '../../../src/util/index'
-import { clearFixture, getFixture } from '../../helpers/fixture'
+import * as Util from '../../../src/util/index.js'
+import { noop } from '../../../src/util/index.js'
+import { clearFixture, getFixture } from '../../helpers/fixture.js'
describe('Util', () => {
let fixtureEl
@@ -21,119 +22,6 @@ describe('Util', () => {
})
})
- describe('getSelectorFromElement', () => {
- it('should get selector from data-bs-target', () => {
- fixtureEl.innerHTML = [
- '<div id="test" data-bs-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-bs-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-bs-target equal to #', () => {
- fixtureEl.innerHTML = [
- '<a id="test" data-bs-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 a selector from a href is a url without an anchor', () => {
- fixtureEl.innerHTML = [
- '<a id="test" data-bs-target="#" href="foo/bar.html"></a>',
- '<div class="target"></div>'
- ].join('')
-
- const testEl = fixtureEl.querySelector('#test')
-
- expect(Util.getSelectorFromElement(testEl)).toBeNull()
- })
-
- it('should return the anchor if a selector from a href is a url', () => {
- fixtureEl.innerHTML = [
- '<a id="test" data-bs-target="#" href="foo/bar.html#target"></a>',
- '<div id="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()
- })
-
- it('should return null if no selector', () => {
- fixtureEl.innerHTML = '<div></div>'
-
- const testEl = fixtureEl.querySelector('div')
-
- expect(Util.getSelectorFromElement(testEl)).toBeNull()
- })
- })
-
- describe('getElementFromSelector', () => {
- it('should get element from data-bs-target', () => {
- fixtureEl.innerHTML = [
- '<div id="test" data-bs-target=".target"></div>',
- '<div class="target"></div>'
- ].join('')
-
- const testEl = fixtureEl.querySelector('#test')
-
- expect(Util.getElementFromSelector(testEl)).toEqual(fixtureEl.querySelector('.target'))
- })
-
- it('should get element from href if no data-bs-target set', () => {
- fixtureEl.innerHTML = [
- '<a id="test" href=".target"></a>',
- '<div class="target"></div>'
- ].join('')
-
- const testEl = fixtureEl.querySelector('#test')
-
- expect(Util.getElementFromSelector(testEl)).toEqual(fixtureEl.querySelector('.target'))
- })
-
- it('should return null if element not found', () => {
- fixtureEl.innerHTML = '<a id="test" href=".target"></a>'
-
- const testEl = fixtureEl.querySelector('#test')
-
- expect(Util.getElementFromSelector(testEl)).toBeNull()
- })
-
- it('should return null if no selector', () => {
- fixtureEl.innerHTML = '<div></div>'
-
- const testEl = fixtureEl.querySelector('div')
-
- expect(Util.getElementFromSelector(testEl)).toBeNull()
- })
- })
-
describe('getTransitionDurationFromElement', () => {
it('should get transition from element', () => {
fixtureEl.innerHTML = '<div style="transition: all 300ms ease-out;"></div>'
@@ -154,34 +42,35 @@ describe('Util', () => {
})
describe('triggerTransitionEnd', () => {
- it('should trigger transitionend event', done => {
- fixtureEl.innerHTML = '<div></div>'
+ it('should trigger transitionend event', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = '<div></div>'
- const el = fixtureEl.querySelector('div')
- const spy = spyOn(el, 'dispatchEvent').and.callThrough()
+ const el = fixtureEl.querySelector('div')
+ const spy = spyOn(el, 'dispatchEvent').and.callThrough()
- el.addEventListener('transitionend', () => {
- expect(spy).toHaveBeenCalled()
- done()
- })
+ el.addEventListener('transitionend', () => {
+ expect(spy).toHaveBeenCalled()
+ resolve()
+ })
- Util.triggerTransitionEnd(el)
+ Util.triggerTransitionEnd(el)
+ })
})
})
describe('isElement', () => {
it('should detect if the parameter is an element or not and return Boolean', () => {
- fixtureEl.innerHTML =
- [
- '<div id="foo" class="test"></div>',
- '<div id="bar" class="test"></div>'
- ].join('')
+ fixtureEl.innerHTML = [
+ '<div id="foo" class="test"></div>',
+ '<div id="bar" class="test"></div>'
+ ].join('')
const el = fixtureEl.querySelector('#foo')
- expect(Util.isElement(el)).toEqual(true)
- expect(Util.isElement({})).toEqual(false)
- expect(Util.isElement(fixtureEl.querySelectorAll('.test'))).toEqual(false)
+ expect(Util.isElement(el)).toBeTrue()
+ expect(Util.isElement({})).toBeFalse()
+ expect(Util.isElement(fixtureEl.querySelectorAll('.test'))).toBeFalse()
})
it('should detect jQuery element', () => {
@@ -193,17 +82,16 @@ describe('Util', () => {
jquery: 'foo'
}
- expect(Util.isElement(fakejQuery)).toEqual(true)
+ expect(Util.isElement(fakejQuery)).toBeTrue()
})
})
describe('getElement', () => {
it('should try to parse element', () => {
- fixtureEl.innerHTML =
- [
- '<div id="foo" class="test"></div>',
- '<div id="bar" class="test"></div>'
- ].join('')
+ fixtureEl.innerHTML = [
+ '<div id="foo" class="test"></div>',
+ '<div id="bar" class="test"></div>'
+ ].join('')
const el = fixtureEl.querySelector('div')
@@ -225,61 +113,14 @@ describe('Util', () => {
})
})
- describe('typeCheckConfig', () => {
- const namePlugin = 'collapse'
-
- it('should check type of the config object', () => {
- const defaultType = {
- toggle: 'boolean',
- parent: '(string|element)'
- }
- const config = {
- toggle: true,
- parent: 777
- }
-
- expect(() => {
- Util.typeCheckConfig(namePlugin, config, defaultType)
- }).toThrowError(TypeError, 'COLLAPSE: Option "parent" provided type "number" but expected type "(string|element)".')
- })
-
- it('should return null stringified when null is passed', () => {
- const defaultType = {
- toggle: 'boolean',
- parent: '(null|element)'
- }
- const config = {
- toggle: true,
- parent: null
- }
-
- Util.typeCheckConfig(namePlugin, config, defaultType)
- expect().nothing()
- })
-
- it('should return undefined stringified when undefined is passed', () => {
- const defaultType = {
- toggle: 'boolean',
- parent: '(undefined|element)'
- }
- const config = {
- toggle: true,
- parent: undefined
- }
-
- Util.typeCheckConfig(namePlugin, config, defaultType)
- expect().nothing()
- })
- })
-
describe('isVisible', () => {
it('should return false if the element is not defined', () => {
- expect(Util.isVisible(null)).toEqual(false)
- expect(Util.isVisible(undefined)).toEqual(false)
+ expect(Util.isVisible(null)).toBeFalse()
+ expect(Util.isVisible(undefined)).toBeFalse()
})
it('should return false if the element provided is not a dom element', () => {
- expect(Util.isVisible({})).toEqual(false)
+ expect(Util.isVisible({})).toBeFalse()
})
it('should return false if the element is not visible with display none', () => {
@@ -287,7 +128,7 @@ describe('Util', () => {
const div = fixtureEl.querySelector('div')
- expect(Util.isVisible(div)).toEqual(false)
+ expect(Util.isVisible(div)).toBeFalse()
})
it('should return false if the element is not visible with visibility hidden', () => {
@@ -295,7 +136,7 @@ describe('Util', () => {
const div = fixtureEl.querySelector('div')
- expect(Util.isVisible(div)).toEqual(false)
+ expect(Util.isVisible(div)).toBeFalse()
})
it('should return false if an ancestor element is display none', () => {
@@ -311,7 +152,7 @@ describe('Util', () => {
const div = fixtureEl.querySelector('.content')
- expect(Util.isVisible(div)).toEqual(false)
+ expect(Util.isVisible(div)).toBeFalse()
})
it('should return false if an ancestor element is visibility hidden', () => {
@@ -327,7 +168,7 @@ describe('Util', () => {
const div = fixtureEl.querySelector('.content')
- expect(Util.isVisible(div)).toEqual(false)
+ expect(Util.isVisible(div)).toBeFalse()
})
it('should return true if an ancestor element is visibility hidden, but reverted', () => {
@@ -343,7 +184,7 @@ describe('Util', () => {
const div = fixtureEl.querySelector('.content')
- expect(Util.isVisible(div)).toEqual(true)
+ expect(Util.isVisible(div)).toBeTrue()
})
it('should return true if the element is visible', () => {
@@ -355,7 +196,7 @@ describe('Util', () => {
const div = fixtureEl.querySelector('#element')
- expect(Util.isVisible(div)).toEqual(true)
+ expect(Util.isVisible(div)).toBeTrue()
})
it('should return false if the element is hidden, but not via display or visibility', () => {
@@ -367,20 +208,56 @@ describe('Util', () => {
const div = fixtureEl.querySelector('#element')
- expect(Util.isVisible(div)).toEqual(false)
+ expect(Util.isVisible(div)).toBeFalse()
+ })
+
+ it('should return true if its a closed details element', () => {
+ fixtureEl.innerHTML = '<details id="element"></details>'
+
+ const div = fixtureEl.querySelector('#element')
+
+ expect(Util.isVisible(div)).toBeTrue()
+ })
+
+ it('should return true if the element is visible inside an open details element', () => {
+ fixtureEl.innerHTML = [
+ '<details open>',
+ ' <div id="element"></div>',
+ '</details>'
+ ].join('')
+
+ const div = fixtureEl.querySelector('#element')
+
+ expect(Util.isVisible(div)).toBeTrue()
+ })
+
+ it('should return true if the element is a visible summary in a closed details element', () => {
+ fixtureEl.innerHTML = [
+ '<details>',
+ ' <summary id="element-1">',
+ ' <span id="element-2"></span>',
+ ' </summary>',
+ '</details>'
+ ].join('')
+
+ const element1 = fixtureEl.querySelector('#element-1')
+ const element2 = fixtureEl.querySelector('#element-2')
+
+ expect(Util.isVisible(element1)).toBeTrue()
+ expect(Util.isVisible(element2)).toBeTrue()
})
})
describe('isDisabled', () => {
it('should return true if the element is not defined', () => {
- expect(Util.isDisabled(null)).toEqual(true)
- expect(Util.isDisabled(undefined)).toEqual(true)
- expect(Util.isDisabled()).toEqual(true)
+ expect(Util.isDisabled(null)).toBeTrue()
+ expect(Util.isDisabled(undefined)).toBeTrue()
+ expect(Util.isDisabled()).toBeTrue()
})
it('should return true if the element provided is not a dom element', () => {
- expect(Util.isDisabled({})).toEqual(true)
- expect(Util.isDisabled('test')).toEqual(true)
+ expect(Util.isDisabled({})).toBeTrue()
+ expect(Util.isDisabled('test')).toBeTrue()
})
it('should return true if the element has disabled attribute', () => {
@@ -396,9 +273,9 @@ describe('Util', () => {
const div1 = fixtureEl.querySelector('#element1')
const div2 = fixtureEl.querySelector('#element2')
- expect(Util.isDisabled(div)).toEqual(true)
- expect(Util.isDisabled(div1)).toEqual(true)
- expect(Util.isDisabled(div2)).toEqual(true)
+ expect(Util.isDisabled(div)).toBeTrue()
+ expect(Util.isDisabled(div1)).toBeTrue()
+ expect(Util.isDisabled(div2)).toBeTrue()
})
it('should return false if the element has disabled attribute with "false" value, or doesn\'t have attribute', () => {
@@ -412,8 +289,8 @@ describe('Util', () => {
const div = fixtureEl.querySelector('#element')
const div1 = fixtureEl.querySelector('#element1')
- expect(Util.isDisabled(div)).toEqual(false)
- expect(Util.isDisabled(div1)).toEqual(false)
+ expect(Util.isDisabled(div)).toBeFalse()
+ expect(Util.isDisabled(div1)).toBeFalse()
})
it('should return false if the element is not disabled ', () => {
@@ -427,15 +304,16 @@ describe('Util', () => {
const el = selector => fixtureEl.querySelector(selector)
- expect(Util.isDisabled(el('#button'))).toEqual(false)
- expect(Util.isDisabled(el('#select'))).toEqual(false)
- expect(Util.isDisabled(el('#input'))).toEqual(false)
+ expect(Util.isDisabled(el('#button'))).toBeFalse()
+ expect(Util.isDisabled(el('#select'))).toBeFalse()
+ expect(Util.isDisabled(el('#input'))).toBeFalse()
})
+
it('should return true if the element has disabled attribute', () => {
fixtureEl.innerHTML = [
'<div>',
- ' <input id="input" disabled="disabled"/>',
- ' <input id="input1" disabled="disabled"/>',
+ ' <input id="input" disabled="disabled">',
+ ' <input id="input1" disabled="disabled">',
' <button id="button" disabled="true"></button>',
' <button id="button1" disabled="disabled"></button>',
' <button id="button2" disabled></button>',
@@ -446,12 +324,12 @@ describe('Util', () => {
const el = selector => fixtureEl.querySelector(selector)
- expect(Util.isDisabled(el('#input'))).toEqual(true)
- expect(Util.isDisabled(el('#input1'))).toEqual(true)
- expect(Util.isDisabled(el('#button'))).toEqual(true)
- expect(Util.isDisabled(el('#button1'))).toEqual(true)
- expect(Util.isDisabled(el('#button2'))).toEqual(true)
- expect(Util.isDisabled(el('#input'))).toEqual(true)
+ expect(Util.isDisabled(el('#input'))).toBeTrue()
+ expect(Util.isDisabled(el('#input1'))).toBeTrue()
+ expect(Util.isDisabled(el('#button'))).toBeTrue()
+ expect(Util.isDisabled(el('#button1'))).toBeTrue()
+ expect(Util.isDisabled(el('#button2'))).toBeTrue()
+ expect(Util.isDisabled(el('#input'))).toBeTrue()
})
it('should return true if the element has class "disabled"', () => {
@@ -463,19 +341,19 @@ describe('Util', () => {
const div = fixtureEl.querySelector('#element')
- expect(Util.isDisabled(div)).toEqual(true)
+ expect(Util.isDisabled(div)).toBeTrue()
})
it('should return true if the element has class "disabled" but disabled attribute is false', () => {
fixtureEl.innerHTML = [
'<div>',
- ' <input id="input" class="disabled" disabled="false"/>',
+ ' <input id="input" class="disabled" disabled="false">',
'</div>'
].join('')
const div = fixtureEl.querySelector('#input')
- expect(Util.isDisabled(div)).toEqual(true)
+ expect(Util.isDisabled(div)).toBeTrue()
})
})
@@ -493,7 +371,7 @@ describe('Util', () => {
spyOn(document.documentElement, 'attachShadow').and.returnValue(null)
- expect(Util.findShadowRoot(div)).toEqual(null)
+ expect(Util.findShadowRoot(div)).toBeNull()
})
it('should return null when we do not find a shadow root', () => {
@@ -505,7 +383,7 @@ describe('Util', () => {
spyOn(document, 'getRootNode').and.returnValue(undefined)
- expect(Util.findShadowRoot(document)).toEqual(null)
+ expect(Util.findShadowRoot(document)).toBeNull()
})
it('should return the shadow root when found', () => {
@@ -532,7 +410,7 @@ describe('Util', () => {
describe('noop', () => {
it('should be a function', () => {
- expect(typeof Util.noop).toEqual('function')
+ expect(Util.noop).toEqual(jasmine.any(Function))
})
})
@@ -569,14 +447,14 @@ describe('Util', () => {
document.body.setAttribute('data-bs-no-jquery', '')
expect(window.jQuery).toEqual(fakejQuery)
- expect(Util.getjQuery()).toEqual(null)
+ expect(Util.getjQuery()).toBeNull()
document.body.removeAttribute('data-bs-no-jquery')
})
it('should not return jQuery if not present', () => {
window.jQuery = undefined
- expect(Util.getjQuery()).toEqual(null)
+ expect(Util.getjQuery()).toBeNull()
})
})
@@ -585,7 +463,7 @@ describe('Util', () => {
const spy = jasmine.createSpy()
const spy2 = jasmine.createSpy()
- spyOn(document, 'addEventListener').and.callThrough()
+ const spyAdd = spyOn(document, 'addEventListener').and.callThrough()
spyOnProperty(document, 'readyState').and.returnValue('loading')
Util.onDOMContentLoaded(spy)
@@ -598,7 +476,7 @@ describe('Util', () => {
expect(spy).toHaveBeenCalled()
expect(spy2).toHaveBeenCalled()
- expect(document.addEventListener).toHaveBeenCalledTimes(1)
+ expect(spyAdd).toHaveBeenCalledTimes(1)
})
it('should execute callback if readyState is not "loading"', () => {
@@ -623,14 +501,14 @@ describe('Util', () => {
})
it('should define a plugin on the jQuery instance', () => {
- const pluginMock = function () {}
+ const pluginMock = Util.noop
pluginMock.NAME = 'test'
- pluginMock.jQueryInterface = function () {}
+ pluginMock.jQueryInterface = Util.noop
Util.defineJQueryPlugin(pluginMock)
- expect(fakejQuery.fn.test).toBe(pluginMock.jQueryInterface)
- expect(fakejQuery.fn.test.Constructor).toBe(pluginMock)
- expect(typeof fakejQuery.fn.test.noConflict).toEqual('function')
+ expect(fakejQuery.fn.test).toEqual(pluginMock.jQueryInterface)
+ expect(fakejQuery.fn.test.Constructor).toEqual(pluginMock)
+ expect(fakejQuery.fn.test.noConflict).toEqual(jasmine.any(Function))
})
})
@@ -640,6 +518,25 @@ describe('Util', () => {
Util.execute(spy)
expect(spy).toHaveBeenCalled()
})
+
+ it('should execute if arg is function & return the result', () => {
+ const functionFoo = (num1, num2 = 10) => num1 + num2
+ const resultFoo = Util.execute(functionFoo, [undefined, 4, 5])
+ expect(resultFoo).toBe(9)
+
+ const resultFoo1 = Util.execute(functionFoo, [undefined, 4])
+ expect(resultFoo1).toBe(14)
+
+ const functionBar = () => 'foo'
+ const resultBar = Util.execute(functionBar)
+ expect(resultBar).toBe('foo')
+ })
+
+ it('should not execute if arg is not function & return default argument', () => {
+ const foo = 'bar'
+ expect(Util.execute(foo)).toBe('bar')
+ expect(Util.execute(foo, [], 4)).toBe(4)
+ })
})
describe('executeAfterTransition', () => {
@@ -670,96 +567,104 @@ describe('Util', () => {
expect(callbackSpy).toHaveBeenCalled()
})
- it('should execute a function after a computed CSS transition duration and there was no transitionend event dispatched', done => {
- const el = document.createElement('div')
- const callbackSpy = jasmine.createSpy('callback spy')
+ it('should execute a function after a computed CSS transition duration and there was no transitionend event dispatched', () => {
+ return new Promise(resolve => {
+ const el = document.createElement('div')
+ const callbackSpy = jasmine.createSpy('callback spy')
- spyOn(window, 'getComputedStyle').and.returnValue({
- transitionDuration: '0.05s',
- transitionDelay: '0s'
- })
+ spyOn(window, 'getComputedStyle').and.returnValue({
+ transitionDuration: '0.05s',
+ transitionDelay: '0s'
+ })
- Util.executeAfterTransition(callbackSpy, el)
+ Util.executeAfterTransition(callbackSpy, el)
- setTimeout(() => {
- expect(callbackSpy).toHaveBeenCalled()
- done()
- }, 70)
+ setTimeout(() => {
+ expect(callbackSpy).toHaveBeenCalled()
+ resolve()
+ }, 70)
+ })
})
- it('should not execute a function a second time after a computed CSS transition duration and if a transitionend event has already been dispatched', done => {
- const el = document.createElement('div')
- const callbackSpy = jasmine.createSpy('callback spy')
+ it('should not execute a function a second time after a computed CSS transition duration and if a transitionend event has already been dispatched', () => {
+ return new Promise(resolve => {
+ const el = document.createElement('div')
+ const callbackSpy = jasmine.createSpy('callback spy')
- spyOn(window, 'getComputedStyle').and.returnValue({
- transitionDuration: '0.05s',
- transitionDelay: '0s'
- })
+ spyOn(window, 'getComputedStyle').and.returnValue({
+ transitionDuration: '0.05s',
+ transitionDelay: '0s'
+ })
- Util.executeAfterTransition(callbackSpy, el)
+ Util.executeAfterTransition(callbackSpy, el)
- setTimeout(() => {
- el.dispatchEvent(new TransitionEvent('transitionend'))
- }, 50)
+ setTimeout(() => {
+ el.dispatchEvent(new TransitionEvent('transitionend'))
+ }, 50)
- setTimeout(() => {
- expect(callbackSpy).toHaveBeenCalledTimes(1)
- done()
- }, 70)
+ setTimeout(() => {
+ expect(callbackSpy).toHaveBeenCalledTimes(1)
+ resolve()
+ }, 70)
+ })
})
- it('should not trigger a transitionend event if another transitionend event had already happened', done => {
- const el = document.createElement('div')
+ it('should not trigger a transitionend event if another transitionend event had already happened', () => {
+ return new Promise(resolve => {
+ const el = document.createElement('div')
- spyOn(window, 'getComputedStyle').and.returnValue({
- transitionDuration: '0.05s',
- transitionDelay: '0s'
- })
+ spyOn(window, 'getComputedStyle').and.returnValue({
+ transitionDuration: '0.05s',
+ transitionDelay: '0s'
+ })
- Util.executeAfterTransition(() => {}, el)
+ Util.executeAfterTransition(noop, el)
- // simulate a event dispatched by the browser
- el.dispatchEvent(new TransitionEvent('transitionend'))
+ // simulate a event dispatched by the browser
+ el.dispatchEvent(new TransitionEvent('transitionend'))
- const dispatchSpy = spyOn(el, 'dispatchEvent').and.callThrough()
+ const dispatchSpy = spyOn(el, 'dispatchEvent').and.callThrough()
- setTimeout(() => {
- // setTimeout should not have triggered another transitionend event.
- expect(dispatchSpy).not.toHaveBeenCalled()
- done()
- }, 70)
+ setTimeout(() => {
+ // setTimeout should not have triggered another transitionend event.
+ expect(dispatchSpy).not.toHaveBeenCalled()
+ resolve()
+ }, 70)
+ })
})
- it('should ignore transitionend events from nested elements', done => {
- fixtureEl.innerHTML = [
- '<div class="outer">',
- ' <div class="nested"></div>',
- '</div>'
- ].join('')
+ it('should ignore transitionend events from nested elements', () => {
+ return new Promise(resolve => {
+ fixtureEl.innerHTML = [
+ '<div class="outer">',
+ ' <div class="nested"></div>',
+ '</div>'
+ ].join('')
- const outer = fixtureEl.querySelector('.outer')
- const nested = fixtureEl.querySelector('.nested')
- const callbackSpy = jasmine.createSpy('callback spy')
+ const outer = fixtureEl.querySelector('.outer')
+ const nested = fixtureEl.querySelector('.nested')
+ const callbackSpy = jasmine.createSpy('callback spy')
- spyOn(window, 'getComputedStyle').and.returnValue({
- transitionDuration: '0.05s',
- transitionDelay: '0s'
- })
+ spyOn(window, 'getComputedStyle').and.returnValue({
+ transitionDuration: '0.05s',
+ transitionDelay: '0s'
+ })
- Util.executeAfterTransition(callbackSpy, outer)
+ Util.executeAfterTransition(callbackSpy, outer)
- nested.dispatchEvent(new TransitionEvent('transitionend', {
- bubbles: true
- }))
+ nested.dispatchEvent(new TransitionEvent('transitionend', {
+ bubbles: true
+ }))
- setTimeout(() => {
- expect(callbackSpy).not.toHaveBeenCalled()
- }, 20)
+ setTimeout(() => {
+ expect(callbackSpy).not.toHaveBeenCalled()
+ }, 20)
- setTimeout(() => {
- expect(callbackSpy).toHaveBeenCalled()
- done()
- }, 70)
+ setTimeout(() => {
+ expect(callbackSpy).toHaveBeenCalled()
+ resolve()
+ }, 70)
+ })
})
})
diff --git a/js/tests/unit/util/sanitizer.spec.js b/js/tests/unit/util/sanitizer.spec.js
index 28d624c87..2b21ef2e1 100644
--- a/js/tests/unit/util/sanitizer.spec.js
+++ b/js/tests/unit/util/sanitizer.spec.js
@@ -1,4 +1,4 @@
-import { DefaultAllowlist, sanitizeHtml } from '../../../src/util/sanitizer'
+import { DefaultAllowlist, sanitizeHtml } from '../../../src/util/sanitizer.js'
describe('Sanitizer', () => {
describe('sanitizeHtml', () => {
@@ -10,17 +10,75 @@ describe('Sanitizer', () => {
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, DefaultAllowlist, null)
+ it('should retain tags with valid URLs', () => {
+ const validUrls = [
+ '',
+ 'http://abc',
+ 'HTTP://abc',
+ 'https://abc',
+ 'HTTPS://abc',
+ 'ftp://abc',
+ 'FTP://abc',
+ 'mailto:[email protected]',
+ 'MAILTO:[email protected]',
+ 'tel:123-123-1234',
+ 'TEL:123-123-1234',
+ '#anchor',
+ '/page1.md',
+ 'http://JavaScript/my.js',
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/', // Truncated.
+ 'data:video/webm;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/',
+ 'data:audio/opus;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/',
+ 'unknown-scheme:abc'
+ ]
+
+ for (const url of validUrls) {
+ const template = [
+ '<div>',
+ ` <a href="${url}">Click me</a>`,
+ ' <span>Some content</span>',
+ '</div>'
+ ].join('')
+
+ const result = sanitizeHtml(template, DefaultAllowlist, null)
+
+ expect(result).toContain(`href="${url}"`)
+ }
+ })
- expect(result).not.toContain('href="javascript:alert(7)')
+ it('should sanitize template by removing tags with XSS', () => {
+ const invalidUrls = [
+ // eslint-disable-next-line no-script-url
+ 'javascript:alert(7)',
+ // eslint-disable-next-line no-script-url
+ 'javascript:evil()',
+ // eslint-disable-next-line no-script-url
+ 'JavaScript:abc',
+ ' javascript:abc',
+ ' \n Java\n Script:abc',
+ '&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;',
+ '&#106&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;',
+ '&#106 &#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;',
+ '&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058',
+ '&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A;',
+ 'jav&#x09;ascript:alert();',
+ 'jav\u0000ascript:alert();'
+ ]
+
+ for (const url of invalidUrls) {
+ const template = [
+ '<div>',
+ ` <a href="${url}">Click me</a>`,
+ ' <span>Some content</span>',
+ '</div>'
+ ].join('')
+
+ const result = sanitizeHtml(template, DefaultAllowlist, null)
+
+ expect(result).not.toContain(`href="${url}"`)
+ }
})
it('should sanitize template and work with multiple regex', () => {
@@ -84,12 +142,12 @@ describe('Sanitizer', () => {
return htmlUnsafe
}
- spyOn(DOMParser.prototype, 'parseFromString')
+ const spy = spyOn(DOMParser.prototype, 'parseFromString')
const result = sanitizeHtml(template, DefaultAllowlist, mySanitize)
expect(result).toEqual(template)
- expect(DOMParser.prototype.parseFromString).not.toHaveBeenCalled()
+ expect(spy).not.toHaveBeenCalled()
})
it('should allow multiple sanitation passes of the same template', () => {
diff --git a/js/tests/unit/util/scrollbar.spec.js b/js/tests/unit/util/scrollbar.spec.js
index 280adb8e5..6dadfcdd1 100644
--- a/js/tests/unit/util/scrollbar.spec.js
+++ b/js/tests/unit/util/scrollbar.spec.js
@@ -1,13 +1,13 @@
-import { clearBodyAndDocument, clearFixture, getFixture } from '../../helpers/fixture'
-import Manipulator from '../../../src/dom/manipulator'
-import ScrollBarHelper from '../../../src/util/scrollbar'
+import Manipulator from '../../../src/dom/manipulator.js'
+import ScrollBarHelper from '../../../src/util/scrollbar.js'
+import { clearBodyAndDocument, clearFixture, getFixture } from '../../helpers/fixture.js'
describe('ScrollBar', () => {
let fixtureEl
const doc = document.documentElement
- const parseInt = arg => Number.parseInt(arg, 10)
- const getPaddingX = el => parseInt(window.getComputedStyle(el).paddingRight)
- const getMarginX = el => parseInt(window.getComputedStyle(el).marginRight)
+ const parseIntDecimal = arg => Number.parseInt(arg, 10)
+ const getPaddingX = el => parseIntDecimal(window.getComputedStyle(el).paddingRight)
+ const getMarginX = el => parseIntDecimal(window.getComputedStyle(el).marginRight)
const getOverFlow = el => el.style.overflow
const getPaddingAttr = el => Manipulator.getDataAttribute(el, 'padding-right')
const getMarginAttr = el => Manipulator.getDataAttribute(el, 'margin-right')
@@ -24,7 +24,9 @@ describe('ScrollBar', () => {
}
}
- const isScrollBarHidden = () => { // IOS devices, Android devices and Browsers on Mac, hide scrollbar by default and appear it, only while scrolling. So the tests for scrollbar would fail
+ // iOS, Android devices and macOS browsers hide scrollbar by default and show it only while scrolling.
+ // So the tests for scrollbar would fail
+ const isScrollBarHidden = () => {
const calc = windowCalculations()
return calc.htmlClient === calc.htmlOffset && calc.htmlClient === calc.window
}
@@ -52,28 +54,24 @@ describe('ScrollBar', () => {
it('should return true if body is overflowing', () => {
document.documentElement.style.overflowY = 'scroll'
document.body.style.overflowY = 'scroll'
- fixtureEl.innerHTML = [
- '<div style="height: 110vh; width: 100%"></div>'
- ].join('')
+ fixtureEl.innerHTML = '<div style="height: 110vh; width: 100%"></div>'
const result = new ScrollBarHelper().isOverflowing()
if (isScrollBarHidden()) {
- expect(result).toEqual(false)
+ expect(result).toBeFalse()
} else {
- expect(result).toEqual(true)
+ expect(result).toBeTrue()
}
})
it('should return false if body is not overflowing', () => {
doc.style.overflowY = 'hidden'
document.body.style.overflowY = 'hidden'
- fixtureEl.innerHTML = [
- '<div style="height: 110vh; width: 100%"></div>'
- ].join('')
+ fixtureEl.innerHTML = '<div style="height: 110vh; width: 100%"></div>'
const scrollBar = new ScrollBarHelper()
const result = scrollBar.isOverflowing()
- expect(result).toEqual(false)
+ expect(result).toBeFalse()
})
})
@@ -81,13 +79,11 @@ describe('ScrollBar', () => {
it('should return an integer greater than zero, if body is overflowing', () => {
doc.style.overflowY = 'scroll'
document.body.style.overflowY = 'scroll'
- fixtureEl.innerHTML = [
- '<div style="height: 110vh; width: 100%"></div>'
- ].join('')
+ fixtureEl.innerHTML = '<div style="height: 110vh; width: 100%"></div>'
const result = new ScrollBarHelper().getWidth()
if (isScrollBarHidden()) {
- expect(result).toBe(0)
+ expect(result).toEqual(0)
} else {
expect(result).toBeGreaterThan(1)
}
@@ -96,9 +92,7 @@ describe('ScrollBar', () => {
it('should return 0 if body is not overflowing', () => {
document.documentElement.style.overflowY = 'hidden'
document.body.style.overflowY = 'hidden'
- fixtureEl.innerHTML = [
- '<div style="height: 110vh; width: 100%"></div>'
- ].join('')
+ fixtureEl.innerHTML = '<div style="height: 110vh; width: 100%"></div>'
const result = new ScrollBarHelper().getWidth()
@@ -107,11 +101,11 @@ describe('ScrollBar', () => {
})
describe('hide - reset', () => {
- it('should adjust the inline padding of fixed elements which are full-width', done => {
+ it('should adjust the inline padding of fixed elements which are full-width', () => {
fixtureEl.innerHTML = [
- '<div style="height: 110vh; width: 100%">' +
- '<div class="fixed-top" id="fixed1" style="padding-right: 0px; width: 100vw"></div>',
- '<div class="fixed-top" id="fixed2" style="padding-right: 5px; width: 100vw"></div>',
+ '<div style="height: 110vh; width: 100%">',
+ ' <div class="fixed-top" id="fixed1" style="padding-right: 0px; width: 100vw"></div>',
+ ' <div class="fixed-top" id="fixed2" style="padding-right: 5px; width: 100vw"></div>',
'</div>'
].join('')
doc.style.overflowY = 'scroll'
@@ -128,25 +122,44 @@ describe('ScrollBar', () => {
let currentPadding = getPaddingX(fixedEl)
let currentPadding2 = getPaddingX(fixedEl2)
- expect(getPaddingAttr(fixedEl)).toEqual(`${originalPadding}px`, 'original fixed element padding should be stored in data-bs-padding-right')
- expect(getPaddingAttr(fixedEl2)).toEqual(`${originalPadding2}px`, 'original fixed element padding should be stored in data-bs-padding-right')
- expect(currentPadding).toEqual(expectedPadding, 'fixed element padding should be adjusted while opening')
- expect(currentPadding2).toEqual(expectedPadding2, 'fixed element padding should be adjusted while opening')
+ expect(getPaddingAttr(fixedEl)).toEqual(`${originalPadding}px`)
+ expect(getPaddingAttr(fixedEl2)).toEqual(`${originalPadding2}px`)
+ expect(currentPadding).toEqual(expectedPadding)
+ expect(currentPadding2).toEqual(expectedPadding2)
scrollBar.reset()
currentPadding = getPaddingX(fixedEl)
currentPadding2 = getPaddingX(fixedEl2)
- expect(getPaddingAttr(fixedEl)).toEqual(null, 'data-bs-padding-right should be cleared after closing')
- expect(getPaddingAttr(fixedEl2)).toEqual(null, 'data-bs-padding-right should be cleared after closing')
- expect(currentPadding).toEqual(originalPadding, 'fixed element padding should be reset after closing')
- expect(currentPadding2).toEqual(originalPadding2, 'fixed element padding should be reset after closing')
- done()
+ expect(getPaddingAttr(fixedEl)).toBeNull()
+ expect(getPaddingAttr(fixedEl2)).toBeNull()
+ expect(currentPadding).toEqual(originalPadding)
+ expect(currentPadding2).toEqual(originalPadding2)
})
- it('should adjust the inline margin and padding of sticky elements', done => {
+ it('should remove padding & margin if not existed before adjustment', () => {
fixtureEl.innerHTML = [
- '<div style="height: 110vh">' +
- '<div class="sticky-top" style="margin-right: 10px; padding-right: 20px; width: 100vw; height: 10px"></div>',
+ '<div style="height: 110vh; width: 100%">',
+ ' <div class="fixed" id="fixed" style="width: 100vw;"></div>',
+ ' <div class="sticky-top" id="sticky" style=" width: 100vw;"></div>',
+ '</div>'
+ ].join('')
+ doc.style.overflowY = 'scroll'
+
+ const fixedEl = fixtureEl.querySelector('#fixed')
+ const stickyEl = fixtureEl.querySelector('#sticky')
+ const scrollBar = new ScrollBarHelper()
+
+ scrollBar.hide()
+ scrollBar.reset()
+
+ expect(fixedEl.getAttribute('style').includes('padding-right')).toBeFalse()
+ expect(stickyEl.getAttribute('style').includes('margin-right')).toBeFalse()
+ })
+
+ it('should adjust the inline margin and padding of sticky elements', () => {
+ fixtureEl.innerHTML = [
+ '<div style="height: 110vh">',
+ ' <div class="sticky-top" style="margin-right: 10px; padding-right: 20px; width: 100vw; height: 10px"></div>',
'</div>'
].join('')
doc.style.overflowY = 'scroll'
@@ -159,23 +172,20 @@ describe('ScrollBar', () => {
const expectedPadding = originalPadding + scrollBar.getWidth()
scrollBar.hide()
- expect(getMarginAttr(stickyTopEl)).toEqual(`${originalMargin}px`, 'original sticky element margin should be stored in data-bs-margin-right')
- expect(getMarginX(stickyTopEl)).toEqual(expectedMargin, 'sticky element margin should be adjusted while opening')
- expect(getPaddingAttr(stickyTopEl)).toEqual(`${originalPadding}px`, 'original sticky element margin should be stored in data-bs-margin-right')
- expect(getPaddingX(stickyTopEl)).toEqual(expectedPadding, 'sticky element margin should be adjusted while opening')
+ expect(getMarginAttr(stickyTopEl)).toEqual(`${originalMargin}px`)
+ expect(getMarginX(stickyTopEl)).toEqual(expectedMargin)
+ expect(getPaddingAttr(stickyTopEl)).toEqual(`${originalPadding}px`)
+ expect(getPaddingX(stickyTopEl)).toEqual(expectedPadding)
scrollBar.reset()
- expect(getMarginAttr(stickyTopEl)).toEqual(null, 'data-bs-margin-right should be cleared after closing')
- expect(getMarginX(stickyTopEl)).toEqual(originalMargin, 'sticky element margin should be reset after closing')
- expect(getPaddingAttr(stickyTopEl)).toEqual(null, 'data-bs-margin-right should be cleared after closing')
- expect(getPaddingX(stickyTopEl)).toEqual(originalPadding, 'sticky element margin should be reset after closing')
- done()
+ expect(getMarginAttr(stickyTopEl)).toBeNull()
+ expect(getMarginX(stickyTopEl)).toEqual(originalMargin)
+ expect(getPaddingAttr(stickyTopEl)).toBeNull()
+ expect(getPaddingX(stickyTopEl)).toEqual(originalPadding)
})
it('should not adjust the inline margin and padding of sticky and fixed elements when element do not have full width', () => {
- fixtureEl.innerHTML = [
- '<div class="sticky-top" style="margin-right: 0px; padding-right: 0px; width: 50vw"></div>'
- ].join('')
+ fixtureEl.innerHTML = '<div class="sticky-top" style="margin-right: 0px; padding-right: 0px; width: 50vw"></div>'
const stickyTopEl = fixtureEl.querySelector('.sticky-top')
const originalMargin = getMarginX(stickyTopEl)
@@ -187,16 +197,16 @@ describe('ScrollBar', () => {
const currentMargin = getMarginX(stickyTopEl)
const currentPadding = getPaddingX(stickyTopEl)
- expect(currentMargin).toEqual(originalMargin, 'sticky element\'s margin should not be adjusted while opening')
- expect(currentPadding).toEqual(originalPadding, 'sticky element\'s padding should not be adjusted while opening')
+ expect(currentMargin).toEqual(originalMargin)
+ expect(currentPadding).toEqual(originalPadding)
scrollBar.reset()
})
it('should not put data-attribute if element doesn\'t have the proper style property, should just remove style property if element didn\'t had one', () => {
fixtureEl.innerHTML = [
- '<div style="height: 110vh; width: 100%">' +
- '<div class="sticky-top" id="sticky" style="width: 100vw"></div>',
+ '<div style="height: 110vh; width: 100%">',
+ ' <div class="sticky-top" id="sticky" style="width: 100vw"></div>',
'</div>'
].join('')
@@ -232,8 +242,8 @@ describe('ScrollBar', () => {
const scrollBarWidth = scrollBar.getWidth()
scrollBar.hide()
- expect(getPaddingX(document.body)).toEqual(scrollBarWidth, 'body does not have inline padding set')
- expect(document.body.style.color).toEqual('red', 'body still has other inline styles set')
+ expect(getPaddingX(document.body)).toEqual(scrollBarWidth)
+ expect(document.body.style.color).toEqual('red')
scrollBar.reset()
})
@@ -243,7 +253,7 @@ describe('ScrollBar', () => {
fixtureEl.innerHTML = [
'<style>',
' body {',
- ` padding-right: ${styleSheetPadding} }`,
+ ` padding-right: ${styleSheetPadding}`,
' }',
'</style>'
].join('')
@@ -253,7 +263,7 @@ describe('ScrollBar', () => {
el.style.paddingRight = inlineStylePadding
const originalPadding = getPaddingX(el)
- expect(originalPadding).toEqual(parseInt(inlineStylePadding)) // Respect only the inline style as it has prevails this of css
+ expect(originalPadding).toEqual(parseIntDecimal(inlineStylePadding)) // Respect only the inline style as it has prevails this of css
const originalOverFlow = 'auto'
el.style.overflow = originalOverFlow
const scrollBar = new ScrollBarHelper()
@@ -264,7 +274,7 @@ describe('ScrollBar', () => {
const currentPadding = getPaddingX(el)
expect(currentPadding).toEqual(scrollBarWidth + originalPadding)
- expect(currentPadding).toEqual(scrollBarWidth + parseInt(inlineStylePadding))
+ expect(currentPadding).toEqual(scrollBarWidth + parseIntDecimal(inlineStylePadding))
expect(getPaddingAttr(el)).toEqual(inlineStylePadding)
expect(getOverFlow(el)).toEqual('hidden')
expect(getOverFlowAttr(el)).toEqual(originalOverFlow)
@@ -273,9 +283,9 @@ describe('ScrollBar', () => {
const currentPadding1 = getPaddingX(el)
expect(currentPadding1).toEqual(originalPadding)
- expect(getPaddingAttr(el)).toEqual(null)
+ expect(getPaddingAttr(el)).toBeNull()
expect(getOverFlow(el)).toEqual(originalOverFlow)
- expect(getOverFlowAttr(el)).toEqual(null)
+ expect(getOverFlowAttr(el)).toBeNull()
})
it('should hide scrollbar and reset it to its initial value - respecting css rules', () => {
@@ -283,7 +293,7 @@ describe('ScrollBar', () => {
fixtureEl.innerHTML = [
'<style>',
' body {',
- ` padding-right: ${styleSheetPadding} }`,
+ ` padding-right: ${styleSheetPadding}`,
' }',
'</style>'
].join('')
@@ -299,7 +309,7 @@ describe('ScrollBar', () => {
const currentPadding = getPaddingX(el)
expect(currentPadding).toEqual(scrollBarWidth + originalPadding)
- expect(currentPadding).toEqual(scrollBarWidth + parseInt(styleSheetPadding))
+ expect(currentPadding).toEqual(scrollBarWidth + parseIntDecimal(styleSheetPadding))
expect(getPaddingAttr(el)).toBeNull() // We do not have to keep css padding
expect(getOverFlow(el)).toEqual('hidden')
expect(getOverFlowAttr(el)).toEqual(originalOverFlow)
@@ -308,9 +318,9 @@ describe('ScrollBar', () => {
const currentPadding1 = getPaddingX(el)
expect(currentPadding1).toEqual(originalPadding)
- expect(getPaddingAttr(el)).toEqual(null)
+ expect(getPaddingAttr(el)).toBeNull()
expect(getOverFlow(el)).toEqual(originalOverFlow)
- expect(getOverFlowAttr(el)).toEqual(null)
+ expect(getOverFlowAttr(el)).toBeNull()
})
it('should not adjust the inline body padding when it does not overflow', () => {
@@ -324,7 +334,7 @@ describe('ScrollBar', () => {
scrollBar.hide()
const currentPadding = getPaddingX(document.body)
- expect(currentPadding).toEqual(originalPadding, 'body padding should not be adjusted')
+ expect(currentPadding).toEqual(originalPadding)
scrollBar.reset()
})
@@ -344,7 +354,7 @@ describe('ScrollBar', () => {
const currentPadding = getPaddingX(document.body)
- expect(currentPadding).toEqual(originalPadding, 'body padding should not be adjusted')
+ expect(currentPadding).toEqual(originalPadding)
scrollBar.reset()
})
diff --git a/js/tests/unit/util/swipe.spec.js b/js/tests/unit/util/swipe.spec.js
index 474e34f65..9252d312b 100644
--- a/js/tests/unit/util/swipe.spec.js
+++ b/js/tests/unit/util/swipe.spec.js
@@ -1,7 +1,7 @@
-import { clearFixture, getFixture } from '../../helpers/fixture'
-import EventHandler from '../../../src/dom/event-handler'
-import Swipe from '../../../src/util/swipe'
-import { noop } from '../../../src/util'
+import EventHandler from '../../../src/dom/event-handler.js'
+import { noop } from '../../../src/util/index.js'
+import Swipe from '../../../src/util/swipe.js'
+import { clearFixture, getFixture } from '../../helpers/fixture.js'
describe('Swipe', () => {
const { Simulator, PointerEvent } = window
@@ -39,17 +39,17 @@ describe('Swipe', () => {
fixtureEl = getFixture()
const cssStyle = [
'<style>',
- ' #fixture .pointer-event {',
- ' touch-action: pan-y;',
+ ' #fixture .pointer-event {',
+ ' touch-action: pan-y;',
' }',
- ' #fixture div {',
- ' width: 300px;',
- ' height: 300px;',
+ ' #fixture div {',
+ ' width: 300px;',
+ ' height: 300px;',
' }',
'</style>'
].join('')
- fixtureEl.innerHTML = '<div id="swipeEl"></div>' + cssStyle
+ fixtureEl.innerHTML = `<div id="swipeEl"></div>${cssStyle}`
swipeEl = fixtureEl.querySelector('div')
})
@@ -78,74 +78,80 @@ describe('Swipe', () => {
})
describe('Config', () => {
- it('Test leftCallback', done => {
- const spyRight = jasmine.createSpy('spy')
- clearPointerEvents()
- defineDocumentElementOntouchstart()
- // eslint-disable-next-line no-new
- new Swipe(swipeEl, {
- leftCallback: () => {
- expect(spyRight).not.toHaveBeenCalled()
- restorePointerEvents()
- done()
- },
- rightCallback: spyRight
- })
-
- mockSwipeGesture(swipeEl, {
- pos: [300, 10],
- deltaX: -300
+ it('Test leftCallback', () => {
+ return new Promise(resolve => {
+ const spyRight = jasmine.createSpy('spy')
+ clearPointerEvents()
+ defineDocumentElementOntouchstart()
+ // eslint-disable-next-line no-new
+ new Swipe(swipeEl, {
+ leftCallback() {
+ expect(spyRight).not.toHaveBeenCalled()
+ restorePointerEvents()
+ resolve()
+ },
+ rightCallback: spyRight
+ })
+
+ mockSwipeGesture(swipeEl, {
+ pos: [300, 10],
+ deltaX: -300
+ })
})
})
- it('Test rightCallback', done => {
- const spyLeft = jasmine.createSpy('spy')
- clearPointerEvents()
- defineDocumentElementOntouchstart()
- // eslint-disable-next-line no-new
- new Swipe(swipeEl, {
- rightCallback: () => {
- expect(spyLeft).not.toHaveBeenCalled()
- restorePointerEvents()
- done()
- },
- leftCallback: spyLeft
- })
-
- mockSwipeGesture(swipeEl, {
- pos: [10, 10],
- deltaX: 300
+ it('Test rightCallback', () => {
+ return new Promise(resolve => {
+ const spyLeft = jasmine.createSpy('spy')
+ clearPointerEvents()
+ defineDocumentElementOntouchstart()
+ // eslint-disable-next-line no-new
+ new Swipe(swipeEl, {
+ rightCallback() {
+ expect(spyLeft).not.toHaveBeenCalled()
+ restorePointerEvents()
+ resolve()
+ },
+ leftCallback: spyLeft
+ })
+
+ mockSwipeGesture(swipeEl, {
+ pos: [10, 10],
+ deltaX: 300
+ })
})
})
- it('Test endCallback', done => {
- clearPointerEvents()
- defineDocumentElementOntouchstart()
- let isFirstTime = true
-
- const callback = () => {
- if (isFirstTime) {
- isFirstTime = false
- return
- }
+ it('Test endCallback', () => {
+ return new Promise(resolve => {
+ clearPointerEvents()
+ defineDocumentElementOntouchstart()
+ let isFirstTime = true
- expect().nothing()
- restorePointerEvents()
- done()
- }
+ const callback = () => {
+ if (isFirstTime) {
+ isFirstTime = false
+ return
+ }
- // eslint-disable-next-line no-new
- new Swipe(swipeEl, {
- endCallback: callback
- })
- mockSwipeGesture(swipeEl, {
- pos: [10, 10],
- deltaX: 300
- })
+ expect().nothing()
+ restorePointerEvents()
+ resolve()
+ }
- mockSwipeGesture(swipeEl, {
- pos: [300, 10],
- deltaX: -300
+ // eslint-disable-next-line no-new
+ new Swipe(swipeEl, {
+ endCallback: callback
+ })
+ mockSwipeGesture(swipeEl, {
+ pos: [10, 10],
+ deltaX: 300
+ })
+
+ mockSwipeGesture(swipeEl, {
+ pos: [300, 10],
+ deltaX: -300
+ })
})
})
})
@@ -157,7 +163,7 @@ describe('Swipe', () => {
deleteDocumentElementOntouchstart()
const swipe = new Swipe(swipeEl)
- spyOn(swipe, '_handleSwipe')
+ const spy = spyOn(swipe, '_handleSwipe')
mockSwipeGesture(swipeEl, {
pos: [300, 10],
@@ -167,56 +173,60 @@ describe('Swipe', () => {
})
restorePointerEvents()
- expect(swipe._handleSwipe).not.toHaveBeenCalled()
+ expect(spy).not.toHaveBeenCalled()
})
- it('should allow swipeRight and call "rightCallback" with pointer events', done => {
- if (!supportPointerEvent) {
- expect().nothing()
- done()
- return
- }
-
- const style = '#fixture .pointer-event { touch-action: none !important; }'
- fixtureEl.innerHTML += style
-
- defineDocumentElementOntouchstart()
- // eslint-disable-next-line no-new
- new Swipe(swipeEl, {
- rightCallback: () => {
- deleteDocumentElementOntouchstart()
+ it('should allow swipeRight and call "rightCallback" with pointer events', () => {
+ return new Promise(resolve => {
+ if (!supportPointerEvent) {
expect().nothing()
- done()
+ resolve()
+ return
}
- })
- mockSwipeGesture(swipeEl, { deltaX: 300 }, 'pointer')
- })
+ const style = '#fixture .pointer-event { touch-action: none !important; }'
+ fixtureEl.innerHTML += style
- it('should allow swipeLeft and call "leftCallback" with pointer events', done => {
- if (!supportPointerEvent) {
- expect().nothing()
- done()
- return
- }
+ defineDocumentElementOntouchstart()
+ // eslint-disable-next-line no-new
+ new Swipe(swipeEl, {
+ rightCallback() {
+ deleteDocumentElementOntouchstart()
+ expect().nothing()
+ resolve()
+ }
+ })
- const style = '#fixture .pointer-event { touch-action: none !important; }'
- fixtureEl.innerHTML += style
+ mockSwipeGesture(swipeEl, { deltaX: 300 }, 'pointer')
+ })
+ })
- defineDocumentElementOntouchstart()
- // eslint-disable-next-line no-new
- new Swipe(swipeEl, {
- leftCallback: () => {
+ it('should allow swipeLeft and call "leftCallback" with pointer events', () => {
+ return new Promise(resolve => {
+ if (!supportPointerEvent) {
expect().nothing()
- deleteDocumentElementOntouchstart()
- done()
+ resolve()
+ return
}
- })
- mockSwipeGesture(swipeEl, {
- pos: [300, 10],
- deltaX: -300
- }, 'pointer')
+ const style = '#fixture .pointer-event { touch-action: none !important; }'
+ fixtureEl.innerHTML += style
+
+ defineDocumentElementOntouchstart()
+ // eslint-disable-next-line no-new
+ new Swipe(swipeEl, {
+ leftCallback() {
+ expect().nothing()
+ deleteDocumentElementOntouchstart()
+ resolve()
+ }
+ })
+
+ mockSwipeGesture(swipeEl, {
+ pos: [300, 10],
+ deltaX: -300
+ }, 'pointer')
+ })
})
})
@@ -266,7 +276,7 @@ describe('Swipe', () => {
expect(Swipe.isSupported()).toBeTrue()
})
- it('should return "false" if "touchstart" not exists in document element and "navigator.maxTouchPoints" are zero (0)', () => {
+ it('should return "false" if "touchstart" not exists in document element and "navigator.maxTouchPoints" are zero (0)', () => {
Object.defineProperty(window.navigator, 'maxTouchPoints', () => 0)
deleteDocumentElementOntouchstart()
diff --git a/js/tests/unit/util/template-factory.spec.js b/js/tests/unit/util/template-factory.spec.js
index 842c480c2..07f4d91c7 100644
--- a/js/tests/unit/util/template-factory.spec.js
+++ b/js/tests/unit/util/template-factory.spec.js
@@ -1,5 +1,5 @@
-import { clearFixture, getFixture } from '../../helpers/fixture'
-import TemplateFactory from '../../../src/util/template-factory'
+import TemplateFactory from '../../../src/util/template-factory.js'
+import { clearFixture, getFixture } from '../../helpers/fixture.js'
describe('TemplateFactory', () => {
let fixtureEl
@@ -86,26 +86,26 @@ describe('TemplateFactory', () => {
const factory = new TemplateFactory({
extraClass: 'testClass'
})
- expect(factory.toHtml().classList.contains('testClass')).toBeTrue()
+ expect(factory.toHtml()).toHaveClass('testClass')
})
it('should add extra classes', () => {
const factory = new TemplateFactory({
extraClass: 'testClass testClass2'
})
- expect(factory.toHtml().classList.contains('testClass')).toBeTrue()
- expect(factory.toHtml().classList.contains('testClass2')).toBeTrue()
+ expect(factory.toHtml()).toHaveClass('testClass')
+ expect(factory.toHtml()).toHaveClass('testClass2')
})
it('should resolve class if function is given', () => {
const factory = new TemplateFactory({
- extraClass: arg => {
+ extraClass(arg) {
expect(arg).toEqual(factory)
return 'testClass'
}
})
- expect(factory.toHtml().classList.contains('testClass')).toBeTrue()
+ expect(factory.toHtml()).toHaveClass('testClass')
})
})
})
@@ -113,11 +113,11 @@ describe('TemplateFactory', () => {
describe('Content', () => {
it('add simple text content', () => {
const template = [
- '<div>' +
- '<div class="foo"></div>' +
- '<div class="foo2"></div>' +
+ '<div>',
+ ' <div class="foo"></div>',
+ ' <div class="foo2"></div>',
'</div>'
- ].join(' ')
+ ].join('')
const factory = new TemplateFactory({
template,
@@ -128,8 +128,8 @@ describe('TemplateFactory', () => {
})
const html = factory.toHtml()
- expect(html.querySelector('.foo').textContent).toBe('bar')
- expect(html.querySelector('.foo2').textContent).toBe('bar2')
+ expect(html.querySelector('.foo').textContent).toEqual('bar')
+ expect(html.querySelector('.foo2').textContent).toEqual('bar2')
})
it('should not fill template if selector not exists', () => {
@@ -140,7 +140,7 @@ describe('TemplateFactory', () => {
content: { '#bar': 'test' }
})
- expect(factory.toHtml().outerHTML).toBe('<div id="foo"></div>')
+ expect(factory.toHtml().outerHTML).toEqual('<div id="foo"></div>')
})
it('should remove template selector, if content is null', () => {
@@ -151,7 +151,7 @@ describe('TemplateFactory', () => {
content: { '#foo': null }
})
- expect(factory.toHtml().outerHTML).toBe('<div></div>')
+ expect(factory.toHtml().outerHTML).toEqual('<div></div>')
})
it('should resolve content if is function', () => {
@@ -162,7 +162,7 @@ describe('TemplateFactory', () => {
content: { '#foo': () => null }
})
- expect(factory.toHtml().outerHTML).toBe('<div></div>')
+ expect(factory.toHtml().outerHTML).toEqual('<div></div>')
})
it('if content is element and "config.html=false", should put content\'s textContent', () => {
@@ -176,9 +176,9 @@ describe('TemplateFactory', () => {
})
const fooEl = factory.toHtml().querySelector('#foo')
- expect(fooEl.innerHTML).not.toBe(contentElement.innerHTML)
- expect(fooEl.textContent).toBe(contentElement.textContent)
- expect(fooEl.textContent).toBe('foobar')
+ expect(fooEl.innerHTML).not.toEqual(contentElement.innerHTML)
+ expect(fooEl.textContent).toEqual(contentElement.textContent)
+ expect(fooEl.textContent).toEqual('foobar')
})
it('if content is element and "config.html=true", should put content\'s outerHtml as child', () => {
@@ -192,8 +192,8 @@ describe('TemplateFactory', () => {
})
const fooEl = factory.toHtml().querySelector('#foo')
- expect(fooEl.innerHTML).toBe(contentElement.outerHTML)
- expect(fooEl.textContent).toBe(contentElement.textContent)
+ expect(fooEl.innerHTML).toEqual(contentElement.outerHTML)
+ expect(fooEl.textContent).toEqual(contentElement.textContent)
})
})
@@ -245,14 +245,15 @@ describe('TemplateFactory', () => {
expect(factory.hasContent()).toBeFalse()
})
})
+
describe('changeContent', () => {
it('should change Content', () => {
const template = [
- '<div>' +
- '<div class="foo"></div>' +
- '<div class="foo2"></div>' +
+ '<div>',
+ ' <div class="foo"></div>',
+ ' <div class="foo2"></div>',
'</div>'
- ].join(' ')
+ ].join('')
const factory = new TemplateFactory({
template,
@@ -276,11 +277,11 @@ describe('TemplateFactory', () => {
it('should change only the given, content', () => {
const template = [
- '<div>' +
- '<div class="foo"></div>' +
- '<div class="foo2"></div>' +
+ '<div>',
+ ' <div class="foo"></div>',
+ ' <div class="foo2"></div>',
'</div>'
- ].join(' ')
+ ].join('')
const factory = new TemplateFactory({
template,
diff --git a/js/tests/visual/alert.html b/js/tests/visual/alert.html
index c5e2efafa..2433b9e8e 100644
--- a/js/tests/visual/alert.html
+++ b/js/tests/visual/alert.html
@@ -43,10 +43,6 @@
</div>
</div>
- <script src="../../dist/dom/event-handler.js"></script>
- <script src="../../dist/dom/selector-engine.js"></script>
- <script src="../../dist/dom/data.js"></script>
- <script src="../../dist/base-component.js"></script>
- <script src="../../dist/alert.js"></script>
+ <script src="../../../dist/js/bootstrap.bundle.js"></script>
</body>
</html>
diff --git a/js/tests/visual/button.html b/js/tests/visual/button.html
index 0bd2f1097..47c50889c 100644
--- a/js/tests/visual/button.html
+++ b/js/tests/visual/button.html
@@ -15,7 +15,7 @@
</button>
<p>For checkboxes and radio buttons, ensure that keyboard behavior is functioning correctly.</p>
- <p>Navigate to the checkboxes with the keyboard (generally, using <kbd>TAB</kbd> / <kbd>SHIFT + TAB</kbd>), and ensure that <kbd>SPACE</kbd> toggles the currently focused checkbox. Click on one of the checkboxes using the mouse, ensure that focus was correctly set on the actual checkbox, and that <kbd>SPACE</kbd> toggles the checkbox again.</p>
+ <p>Navigate to the checkboxes with the keyboard (generally, using <kbd>Tab</kbd> / <kbd><kbd>Shift</kbd> + <kbd>Tab</kbd></kbd>), and ensure that <kbd>Space</kbd> toggles the currently focused checkbox. Click on one of the checkboxes using the mouse, ensure that focus was correctly set on the actual checkbox, and that <kbd>Space</kbd> toggles the checkbox again.</p>
<div class="btn-group" data-bs-toggle="buttons">
<label class="btn btn-primary active">
@@ -29,7 +29,7 @@
</label>
</div>
- <p>Navigate to the radio button group with the keyboard (generally, using <kbd>TAB</kbd> / <kbd>SHIFT + TAB</kbd>). If no radio button was initially set to be selected, the first/last radio button should receive focus (depending on whether you navigated "forward" to the group with <kbd>TAB</kbd> or "backwards" using <kbd>SHIFT + TAB</kbd>). If a radio button was already selected, navigating with the keyboard should set focus to that particular radio button. Only one radio button in a group should receive focus at any given time. Ensure that the selected radio button can be changed by using the <kbd>←</kbd> and <kbd>→</kbd> arrow keys. Click on one of the radio buttons with the mouse, ensure that focus was correctly set on the actual radio button, and that <kbd>←</kbd> and <kbd>→</kbd> change the selected radio button again.</p>
+ <p>Navigate to the radio button group with the keyboard (generally, using <kbd>Tab</kbd> / <kbd><kbd>Shift</kbd> + <kbd>Tab</kbd></kbd>). If no radio button was initially set to be selected, the first/last radio button should receive focus (depending on whether you navigated "forward" to the group with <kbd>Tab</kbd> or "backwards" using <kbd><kbd>Shift</kbd> + <kbd>Tab</kbd></kbd>). If a radio button was already selected, navigating with the keyboard should set focus to that particular radio button. Only one radio button in a group should receive focus at any given time. Ensure that the selected radio button can be changed by using the <kbd>←</kbd> and <kbd>→</kbd> arrow keys. Click on one of the radio buttons with the mouse, ensure that focus was correctly set on the actual radio button, and that <kbd>←</kbd> and <kbd>→</kbd> change the selected radio button again.</p>
<div class="btn-group" data-bs-toggle="buttons">
<label class="btn btn-primary active">
@@ -44,10 +44,6 @@
</div>
</div>
- <script src="../../dist/dom/event-handler.js"></script>
- <script src="../../dist/dom/selector-engine.js"></script>
- <script src="../../dist/dom/data.js"></script>
- <script src="../../dist/base-component.js"></script>
- <script src="../../dist/button.js"></script>
+ <script src="../../../dist/js/bootstrap.bundle.js"></script>
</body>
</html>
diff --git a/js/tests/visual/carousel.html b/js/tests/visual/carousel.html
index 561b6ce5a..1b2de5291 100644
--- a/js/tests/visual/carousel.html
+++ b/js/tests/visual/carousel.html
@@ -45,24 +45,19 @@
</div>
</div>
- <script src="../../dist/dom/event-handler.js"></script>
- <script src="../../dist/dom/selector-engine.js"></script>
- <script src="../../dist/dom/manipulator.js"></script>
- <script src="../../dist/dom/data.js"></script>
- <script src="../../dist/base-component.js"></script>
- <script src="../../dist/carousel.js"></script>
+ <script src="../../../dist/js/bootstrap.bundle.js"></script>
<script>
- var t0
- var t1
- var carousel = document.getElementById('carousel-example-generic')
+ let t0
+ let t1
+ const carousel = document.getElementById('carousel-example-generic')
// Test to show that the carousel doesn't slide when the current tab isn't visible
// Test to show that transition-duration can be changed with css
- carousel.addEventListener('slid.bs.carousel', function (event) {
+ carousel.addEventListener('slid.bs.carousel', event => {
t1 = performance.now()
- console.log('transition-duration took ' + (t1 - t0) + 'ms, slid at ' + event.timeStamp)
+ console.log(`transition-duration took ${t1 - t0}ms, slid at ${event.timeStamp}`)
})
- carousel.addEventListener('slide.bs.carousel', function () {
+ carousel.addEventListener('slide.bs.carousel', () => {
t0 = performance.now()
})
</script>
diff --git a/js/tests/visual/collapse.html b/js/tests/visual/collapse.html
index e4e534bcf..2782c56b6 100644
--- a/js/tests/visual/collapse.html
+++ b/js/tests/visual/collapse.html
@@ -71,11 +71,6 @@
</div>
</div>
- <script src="../../dist/dom/event-handler.js"></script>
- <script src="../../dist/dom/selector-engine.js"></script>
- <script src="../../dist/dom/manipulator.js"></script>
- <script src="../../dist/dom/data.js"></script>
- <script src="../../dist/base-component.js"></script>
- <script src="../../dist/collapse.js"></script>
+ <script src="../../../dist/js/bootstrap.bundle.js"></script>
</body>
</html>
diff --git a/js/tests/visual/dropdown.html b/js/tests/visual/dropdown.html
index f1dd705f3..04cf06d7b 100644
--- a/js/tests/visual/dropdown.html
+++ b/js/tests/visual/dropdown.html
@@ -10,7 +10,7 @@
<div class="container">
<h1>Dropdown <small>Bootstrap Visual Test</small></h1>
- <nav class="navbar navbar-expand-md navbar-light bg-light">
+ <nav class="navbar navbar-expand-md bg-light">
<a class="navbar-brand" href="#">Navbar</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
@@ -28,8 +28,8 @@
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item dropdown">
- <a class="nav-link dropdown-toggle" href="#" id="dropdown" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</a>
- <ul class="dropdown-menu" aria-labelledby="dropdown">
+ <a class="nav-link dropdown-toggle" href="#" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</a>
+ <ul class="dropdown-menu">
<li><a class="dropdown-item" href="#">Action</a></li>
<li><a class="dropdown-item" href="#">Another action</a></li>
<li><a class="dropdown-item" href="#">Something else here</a></li>
@@ -50,8 +50,8 @@
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item dropdown">
- <a class="nav-link dropdown-toggle" href="#" id="dropdown2" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</a>
- <ul class="dropdown-menu" aria-labelledby="dropdown2">
+ <a class="nav-link dropdown-toggle" href="#" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</a>
+ <ul class="dropdown-menu">
<li><a class="dropdown-item" href="#">Action</a></li>
<li><a class="dropdown-item" href="#">Another action</a></li>
<li><a class="dropdown-item" href="#">Something else here</a></li>
@@ -103,7 +103,7 @@
</div>
<div class="col-sm-12 mt-4">
- <div class="btn-group dropup" role="group">
+ <div class="btn-group dropup">
<a href="#" class="btn btn-secondary">Dropup split align end</a>
<button type="button" id="dropdown-page-subheader-button-3" class="btn btn-secondary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden">Product actions</span>
@@ -125,7 +125,7 @@
</div>
<div class="col-sm-12 mt-4">
- <div class="btn-group dropend" role="group">
+ <div class="btn-group dropend">
<a href="#" class="btn btn-secondary">Dropend split</a>
<button type="button" id="dropdown-page-subheader-button-4" class="btn btn-secondary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden">Product actions</span>
@@ -137,17 +137,17 @@
</div>
</div>
<div class="btn-group dropend">
- <button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuRight" data-bs-toggle="dropdown" aria-expanded="false">
+ <button class="btn btn-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
Dropend
</button>
- <div class="dropdown-menu" aria-labelledby="dropdownMenuRight">
+ <div class="dropdown-menu">
<button class="dropdown-item" type="button">Action</button>
<button class="dropdown-item" type="button">Another action</button>
<button class="dropdown-item" type="button">Something else here</button>
</div>
</div>
<!-- dropstart -->
- <div class="btn-group dropstart" role="group">
+ <div class="btn-group dropstart">
<a href="#" class="btn btn-secondary">Dropstart split</a>
<button type="button" id="dropdown-page-subheader-button-5" class="btn btn-secondary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden">Product actions</span>
@@ -159,10 +159,10 @@
</div>
</div>
<div class="btn-group dropstart">
- <button class="btn btn-secondary dropdown-toggle" type="button" id="dropstartMenu" data-bs-toggle="dropdown" aria-expanded="false">
+ <button class="btn btn-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
Dropstart
</button>
- <div class="dropdown-menu" aria-labelledby="dropstartMenu">
+ <div class="dropdown-menu">
<button class="dropdown-item" type="button">Action</button>
<button class="dropdown-item" type="button">Another action</button>
<button class="dropdown-item" type="button">Something else here</button>
@@ -187,10 +187,10 @@
</div>
<div class="col-sm-3 mt-4">
<div class="dropdown">
- <button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-bs-toggle="dropdown" data-bs-display="static" aria-expanded="false">
+ <button class="btn btn-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" data-bs-display="static" aria-expanded="false">
Dropdown menu without Popper
</button>
- <ul class="dropdown-menu" aria-labelledby="dropdownMenuButton">
+ <ul class="dropdown-menu">
<li><a class="dropdown-item" href="#">Action</a></li>
<li><a class="dropdown-item" href="#">Another action</a></li>
<li><a class="dropdown-item" href="#">Something else here</a></li>
@@ -200,13 +200,6 @@
</div>
</div>
- <script src="../../../node_modules/@popperjs/core/dist/umd/popper.min.js"></script>
- <script src="../../dist/dom/event-handler.js"></script>
- <script src="../../dist/dom/selector-engine.js"></script>
- <script src="../../dist/dom/data.js"></script>
- <script src="../../dist/dom/manipulator.js"></script>
- <script src="../../dist/base-component.js"></script>
- <script src="../../dist/dropdown.js"></script>
- <script src="../../dist/collapse.js"></script>
+ <script src="../../../dist/js/bootstrap.bundle.js"></script>
</body>
</html>
diff --git a/js/tests/visual/floating-label.html b/js/tests/visual/floating-label.html
new file mode 100644
index 000000000..f78442eff
--- /dev/null
+++ b/js/tests/visual/floating-label.html
@@ -0,0 +1,337 @@
+<!doctype html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <link href="../../../dist/css/bootstrap.min.css" rel="stylesheet">
+ <title>Form</title>
+ <style></style>
+ </head>
+ <body>
+ <div class="container py-5 d-flex flex-column gap-3">
+ <div class="form-floating">
+ <input type="email" class="form-control" id="floatingInput" placeholder="[email protected]">
+ <label for="floatingInput">Email address</label>
+ </div>
+ <div class="form-floating">
+ <input type="email" class="form-control" id="floatingInput1" placeholder="[email protected]">
+ <label for="floatingInput1">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</label>
+ </div>
+ <form class="form-floating">
+ <input type="email" class="form-control" id="floatingInputValue" placeholder="[email protected]" value="[email protected]">
+ <label for="floatingInputValue">Input with value</label>
+ </form>
+ <form class="form-floating">
+ <input type="email" class="form-control" id="floatingInputValue1" placeholder="[email protected]" value="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.">
+ <label for="floatingInputValue1">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</label>
+ </form>
+ <form class="form-floating">
+ <input type="email" class="form-control is-invalid" id="floatingInputInvalid" placeholder="[email protected]" value="[email protected]">
+ <label for="floatingInputInvalid">Invalid input</label>
+ </form>
+ <form class="form-floating">
+ <input type="email" class="form-control is-invalid" id="floatingInputInvalid1" placeholder="[email protected]" value="[email protected]">
+ <label for="floatingInputInvalid1">Invalid input</label>
+ </form>
+ <form class="form-floating">
+ <input type="email" class="form-control is-invalid" id="floatingInputInvalid2" placeholder="[email protected]">
+ <label for="floatingInputInvalid2">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</label>
+ </form>
+ <form class="form-floating">
+ <input type="email" class="form-control is-invalid" id="floatingInputInvalid3" placeholder="[email protected]" value="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.">
+ <label for="floatingInputInvalid3">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</label>
+ </form>
+ <div class="form-floating">
+ <textarea class="form-control" placeholder="Leave a comment here" id="floatingTextarea"></textarea>
+ <label for="floatingTextarea">Comments</label>
+ </div>
+ <div class="form-floating">
+ <textarea class="form-control" placeholder="Leave a comment here" id="floatingTextarea1"></textarea>
+ <label for="floatingTextarea1">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</label>
+ </div>
+ <div class="form-floating">
+ <textarea class="form-control" placeholder="Leave a comment here" id="floatingTextarea2" style="height: 50px"></textarea>
+ <label for="floatingTextarea2">Comments</label>
+ </div>
+ <div class="form-floating">
+ <textarea class="form-control" placeholder="Leave a comment here" id="floatingTextarea3" style="height: 50px"></textarea>
+ <label for="floatingTextarea3">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</label>
+ </div>
+ <div class="form-floating">
+ <textarea class="form-control" placeholder="Leave a comment here" id="floatingTextarea4" style="height: 50px">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</textarea>
+ <label for="floatingTextarea4">Comments</label>
+ </div>
+ <div class="form-floating">
+ <textarea class="form-control" placeholder="Leave a comment here" id="floatingTextarea5" style="height: 50px">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</textarea>
+ <label for="floatingTextarea5">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</label>
+ </div>
+ <div class="form-floating">
+ <textarea class="form-control is-invalid" placeholder="Leave a comment here" id="floatingTextarea6" style="height: 50px">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</textarea>
+ <label for="floatingTextarea6">Comments</label>
+ </div>
+ <div class="form-floating">
+ <textarea class="form-control is-invalid" placeholder="Leave a comment here" id="floatingTextarea7" style="height: 50px">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</textarea>
+ <label for="floatingTextarea7">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</label>
+ </div>
+ <div class="form-floating">
+ <select class="form-select" id="floatingSelect" aria-label="Floating label select example">
+ <option selected>Open this select menu</option>
+ <option value="1">One</option>
+ <option value="2">Two</option>
+ <option value="3">Three</option>
+ </select>
+ <label for="floatingSelect">Works with selects</label>
+ </div>
+ <div class="form-floating">
+ <select class="form-select is-invalid" id="floatingSelect1" aria-label="Floating label select example">
+ <option selected>Open this select menu</option>
+ <option value="1">One</option>
+ <option value="2">Two</option>
+ <option value="3">Three</option>
+ </select>
+ <label for="floatingSelect1">Works with selects</label>
+ </div>
+ <div class="form-floating">
+ <select class="form-select" id="floatingSelect2" aria-label="Floating label select example">
+ <option selected>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</option>
+ <option value="1">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</option>
+ <option value="2">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</option>
+ <option value="3">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</option>
+ </select>
+ <label for="floatingSelect2">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</label>
+ </div>
+ <div class="form-floating">
+ <select class="form-select is-invalid" id="floatingSelect3" aria-label="Floating label select example">
+ <option selected>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</option>
+ <option value="1">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</option>
+ <option value="2">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</option>
+ <option value="3">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</option>
+ </select>
+ <label for="floatingSelect3">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</label>
+ </div>
+ <div class="form-floating mb-3">
+ <input type="email" class="form-control" id="floatingInputDisabled" placeholder="[email protected]" disabled>
+ <label for="floatingInputDisabled">Email address</label>
+ </div>
+ <div class="form-floating mb-3">
+ <textarea class="form-control" placeholder="Leave a comment here" id="floatingTextareaDisabled" disabled></textarea>
+ <label for="floatingTextareaDisabled">Comments</label>
+ </div>
+ <div class="form-floating mb-3">
+ <textarea class="form-control" placeholder="Leave a comment here" id="floatingTextareaDisabled1" style="height: 50px" disabled>Disabled textarea with some text inside</textarea>
+ <label for="floatingTextareaDisabled1">Comments</label>
+ </div>
+ <div class="form-floating">
+ <select class="form-select" id="floatingSelectDisabled" aria-label="Floating label disabled select example" disabled>
+ <option selected>Open this select menu</option>
+ <option value="1">One</option>
+ <option value="2">Two</option>
+ <option value="3">Three</option>
+ </select>
+ <label for="floatingSelectDisabled">Works with selects</label>
+ </div>
+ <div class="form-floating mb-3">
+ <input type="email" class="form-control" id="floatingInputDisabled1" placeholder="[email protected]" disabled>
+ <label for="floatingInputDisabled1">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</label>
+ </div>
+ <div class="form-floating mb-3">
+ <textarea class="form-control" placeholder="Leave a comment here" id="floatingTextareaDisabled2" disabled></textarea>
+ <label for="floatingTextareaDisabled2">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</label>
+ </div>
+ <div class="form-floating mb-3">
+ <textarea class="form-control" placeholder="Leave a comment here" id="floatingTextareaDisabled3" style="height: 50px" disabled>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</textarea>
+ <label for="floatingTextareaDisabled3">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</label>
+ </div>
+ <div class="form-floating">
+ <select class="form-select" id="floatingSelectDisabled1" aria-label="Floating label disabled select example" disabled>
+ <option selected>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</option>
+ <option value="1">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</option>
+ <option value="2">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</option>
+ <option value="3">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</option>
+ </select>
+ <label for="floatingSelectDisabled1">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</label>
+ </div>
+ <div class="form-floating mb-3">
+ <input type="email" readonly class="form-control-plaintext" id="floatingEmptyPlaintextInput" placeholder="[email protected]">
+ <label for="floatingEmptyPlaintextInput">Empty input</label>
+ </div>
+ <div class="form-floating mb-3">
+ <input type="email" readonly class="form-control-plaintext" id="floatingPlaintextInput" placeholder="[email protected]" value="[email protected]">
+ <label for="floatingPlaintextInput">Input with value</label>
+ </div>
+ <div class="form-floating mb-3">
+ <input type="email" readonly class="form-control-plaintext" id="floatingEmptyPlaintextInput1" placeholder="[email protected]">
+ <label for="floatingEmptyPlaintextInput1">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</label>
+ </div>
+ <div class="form-floating mb-3">
+ <input type="email" readonly class="form-control-plaintext" id="floatingPlaintextInput1" placeholder="[email protected]" value="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.">
+ <label for="floatingPlaintextInput1">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</label>
+ </div>
+ </div>
+
+ <div class="container-fluid bg-body" data-bs-theme="dark">
+ <div class="container py-5 d-flex flex-column gap-3">
+ <div class="form-floating">
+ <input type="email" class="form-control" id="floatingInput2" placeholder="[email protected]">
+ <label for="floatingInput2">Email address</label>
+ </div>
+ <div class="form-floating">
+ <input type="email" class="form-control" id="floatingInput3" placeholder="[email protected]">
+ <label for="floatingInput3">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</label>
+ </div>
+ <form class="form-floating">
+ <input type="email" class="form-control" id="floatingInputValue2" placeholder="[email protected]" value="[email protected]">
+ <label for="floatingInputValue2">Input with value</label>
+ </form>
+ <form class="form-floating">
+ <input type="email" class="form-control" id="floatingInputValue3" placeholder="[email protected]" value="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.">
+ <label for="floatingInputValue3">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</label>
+ </form>
+ <form class="form-floating">
+ <input type="email" class="form-control is-invalid" id="floatingInputInvalid4" placeholder="[email protected]" value="[email protected]">
+ <label for="floatingInputInvalid4">Invalid input</label>
+ </form>
+ <form class="form-floating">
+ <input type="email" class="form-control is-invalid" id="floatingInputInvalid5" placeholder="[email protected]" value="[email protected]">
+ <label for="floatingInputInvalid5">Invalid input</label>
+ </form>
+ <form class="form-floating">
+ <input type="email" class="form-control is-invalid" id="floatingInputInvalid6" placeholder="[email protected]">
+ <label for="floatingInputInvalid6">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</label>
+ </form>
+ <form class="form-floating">
+ <input type="email" class="form-control is-invalid" id="floatingInputInvalid7" placeholder="[email protected]" value="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.">
+ <label for="floatingInputInvalid7">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</label>
+ </form>
+ <div class="form-floating">
+ <textarea class="form-control" placeholder="Leave a comment here" id="floatingTextarea8"></textarea>
+ <label for="floatingTextarea8">Comments</label>
+ </div>
+ <div class="form-floating">
+ <textarea class="form-control" placeholder="Leave a comment here" id="floatingTextarea9"></textarea>
+ <label for="floatingTextarea9">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</label>
+ </div>
+ <div class="form-floating">
+ <textarea class="form-control" placeholder="Leave a comment here" id="floatingTextarea10" style="height: 50px"></textarea>
+ <label for="floatingTextarea10">Comments</label>
+ </div>
+ <div class="form-floating">
+ <textarea class="form-control" placeholder="Leave a comment here" id="floatingTextarea11" style="height: 50px"></textarea>
+ <label for="floatingTextarea11">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</label>
+ </div>
+ <div class="form-floating">
+ <textarea class="form-control" placeholder="Leave a comment here" id="floatingTextarea12" style="height: 50px">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</textarea>
+ <label for="floatingTextarea12">Comments</label>
+ </div>
+ <div class="form-floating">
+ <textarea class="form-control" placeholder="Leave a comment here" id="floatingTextarea13" style="height: 50px">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</textarea>
+ <label for="floatingTextarea13">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</label>
+ </div>
+ <div class="form-floating">
+ <textarea class="form-control is-invalid" placeholder="Leave a comment here" id="floatingTextarea14" style="height: 50px">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</textarea>
+ <label for="floatingTextarea14">Comments</label>
+ </div>
+ <div class="form-floating">
+ <textarea class="form-control is-invalid" placeholder="Leave a comment here" id="floatingTextarea15" style="height: 50px">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</textarea>
+ <label for="floatingTextarea15">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</label>
+ </div>
+ <div class="form-floating">
+ <select class="form-select" id="floatingSelect4" aria-label="Floating label select example">
+ <option selected>Open this select menu</option>
+ <option value="1">One</option>
+ <option value="2">Two</option>
+ <option value="3">Three</option>
+ </select>
+ <label for="floatingSelect4">Works with selects</label>
+ </div>
+ <div class="form-floating">
+ <select class="form-select is-invalid" id="floatingSelect5" aria-label="Floating label select example">
+ <option selected>Open this select menu</option>
+ <option value="1">One</option>
+ <option value="2">Two</option>
+ <option value="3">Three</option>
+ </select>
+ <label for="floatingSelect5">Works with selects</label>
+ </div>
+ <div class="form-floating">
+ <select class="form-select" id="floatingSelect6" aria-label="Floating label select example">
+ <option selected>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</option>
+ <option value="1">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</option>
+ <option value="2">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</option>
+ <option value="3">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</option>
+ </select>
+ <label for="floatingSelect6">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</label>
+ </div>
+ <div class="form-floating">
+ <select class="form-select is-invalid" id="floatingSelect7" aria-label="Floating label select example">
+ <option selected>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</option>
+ <option value="1">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</option>
+ <option value="2">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</option>
+ <option value="3">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</option>
+ </select>
+ <label for="floatingSelect7">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</label>
+ </div>
+ <div class="form-floating mb-3">
+ <input type="email" class="form-control" id="floatingInputDisabled2" placeholder="[email protected]" disabled>
+ <label for="floatingInputDisabled2">Email address</label>
+ </div>
+ <div class="form-floating mb-3">
+ <textarea class="form-control" placeholder="Leave a comment here" id="floatingTextareaDisabled4" disabled></textarea>
+ <label for="floatingTextareaDisabled4">Comments</label>
+ </div>
+ <div class="form-floating mb-3">
+ <textarea class="form-control" placeholder="Leave a comment here" id="floatingTextareaDisabled5" style="height: 50px" disabled>Disabled textarea with some text inside</textarea>
+ <label for="floatingTextareaDisabled5">Comments</label>
+ </div>
+ <div class="form-floating">
+ <select class="form-select" id="floatingSelectDisabled2" aria-label="Floating label disabled select example" disabled>
+ <option selected>Open this select menu</option>
+ <option value="1">One</option>
+ <option value="2">Two</option>
+ <option value="3">Three</option>
+ </select>
+ <label for="floatingSelectDisabled2">Works with selects</label>
+ </div>
+ <div class="form-floating mb-3">
+ <input type="email" class="form-control" id="floatingInputDisabled3" placeholder="[email protected]" disabled>
+ <label for="floatingInputDisabled3">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</label>
+ </div>
+ <div class="form-floating mb-3">
+ <textarea class="form-control" placeholder="Leave a comment here" id="floatingTextareaDisabled6" disabled></textarea>
+ <label for="floatingTextareaDisabled6">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</label>
+ </div>
+ <div class="form-floating mb-3">
+ <textarea class="form-control" placeholder="Leave a comment here" id="floatingTextareaDisabled7" style="height: 50px" disabled>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</textarea>
+ <label for="floatingTextareaDisabled7">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</label>
+ </div>
+ <div class="form-floating">
+ <select class="form-select" id="floatingSelectDisabled3" aria-label="Floating label disabled select example" disabled>
+ <option selected>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</option>
+ <option value="1">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</option>
+ <option value="2">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</option>
+ <option value="3">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</option>
+ </select>
+ <label for="floatingSelectDisabled3">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</label>
+ </div>
+ <div class="form-floating mb-3">
+ <input type="email" readonly class="form-control-plaintext" id="floatingEmptyPlaintextInput2" placeholder="[email protected]">
+ <label for="floatingEmptyPlaintextInput2">Empty input</label>
+ </div>
+ <div class="form-floating mb-3">
+ <input type="email" readonly class="form-control-plaintext" id="floatingPlaintextInput2" placeholder="[email protected]" value="[email protected]">
+ <label for="floatingPlaintextInput2">Input with value</label>
+ </div>
+ <div class="form-floating mb-3">
+ <input type="email" readonly class="form-control-plaintext" id="floatingEmptyPlaintextInput3" placeholder="[email protected]">
+ <label for="floatingEmptyPlaintextInput3">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</label>
+ </div>
+ <div class="form-floating mb-3">
+ <input type="email" readonly class="form-control-plaintext" id="floatingPlaintextInput3" placeholder="[email protected]" value="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.">
+ <label for="floatingPlaintextInput3">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</label>
+ </div>
+ </div>
+ </div>
+
+ <script src="../../../dist/js/bootstrap.bundle.js"></script>
+ </body>
+</html>
diff --git a/js/tests/visual/input.html b/js/tests/visual/input.html
new file mode 100644
index 000000000..1e5eec2d1
--- /dev/null
+++ b/js/tests/visual/input.html
@@ -0,0 +1,78 @@
+<!doctype html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <link href="../../../dist/css/bootstrap.min.css" rel="stylesheet">
+ <title>Form</title>
+ <style></style>
+ </head>
+ <body>
+ <div class="container">
+ <h1>Input <small>Bootstrap Visual Test</small></h1>
+
+ <h2>No layout</h2>
+
+ <div class="mb-3">
+ Text
+ <input class="form-control">
+ </div>
+ <div class="mb-3">
+ Email
+ <input type="email" class="form-control">
+ </div>
+ <div class="mb-3">
+ Number
+ <input type="number" class="form-control">
+ </div>
+ <div class="mb-3">
+ Date
+ <input type="date" class="form-control">
+ </div>
+
+ <h2>Flex</h2>
+
+ <div class="d-flex flex-wrap gap-3 mb-3">
+ <div>
+ Text
+ <input class="form-control">
+ </div>
+ <div>
+ Email
+ <input type="email" class="form-control">
+ </div>
+ <div>
+ Number
+ <input type="number" class="form-control">
+ </div>
+ <div>
+ Date
+ <input type="date" class="form-control">
+ </div>
+ </div>
+
+ <h2>Grid</h2>
+
+ <div class="row mb-3">
+ <div class="col">
+ Text
+ <input class="form-control">
+ </div>
+ <div class="col">
+ Email
+ <input type="email" class="form-control">
+ </div>
+ <div class="col">
+ Number
+ <input type="number" class="form-control">
+ </div>
+ <div class="col">
+ Date
+ <input type="date" class="form-control">
+ </div>
+ </div>
+ </div>
+
+ <script src="../../../dist/js/bootstrap.bundle.js"></script>
+ </body>
+</html>
diff --git a/js/tests/visual/modal.html b/js/tests/visual/modal.html
index 719e9cc10..efb5127b5 100644
--- a/js/tests/visual/modal.html
+++ b/js/tests/visual/modal.html
@@ -13,21 +13,25 @@
</style>
</head>
<body>
- <nav class="navbar navbar-full navbar-dark bg-dark">
- <button class="navbar-toggler hidden-lg-up" type="button" data-bs-toggle="collapse" data-bs-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation"></button>
- <div class="collapse navbar-expand-md" id="navbarResponsive">
- <a class="navbar-brand" href="#">This shouldn't jump!</a>
- <ul class="navbar-nav">
- <li class="nav-item">
- <a class="nav-link active" href="#" aria-current="page">Home</a>
- </li>
- <li class="nav-item">
- <a class="nav-link" href="#">Link</a>
- </li>
- <li class="nav-item">
- <a class="nav-link" href="#">Link</a>
- </li>
- </ul>
+ <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
+ <div class="container-fluid">
+ <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
+ <span class="navbar-toggler-icon"></span>
+ </button>
+ <div class="collapse navbar-collapse" id="navbarResponsive">
+ <a class="navbar-brand" href="#">This shouldn't jump!</a>
+ <ul class="navbar-nav">
+ <li class="nav-item">
+ <a class="nav-link active" href="#" aria-current="page">Home</a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="#">Link</a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="#">Link</a>
+ </li>
+ </ul>
+ </div>
</div>
</nav>
@@ -38,7 +42,7 @@
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
- <h4 class="modal-title" id="myModalLabel">Modal title</h4>
+ <h1 class="modal-title fs-4" id="myModalLabel">Modal title</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
@@ -123,7 +127,7 @@
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
- <h4 class="modal-title" id="firefoxModalLabel">Firefox Bug Test</h4>
+ <h1 class="modal-title fs-4" id="firefoxModalLabel">Firefox Bug Test</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
@@ -147,7 +151,7 @@
<div class="modal-dialog" style="transition-duration: inherit;">
<div class="modal-content">
<div class="modal-header">
- <h4 class="modal-title" id="slowModalLabel">Lorem slowly</h4>
+ <h1 class="modal-title fs-4" id="slowModalLabel">Lorem slowly</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
@@ -184,11 +188,11 @@
<br><br>
- <div class="bg-dark text-white p-2" id="tall" style="display: none;">
+ <div class="text-bg-dark p-2" id="tall" style="display: none;">
Tall body content to force the page to have a scrollbar.
</div>
- <button type="button" class="btn btn-secondary btn-lg" data-bs-toggle="modal" data-bs-target="&#x3C;div class=&#x22;modal fade the-bad&#x22; tabindex=&#x22;-1&#x22;&#x3E;&#x3C;div class=&#x22;modal-dialog&#x22;&#x3E;&#x3C;div class=&#x22;modal-content&#x22;&#x3E;&#x3C;div class=&#x22;modal-header&#x22;&#x3E;&#x3C;button type=&#x22;button&#x22; class=&#x22;btn-close&#x22; data-bs-dismiss=&#x22;modal&#x22; aria-label=&#x22;Close&#x22;&#x3E;&#x3C;span aria-hidden=&#x22;true&#x22;&#x3E;&#x26;times;&#x3C;/span&#x3E;&#x3C;/button&#x3E;&#x3C;h4 class=&#x22;modal-title&#x22;&#x3E;The Bad Modal&#x3C;/h4&#x3E;&#x3C;/div&#x3E;&#x3C;div class=&#x22;modal-body&#x22;&#x3E;This modal&#x27;s HTTML source code is declared inline, inside the data-bs-target attribute of it&#x27;s show-button&#x3C;/div&#x3E;&#x3C;/div&#x3E;&#x3C;/div&#x3E;&#x3C;/div&#x3E;">
+ <button type="button" class="btn btn-secondary btn-lg" data-bs-toggle="modal" data-bs-target="&#x3C;div class=&#x22;modal fade the-bad&#x22; tabindex=&#x22;-1&#x22;&#x3E;&#x3C;div class=&#x22;modal-dialog&#x22;&#x3E;&#x3C;div class=&#x22;modal-content&#x22;&#x3E;&#x3C;div class=&#x22;modal-header&#x22;&#x3E;&#x3C;button type=&#x22;button&#x22; class=&#x22;btn-close&#x22; data-bs-dismiss=&#x22;modal&#x22; aria-label=&#x22;Close&#x22;&#x3E;&#x3C;span aria-hidden=&#x22;true&#x22;&#x3E;&#x26;times;&#x3C;/span&#x3E;&#x3C;/button&#x3E;&#x3C;h1 class=&#x22;modal-title fs-4&#x22;&#x3E;The Bad Modal&#x3C;/h1&#x3E;&#x3C;/div&#x3E;&#x3C;div class=&#x22;modal-body&#x22;&#x3E;This modal&#x27;s HTTML source code is declared inline, inside the data-bs-target attribute of it&#x27;s show-button&#x3C;/div&#x3E;&#x3C;/div&#x3E;&#x3C;/div&#x3E;&#x3C;/div&#x3E;">
Modal with an XSS inside the data-bs-target
</button>
@@ -199,19 +203,12 @@
</button>
</div>
- <script src="../../../node_modules/@popperjs/core/dist/umd/popper.min.js"></script>
- <script src="../../dist/dom/event-handler.js"></script>
- <script src="../../dist/dom/selector-engine.js"></script>
- <script src="../../dist/dom/data.js"></script>
- <script src="../../dist/dom/manipulator.js"></script>
- <script src="../../dist/base-component.js"></script>
- <script src="../../dist/modal.js"></script>
- <script src="../../dist/collapse.js"></script>
- <script src="../../dist/tooltip.js"></script>
- <script src="../../dist/popover.js"></script>
+ <script src="../../../dist/js/bootstrap.bundle.js"></script>
<script>
- var ffBugTestResult = document.getElementById('ff-bug-test-result')
- var firefoxTestDone = false
+ /* global bootstrap: false */
+
+ const ffBugTestResult = document.getElementById('ff-bug-test-result')
+ const firefoxTestDone = false
function reportFirefoxTestResult(result) {
if (!firefoxTestDone) {
@@ -220,44 +217,34 @@
}
}
- var popoverElements = document.querySelectorAll('[data-bs-toggle="popover"]')
- for (const popoverEl of popoverElements) {
- new Popover(popoverEl)
- }
- var tooltipElements = document.querySelectorAll('[data-bs-toggle="tooltip"]')
- for (const tooltipEl of tooltipElements) {
- new Tooltip(tooltipEl)
- }
+ document.querySelectorAll('[data-bs-toggle="popover"]').forEach(popoverEl => new bootstrap.Popover(popoverEl))
- var tallToggle = document.getElementById('tall-toggle')
- var tall = document.getElementById('tall')
- tallToggle.addEventListener('click', function () {
- if (tall.style.display === 'none') {
- tall.style.display = 'block'
- } else {
- tall.style.display = 'none'
- }
+ document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(tooltipEl => new bootstrap.Tooltip(tooltipEl))
+
+ const tall = document.getElementById('tall')
+ document.getElementById('tall-toggle').addEventListener('click', () => {
+ tall.style.display = tall.style.display === 'none' ? 'block' : 'none'
})
- var ffBugInput = document.getElementById('ff-bug-input')
- var firefoxModal = document.getElementById('firefoxModal')
+ const ffBugInput = document.getElementById('ff-bug-input')
+ const firefoxModal = document.getElementById('firefoxModal')
function handlerClickFfBugInput() {
firefoxModal.addEventListener('focus', reportFirefoxTestResult.bind(false))
ffBugInput.addEventListener('focus', reportFirefoxTestResult.bind(true))
ffBugInput.removeEventListener('focus', handlerClickFfBugInput)
}
+
ffBugInput.addEventListener('focus', handlerClickFfBugInput)
- var btnPreventModal = document.getElementById('btnPreventModal')
- var modalFf = new Modal(firefoxModal)
+ const modalFf = new bootstrap.Modal(firefoxModal)
- btnPreventModal.addEventListener('click', function () {
- function shownFirefoxModal() {
+ document.getElementById('btnPreventModal').addEventListener('click', () => {
+ const shownFirefoxModal = () => {
modalFf.hide()
firefoxModal.removeEventListener('shown.bs.modal', hideFirefoxModal)
}
- function hideFirefoxModal(event) {
+ const hideFirefoxModal = event => {
event.preventDefault()
firefoxModal.removeEventListener('hide.bs.modal', hideFirefoxModal)
@@ -275,16 +262,16 @@
})
// Test transition duration
- var t0
- var t1
- var slowModal = document.getElementById('slowModal')
+ let t0
+ let t1
+ const slowModal = document.getElementById('slowModal')
- slowModal.addEventListener('shown.bs.modal', function () {
+ slowModal.addEventListener('shown.bs.modal', () => {
t1 = performance.now()
- console.log('transition-duration took ' + (t1 - t0) + 'ms.')
+ console.log(`transition-duration took ${t1 - t0}ms.`)
})
- slowModal.addEventListener('show.bs.modal', function () {
+ slowModal.addEventListener('show.bs.modal', () => {
t0 = performance.now()
})
</script>
diff --git a/js/tests/visual/popover.html b/js/tests/visual/popover.html
index 3503271ae..73edf998d 100644
--- a/js/tests/visual/popover.html
+++ b/js/tests/visual/popover.html
@@ -31,19 +31,11 @@
</button>
</div>
- <script src="../../../node_modules/@popperjs/core/dist/umd/popper.min.js"></script>
- <script src="../../dist/dom/event-handler.js"></script>
- <script src="../../dist/dom/selector-engine.js"></script>
- <script src="../../dist/dom/manipulator.js"></script>
- <script src="../../dist/dom/data.js"></script>
- <script src="../../dist/base-component.js"></script>
- <script src="../../dist/tooltip.js"></script>
- <script src="../../dist/popover.js"></script>
+ <script src="../../../dist/js/bootstrap.bundle.js"></script>
<script>
- var popoverElements = document.querySelectorAll('[data-bs-toggle="popover"]')
- for (const popoverEl of popoverElements) {
- new Popover(popoverEl)
- }
+ /* global bootstrap: false */
+
+ document.querySelectorAll('[data-bs-toggle="popover"]').forEach(popoverEl => new bootstrap.Popover(popoverEl))
</script>
</body>
</html>
diff --git a/js/tests/visual/scrollspy.html b/js/tests/visual/scrollspy.html
index 1e3215b6f..541028478 100644
--- a/js/tests/visual/scrollspy.html
+++ b/js/tests/visual/scrollspy.html
@@ -24,11 +24,12 @@
<a class="nav-link" href="#mdo">@mdo</a>
</li>
<li class="nav-item dropdown">
- <a class="nav-link dropdown-toggle" href="#" id="dropdown" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</a>
- <ul class="dropdown-menu" aria-labelledby="dropdown">
+ <a class="nav-link dropdown-toggle" href="#" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</a>
+ <ul class="dropdown-menu">
<li><a class="dropdown-item" href="#one">One</a></li>
<li><a class="dropdown-item" href="#two">Two</a></li>
<li><a class="dropdown-item" href="#three">Three</a></li>
+ <li><a class="dropdown-item" href="#présentation">Présentation</a></li>
</ul>
</li>
<li class="nav-item">
@@ -82,17 +83,18 @@
<p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>
<p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>
<hr>
+ <h2 id="présentation">Présentation</h2>
+ <p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>
+ <p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>
+ <p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>
+ <p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>
+ <p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>
+ <p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>
+ <hr>
<h2 id="final">Final section</h2>
<p>Ad leggings keytar, brunch id art party dolor labore.</p>
</div>
- <script src="../../dist/dom/data.js"></script>
- <script src="../../dist/dom/event-handler.js"></script>
- <script src="../../dist/dom/selector-engine.js"></script>
- <script src="../../dist/dom/manipulator.js"></script>
- <script src="../../dist/base-component.js"></script>
- <script src="../../dist/scrollspy.js"></script>
- <script src="../../dist/dropdown.js"></script>
- <script src="../../dist/collapse.js"></script>
+ <script src="../../../dist/js/bootstrap.bundle.js"></script>
</body>
</html>
diff --git a/js/tests/visual/tab.html b/js/tests/visual/tab.html
index 0ce8be03c..a424175b0 100644
--- a/js/tests/visual/tab.html
+++ b/js/tests/visual/tab.html
@@ -161,12 +161,12 @@
<h4>Tabs with nav and using links (with fade)</h4>
<nav>
- <div class="nav nav-pills" id="nav-tab" role="tablist">
+ <div class="nav nav-pills" id="nav-tab" role="tablist">
<a class="nav-link nav-item active" role="tab" data-bs-toggle="tab" href="#home5">Home</a>
<a class="nav-link nav-item" role="tab" data-bs-toggle="tab" href="#profile5">Profile</a>
<a class="nav-link nav-item" role="tab" data-bs-toggle="tab" href="#fat5">@fat</a>
<a class="nav-link nav-item" role="tab" data-bs-toggle="tab" href="#mdo5">@mdo</a>
- <a class="nav-link nav-item disabled" role="tab" href="#">Disabled</a>
+ <a class="nav-link nav-item disabled" role="tab" href="#" aria-disabled="true">Disabled</a>
</div>
</nav>
@@ -218,11 +218,6 @@
</div>
</div>
- <script src="../../dist/dom/event-handler.js"></script>
- <script src="../../dist/dom/selector-engine.js"></script>
- <script src="../../dist/dom/data.js"></script>
- <script src="../../dist/dom/manipulator.js"></script>
- <script src="../../dist/base-component.js"></script>
- <script src="../../dist/tab.js"></script>
+ <script src="../../../dist/js/bootstrap.bundle.js"></script>
</body>
</html>
diff --git a/js/tests/visual/toast.html b/js/tests/visual/toast.html
index f9e1741cf..e64fd1d88 100644
--- a/js/tests/visual/toast.html
+++ b/js/tests/visual/toast.html
@@ -41,7 +41,7 @@
<div class="toast-header">
<span class="d-block bg-primary rounded me-2" style="width: 20px; height: 20px;"></span>
<strong class="me-auto">Bootstrap</strong>
- <small class="text-muted">2 seconds ago</small>
+ <small class="text-body-secondary">2 seconds ago</small>
<button type="button" class="ms-2 mb-1 btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
<div class="toast-body">
@@ -50,32 +50,19 @@
</div>
</div>
- <script src="../../dist/dom/event-handler.js"></script>
- <script src="../../dist/dom/manipulator.js"></script>
- <script src="../../dist/dom/data.js"></script>
- <script src="../../dist/base-component.js"></script>
- <script src="../../dist/toast.js"></script>
+ <script src="../../../dist/js/bootstrap.bundle.js"></script>
<script>
- window.addEventListener('load', function () {
- var toastElements = document.querySelectorAll('.toast')
- for (const toastEl of toastElements) {
- new Toast(toastEl)
- }
+ /* global bootstrap: false */
- document.getElementById('btnShowToast').addEventListener('click', function () {
- var toastElements = document.querySelectorAll('.toast')
- for (const toastEl of toastElements) {
- var toast = Toast.getInstance(toastEl)
- toast.show()
- }
+ window.addEventListener('load', () => {
+ document.querySelectorAll('.toast').forEach(toastEl => new bootstrap.Toast(toastEl))
+
+ document.getElementById('btnShowToast').addEventListener('click', () => {
+ document.querySelectorAll('.toast').forEach(toastEl => bootstrap.Toast.getInstance(toastEl).show())
})
- document.getElementById('btnHideToast').addEventListener('click', function () {
- var toastElements = document.querySelectorAll('.toast')
- for (const toastEl of toastElements) {
- var toast = Toast.getInstance(toastEl)
- toast.hide()
- }
+ document.getElementById('btnHideToast').addEventListener('click', () => {
+ document.querySelectorAll('.toast').forEach(toastEl => bootstrap.Toast.getInstance(toastEl).hide())
})
})
</script>
diff --git a/js/tests/visual/tooltip.html b/js/tests/visual/tooltip.html
index 448841e58..09144b572 100644
--- a/js/tests/visual/tooltip.html
+++ b/js/tests/visual/tooltip.html
@@ -20,7 +20,7 @@
<div class="container">
<h1>Tooltip <small>Bootstrap Visual Test</small></h1>
- <p class="text-muted">Tight pants next level keffiyeh <a href="#" data-bs-toggle="tooltip" title="Default tooltip">you probably</a> haven't heard of them. Photo booth beard raw denim letterpress vegan messenger bag stumptown. Farm-to-table seitan, mcsweeney's fixie sustainable quinoa 8-bit american apparel <a href="#" data-bs-toggle="tooltip" title="Another tooltip">have a</a> terry richardson vinyl chambray. Beard stumptown, cardigans banh mi lomo thundercats. Tofu biodiesel williamsburg marfa, four loko mcsweeney's cleanse vegan chambray. A really ironic artisan <a href="#" data-bs-toggle="tooltip" title="Another one here too">whatever keytar</a>, scenester farm-to-table banksy Austin <a href="#" data-bs-toggle="tooltip" title="The last tip!">twitter handle</a> freegan cred raw denim single-origin coffee viral.</p>
+ <p class="text-body-secondary">Tight pants next level keffiyeh <a href="#" data-bs-toggle="tooltip" title="Default tooltip">you probably</a> haven't heard of them. Photo booth beard raw denim letterpress vegan messenger bag stumptown. Farm-to-table seitan, mcsweeney's fixie sustainable quinoa 8-bit american apparel <a href="#" data-bs-toggle="tooltip" title="Another tooltip">have a</a> terry richardson vinyl chambray. Beard stumptown, cardigans banh mi lomo thundercats. Tofu biodiesel williamsburg marfa, four loko mcsweeney's cleanse vegan chambray. A really ironic artisan <a href="#" data-bs-toggle="tooltip" title="Another one here too">whatever keytar</a>, scenester farm-to-table banksy Austin <a href="#" data-bs-toggle="tooltip" title="The last tip!">twitter handle</a> freegan cred raw denim single-origin coffee viral.</p>
<hr>
@@ -66,48 +66,73 @@
<div id="shadow" class="pt-5"></div>
</div>
<div id="customContainer"></div>
+
+ <div class="row mt-4 border-top">
+ <hr>
+ <div class="h4">Test Selector triggered tooltips</div>
+ <div id="wrapperTriggeredBySelector">
+ <div class="py-2 selectorButtonsBlock">
+ <button type="button" class="btn btn-secondary bs-dynamic-tooltip" title="random title">Using title</button>
+ <button type="button" class="btn btn-secondary bs-dynamic-tooltip" data-bs-title="random title">Using bs-title</button>
+ </div>
+
+ </div>
+ <div class="mt-3">
+ <button type="button" class="btn btn-primary" onclick="duplicateButtons()">Duplicate above two buttons</button>
+ </div>
+ </div>
</div>
- <script src="../../../node_modules/@popperjs/core/dist/umd/popper.min.js"></script>
- <script src="../../dist/dom/selector-engine.js"></script>
- <script src="../../dist/dom/event-handler.js"></script>
- <script src="../../dist/dom/manipulator.js"></script>
- <script src="../../dist/dom/data.js"></script>
- <script src="../../dist/base-component.js"></script>
- <script src="../../dist/tooltip.js"></script>
+ <script src="../../../dist/js/bootstrap.bundle.js"></script>
<script>
+ /* global bootstrap: false */
+
if (typeof document.body.attachShadow === 'function') {
- var shadowRoot = document.getElementById('shadow').attachShadow({ mode: 'open' })
+ const shadowRoot = document.getElementById('shadow').attachShadow({ mode: 'open' })
shadowRoot.innerHTML =
- '<button type="button" class="btn btn-secondary" data-bs-toggle="tooltip" data-bs-placement="top" title="Tooltip on top in a shadow dom">' +
+ '<button id="firstShadowTooltip" type="button" class="btn btn-secondary" data-bs-toggle="tooltip" data-bs-placement="top" title="Tooltip on top in a shadow dom">' +
' Tooltip on top in a shadow dom' +
'</button>' +
- '<button id="secondTooltip" type="button" class="btn btn-secondary" data-bs-toggle="tooltip" data-bs-placement="top" title="Tooltip on top in a shadow dom with container option">' +
+ '<button id="secondShadowTooltip" type="button" class="btn btn-secondary" data-bs-toggle="tooltip" data-bs-placement="top" title="Tooltip on top in a shadow dom with container option">' +
' Tooltip on top in a shadow dom' +
'</button>'
- var firstChildTooltip = new Tooltip(shadowRoot.firstChild)
- var secondChildTooltip = new Tooltip(shadowRoot.getElementById('secondTooltip'), {
+ new bootstrap.Tooltip(shadowRoot.firstChild)
+ new bootstrap.Tooltip(shadowRoot.getElementById('secondShadowTooltip'), {
container: shadowRoot
})
}
- var tooltipElements = document.querySelectorAll('[data-bs-toggle="tooltip"]')
- for (const tooltipEl of tooltipElements) {
- new Tooltip(tooltipEl)
- }
-
- var tooltipElement = document.getElementById('tooltipElement')
- var tooltipElementInstance = new Tooltip(tooltipElement, {
- container: document.getElementById('customContainer')
+ new bootstrap.Tooltip('#tooltipElement', {
+ container: '#customContainer'
})
- var target = document.getElementById('target')
- var targetTooltip = new Tooltip(target, {
- placement : 'top',
- trigger : 'manual'
+ const targetTooltip = new bootstrap.Tooltip('#target', {
+ placement: 'top',
+ trigger: 'manual'
})
targetTooltip.show()
+
+ document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(tooltipEl => new bootstrap.Tooltip(tooltipEl))
</script>
+
+ <script>
+ /* global bootstrap: false */
+
+ new bootstrap.Tooltip('#wrapperTriggeredBySelector', {
+ animation: false,
+ selector: '.bs-dynamic-tooltip'
+ })
+
+ /* eslint-disable no-unused-vars */
+ function duplicateButtons() {
+ const buttonsBlock = document.querySelector('.selectorButtonsBlock')// get first
+ const buttonsBlockClone = buttonsBlock.cloneNode(true)
+ buttonsBlockClone.innerHTML += new Date().toLocaleString()
+ document.querySelector('#wrapperTriggeredBySelector').append(buttonsBlockClone)
+ }
+ /* eslint-enable no-unused-vars */
+ </script>
+
</body>
</html>