diff options
Diffstat (limited to 'js')
69 files changed, 876 insertions, 639 deletions
diff --git a/js/index.esm.js b/js/index.esm.js index b837649..155d9fb 100644 --- a/js/index.esm.js +++ b/js/index.esm.js @@ -1,19 +1,19 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): index.esm.js + * Bootstrap index.esm.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -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 5abe8db..a33df74 100644 --- a/js/index.umd.js +++ b/js/index.umd.js @@ -1,22 +1,22 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): index.umd.js + * Bootstrap index.umd.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -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 59de828..88232bc 100644 --- a/js/src/alert.js +++ b/js/src/alert.js @@ -1,14 +1,14 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): alert.js + * Bootstrap alert.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -import { defineJQueryPlugin } from './util/index' -import EventHandler from './dom/event-handler' -import BaseComponent from './base-component' -import { enableDismissTrigger } from './util/component-functions' +import BaseComponent from './base-component.js' +import EventHandler from './dom/event-handler.js' +import { enableDismissTrigger } from './util/component-functions.js' +import { defineJQueryPlugin } from './util/index.js' /** * Constants diff --git a/js/src/base-component.js b/js/src/base-component.js index 0c1a259..d13c7ab 100644 --- a/js/src/base-component.js +++ b/js/src/base-component.js @@ -1,20 +1,20 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): base-component.js + * Bootstrap base-component.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -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 EventHandler from './dom/event-handler.js' +import Config from './util/config.js' +import { executeAfterTransition, getElement } from './util/index.js' /** * Constants */ -const VERSION = '5.2.3' +const VERSION = '5.3.0' /** * Class definition diff --git a/js/src/button.js b/js/src/button.js index 03e7604..a797f50 100644 --- a/js/src/button.js +++ b/js/src/button.js @@ -1,13 +1,13 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): button.js + * Bootstrap button.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -import { defineJQueryPlugin } from './util/index' -import EventHandler from './dom/event-handler' -import BaseComponent from './base-component' +import BaseComponent from './base-component.js' +import EventHandler from './dom/event-handler.js' +import { defineJQueryPlugin } from './util/index.js' /** * Constants diff --git a/js/src/carousel.js b/js/src/carousel.js index 24bbe39..68d11a3 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -1,24 +1,23 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): carousel.js + * Bootstrap carousel.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ +import BaseComponent from './base-component.js' +import EventHandler from './dom/event-handler.js' +import Manipulator from './dom/manipulator.js' +import SelectorEngine from './dom/selector-engine.js' 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 Swipe from './util/swipe.js' /** * Constants @@ -330,7 +329,7 @@ class Carousel extends BaseComponent { if (!activeElement || !nextElement) { // Some weirdness is happening, so we bail - // todo: change tests that use empty divs to avoid this check + // TODO: change tests that use empty divs to avoid this check return } @@ -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 204d180..9f0c60c 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -1,20 +1,18 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): collapse.js + * Bootstrap collapse.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ +import BaseComponent from './base-component.js' +import EventHandler from './dom/event-handler.js' +import SelectorEngine from './dom/selector-engine.js' 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' /** * 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/data.js b/js/src/dom/data.js index 2c6a46e..407f67e 100644 --- a/js/src/dom/data.js +++ b/js/src/dom/data.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): dom/data.js + * Bootstrap dom/data.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/dom/event-handler.js b/js/src/dom/event-handler.js index 9876d77..561d875 100644 --- a/js/src/dom/event-handler.js +++ b/js/src/dom/event-handler.js @@ -1,11 +1,11 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): dom/event-handler.js + * Bootstrap dom/event-handler.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -import { getjQuery } from '../util/index' +import { getjQuery } from '../util/index.js' /** * Constants @@ -128,7 +128,7 @@ function findHandler(events, callable, delegationSelector = null) { function normalizeParameters(originalTypeEvent, handler, delegationFunction) { const isDelegated = typeof handler === 'string' - // todo: tooltip passes `false` instead of selector, so we need to check + // TODO: tooltip passes `false` instead of selector, so we need to check const callable = isDelegated ? delegationFunction : (handler || delegationFunction) let typeEvent = getTypeEvent(originalTypeEvent) @@ -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) } } @@ -281,8 +279,7 @@ const EventHandler = { defaultPrevented = jQueryEvent.isDefaultPrevented() } - let evt = new Event(event, { bubbles, cancelable: true }) - evt = hydrateObj(evt, args) + const evt = hydrateObj(new Event(event, { bubbles, cancelable: true }), args) if (defaultPrevented) { evt.preventDefault() @@ -300,8 +297,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/manipulator.js b/js/src/dom/manipulator.js index 38ecfe4..dd86a9f 100644 --- a/js/src/dom/manipulator.js +++ b/js/src/dom/manipulator.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): dom/manipulator.js + * Bootstrap dom/manipulator.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ diff --git a/js/src/dom/selector-engine.js b/js/src/dom/selector-engine.js index 1ba104f..3cecf6f 100644 --- a/js/src/dom/selector-engine.js +++ b/js/src/dom/selector-engine.js @@ -1,15 +1,36 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): dom/selector-engine.js + * Bootstrap dom/selector-engine.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -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 9596baa..af5fd16 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -1,13 +1,18 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): dropdown.js + * Bootstrap dropdown.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ import * as Popper from '@popperjs/core' +import BaseComponent from './base-component.js' +import EventHandler from './dom/event-handler.js' +import Manipulator from './dom/manipulator.js' +import SelectorEngine from './dom/selector-engine.js' import { defineJQueryPlugin, + execute, getElement, getNextActiveElement, isDisabled, @@ -15,11 +20,7 @@ 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' /** * Constants @@ -95,7 +96,7 @@ class Dropdown extends BaseComponent { this._popper = null this._parent = this._element.parentNode // dropdown wrapper - // todo: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.2/forms/input-group/ + // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/ this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] || SelectorEngine.prev(this._element, SELECTOR_MENU)[0] || SelectorEngine.findOne(SELECTOR_MENU, this._parent) @@ -310,7 +311,7 @@ class Dropdown extends BaseComponent { // Disable Popper if we have a static display or Dropdown is in Navbar if (this._inNavbar || this._config.display === 'static') { - Manipulator.setDataAttribute(this._menu, 'popper', 'static') // todo:v6 remove + Manipulator.setDataAttribute(this._menu, 'popper', 'static') // TODO: v6 remove defaultBsPopperConfig.modifiers = [{ name: 'applyStyles', enabled: false @@ -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]) } } @@ -408,7 +409,7 @@ class Dropdown extends BaseComponent { event.preventDefault() - // todo: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.2/forms/input-group/ + // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/ const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE) ? this : (SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0] || diff --git a/js/src/modal.js b/js/src/modal.js index 26c7e8c..b44cbb9 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -1,18 +1,18 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): modal.js + * Bootstrap modal.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -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 BaseComponent from './base-component.js' +import EventHandler from './dom/event-handler.js' +import SelectorEngine from './dom/selector-engine.js' +import Backdrop from './util/backdrop.js' +import { enableDismissTrigger } from './util/component-functions.js' +import FocusTrap from './util/focustrap.js' +import { defineJQueryPlugin, isRTL, isVisible, reflow } from './util/index.js' +import ScrollBarHelper from './util/scrollbar.js' /** * Constants @@ -139,12 +139,12 @@ class Modal extends BaseComponent { } dispose() { - for (const htmlElement of [window, this._dialog]) { - EventHandler.off(htmlElement, EVENT_KEY) - } + EventHandler.off(window, EVENT_KEY) + EventHandler.off(this._dialog, EVENT_KEY) this._backdrop.dispose() this._focustrap.deactivate() + super.dispose() } @@ -208,7 +208,6 @@ class Modal extends BaseComponent { } if (this._config.keyboard) { - event.preventDefault() this.hide() return } @@ -336,7 +335,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 7dd06fd..8d1feb1 100644 --- a/js/src/offcanvas.js +++ b/js/src/offcanvas.js @@ -1,23 +1,22 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): offcanvas.js + * Bootstrap offcanvas.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ +import BaseComponent from './base-component.js' +import EventHandler from './dom/event-handler.js' +import SelectorEngine from './dom/selector-engine.js' +import Backdrop from './util/backdrop.js' +import { enableDismissTrigger } from './util/component-functions.js' +import FocusTrap from './util/focustrap.js' 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' /** * Constants @@ -199,12 +198,12 @@ class Offcanvas extends BaseComponent { return } - if (!this._config.keyboard) { - EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED) + if (this._config.keyboard) { + this.hide() return } - this.hide() + EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED) }) } @@ -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 1b09dd4..612c521 100644 --- a/js/src/popover.js +++ b/js/src/popover.js @@ -1,12 +1,12 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): popover.js + * Bootstrap popover.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -import { defineJQueryPlugin } from './util/index' -import Tooltip from './tooltip' +import Tooltip from './tooltip.js' +import { defineJQueryPlugin } from './util/index.js' /** * Constants diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index 01aba99..69de715 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -1,14 +1,14 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): scrollspy.js + * Bootstrap scrollspy.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -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 BaseComponent from './base-component.js' +import EventHandler from './dom/event-handler.js' +import SelectorEngine from './dom/selector-engine.js' +import { defineJQueryPlugin, getElement, isDisabled, isVisible } from './util/index.js' /** * Constants @@ -208,11 +208,11 @@ class ScrollSpy extends BaseComponent { continue } - const observableSection = SelectorEngine.findOne(anchor.hash, this._element) + const observableSection = SelectorEngine.findOne(decodeURI(anchor.hash), this._element) // ensure that the observableSection exists & is visible if (isVisible(observableSection)) { - this._targetLinks.set(anchor.hash, anchor) + this._targetLinks.set(decodeURI(anchor.hash), anchor) this._observableSections.set(anchor.hash, observableSection) } } diff --git a/js/src/tab.js b/js/src/tab.js index 8dc4644..d9993d5 100644 --- a/js/src/tab.js +++ b/js/src/tab.js @@ -1,14 +1,14 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): tab.js + * Bootstrap tab.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -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 BaseComponent from './base-component.js' +import EventHandler from './dom/event-handler.js' +import SelectorEngine from './dom/selector-engine.js' +import { defineJQueryPlugin, getNextActiveElement, isDisabled } from './util/index.js' /** * Constants @@ -43,7 +43,7 @@ const NOT_SELECTOR_DROPDOWN_TOGGLE = ':not(.dropdown-toggle)' const SELECTOR_TAB_PANEL = '.list-group, .nav, [role="tablist"]' const SELECTOR_OUTER = '.nav-item, .list-group-item' const SELECTOR_INNER = `.nav-link${NOT_SELECTOR_DROPDOWN_TOGGLE}, .list-group-item${NOT_SELECTOR_DROPDOWN_TOGGLE}, [role="tab"]${NOT_SELECTOR_DROPDOWN_TOGGLE}` -const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]' // todo:v6: could be only `tab` +const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]' // TODO: could only be `tab` in v6 const SELECTOR_INNER_ELEM = `${SELECTOR_INNER}, ${SELECTOR_DATA_TOGGLE}` const SELECTOR_DATA_TOGGLE_ACTIVE = `.${CLASS_NAME_ACTIVE}[data-bs-toggle="tab"], .${CLASS_NAME_ACTIVE}[data-bs-toggle="pill"], .${CLASS_NAME_ACTIVE}[data-bs-toggle="list"]` @@ -59,7 +59,7 @@ class Tab extends BaseComponent { if (!this._parent) { return - // todo: should Throw exception on v6 + // TODO: should throw exception in v6 // throw new TypeError(`${element.outerHTML} has not a valid parent ${SELECTOR_INNER_ELEM}`) } @@ -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 @@ -212,7 +212,7 @@ class Tab extends BaseComponent { this._setAttributeIfNotExists(target, 'role', 'tabpanel') if (child.id) { - this._setAttributeIfNotExists(target, 'aria-labelledby', `#${child.id}`) + this._setAttributeIfNotExists(target, 'aria-labelledby', `${child.id}`) } } diff --git a/js/src/toast.js b/js/src/toast.js index a7fe775..d5d9c0e 100644 --- a/js/src/toast.js +++ b/js/src/toast.js @@ -1,14 +1,14 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): toast.js + * Bootstrap toast.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -import { defineJQueryPlugin, reflow } from './util/index' -import EventHandler from './dom/event-handler' -import BaseComponent from './base-component' -import { enableDismissTrigger } from './util/component-functions' +import BaseComponent from './base-component.js' +import EventHandler from './dom/event-handler.js' +import { enableDismissTrigger } from './util/component-functions.js' +import { defineJQueryPlugin, reflow } from './util/index.js' /** * Constants diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 748a0e1..1252811 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -1,17 +1,17 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): tooltip.js + * Bootstrap tooltip.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ 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 BaseComponent from './base-component.js' +import EventHandler from './dom/event-handler.js' +import Manipulator from './dom/manipulator.js' +import { defineJQueryPlugin, execute, findShadowRoot, getElement, getUID, isRTL, noop } from './util/index.js' +import { DefaultAllowlist } from './util/sanitizer.js' +import TemplateFactory from './util/template-factory.js' /** * Constants @@ -62,7 +62,7 @@ const Default = { delay: 0, fallbackPlacements: ['top', 'right', 'bottom', 'left'], html: false, - offset: [0, 0], + offset: [0, 6], placement: 'top', popperConfig: null, sanitize: true, @@ -197,7 +197,7 @@ class Tooltip extends BaseComponent { return } - // todo v6 remove this OR make it optional + // TODO: v6 remove this or make it optional this._disposePopper() const tip = this._getTipElement() @@ -302,13 +302,13 @@ class Tooltip extends BaseComponent { _createTipElement(content) { const tip = this._getTemplateFactory(content).toHtml() - // todo: remove this check on v6 + // TODO: remove this check in v6 if (!tip) { return null } tip.classList.remove(CLASS_NAME_FADE, CLASS_NAME_SHOW) - // todo: on v6 the following can be achieved with CSS only + // TODO: v6 the following can be achieved with CSS only tip.classList.add(`bs-${this.constructor.NAME}-auto`) const tipId = getUID(this.constructor.NAME).toString() @@ -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 78279e0..0d478e9 100644 --- a/js/src/util/backdrop.js +++ b/js/src/util/backdrop.js @@ -1,13 +1,13 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): util/backdrop.js + * Bootstrap util/backdrop.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -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 Config from './config.js' +import { execute, executeAfterTransition, getElement, reflow } from './index.js' /** * Constants diff --git a/js/src/util/component-functions.js b/js/src/util/component-functions.js index c2f99cc..4be828f 100644 --- a/js/src/util/component-functions.js +++ b/js/src/util/component-functions.js @@ -1,12 +1,13 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): util/component-functions.js + * Bootstrap util/component-functions.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -import EventHandler from '../dom/event-handler' -import { getElementFromSelector, isDisabled } from './index' +import EventHandler from '../dom/event-handler.js' +import SelectorEngine from '../dom/selector-engine.js' +import { isDisabled } from './index.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 1205905..a2b4bfb 100644 --- a/js/src/util/config.js +++ b/js/src/util/config.js @@ -1,12 +1,12 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): util/config.js + * Bootstrap util/config.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -import { isElement, toType } from './index' -import Manipulator from '../dom/manipulator' +import Manipulator from '../dom/manipulator.js' +import { isElement, toType } from './index.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 ef69166..158f3d1 100644 --- a/js/src/util/focustrap.js +++ b/js/src/util/focustrap.js @@ -1,13 +1,13 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): util/focustrap.js + * Bootstrap util/focustrap.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -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 297e571..68b8d89 100644 --- a/js/src/util/index.js +++ b/js/src/util/index.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): util/index.js + * Bootstrap util/index.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ @@ -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/sanitizer.js b/js/src/util/sanitizer.js index 5328691..d2b0808 100644 --- a/js/src/util/sanitizer.js +++ b/js/src/util/sanitizer.js @@ -1,53 +1,13 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): util/sanitizer.js + * Bootstrap util/sanitizer.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -const uriAttributes = new Set([ - 'background', - 'cite', - 'href', - 'itemtype', - 'longdesc', - 'poster', - 'src', - 'xlink:href' -]) - +// js-docs-start allow-list const ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i -/** - * A pattern that recognizes a commonly useful subset of URLs that are safe. - * - * Shout-out to Angular https://github.com/angular/angular/blob/12.2.x/packages/core/src/sanitization/url_sanitizer.ts - */ -const SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file|sms):|[^#&/:?]*(?:[#/?]|$))/i - -/** - * A pattern that matches safe data URLs. Only matches image, video and audio types. - * - * Shout-out to Angular https://github.com/angular/angular/blob/12.2.x/packages/core/src/sanitization/url_sanitizer.ts - */ -const DATA_URL_PATTERN = /^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[\d+/a-z]+=*$/i - -const allowedAttribute = (attribute, allowedAttributeList) => { - const attributeName = attribute.nodeName.toLowerCase() - - if (allowedAttributeList.includes(attributeName)) { - if (uriAttributes.has(attributeName)) { - return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue) || DATA_URL_PATTERN.test(attribute.nodeValue)) - } - - return true - } - - // Check if a regular expression validates the attribute. - return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp) - .some(regex => regex.test(attributeName)) -} - export const DefaultAllowlist = { // Global attributes allowed on any supplied element below. '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN], @@ -81,6 +41,43 @@ export const DefaultAllowlist = { u: [], ul: [] } +// js-docs-end allow-list + +const uriAttributes = new Set([ + 'background', + 'cite', + 'href', + 'itemtype', + 'longdesc', + 'poster', + 'src', + 'xlink:href' +]) + +/** + * A pattern that recognizes URLs that are safe wrt. XSS in URL navigation + * contexts. + * + * Shout-out to Angular https://github.com/angular/angular/blob/15.2.8/packages/core/src/sanitization/url_sanitizer.ts#L38 + */ +// eslint-disable-next-line unicorn/better-regex +const SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i + +const allowedAttribute = (attribute, allowedAttributeList) => { + const attributeName = attribute.nodeName.toLowerCase() + + if (allowedAttributeList.includes(attributeName)) { + if (uriAttributes.has(attributeName)) { + return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue)) + } + + return true + } + + // Check if a regular expression validates the attribute. + return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp) + .some(regex => regex.test(attributeName)) +} export function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) { if (!unsafeHtml.length) { @@ -100,7 +97,6 @@ export function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) { if (!Object.keys(allowList).includes(elementName)) { element.remove() - continue } diff --git a/js/src/util/scrollbar.js b/js/src/util/scrollbar.js index 5cac7b6..413f178 100644 --- a/js/src/util/scrollbar.js +++ b/js/src/util/scrollbar.js @@ -1,13 +1,13 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): util/scrollBar.js + * Bootstrap util/scrollBar.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -import SelectorEngine from '../dom/selector-engine' -import Manipulator from '../dom/manipulator' -import { isElement } from './index' +import Manipulator from '../dom/manipulator.js' +import SelectorEngine from '../dom/selector-engine.js' +import { isElement } from './index.js' /** * Constants diff --git a/js/src/util/swipe.js b/js/src/util/swipe.js index 7126360..d2f7087 100644 --- a/js/src/util/swipe.js +++ b/js/src/util/swipe.js @@ -1,13 +1,13 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): util/swipe.js + * Bootstrap util/swipe.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -import Config from './config' -import EventHandler from '../dom/event-handler' -import { execute } from './index' +import EventHandler from '../dom/event-handler.js' +import Config from './config.js' +import { execute } from './index.js' /** * Constants diff --git a/js/src/util/template-factory.js b/js/src/util/template-factory.js index cf402fa..f73589b 100644 --- a/js/src/util/template-factory.js +++ b/js/src/util/template-factory.js @@ -1,14 +1,14 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): util/template-factory.js + * Bootstrap util/template-factory.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -import { DefaultAllowlist, sanitizeHtml } from './sanitizer' -import { getElement, isElement } from '../util/index' -import SelectorEngine from '../dom/selector-engine' -import Config from './config' +import SelectorEngine from '../dom/selector-engine.js' +import Config from './config.js' +import { DefaultAllowlist, sanitizeHtml } from './sanitizer.js' +import { execute, getElement, isElement } from './index.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/browsers.js b/js/tests/browsers.js index 8adedc6..c515e64 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', diff --git a/js/tests/integration/bundle-modularity.js b/js/tests/integration/bundle-modularity.js index 8546141..3c1eec9 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 a8670ca..63d6515 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 caddcab..8b3c578 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 6636ff1..36bf7f2 100644 --- a/js/tests/karma.conf.js +++ b/js/tests/karma.conf.js @@ -1,5 +1,3 @@ -/* eslint-env node */ - 'use strict' const path = require('node:path') @@ -8,7 +6,7 @@ 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 } = require('./browsers') +const { browsers } = require('./browsers.js') const ENV = process.env const BROWSERSTACK = Boolean(ENV.BROWSERSTACK) @@ -105,7 +103,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/.eslintrc.json b/js/tests/unit/.eslintrc.json deleted file mode 100644 index 6362a1a..0000000 --- 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 d3740c9..97cc3cc 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 diff --git a/js/tests/unit/base-component.spec.js b/js/tests/unit/base-component.spec.js index b2352d6..5b7d52e 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) { diff --git a/js/tests/unit/button.spec.js b/js/tests/unit/button.spec.js index 09ed17e..6624fee 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 diff --git a/js/tests/unit/carousel.spec.js b/js/tests/unit/carousel.spec.js index d951bd5..c468b5c 100644 --- a/js/tests/unit/carousel.spec.js +++ b/js/tests/unit/carousel.spec.js @@ -1,8 +1,8 @@ -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 diff --git a/js/tests/unit/collapse.spec.js b/js/tests/unit/collapse.spec.js index 9c86719..58c5367 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 @@ -277,25 +277,25 @@ describe('Collapse', () => { return new Promise(resolve => { 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 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" aria-labelledby="parentHeader" data-bs-parent="#parentGroup">', + ' <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 id="childHeader1" class="accordion-header">', - ' <button data-bs-target="#childContent1" data-bs-toggle="collapse" role="button" class="accordion-toggle">Child 1</button>', + ' <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" aria-labelledby="childHeader1" data-bs-parent="#childGroup">', + ' <div id="childContent1" class="accordion-collapse collapse" 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 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" aria-labelledby="childHeader2" data-bs-parent="#childGroup">', + ' <div id="childContent2" class="accordion-collapse collapse" data-bs-parent="#childGroup">', ' <div>content</div>', ' </div>', ' </div>', @@ -338,12 +338,12 @@ describe('Collapse', () => { fixtureEl.innerHTML = [ '<div class="accordion" id="accordionExample">', ' <div class="accordion-item">', - ' <h2 class="accordion-header" id="headingOne">', + ' <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" aria-labelledby="headingOne" data-bs-parent="#accordionExample">', + ' <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">', @@ -640,11 +640,11 @@ describe('Collapse', () => { '<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 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" aria-labelledby="headingTwo" data-bs-parent="#accordion"></div>', + ' <div id="collapseTwo" class="collapse show" role="tabpanel" data-bs-parent="#accordion"></div>', ' </div>', '</div>' ].join('') @@ -699,13 +699,13 @@ describe('Collapse', () => { ' <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 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" aria-labelledby="headingTwo" data-bs-parent="#accordion"></div>', + ' <div id="collapseTwo" class="collapse show" role="tabpanel" data-bs-parent="#accordion"></div>', ' </div>', ' </div>', ' </div>', @@ -829,18 +829,18 @@ describe('Collapse', () => { '<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="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" aria-labelledby="headingThree"></div>', + ' <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" aria-labelledby="headingTwo"></div>', + ' <div id="collapseTwo" data-bs-parent="#accordion" class="collapse show" role="tabpanel"></div>', ' </div>', '</div>' ].join('') @@ -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/data.spec.js b/js/tests/unit/dom/data.spec.js index e898cbb..04e57a8 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' @@ -89,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') @@ -102,5 +101,4 @@ describe('Data', () => { expect(console.error).toHaveBeenCalled() 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 623b9c1..7f99c41 100644 --- a/js/tests/unit/dom/event-handler.spec.js +++ b/js/tests/unit/dom/event-handler.spec.js @@ -1,6 +1,6 @@ -import EventHandler from '../../../src/dom/event-handler' -import { clearFixture, getFixture } from '../../helpers/fixture' -import { noop } from '../../../src/util' +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 diff --git a/js/tests/unit/dom/manipulator.spec.js b/js/tests/unit/dom/manipulator.spec.js index 4561e2e..9d0be32 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 { clearFixture, getFixture } from '../../helpers/fixture' +import Manipulator from '../../../src/dom/manipulator.js' +import { clearFixture, getFixture } from '../../helpers/fixture.js' describe('Manipulator', () => { let fixtureEl diff --git a/js/tests/unit/dom/selector-engine.spec.js b/js/tests/unit/dom/selector-engine.spec.js index 0245896..8dd7b1f 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 @@ -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/dropdown.spec.js b/js/tests/unit/dropdown.spec.js index 2bbd7c0..8447be6 100644 --- a/js/tests/unit/dropdown.spec.js +++ b/js/tests/unit/dropdown.spec.js @@ -1,7 +1,7 @@ -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 @@ -75,6 +75,7 @@ describe('Dropdown', () => { resolve() }) + expect().nothing() dropdown.show() }) }) diff --git a/js/tests/unit/jquery.spec.js b/js/tests/unit/jquery.spec.js index 7da39d6..7d7f29d 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 { clearFixture, getFixture } 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 diff --git a/js/tests/unit/modal.spec.js b/js/tests/unit/modal.spec.js index fdee29e..6434d8b 100644 --- a/js/tests/unit/modal.spec.js +++ b/js/tests/unit/modal.spec.js @@ -1,7 +1,7 @@ -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 diff --git a/js/tests/unit/offcanvas.spec.js b/js/tests/unit/offcanvas.spec.js index da2fb97..03e7d9e 100644 --- a/js/tests/unit/offcanvas.spec.js +++ b/js/tests/unit/offcanvas.spec.js @@ -1,8 +1,8 @@ -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 diff --git a/js/tests/unit/popover.spec.js b/js/tests/unit/popover.spec.js index baf691c..53dc7d8 100644 --- a/js/tests/unit/popover.spec.js +++ b/js/tests/unit/popover.spec.js @@ -1,6 +1,6 @@ -import Popover from '../../src/popover' -import EventHandler from '../../src/dom/event-handler' -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 diff --git a/js/tests/unit/scrollspy.spec.js b/js/tests/unit/scrollspy.spec.js index c7951e6..ecbd952 100644 --- a/js/tests/unit/scrollspy.spec.js +++ b/js/tests/unit/scrollspy.spec.js @@ -1,8 +1,6 @@ -import ScrollSpy from '../../src/scrollspy' - -/** Test helpers */ -import { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture' -import EventHandler from '../../src/dom/event-handler' +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 @@ -942,5 +940,39 @@ describe('ScrollSpy', () => { }, 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 e0c7d86..84690fc 100644 --- a/js/tests/unit/tab.spec.js +++ b/js/tests/unit/tab.spec.js @@ -1,5 +1,5 @@ -import Tab from '../../src/tab' -import { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture' +import Tab from '../../src/tab.js' +import { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture.js' describe('Tab', () => { let fixtureEl @@ -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>' @@ -477,7 +514,7 @@ describe('Tab', () => { expect(tabPanel.hasAttribute('tabindex')).toBeFalse() expect(tabPanel.hasAttribute('tabindex2')).toBeFalse() - expect(tabPanel.getAttribute('aria-labelledby')).toEqual('#foo') + expect(tabPanel.getAttribute('aria-labelledby')).toEqual('foo') expect(tabPanel2.hasAttribute('aria-labelledby')).toBeFalse() }) }) @@ -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/toast.spec.js b/js/tests/unit/toast.spec.js index 42d2515..cfc56c7 100644 --- a/js/tests/unit/toast.spec.js +++ b/js/tests/unit/toast.spec.js @@ -1,5 +1,5 @@ -import Toast from '../../src/toast' -import { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture' +import Toast from '../../src/toast.js' +import { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture.js' describe('Toast', () => { let fixtureEl diff --git a/js/tests/unit/tooltip.spec.js b/js/tests/unit/tooltip.spec.js index 4330571..080432e 100644 --- a/js/tests/unit/tooltip.spec.js +++ b/js/tests/unit/tooltip.spec.js @@ -1,7 +1,7 @@ -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 @@ -56,7 +56,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') @@ -67,7 +67,7 @@ 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) @@ -76,7 +76,7 @@ describe('Tooltip', () => { }) 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, { @@ -98,7 +98,7 @@ describe('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') @@ -114,7 +114,7 @@ describe('Tooltip', () => { 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">' + 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') @@ -141,7 +141,7 @@ describe('Tooltip', () => { }) 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) @@ -150,7 +150,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, { @@ -165,7 +165,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' }) @@ -192,7 +192,7 @@ describe('Tooltip', () => { describe('enable', () => { it('should enable a tooltip', () => { return new Promise(resolve => { - 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) @@ -212,7 +212,7 @@ describe('Tooltip', () => { describe('disable', () => { it('should disable tooltip', () => { return new Promise((resolve, reject) => { - 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) @@ -235,7 +235,7 @@ describe('Tooltip', () => { 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) @@ -251,7 +251,7 @@ describe('Tooltip', () => { describe('toggle', () => { it('should do nothing if disabled', () => { return new Promise((resolve, reject) => { - 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) @@ -273,7 +273,7 @@ describe('Tooltip', () => { it('should show a tooltip', () => { return new Promise(resolve => { - 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) @@ -289,7 +289,7 @@ describe('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">' + fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>' const tooltipEl = fixtureEl.querySelector('a') const tooltip = new Tooltip(tooltipEl, { @@ -309,7 +309,7 @@ describe('Tooltip', () => { it('should hide a tooltip', () => { return new Promise(resolve => { - 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) @@ -329,7 +329,7 @@ describe('Tooltip', () => { 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">' + fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>' const tooltipEl = fixtureEl.querySelector('a') const tooltip = new Tooltip(tooltipEl, { @@ -354,7 +354,7 @@ describe('Tooltip', () => { 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() @@ -381,7 +381,7 @@ describe('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">' + fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>' const tooltipEl = fixtureEl.querySelector('a') const tooltip = new Tooltip(tooltipEl) @@ -402,7 +402,7 @@ describe('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">' + fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>' const tooltipEl = fixtureEl.querySelector('a') const tooltip = new Tooltip(tooltipEl) @@ -443,7 +443,7 @@ describe('Tooltip', () => { describe('show', () => { it('should show a tooltip', () => { return new Promise(resolve => { - 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) @@ -488,7 +488,7 @@ describe('Tooltip', () => { it('should show a tooltip on mobile', () => { return new Promise(resolve => { - 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) @@ -509,7 +509,7 @@ describe('Tooltip', () => { it('should show a tooltip relative to placement option', () => { return new Promise(resolve => { - 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, { @@ -532,7 +532,7 @@ describe('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">' + fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>' const tooltipEl = fixtureEl.querySelector('a') const tooltip = new Tooltip(tooltipEl) @@ -561,7 +561,7 @@ describe('Tooltip', () => { it('should show a tooltip with a dom element container', () => { return new Promise(resolve => { - 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, { @@ -579,7 +579,7 @@ describe('Tooltip', () => { it('should show a tooltip with a jquery element container', () => { return new Promise(resolve => { - 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, { @@ -600,7 +600,7 @@ describe('Tooltip', () => { it('should show a tooltip with a selector in container', () => { return new Promise(resolve => { - 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, { @@ -618,7 +618,7 @@ describe('Tooltip', () => { it('should show a tooltip with placement as a function', () => { return new Promise(resolve => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' + fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>' const spy = jasmine.createSpy('placement').and.returnValue('top') const tooltipEl = fixtureEl.querySelector('a') @@ -638,7 +638,7 @@ describe('Tooltip', () => { it('should show a tooltip without the animation', () => { return new Promise(resolve => { - 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, { @@ -658,7 +658,7 @@ describe('Tooltip', () => { }) 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) @@ -672,7 +672,7 @@ describe('Tooltip', () => { 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">' + fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>' const tooltipEl = fixtureEl.querySelector('a') const tooltip = new Tooltip(tooltipEl) @@ -699,7 +699,7 @@ describe('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">' + fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>' const tooltipEl = fixtureEl.querySelector('a') const tooltip = new Tooltip(tooltipEl, { @@ -723,7 +723,7 @@ describe('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">' + fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>' const tooltipEl = fixtureEl.querySelector('a') const tooltip = new Tooltip(tooltipEl, { @@ -845,7 +845,7 @@ describe('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">' + fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>' const tooltipEl = fixtureEl.querySelector('a') const tooltip = new Tooltip(tooltipEl) @@ -878,7 +878,7 @@ describe('Tooltip', () => { 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">' + 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) @@ -896,7 +896,7 @@ describe('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">' + fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>' const tooltipEl = fixtureEl.querySelector('a') const tooltip = new Tooltip(tooltipEl, { @@ -917,7 +917,7 @@ describe('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">' + fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>' const spy = jasmine.createSpy('customClass').and.returnValue('custom-class') const tooltipEl = fixtureEl.querySelector('a') @@ -956,7 +956,7 @@ describe('Tooltip', () => { describe('hide', () => { it('should hide a tooltip', () => { return new Promise(resolve => { - 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) @@ -974,7 +974,7 @@ describe('Tooltip', () => { it('should hide a tooltip on mobile', () => { return new Promise(resolve => { - 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) @@ -998,7 +998,7 @@ describe('Tooltip', () => { it('should hide a tooltip without animation', () => { return new Promise(resolve => { - 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, { @@ -1018,7 +1018,7 @@ describe('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">' + fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>' const assertDone = () => { setTimeout(() => { @@ -1063,7 +1063,7 @@ describe('Tooltip', () => { describe('update', () => { it('should call popper update', () => { return new Promise(resolve => { - 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) @@ -1082,7 +1082,7 @@ describe('Tooltip', () => { }) 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) @@ -1094,7 +1094,7 @@ describe('Tooltip', () => { 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) @@ -1103,7 +1103,7 @@ describe('Tooltip', () => { }) 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) @@ -1114,7 +1114,7 @@ describe('Tooltip', () => { 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) @@ -1126,7 +1126,7 @@ describe('Tooltip', () => { }) 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) @@ -1145,7 +1145,7 @@ describe('Tooltip', () => { 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 }) @@ -1160,7 +1160,7 @@ describe('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) @@ -1175,7 +1175,7 @@ describe('Tooltip', () => { }) 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) @@ -1190,7 +1190,7 @@ describe('Tooltip', () => { }) it('"setContent" should keep the initial template', () => { - 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) @@ -1207,7 +1207,7 @@ describe('Tooltip', () => { 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) diff --git a/js/tests/unit/util/backdrop.spec.js b/js/tests/unit/util/backdrop.spec.js index 73384fc..0faaac6 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' diff --git a/js/tests/unit/util/component-functions.spec.js b/js/tests/unit/util/component-functions.spec.js index ec36672..ce83785 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() { diff --git a/js/tests/unit/util/config.spec.js b/js/tests/unit/util/config.spec.js index e1693c0..93987a7 100644 --- a/js/tests/unit/util/config.spec.js +++ b/js/tests/unit/util/config.spec.js @@ -1,5 +1,5 @@ -import Config from '../../../src/util/config' -import { clearFixture, getFixture } from '../../helpers/fixture' +import Config from '../../../src/util/config.js' +import { clearFixture, getFixture } from '../../helpers/fixture.js' class DummyConfigClass extends Config { static get NAME() { @@ -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/focustrap.spec.js b/js/tests/unit/util/focustrap.spec.js index bedd124..0a20017 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, createEvent, getFixture } 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 diff --git a/js/tests/unit/util/index.spec.js b/js/tests/unit/util/index.spec.js index 9f28ce0..4065a91 100644 --- a/js/tests/unit/util/index.spec.js +++ b/js/tests/unit/util/index.spec.js @@ -1,6 +1,6 @@ -import * as Util from '../../../src/util/index' -import { clearFixture, getFixture } from '../../helpers/fixture' -import { noop } from '../../../src/util/index' +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 @@ -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/unit/util/sanitizer.spec.js b/js/tests/unit/util/sanitizer.spec.js index c656aed..2b21ef2 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:me@example.com', + 'MAILTO:me@example.com', + 'tel:123-123-1234', + 'TEL:123-123-1234', + 'sip:me@example.com', + 'SIP:me@example.com', + '#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', + 'javascript:', + 'javascript:', + 'j avascript:', + 'javascript:', + 'javascript:', + 'jav	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', () => { diff --git a/js/tests/unit/util/scrollbar.spec.js b/js/tests/unit/util/scrollbar.spec.js index 6fcf571..6dadfcd 100644 --- a/js/tests/unit/util/scrollbar.spec.js +++ b/js/tests/unit/util/scrollbar.spec.js @@ -1,6 +1,6 @@ -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 diff --git a/js/tests/unit/util/swipe.spec.js b/js/tests/unit/util/swipe.spec.js index f92bb5d..9252d31 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 diff --git a/js/tests/unit/util/template-factory.spec.js b/js/tests/unit/util/template-factory.spec.js index 5e5724c..07f4d91 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 diff --git a/js/tests/visual/.eslintrc.json b/js/tests/visual/.eslintrc.json deleted file mode 100644 index 8a33225..0000000 --- a/js/tests/visual/.eslintrc.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "plugins": [ - "html" - ], - "extends": "../../../.eslintrc.json", - "parserOptions": { - "sourceType": "module" - }, - "settings": { - "html/html-extensions": [ - ".html" - ] - }, - "rules": { - "no-console": "off", - "no-new": "off", - "unicorn/no-array-for-each": "off" - } -} diff --git a/js/tests/visual/button.html b/js/tests/visual/button.html index 0c54934..47c5088 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 153c866..1b2de52 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/input.html b/js/tests/visual/input.html new file mode 100644 index 0000000..1e5eec2 --- /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 b738d9e..09d4233 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', () => { diff --git a/js/tests/visual/scrollspy.html b/js/tests/visual/scrollspy.html index 2daa7ab..5410284 100644 --- a/js/tests/visual/scrollspy.html +++ b/js/tests/visual/scrollspy.html @@ -29,6 +29,7 @@ <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,6 +83,14 @@ <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> diff --git a/js/tests/visual/toast.html b/js/tests/visual/toast.html index f86926d..e64fd1d 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"> diff --git a/js/tests/visual/tooltip.html b/js/tests/visual/tooltip.html index 1a3b9f2..09144b5 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> |