aboutsummaryrefslogtreecommitdiff
path: root/js
diff options
context:
space:
mode:
authorXhmikosR <[email protected]>2022-11-22 09:51:29 +0200
committerGitHub <[email protected]>2022-11-22 09:51:29 +0200
commit8f6de23682cf08f0e70b9561f75642cbcca14250 (patch)
treef56a8abfaef937458b7ae272dd53e01795d73524 /js
parentcb021439c683d9805e2864c58095b92d405e9b11 (diff)
parentabdd3fef1fc742d2a21fc6f536cdad0ebb67aa3c (diff)
downloadbootstrap-8f6de23682cf08f0e70b9561f75642cbcca14250.tar.xz
bootstrap-8f6de23682cf08f0e70b9561f75642cbcca14250.zip
Merge branch 'main' into prepare-523
Diffstat (limited to 'js')
-rw-r--r--js/.eslintrc.json26
-rw-r--r--js/index.esm.js24
-rw-r--r--js/index.umd.js24
-rw-r--r--js/src/alert.js8
-rw-r--r--js/src/base-component.js8
-rw-r--r--js/src/button.js6
-rw-r--r--js/src/carousel.js15
-rw-r--r--js/src/collapse.js21
-rw-r--r--js/src/dom/event-handler.js12
-rw-r--r--js/src/dom/selector-engine.js51
-rw-r--r--js/src/dropdown.js13
-rw-r--r--js/src/modal.js18
-rw-r--r--js/src/offcanvas.js19
-rw-r--r--js/src/popover.js4
-rw-r--r--js/src/scrollspy.js8
-rw-r--r--js/src/tab.js14
-rw-r--r--js/src/toast.js8
-rw-r--r--js/src/tooltip.js26
-rw-r--r--js/src/util/backdrop.js6
-rw-r--r--js/src/util/component-functions.js7
-rw-r--r--js/src/util/config.js7
-rw-r--r--js/src/util/focustrap.js6
-rw-r--r--js/src/util/index.js66
-rw-r--r--js/src/util/scrollbar.js6
-rw-r--r--js/src/util/swipe.js6
-rw-r--r--js/src/util/template-factory.js10
-rw-r--r--js/tests/karma.conf.js2
-rw-r--r--js/tests/unit/collapse.spec.js12
-rw-r--r--js/tests/unit/dom/selector-engine.spec.js158
-rw-r--r--js/tests/unit/tab.spec.js55
-rw-r--r--js/tests/unit/util/config.spec.js2
-rw-r--r--js/tests/unit/util/index.spec.js132
-rw-r--r--js/tests/visual/button.html4
-rw-r--r--js/tests/visual/carousel.html2
-rw-r--r--js/tests/visual/modal.html2
35 files changed, 457 insertions, 331 deletions
diff --git a/js/.eslintrc.json b/js/.eslintrc.json
new file mode 100644
index 000000000..97ea9e043
--- /dev/null
+++ b/js/.eslintrc.json
@@ -0,0 +1,26 @@
+{
+ "extends": "../.eslintrc.json",
+ "env": {
+ "es2022": true
+ },
+ "parserOptions": {
+ "ecmaVersion": "latest",
+ "sourceType": "module"
+ },
+ "overrides": [
+ {
+ "files": [
+ "./*.js",
+ "./src/**/*.js"
+ ],
+ "rules": {
+ "import/extensions": [
+ 2,
+ {
+ "js": "always"
+ }
+ ]
+ }
+ }
+ ]
+}
diff --git a/js/index.esm.js b/js/index.esm.js
index b83764990..a3c9c84d4 100644
--- a/js/index.esm.js
+++ b/js/index.esm.js
@@ -5,15 +5,15 @@
* --------------------------------------------------------------------------
*/
-export { default as Alert } from './src/alert'
-export { default as Button } from './src/button'
-export { default as Carousel } from './src/carousel'
-export { default as Collapse } from './src/collapse'
-export { default as Dropdown } from './src/dropdown'
-export { default as Modal } from './src/modal'
-export { default as Offcanvas } from './src/offcanvas'
-export { default as Popover } from './src/popover'
-export { default as ScrollSpy } from './src/scrollspy'
-export { default as Tab } from './src/tab'
-export { default as Toast } from './src/toast'
-export { default as Tooltip } from './src/tooltip'
+export { default as Alert } from './src/alert.js'
+export { default as Button } from './src/button.js'
+export { default as Carousel } from './src/carousel.js'
+export { default as Collapse } from './src/collapse.js'
+export { default as Dropdown } from './src/dropdown.js'
+export { default as Modal } from './src/modal.js'
+export { default as Offcanvas } from './src/offcanvas.js'
+export { default as Popover } from './src/popover.js'
+export { default as ScrollSpy } from './src/scrollspy.js'
+export { default as Tab } from './src/tab.js'
+export { default as Toast } from './src/toast.js'
+export { default as Tooltip } from './src/tooltip.js'
diff --git a/js/index.umd.js b/js/index.umd.js
index 5abe8db53..a8113f3de 100644
--- a/js/index.umd.js
+++ b/js/index.umd.js
@@ -5,18 +5,18 @@
* --------------------------------------------------------------------------
*/
-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 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'
export default {
Alert,
diff --git a/js/src/alert.js b/js/src/alert.js
index 59de828e9..a58408b7a 100644
--- a/js/src/alert.js
+++ b/js/src/alert.js
@@ -5,10 +5,10 @@
* --------------------------------------------------------------------------
*/
-import { defineJQueryPlugin } from './util/index'
-import EventHandler from './dom/event-handler'
-import BaseComponent from './base-component'
-import { enableDismissTrigger } from './util/component-functions'
+import { defineJQueryPlugin } from './util/index.js'
+import EventHandler from './dom/event-handler.js'
+import BaseComponent from './base-component.js'
+import { enableDismissTrigger } from './util/component-functions.js'
/**
* Constants
diff --git a/js/src/base-component.js b/js/src/base-component.js
index 0c1a2592d..ea266cee8 100644
--- a/js/src/base-component.js
+++ b/js/src/base-component.js
@@ -5,10 +5,10 @@
* --------------------------------------------------------------------------
*/
-import Data from './dom/data'
-import { executeAfterTransition, getElement } from './util/index'
-import EventHandler from './dom/event-handler'
-import Config from './util/config'
+import Data from './dom/data.js'
+import { executeAfterTransition, getElement } from './util/index.js'
+import EventHandler from './dom/event-handler.js'
+import Config from './util/config.js'
/**
* Constants
diff --git a/js/src/button.js b/js/src/button.js
index 03e76041a..f3185cb89 100644
--- a/js/src/button.js
+++ b/js/src/button.js
@@ -5,9 +5,9 @@
* --------------------------------------------------------------------------
*/
-import { defineJQueryPlugin } from './util/index'
-import EventHandler from './dom/event-handler'
-import BaseComponent from './base-component'
+import { defineJQueryPlugin } from './util/index.js'
+import EventHandler from './dom/event-handler.js'
+import BaseComponent from './base-component.js'
/**
* Constants
diff --git a/js/src/carousel.js b/js/src/carousel.js
index 24bbe392a..d190075eb 100644
--- a/js/src/carousel.js
+++ b/js/src/carousel.js
@@ -7,18 +7,17 @@
import {
defineJQueryPlugin,
- getElementFromSelector,
getNextActiveElement,
isRTL,
isVisible,
reflow,
triggerTransitionEnd
-} from './util/index'
-import EventHandler from './dom/event-handler'
-import Manipulator from './dom/manipulator'
-import SelectorEngine from './dom/selector-engine'
-import Swipe from './util/swipe'
-import BaseComponent from './base-component'
+} from './util/index.js'
+import EventHandler from './dom/event-handler.js'
+import Manipulator from './dom/manipulator.js'
+import SelectorEngine from './dom/selector-engine.js'
+import Swipe from './util/swipe.js'
+import BaseComponent from './base-component.js'
/**
* Constants
@@ -431,7 +430,7 @@ class Carousel extends BaseComponent {
*/
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_SLIDE, function (event) {
- const target = getElementFromSelector(this)
+ const target = SelectorEngine.getElementFromSelector(this)
if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) {
return
diff --git a/js/src/collapse.js b/js/src/collapse.js
index 204d18081..16250c870 100644
--- a/js/src/collapse.js
+++ b/js/src/collapse.js
@@ -8,13 +8,11 @@
import {
defineJQueryPlugin,
getElement,
- getElementFromSelector,
- getSelectorFromElement,
reflow
-} from './util/index'
-import EventHandler from './dom/event-handler'
-import SelectorEngine from './dom/selector-engine'
-import BaseComponent from './base-component'
+} from './util/index.js'
+import EventHandler from './dom/event-handler.js'
+import SelectorEngine from './dom/selector-engine.js'
+import BaseComponent from './base-component.js'
/**
* Constants
@@ -68,7 +66,7 @@ class Collapse extends BaseComponent {
const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE)
for (const elem of toggleList) {
- const selector = getSelectorFromElement(elem)
+ const selector = SelectorEngine.getSelectorFromElement(elem)
const filterElement = SelectorEngine.find(selector)
.filter(foundElement => foundElement === this._element)
@@ -185,7 +183,7 @@ class Collapse extends BaseComponent {
this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW)
for (const trigger of this._triggerArray) {
- const element = getElementFromSelector(trigger)
+ const element = SelectorEngine.getElementFromSelector(trigger)
if (element && !this._isShown(element)) {
this._addAriaAndCollapsedClass([trigger], false)
@@ -229,7 +227,7 @@ class Collapse extends BaseComponent {
const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE)
for (const element of children) {
- const selected = getElementFromSelector(element)
+ const selected = SelectorEngine.getElementFromSelector(element)
if (selected) {
this._addAriaAndCollapsedClass([element], this._isShown(selected))
@@ -285,10 +283,7 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (
event.preventDefault()
}
- const selector = getSelectorFromElement(this)
- const selectorElements = SelectorEngine.find(selector)
-
- for (const element of selectorElements) {
+ for (const element of SelectorEngine.getMultipleElementsFromSelector(this)) {
Collapse.getOrCreateInstance(element, { toggle: false }).toggle()
}
})
diff --git a/js/src/dom/event-handler.js b/js/src/dom/event-handler.js
index 9876d7733..fde195e7c 100644
--- a/js/src/dom/event-handler.js
+++ b/js/src/dom/event-handler.js
@@ -5,7 +5,7 @@
* --------------------------------------------------------------------------
*/
-import { getjQuery } from '../util/index'
+import { getjQuery } from '../util/index.js'
/**
* Constants
@@ -198,9 +198,8 @@ function removeHandler(element, events, typeEvent, handler, delegationSelector)
function removeNamespacedHandlers(element, events, typeEvent, namespace) {
const storeElementEvent = events[typeEvent] || {}
- for (const handlerKey of Object.keys(storeElementEvent)) {
+ for (const [handlerKey, event] of Object.entries(storeElementEvent)) {
if (handlerKey.includes(namespace)) {
- const event = storeElementEvent[handlerKey]
removeHandler(element, events, typeEvent, event.callable, event.delegationSelector)
}
}
@@ -248,11 +247,10 @@ const EventHandler = {
}
}
- for (const keyHandlers of Object.keys(storeElementEvent)) {
+ for (const [keyHandlers, event] of Object.entries(storeElementEvent)) {
const handlerKey = keyHandlers.replace(stripUidRegex, '')
if (!inNamespace || originalTypeEvent.includes(handlerKey)) {
- const event = storeElementEvent[keyHandlers]
removeHandler(element, events, typeEvent, event.callable, event.delegationSelector)
}
}
@@ -300,8 +298,8 @@ const EventHandler = {
}
}
-function hydrateObj(obj, meta) {
- for (const [key, value] of Object.entries(meta || {})) {
+function hydrateObj(obj, meta = {}) {
+ for (const [key, value] of Object.entries(meta)) {
try {
obj[key] = value
} catch {
diff --git a/js/src/dom/selector-engine.js b/js/src/dom/selector-engine.js
index 1ba104f9e..da93a35be 100644
--- a/js/src/dom/selector-engine.js
+++ b/js/src/dom/selector-engine.js
@@ -5,11 +5,32 @@
* --------------------------------------------------------------------------
*/
-import { isDisabled, isVisible } from '../util/index'
+import { isDisabled, isVisible, parseSelector } from '../util/index.js'
-/**
- * Constants
- */
+const getSelector = element => {
+ let selector = element.getAttribute('data-bs-target')
+
+ if (!selector || selector === '#') {
+ let hrefAttribute = element.getAttribute('href')
+
+ // The only valid content that could double as a selector are IDs or classes,
+ // so everything starting with `#` or `.`. If a "real" URL is used as the selector,
+ // `document.querySelector` will rightfully complain it is invalid.
+ // See https://github.com/twbs/bootstrap/issues/32273
+ if (!hrefAttribute || (!hrefAttribute.includes('#') && !hrefAttribute.startsWith('.'))) {
+ return null
+ }
+
+ // Just in case some CMS puts out a full URL with the anchor appended
+ if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) {
+ hrefAttribute = `#${hrefAttribute.split('#')[1]}`
+ }
+
+ selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null
+ }
+
+ return parseSelector(selector)
+}
const SelectorEngine = {
find(selector, element = document.documentElement) {
@@ -77,6 +98,28 @@ const SelectorEngine = {
].map(selector => `${selector}:not([tabindex^="-"])`).join(',')
return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el))
+ },
+
+ getSelectorFromElement(element) {
+ const selector = getSelector(element)
+
+ if (selector) {
+ return SelectorEngine.findOne(selector) ? selector : null
+ }
+
+ return null
+ },
+
+ getElementFromSelector(element) {
+ const selector = getSelector(element)
+
+ return selector ? SelectorEngine.findOne(selector) : null
+ },
+
+ getMultipleElementsFromSelector(element) {
+ const selector = getSelector(element)
+
+ return selector ? SelectorEngine.find(selector) : []
}
}
diff --git a/js/src/dropdown.js b/js/src/dropdown.js
index 9596baa9a..c699598f7 100644
--- a/js/src/dropdown.js
+++ b/js/src/dropdown.js
@@ -8,6 +8,7 @@
import * as Popper from '@popperjs/core'
import {
defineJQueryPlugin,
+ execute,
getElement,
getNextActiveElement,
isDisabled,
@@ -15,11 +16,11 @@ import {
isRTL,
isVisible,
noop
-} from './util/index'
-import EventHandler from './dom/event-handler'
-import Manipulator from './dom/manipulator'
-import SelectorEngine from './dom/selector-engine'
-import BaseComponent from './base-component'
+} from './util/index.js'
+import EventHandler from './dom/event-handler.js'
+import Manipulator from './dom/manipulator.js'
+import SelectorEngine from './dom/selector-engine.js'
+import BaseComponent from './base-component.js'
/**
* Constants
@@ -319,7 +320,7 @@ class Dropdown extends BaseComponent {
return {
...defaultBsPopperConfig,
- ...(typeof this._config.popperConfig === 'function' ? this._config.popperConfig(defaultBsPopperConfig) : this._config.popperConfig)
+ ...execute(this._config.popperConfig, [defaultBsPopperConfig])
}
}
diff --git a/js/src/modal.js b/js/src/modal.js
index 26c7e8c93..da1bcdca3 100644
--- a/js/src/modal.js
+++ b/js/src/modal.js
@@ -5,14 +5,14 @@
* --------------------------------------------------------------------------
*/
-import { defineJQueryPlugin, getElementFromSelector, isRTL, isVisible, reflow } from './util/index'
-import EventHandler from './dom/event-handler'
-import SelectorEngine from './dom/selector-engine'
-import ScrollBarHelper from './util/scrollbar'
-import BaseComponent from './base-component'
-import Backdrop from './util/backdrop'
-import FocusTrap from './util/focustrap'
-import { enableDismissTrigger } from './util/component-functions'
+import { defineJQueryPlugin, isRTL, isVisible, reflow } from './util/index.js'
+import EventHandler from './dom/event-handler.js'
+import SelectorEngine from './dom/selector-engine.js'
+import ScrollBarHelper from './util/scrollbar.js'
+import BaseComponent from './base-component.js'
+import Backdrop from './util/backdrop.js'
+import FocusTrap from './util/focustrap.js'
+import { enableDismissTrigger } from './util/component-functions.js'
/**
* Constants
@@ -336,7 +336,7 @@ class Modal extends BaseComponent {
*/
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
- const target = getElementFromSelector(this)
+ const target = SelectorEngine.getElementFromSelector(this)
if (['A', 'AREA'].includes(this.tagName)) {
event.preventDefault()
diff --git a/js/src/offcanvas.js b/js/src/offcanvas.js
index 7dd06fd71..6dd1e720f 100644
--- a/js/src/offcanvas.js
+++ b/js/src/offcanvas.js
@@ -7,17 +7,16 @@
import {
defineJQueryPlugin,
- getElementFromSelector,
isDisabled,
isVisible
-} from './util/index'
-import ScrollBarHelper from './util/scrollbar'
-import EventHandler from './dom/event-handler'
-import BaseComponent from './base-component'
-import SelectorEngine from './dom/selector-engine'
-import Backdrop from './util/backdrop'
-import FocusTrap from './util/focustrap'
-import { enableDismissTrigger } from './util/component-functions'
+} from './util/index.js'
+import ScrollBarHelper from './util/scrollbar.js'
+import EventHandler from './dom/event-handler.js'
+import BaseComponent from './base-component.js'
+import SelectorEngine from './dom/selector-engine.js'
+import Backdrop from './util/backdrop.js'
+import FocusTrap from './util/focustrap.js'
+import { enableDismissTrigger } from './util/component-functions.js'
/**
* Constants
@@ -231,7 +230,7 @@ class Offcanvas extends BaseComponent {
*/
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
- const target = getElementFromSelector(this)
+ const target = SelectorEngine.getElementFromSelector(this)
if (['A', 'AREA'].includes(this.tagName)) {
event.preventDefault()
diff --git a/js/src/popover.js b/js/src/popover.js
index 1b09dd425..477545672 100644
--- a/js/src/popover.js
+++ b/js/src/popover.js
@@ -5,8 +5,8 @@
* --------------------------------------------------------------------------
*/
-import { defineJQueryPlugin } from './util/index'
-import Tooltip from './tooltip'
+import { defineJQueryPlugin } from './util/index.js'
+import Tooltip from './tooltip.js'
/**
* Constants
diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js
index 01aba997d..b2c8555c7 100644
--- a/js/src/scrollspy.js
+++ b/js/src/scrollspy.js
@@ -5,10 +5,10 @@
* --------------------------------------------------------------------------
*/
-import { defineJQueryPlugin, getElement, isDisabled, isVisible } from './util/index'
-import EventHandler from './dom/event-handler'
-import SelectorEngine from './dom/selector-engine'
-import BaseComponent from './base-component'
+import { defineJQueryPlugin, getElement, isDisabled, isVisible } from './util/index.js'
+import EventHandler from './dom/event-handler.js'
+import SelectorEngine from './dom/selector-engine.js'
+import BaseComponent from './base-component.js'
/**
* Constants
diff --git a/js/src/tab.js b/js/src/tab.js
index 8dc4644c9..efd0398ff 100644
--- a/js/src/tab.js
+++ b/js/src/tab.js
@@ -5,10 +5,10 @@
* --------------------------------------------------------------------------
*/
-import { defineJQueryPlugin, getElementFromSelector, getNextActiveElement, isDisabled } from './util/index'
-import EventHandler from './dom/event-handler'
-import SelectorEngine from './dom/selector-engine'
-import BaseComponent from './base-component'
+import { defineJQueryPlugin, getNextActiveElement, isDisabled } from './util/index.js'
+import EventHandler from './dom/event-handler.js'
+import SelectorEngine from './dom/selector-engine.js'
+import BaseComponent from './base-component.js'
/**
* Constants
@@ -106,7 +106,7 @@ class Tab extends BaseComponent {
element.classList.add(CLASS_NAME_ACTIVE)
- this._activate(getElementFromSelector(element)) // Search and activate/show the proper section
+ this._activate(SelectorEngine.getElementFromSelector(element)) // Search and activate/show the proper section
const complete = () => {
if (element.getAttribute('role') !== 'tab') {
@@ -133,7 +133,7 @@ class Tab extends BaseComponent {
element.classList.remove(CLASS_NAME_ACTIVE)
element.blur()
- this._deactivate(getElementFromSelector(element)) // Search and deactivate the shown section too
+ this._deactivate(SelectorEngine.getElementFromSelector(element)) // Search and deactivate the shown section too
const complete = () => {
if (element.getAttribute('role') !== 'tab') {
@@ -203,7 +203,7 @@ class Tab extends BaseComponent {
}
_setInitialAttributesOnTargetPanel(child) {
- const target = getElementFromSelector(child)
+ const target = SelectorEngine.getElementFromSelector(child)
if (!target) {
return
diff --git a/js/src/toast.js b/js/src/toast.js
index a7fe7751d..7c6c7c10b 100644
--- a/js/src/toast.js
+++ b/js/src/toast.js
@@ -5,10 +5,10 @@
* --------------------------------------------------------------------------
*/
-import { defineJQueryPlugin, reflow } from './util/index'
-import EventHandler from './dom/event-handler'
-import BaseComponent from './base-component'
-import { enableDismissTrigger } from './util/component-functions'
+import { defineJQueryPlugin, reflow } from './util/index.js'
+import EventHandler from './dom/event-handler.js'
+import BaseComponent from './base-component.js'
+import { enableDismissTrigger } from './util/component-functions.js'
/**
* Constants
diff --git a/js/src/tooltip.js b/js/src/tooltip.js
index 748a0e198..85c4a39c8 100644
--- a/js/src/tooltip.js
+++ b/js/src/tooltip.js
@@ -6,12 +6,12 @@
*/
import * as Popper from '@popperjs/core'
-import { defineJQueryPlugin, findShadowRoot, getElement, getUID, isRTL, noop } from './util/index'
-import { DefaultAllowlist } from './util/sanitizer'
-import EventHandler from './dom/event-handler'
-import Manipulator from './dom/manipulator'
-import BaseComponent from './base-component'
-import TemplateFactory from './util/template-factory'
+import { defineJQueryPlugin, execute, findShadowRoot, getElement, getUID, isRTL, noop } from './util/index.js'
+import { DefaultAllowlist } from './util/sanitizer.js'
+import EventHandler from './dom/event-handler.js'
+import Manipulator from './dom/manipulator.js'
+import BaseComponent from './base-component.js'
+import TemplateFactory from './util/template-factory.js'
/**
* Constants
@@ -370,9 +370,7 @@ class Tooltip extends BaseComponent {
}
_createPopper(tip) {
- const placement = typeof this._config.placement === 'function' ?
- this._config.placement.call(this, tip, this._element) :
- this._config.placement
+ const placement = execute(this._config.placement, [this, tip, this._element])
const attachment = AttachmentMap[placement.toUpperCase()]
return Popper.createPopper(this._element, tip, this._getPopperConfig(attachment))
}
@@ -392,7 +390,7 @@ class Tooltip extends BaseComponent {
}
_resolvePossibleFunction(arg) {
- return typeof arg === 'function' ? arg.call(this._element) : arg
+ return execute(arg, [this._element])
}
_getPopperConfig(attachment) {
@@ -438,7 +436,7 @@ class Tooltip extends BaseComponent {
return {
...defaultBsPopperConfig,
- ...(typeof this._config.popperConfig === 'function' ? this._config.popperConfig(defaultBsPopperConfig) : this._config.popperConfig)
+ ...execute(this._config.popperConfig, [defaultBsPopperConfig])
}
}
@@ -579,9 +577,9 @@ class Tooltip extends BaseComponent {
_getDelegateConfig() {
const config = {}
- for (const key in this._config) {
- if (this.constructor.Default[key] !== this._config[key]) {
- config[key] = this._config[key]
+ for (const [key, value] of Object.entries(this._config)) {
+ if (this.constructor.Default[key] !== value) {
+ config[key] = value
}
}
diff --git a/js/src/util/backdrop.js b/js/src/util/backdrop.js
index 78279e056..ee560b85e 100644
--- a/js/src/util/backdrop.js
+++ b/js/src/util/backdrop.js
@@ -5,9 +5,9 @@
* --------------------------------------------------------------------------
*/
-import EventHandler from '../dom/event-handler'
-import { execute, executeAfterTransition, getElement, reflow } from './index'
-import Config from './config'
+import EventHandler from '../dom/event-handler.js'
+import { execute, executeAfterTransition, getElement, reflow } from './index.js'
+import Config from './config.js'
/**
* Constants
diff --git a/js/src/util/component-functions.js b/js/src/util/component-functions.js
index c2f99cceb..15f2f8c52 100644
--- a/js/src/util/component-functions.js
+++ b/js/src/util/component-functions.js
@@ -5,8 +5,9 @@
* --------------------------------------------------------------------------
*/
-import EventHandler from '../dom/event-handler'
-import { getElementFromSelector, isDisabled } from './index'
+import EventHandler from '../dom/event-handler.js'
+import { isDisabled } from './index.js'
+import SelectorEngine from '../dom/selector-engine.js'
const enableDismissTrigger = (component, method = 'hide') => {
const clickEvent = `click.dismiss${component.EVENT_KEY}`
@@ -21,7 +22,7 @@ const enableDismissTrigger = (component, method = 'hide') => {
return
}
- const target = getElementFromSelector(this) || this.closest(`.${name}`)
+ const target = SelectorEngine.getElementFromSelector(this) || this.closest(`.${name}`)
const instance = component.getOrCreateInstance(target)
// Method argument is left, for Alert and only, as it doesn't implement the 'hide' method
diff --git a/js/src/util/config.js b/js/src/util/config.js
index 1205905f7..a37fe52a1 100644
--- a/js/src/util/config.js
+++ b/js/src/util/config.js
@@ -5,8 +5,8 @@
* --------------------------------------------------------------------------
*/
-import { isElement, toType } from './index'
-import Manipulator from '../dom/manipulator'
+import { isElement, toType } from './index.js'
+import Manipulator from '../dom/manipulator.js'
/**
* Class definition
@@ -49,8 +49,7 @@ class Config {
}
_typeCheckConfig(config, configTypes = this.constructor.DefaultType) {
- for (const property of Object.keys(configTypes)) {
- const expectedTypes = configTypes[property]
+ for (const [property, expectedTypes] of Object.entries(configTypes)) {
const value = config[property]
const valueType = isElement(value) ? 'element' : toType(value)
diff --git a/js/src/util/focustrap.js b/js/src/util/focustrap.js
index ef6916679..1736c4f7c 100644
--- a/js/src/util/focustrap.js
+++ b/js/src/util/focustrap.js
@@ -5,9 +5,9 @@
* --------------------------------------------------------------------------
*/
-import EventHandler from '../dom/event-handler'
-import SelectorEngine from '../dom/selector-engine'
-import Config from './config'
+import EventHandler from '../dom/event-handler.js'
+import SelectorEngine from '../dom/selector-engine.js'
+import Config from './config.js'
/**
* Constants
diff --git a/js/src/util/index.js b/js/src/util/index.js
index 297e57149..77de14d3a 100644
--- a/js/src/util/index.js
+++ b/js/src/util/index.js
@@ -9,6 +9,20 @@ const MAX_UID = 1_000_000
const MILLISECONDS_MULTIPLIER = 1000
const TRANSITION_END = 'transitionend'
+/**
+ * Properly escape IDs selectors to handle weird IDs
+ * @param {string} selector
+ * @returns {string}
+ */
+const parseSelector = selector => {
+ if (selector && window.CSS && window.CSS.escape) {
+ // document.querySelector needs escaping to handle IDs (html5+) containing for instance /
+ selector = selector.replace(/#([^\s"#']+)/g, (match, id) => `#${CSS.escape(id)}`)
+ }
+
+ return selector
+}
+
// Shout-out Angus Croll (https://goo.gl/pxwQGp)
const toType = object => {
if (object === null || object === undefined) {
@@ -30,47 +44,6 @@ const getUID = prefix => {
return prefix
}
-const getSelector = element => {
- let selector = element.getAttribute('data-bs-target')
-
- if (!selector || selector === '#') {
- let hrefAttribute = element.getAttribute('href')
-
- // The only valid content that could double as a selector are IDs or classes,
- // so everything starting with `#` or `.`. If a "real" URL is used as the selector,
- // `document.querySelector` will rightfully complain it is invalid.
- // See https://github.com/twbs/bootstrap/issues/32273
- if (!hrefAttribute || (!hrefAttribute.includes('#') && !hrefAttribute.startsWith('.'))) {
- return null
- }
-
- // Just in case some CMS puts out a full URL with the anchor appended
- if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) {
- hrefAttribute = `#${hrefAttribute.split('#')[1]}`
- }
-
- selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null
- }
-
- return selector
-}
-
-const getSelectorFromElement = element => {
- const selector = getSelector(element)
-
- if (selector) {
- return document.querySelector(selector) ? selector : null
- }
-
- return null
-}
-
-const getElementFromSelector = element => {
- const selector = getSelector(element)
-
- return selector ? document.querySelector(selector) : null
-}
-
const getTransitionDurationFromElement = element => {
if (!element) {
return 0
@@ -117,7 +90,7 @@ const getElement = object => {
}
if (typeof object === 'string' && object.length > 0) {
- return document.querySelector(object)
+ return document.querySelector(parseSelector(object))
}
return null
@@ -249,10 +222,8 @@ const defineJQueryPlugin = plugin => {
})
}
-const execute = callback => {
- if (typeof callback === 'function') {
- callback()
- }
+const execute = (possibleCallback, args = [], defaultValue = possibleCallback) => {
+ return typeof possibleCallback === 'function' ? possibleCallback(...args) : defaultValue
}
const executeAfterTransition = (callback, transitionElement, waitForTransition = true) => {
@@ -318,10 +289,8 @@ export {
executeAfterTransition,
findShadowRoot,
getElement,
- getElementFromSelector,
getjQuery,
getNextActiveElement,
- getSelectorFromElement,
getTransitionDurationFromElement,
getUID,
isDisabled,
@@ -330,6 +299,7 @@ export {
isVisible,
noop,
onDOMContentLoaded,
+ parseSelector,
reflow,
triggerTransitionEnd,
toType
diff --git a/js/src/util/scrollbar.js b/js/src/util/scrollbar.js
index 5cac7b6d1..f6bd9de31 100644
--- a/js/src/util/scrollbar.js
+++ b/js/src/util/scrollbar.js
@@ -5,9 +5,9 @@
* --------------------------------------------------------------------------
*/
-import SelectorEngine from '../dom/selector-engine'
-import Manipulator from '../dom/manipulator'
-import { isElement } from './index'
+import SelectorEngine from '../dom/selector-engine.js'
+import Manipulator from '../dom/manipulator.js'
+import { isElement } from './index.js'
/**
* Constants
diff --git a/js/src/util/swipe.js b/js/src/util/swipe.js
index 7126360ce..fe04a2437 100644
--- a/js/src/util/swipe.js
+++ b/js/src/util/swipe.js
@@ -5,9 +5,9 @@
* --------------------------------------------------------------------------
*/
-import Config from './config'
-import EventHandler from '../dom/event-handler'
-import { execute } from './index'
+import Config from './config.js'
+import EventHandler from '../dom/event-handler.js'
+import { execute } from './index.js'
/**
* Constants
diff --git a/js/src/util/template-factory.js b/js/src/util/template-factory.js
index cf402fa3b..5046f0e31 100644
--- a/js/src/util/template-factory.js
+++ b/js/src/util/template-factory.js
@@ -5,10 +5,10 @@
* --------------------------------------------------------------------------
*/
-import { DefaultAllowlist, sanitizeHtml } from './sanitizer'
-import { getElement, isElement } from '../util/index'
-import SelectorEngine from '../dom/selector-engine'
-import Config from './config'
+import { DefaultAllowlist, sanitizeHtml } from './sanitizer.js'
+import { execute, getElement, isElement } from './index.js'
+import SelectorEngine from '../dom/selector-engine.js'
+import Config from './config.js'
/**
* Constants
@@ -143,7 +143,7 @@ class TemplateFactory extends Config {
}
_resolvePossibleFunction(arg) {
- return typeof arg === 'function' ? arg(this) : arg
+ return execute(arg, [this])
}
_putElementInTemplate(element, templateElement) {
diff --git a/js/tests/karma.conf.js b/js/tests/karma.conf.js
index 6636ff15d..11c6f3045 100644
--- a/js/tests/karma.conf.js
+++ b/js/tests/karma.conf.js
@@ -105,7 +105,7 @@ if (BROWSERSTACK) {
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
}
diff --git a/js/tests/unit/collapse.spec.js b/js/tests/unit/collapse.spec.js
index 9c8671988..fa50ad361 100644
--- a/js/tests/unit/collapse.spec.js
+++ b/js/tests/unit/collapse.spec.js
@@ -278,14 +278,14 @@ describe('Collapse', () => {
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>',
+ ' <button data-bs-target="#parentContent" data-bs-toggle="collapse" 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>',
+ ' <button data-bs-target="#childContent1" data-bs-toggle="collapse" class="accordion-toggle">Child 1</button>',
' </div>',
' <div id="childContent1" class="accordion-collapse collapse" aria-labelledby="childHeader1" data-bs-parent="#childGroup">',
' <div>content</div>',
@@ -293,7 +293,7 @@ describe('Collapse', () => {
' </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>',
+ ' <button data-bs-target="#childContent2" data-bs-toggle="collapse" class="accordion-toggle">Child 2</button>',
' </div>',
' <div id="childContent2" class="accordion-collapse collapse" aria-labelledby="childHeader2" data-bs-parent="#childGroup">',
' <div>content</div>',
@@ -887,17 +887,17 @@ describe('Collapse', () => {
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="#test2"></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="test2" 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('#test2')
+ const target2 = fixtureEl.querySelector(`#${CSS.escape('0/my/id')}`)
const target2Shown = () => {
expect(trigger1).not.toHaveClass('collapsed')
diff --git a/js/tests/unit/dom/selector-engine.spec.js b/js/tests/unit/dom/selector-engine.spec.js
index 0245896c6..905e25bae 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 { clearFixture, getFixture } from '../../helpers/fixture'
describe('SelectorEngine', () => {
let fixtureEl
@@ -232,5 +232,159 @@ describe('SelectorEngine', () => {
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 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/tab.spec.js b/js/tests/unit/tab.spec.js
index e0c7d86a6..1ac5929e1 100644
--- a/js/tests/unit/tab.spec.js
+++ b/js/tests/unit/tab.spec.js
@@ -177,6 +177,43 @@ describe('Tab', () => {
})
})
+ it('should work with tab id being an int', done => {
+ fixtureEl.innerHTML = [
+ '<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('#trigger2')
+ const tab = new Tab(profileTriggerEl)
+
+ profileTriggerEl.addEventListener('shown.bs.tab', () => {
+ expect(fixtureEl.querySelector(`#${CSS.escape('2')}`)).toHaveClass('active')
+ done()
+ })
+
+ tab.show()
+ })
+
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>'
@@ -603,19 +640,19 @@ describe('Tab', () => {
'</div>'
].join('')
- const tabEl = fixtureEl.querySelector('#tab1')
+ const tabEl1 = fixtureEl.querySelector('#tab1')
const tabEl2 = fixtureEl.querySelector('#tab2')
const tabEl3 = fixtureEl.querySelector('#tab3')
const tabEl4 = fixtureEl.querySelector('#tab4')
- const tab = new Tab(tabEl)
+ const tab1 = new Tab(tabEl1)
const tab2 = new Tab(tabEl2)
const tab3 = new Tab(tabEl3)
const tab4 = new Tab(tabEl4)
- const spy1 = spyOn(tab, 'show').and.callThrough()
+ 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(tabEl, 'focus').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()
@@ -623,7 +660,7 @@ describe('Tab', () => {
const keydown = createEvent('keydown')
keydown.key = 'ArrowRight'
- tabEl.dispatchEvent(keydown)
+ tabEl1.dispatchEvent(keydown)
expect(spy1).not.toHaveBeenCalled()
expect(spy2).not.toHaveBeenCalled()
expect(spy3).not.toHaveBeenCalled()
@@ -644,19 +681,19 @@ describe('Tab', () => {
'</div>'
].join('')
- const tabEl = fixtureEl.querySelector('#tab1')
+ const tabEl1 = fixtureEl.querySelector('#tab1')
const tabEl2 = fixtureEl.querySelector('#tab2')
const tabEl3 = fixtureEl.querySelector('#tab3')
const tabEl4 = fixtureEl.querySelector('#tab4')
- const tab = new Tab(tabEl)
+ const tab1 = new Tab(tabEl1)
const tab2 = new Tab(tabEl2)
const tab3 = new Tab(tabEl3)
const tab4 = new Tab(tabEl4)
- const spy1 = spyOn(tab, 'show').and.callThrough()
+ 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(tabEl, 'focus').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()
diff --git a/js/tests/unit/util/config.spec.js b/js/tests/unit/util/config.spec.js
index e1693c0c1..0037e09d7 100644
--- a/js/tests/unit/util/config.spec.js
+++ b/js/tests/unit/util/config.spec.js
@@ -128,7 +128,7 @@ describe('Config', () => {
const obj = new DummyConfigClass()
expect(() => {
obj._typeCheckConfig(config)
- }).toThrowError(TypeError, obj.constructor.NAME.toUpperCase() + ': Option "parent" provided type "number" but expected type "(string|element)".')
+ }).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', () => {
diff --git a/js/tests/unit/util/index.spec.js b/js/tests/unit/util/index.spec.js
index 9f28ce0aa..202c72061 100644
--- a/js/tests/unit/util/index.spec.js
+++ b/js/tests/unit/util/index.spec.js
@@ -22,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>'
@@ -631,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, [4, 5])
+ expect(resultFoo).toBe(9)
+
+ const resultFoo1 = Util.execute(functionFoo, [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', () => {
diff --git a/js/tests/visual/button.html b/js/tests/visual/button.html
index 0c54934f0..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">
diff --git a/js/tests/visual/carousel.html b/js/tests/visual/carousel.html
index 153c86604..1b2de5291 100644
--- a/js/tests/visual/carousel.html
+++ b/js/tests/visual/carousel.html
@@ -55,7 +55,7 @@
// Test to show that transition-duration can be changed with css
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', () => {
t0 = performance.now()
diff --git a/js/tests/visual/modal.html b/js/tests/visual/modal.html
index b738d9e81..09d42333d 100644
--- a/js/tests/visual/modal.html
+++ b/js/tests/visual/modal.html
@@ -264,7 +264,7 @@
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', () => {