2
0
mirror of https://github.com/tenrok/bootstrap.git synced 2026-06-23 20:40:36 +03:00

Merge branch 'main' into main-fod-table-separator

This commit is contained in:
Gaël Poupard
2021-12-08 15:59:17 +01:00
committed by GitHub
104 changed files with 4154 additions and 3847 deletions
+1 -1
View File
@@ -46,7 +46,7 @@
}, },
{ {
"path": "./dist/js/bootstrap.esm.min.js", "path": "./dist/js/bootstrap.esm.min.js",
"maxSize": "18.25 kB" "maxSize": "18.5 kB"
}, },
{ {
"path": "./dist/js/bootstrap.js", "path": "./dist/js/bootstrap.js",
+1 -1
View File
@@ -112,7 +112,7 @@
"Woohoo", "Woohoo",
"zindex", "zindex",
"بالعالم", "بالعالم",
"مرحبا" "مرحبًا"
], ],
"language": "en,en-US", "language": "en,en-US",
"ignorePaths": [ "ignorePaths": [
+1
View File
@@ -2,6 +2,7 @@ name: BrowserStack
on: on:
push: push:
workflow_dispatch:
env: env:
FORCE_COLOR: 2 FORCE_COLOR: 2
+1
View File
@@ -5,6 +5,7 @@ on:
branches-ignore: branches-ignore:
- "dependabot/**" - "dependabot/**"
pull_request: pull_request:
workflow_dispatch:
env: env:
FORCE_COLOR: 2 FORCE_COLOR: 2
+1
View File
@@ -14,6 +14,7 @@ on:
- "!dependabot/**" - "!dependabot/**"
schedule: schedule:
- cron: "0 2 * * 5" - cron: "0 2 * * 5"
workflow_dispatch:
jobs: jobs:
analyze: analyze:
+1
View File
@@ -5,6 +5,7 @@ on:
branches-ignore: branches-ignore:
- "dependabot/**" - "dependabot/**"
pull_request: pull_request:
workflow_dispatch:
env: env:
FORCE_COLOR: 2 FORCE_COLOR: 2
+1
View File
@@ -5,6 +5,7 @@ on:
branches-ignore: branches-ignore:
- "dependabot/**" - "dependabot/**"
pull_request: pull_request:
workflow_dispatch:
env: env:
FORCE_COLOR: 2 FORCE_COLOR: 2
+1
View File
@@ -5,6 +5,7 @@ on:
branches-ignore: branches-ignore:
- "dependabot/**" - "dependabot/**"
pull_request: pull_request:
workflow_dispatch:
env: env:
FORCE_COLOR: 2 FORCE_COLOR: 2
+1
View File
@@ -5,6 +5,7 @@ on:
branches-ignore: branches-ignore:
- "dependabot/**" - "dependabot/**"
pull_request: pull_request:
workflow_dispatch:
env: env:
FORCE_COLOR: 2 FORCE_COLOR: 2
+1
View File
@@ -5,6 +5,7 @@ on:
branches-ignore: branches-ignore:
- "dependabot/**" - "dependabot/**"
pull_request: pull_request:
workflow_dispatch:
env: env:
FORCE_COLOR: 2 FORCE_COLOR: 2
+1
View File
@@ -4,6 +4,7 @@ on:
push: push:
branches: branches:
- main - main
workflow_dispatch:
jobs: jobs:
update_release_draft: update_release_draft:
+2 -1
View File
@@ -1,7 +1,8 @@
# Ignore docs files # Ignore docs files
/_site/ /_site/
# Hugo resources folder # Hugo files
/resources/ /resources/
/.hugo_build.lock
# Numerous always-ignore extensions # Numerous always-ignore extensions
*.diff *.diff
+4 -3
View File
@@ -65,8 +65,6 @@ Read the [Getting started page](https://getbootstrap.com/docs/5.1/getting-starte
[![Meteor Atmosphere](https://img.shields.io/badge/meteor-twbs%3Abootstrap-blue)](https://atmospherejs.com/twbs/bootstrap) [![Meteor Atmosphere](https://img.shields.io/badge/meteor-twbs%3Abootstrap-blue)](https://atmospherejs.com/twbs/bootstrap)
[![Packagist Prerelease](https://img.shields.io/packagist/vpre/twbs/bootstrap)](https://packagist.org/packages/twbs/bootstrap) [![Packagist Prerelease](https://img.shields.io/packagist/vpre/twbs/bootstrap)](https://packagist.org/packages/twbs/bootstrap)
[![NuGet](https://img.shields.io/nuget/vpre/bootstrap)](https://www.nuget.org/packages/bootstrap/absoluteLatest) [![NuGet](https://img.shields.io/nuget/vpre/bootstrap)](https://www.nuget.org/packages/bootstrap/absoluteLatest)
[![peerDependencies Status](https://img.shields.io/david/peer/twbs/bootstrap)](https://david-dm.org/twbs/bootstrap?type=peer)
[![devDependency Status](https://img.shields.io/david/dev/twbs/bootstrap)](https://david-dm.org/twbs/bootstrap?type=dev)
[![Coverage Status](https://img.shields.io/coveralls/github/twbs/bootstrap/main)](https://coveralls.io/github/twbs/bootstrap?branch=main) [![Coverage Status](https://img.shields.io/coveralls/github/twbs/bootstrap/main)](https://coveralls.io/github/twbs/bootstrap?branch=main)
[![CSS gzip size](https://img.badgesize.io/twbs/bootstrap/main/dist/css/bootstrap.min.css?compression=gzip&label=CSS%20gzip%20size)](https://github.com/twbs/bootstrap/blob/main/dist/css/bootstrap.min.css) [![CSS gzip size](https://img.badgesize.io/twbs/bootstrap/main/dist/css/bootstrap.min.css?compression=gzip&label=CSS%20gzip%20size)](https://github.com/twbs/bootstrap/blob/main/dist/css/bootstrap.min.css)
[![CSS Brotli size](https://img.badgesize.io/twbs/bootstrap/main/dist/css/bootstrap.min.css?compression=brotli&label=CSS%20Brotli%20size)](https://github.com/twbs/bootstrap/blob/main/dist/css/bootstrap.min.css) [![CSS Brotli size](https://img.badgesize.io/twbs/bootstrap/main/dist/css/bootstrap.min.css?compression=brotli&label=CSS%20Brotli%20size)](https://github.com/twbs/bootstrap/blob/main/dist/css/bootstrap.min.css)
@@ -79,7 +77,9 @@ Read the [Getting started page](https://getbootstrap.com/docs/5.1/getting-starte
## What's included ## What's included
Within the download you'll find the following directories and files, logically grouping common assets and providing both compiled and minified variations. You'll see something like this: Within the download you'll find the following directories and files, logically grouping common assets and providing both compiled and minified variations.
<details><summary>Download contents</summary>
```text ```text
bootstrap/ bootstrap/
@@ -130,6 +130,7 @@ bootstrap/
├── bootstrap.min.js ├── bootstrap.min.js
└── bootstrap.min.js.map └── bootstrap.min.js.map
``` ```
</details>
We provide compiled CSS and JS (`bootstrap.*`), as well as compiled and minified CSS and JS (`bootstrap.min.*`). [Source maps](https://developers.google.com/web/tools/chrome-devtools/javascript/source-maps) (`bootstrap.*.map`) are available for use with certain browsers' developer tools. Bundled JS files (`bootstrap.bundle.js` and minified `bootstrap.bundle.min.js`) include [Popper](https://popper.js.org/). We provide compiled CSS and JS (`bootstrap.*`), as well as compiled and minified CSS and JS (`bootstrap.min.*`). [Source maps](https://developers.google.com/web/tools/chrome-devtools/javascript/source-maps) (`bootstrap.*.map`) are available for use with certain browsers' developer tools. Bundled JS files (`bootstrap.bundle.js` and minified `bootstrap.bundle.min.js`) include [Popper](https://popper.js.org/).
+59 -138
View File
@@ -11,173 +11,94 @@
const path = require('path') const path = require('path')
const rollup = require('rollup') const rollup = require('rollup')
const glob = require('glob')
const { babel } = require('@rollup/plugin-babel') const { babel } = require('@rollup/plugin-babel')
const banner = require('./banner.js') const banner = require('./banner.js')
const rootPath = path.resolve(__dirname, '../js/dist/') const srcPath = path.resolve(__dirname, '../js/src/')
const plugins = [ const jsFiles = glob.sync(srcPath + '/**/*.js')
// Array which holds the resolved plugins
const resolvedPlugins = []
// Trims the "js" extension and uppercases => first letter, hyphens, backslashes & slashes
const filenameToEntity = filename => filename.replace('.js', '')
.replace(/(?:^|-|\/|\\)[a-z]/g, str => str.slice(-1).toUpperCase())
for (const file of jsFiles) {
resolvedPlugins.push({
src: file.replace('.js', ''),
dist: file.replace('src', 'dist'),
fileName: path.basename(file),
className: filenameToEntity(path.basename(file))
// safeClassName: filenameToEntity(path.relative(srcPath, file))
})
}
const build = async plugin => {
const globals = {}
const bundle = await rollup.rollup({
input: plugin.src,
plugins: [
babel({ babel({
// Only transpile our source code // Only transpile our source code
exclude: 'node_modules/**', exclude: 'node_modules/**',
// Include the helpers in each file, at most one copy of each // Include the helpers in each file, at most one copy of each
babelHelpers: 'bundled' babelHelpers: 'bundled'
}) })
]
const bsPlugins = {
Data: path.resolve(__dirname, '../js/src/dom/data.js'),
EventHandler: path.resolve(__dirname, '../js/src/dom/event-handler.js'),
Manipulator: path.resolve(__dirname, '../js/src/dom/manipulator.js'),
SelectorEngine: path.resolve(__dirname, '../js/src/dom/selector-engine.js'),
Alert: path.resolve(__dirname, '../js/src/alert.js'),
Base: path.resolve(__dirname, '../js/src/base-component.js'),
Button: path.resolve(__dirname, '../js/src/button.js'),
Carousel: path.resolve(__dirname, '../js/src/carousel.js'),
Collapse: path.resolve(__dirname, '../js/src/collapse.js'),
Dropdown: path.resolve(__dirname, '../js/src/dropdown.js'),
Modal: path.resolve(__dirname, '../js/src/modal.js'),
Offcanvas: path.resolve(__dirname, '../js/src/offcanvas.js'),
Popover: path.resolve(__dirname, '../js/src/popover.js'),
ScrollSpy: path.resolve(__dirname, '../js/src/scrollspy.js'),
Tab: path.resolve(__dirname, '../js/src/tab.js'),
Toast: path.resolve(__dirname, '../js/src/toast.js'),
Tooltip: path.resolve(__dirname, '../js/src/tooltip.js')
}
const defaultPluginConfig = {
external: [
bsPlugins.Data,
bsPlugins.Base,
bsPlugins.EventHandler,
bsPlugins.SelectorEngine
], ],
globals: { external: source => {
[bsPlugins.Data]: 'Data', // Pattern to identify local files
[bsPlugins.Base]: 'Base', const pattern = /^(\.{1,2})\//
[bsPlugins.EventHandler]: 'EventHandler',
[bsPlugins.SelectorEngine]: 'SelectorEngine'
}
}
const getConfigByPluginKey = pluginKey => { // It's not a local file, e.g a Node.js package
switch (pluginKey) { if (!pattern.test(source)) {
case 'Alert': globals[source] = source
case 'Offcanvas': return true
case 'Tab':
return defaultPluginConfig
case 'Base':
case 'Button':
case 'Carousel':
case 'Collapse':
case 'Modal':
case 'ScrollSpy': {
const config = Object.assign(defaultPluginConfig)
config.external.push(bsPlugins.Manipulator)
config.globals[bsPlugins.Manipulator] = 'Manipulator'
return config
} }
case 'Dropdown': const usedPlugin = resolvedPlugins.find(plugin => {
case 'Tooltip': { return plugin.src.includes(source.replace(pattern, ''))
const config = Object.assign(defaultPluginConfig) })
config.external.push(bsPlugins.Manipulator, '@popperjs/core')
config.globals[bsPlugins.Manipulator] = 'Manipulator' if (!usedPlugin) {
config.globals['@popperjs/core'] = 'Popper' throw new Error(`Source ${source} is not mapped!`)
return config
} }
case 'Popover': // We can change `Index` with `UtilIndex` etc if we use
return { // `safeClassName` instead of `className` everywhere
external: [ globals[path.normalize(usedPlugin.src)] = usedPlugin.className
bsPlugins.Data, return true
bsPlugins.SelectorEngine,
bsPlugins.Tooltip
],
globals: {
[bsPlugins.Data]: 'Data',
[bsPlugins.SelectorEngine]: 'SelectorEngine',
[bsPlugins.Tooltip]: 'Tooltip'
} }
}
case 'Toast':
return {
external: [
bsPlugins.Data,
bsPlugins.Base,
bsPlugins.EventHandler,
bsPlugins.Manipulator
],
globals: {
[bsPlugins.Data]: 'Data',
[bsPlugins.Base]: 'Base',
[bsPlugins.EventHandler]: 'EventHandler',
[bsPlugins.Manipulator]: 'Manipulator'
}
}
default:
return {
external: []
}
}
}
const utilObjects = new Set([
'Util',
'Sanitizer',
'Backdrop'
])
const domObjects = new Set([
'Data',
'EventHandler',
'Manipulator',
'SelectorEngine'
])
const build = async plugin => {
console.log(`Building ${plugin} plugin...`)
const { external, globals } = getConfigByPluginKey(plugin)
const pluginFilename = path.basename(bsPlugins[plugin])
let pluginPath = rootPath
if (utilObjects.has(plugin)) {
pluginPath = `${rootPath}/util/`
}
if (domObjects.has(plugin)) {
pluginPath = `${rootPath}/dom/`
}
const bundle = await rollup.rollup({
input: bsPlugins[plugin],
plugins,
external
}) })
await bundle.write({ await bundle.write({
banner: banner(pluginFilename), banner: banner(plugin.fileName),
format: 'umd', format: 'umd',
name: plugin, name: plugin.className,
sourcemap: true, sourcemap: true,
globals, globals,
generatedCode: 'es2015', generatedCode: 'es2015',
file: path.resolve(__dirname, `${pluginPath}/${pluginFilename}`) file: plugin.dist
}) })
console.log(`Building ${plugin} plugin... Done!`) console.log(`Built ${plugin.className}`)
} }
const main = async () => { (async () => {
try { try {
await Promise.all(Object.keys(bsPlugins).map(plugin => build(plugin))) const basename = path.basename(__filename)
const timeLabel = `[${basename}] finished`
console.log('Building individual plugins...')
console.time(timeLabel)
await Promise.all(Object.values(resolvedPlugins).map(plugin => build(plugin)))
console.timeEnd(timeLabel)
} catch (error) { } catch (error) {
console.error(error) console.error(error)
process.exit(1) process.exit(1)
} }
} })()
main()
+2 -2
View File
@@ -57,7 +57,7 @@ async function replaceRecursively(file, oldVersion, newVersion) {
} }
async function main(args) { async function main(args) {
const [oldVersion, newVersion] = args let [oldVersion, newVersion] = args
if (!oldVersion || !newVersion) { if (!oldVersion || !newVersion) {
console.error('USAGE: change-version old_version new_version [--verbose] [--dry[-run]]') console.error('USAGE: change-version old_version new_version [--verbose] [--dry[-run]]')
@@ -66,7 +66,7 @@ async function main(args) {
} }
// Strip any leading `v` from arguments because otherwise we will end up with duplicate `v`s // Strip any leading `v` from arguments because otherwise we will end up with duplicate `v`s
[oldVersion, newVersion].map(arg => arg.startsWith('v') ? arg.slice(1) : arg) [oldVersion, newVersion] = [oldVersion, newVersion].map(arg => arg.startsWith('v') ? arg.slice(1) : arg)
try { try {
const files = await globby(GLOB, GLOBBY_OPTIONS) const files = await globby(GLOB, GLOBBY_OPTIONS)
-59
View File
@@ -1,59 +0,0 @@
# Usage:
# install svgo globally: `npm i -g svgo`
# svgo --config=build/svgo.yml --input=foo.svg
# https://github.com/svg/svgo/blob/master/docs/how-it-works/en.md
# replace default config
multipass: true
#full: true
# https://github.com/svg/svgo/blob/master/lib/svgo/js2svg.js#L6 for more config options
js2svg:
pretty: true
indent: 2
plugins:
# - addAttributesToSVGElement:
# attributes:
# - focusable: false
- cleanupAttrs: true
- cleanupEnableBackground: true
- cleanupIDs: true
- cleanupListOfValues: true
- cleanupNumericValues: true
- collapseGroups: true
- convertColors: true
- convertPathData: true
- convertShapeToPath: true
- convertStyleToAttrs: true
- convertTransform: true
- inlineStyles: true
- mergePaths: true
- minifyStyles: true
- moveElemsAttrsToGroup: true
- moveGroupAttrsToElems: true
- removeAttrs:
attrs:
- "data-name"
- removeComments: true
- removeDesc: true
- removeDoctype: true
- removeEditorsNSData: true
- removeEmptyAttrs: true
- removeEmptyContainers: true
- removeEmptyText: true
- removeHiddenElems: true
- removeMetadata: true
- removeNonInheritableGroupAttrs: true
- removeTitle: false
- removeUnknownsAndDefaults:
keepRoleAttr: true
- removeUnusedNS: true
- removeUselessDefs: true
- removeUselessStrokeAndFill: true
- removeViewBox: false
- removeXMLNS: false
- removeXMLProcInst: true
- sortAttrs: true
+2 -2
View File
@@ -75,5 +75,5 @@ params:
js_hash: "sha384-QJHtvGhmr9XOIpI6YVutG+2QOK9T+ZnN4kzFN1RtK3zEFEIsxhlmWl5/YESvpZ13" js_hash: "sha384-QJHtvGhmr9XOIpI6YVutG+2QOK9T+ZnN4kzFN1RtK3zEFEIsxhlmWl5/YESvpZ13"
js_bundle: "https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" js_bundle: "https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
js_bundle_hash: "sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" js_bundle_hash: "sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p"
popper: "https://cdn.jsdelivr.net/npm/@popperjs/core@2.10.2/dist/umd/popper.min.js" popper: "https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.0/dist/umd/popper.min.js"
popper_hash: "sha384-7+zCNj/IqJ95wo16oMtfsKbZ9ccEh31eOz1HGyDuCQ6wgnyJNSYdrPa03rtR1zdB" popper_hash: "sha384-ThCKQ0fDhFQh8PSFLvjhmjy7oRKp5gRbY+bcEnQWtRhuvli/qxsn0xMcMmzXkuIa"
+12 -27
View File
@@ -5,30 +5,15 @@
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
*/ */
import Alert from './src/alert' export { default as Alert } from './src/alert'
import Button from './src/button' export { default as Button } from './src/button'
import Carousel from './src/carousel' export { default as Carousel } from './src/carousel'
import Collapse from './src/collapse' export { default as Collapse } from './src/collapse'
import Dropdown from './src/dropdown' export { default as Dropdown } from './src/dropdown'
import Modal from './src/modal' export { default as Modal } from './src/modal'
import Offcanvas from './src/offcanvas' export { default as Offcanvas } from './src/offcanvas'
import Popover from './src/popover' export { default as Popover } from './src/popover'
import ScrollSpy from './src/scrollspy' export { default as ScrollSpy } from './src/scrollspy'
import Tab from './src/tab' export { default as Tab } from './src/tab'
import Toast from './src/toast' export { default as Toast } from './src/toast'
import Tooltip from './src/tooltip' export { default as Tooltip } from './src/tooltip'
export {
Alert,
Button,
Carousel,
Collapse,
Dropdown,
Modal,
Offcanvas,
Popover,
ScrollSpy,
Tab,
Toast,
Tooltip
}
+27 -47
View File
@@ -33,14 +33,11 @@ const EVENT_KEY = `.${DATA_KEY}`
const DATA_API_KEY = '.data-api' const DATA_API_KEY = '.data-api'
const ESCAPE_KEY = 'Escape' const ESCAPE_KEY = 'Escape'
const SPACE_KEY = 'Space'
const TAB_KEY = 'Tab' const TAB_KEY = 'Tab'
const ARROW_UP_KEY = 'ArrowUp' const ARROW_UP_KEY = 'ArrowUp'
const ARROW_DOWN_KEY = 'ArrowDown' const ARROW_DOWN_KEY = 'ArrowDown'
const RIGHT_MOUSE_BUTTON = 2 // MouseEvent.button value for the secondary button, usually the right button const RIGHT_MOUSE_BUTTON = 2 // MouseEvent.button value for the secondary button, usually the right button
const REGEXP_KEYDOWN = new RegExp(`${ARROW_UP_KEY}|${ARROW_DOWN_KEY}|${ESCAPE_KEY}`)
const EVENT_HIDE = `hide${EVENT_KEY}` const EVENT_HIDE = `hide${EVENT_KEY}`
const EVENT_HIDDEN = `hidden${EVENT_KEY}` const EVENT_HIDDEN = `hidden${EVENT_KEY}`
const EVENT_SHOW = `show${EVENT_KEY}` const EVENT_SHOW = `show${EVENT_KEY}`
@@ -53,10 +50,10 @@ const CLASS_NAME_SHOW = 'show'
const CLASS_NAME_DROPUP = 'dropup' const CLASS_NAME_DROPUP = 'dropup'
const CLASS_NAME_DROPEND = 'dropend' const CLASS_NAME_DROPEND = 'dropend'
const CLASS_NAME_DROPSTART = 'dropstart' const CLASS_NAME_DROPSTART = 'dropstart'
const CLASS_NAME_NAVBAR = 'navbar'
const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="dropdown"]' const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="dropdown"]'
const SELECTOR_MENU = '.dropdown-menu' const SELECTOR_MENU = '.dropdown-menu'
const SELECTOR_NAVBAR = '.navbar'
const SELECTOR_NAVBAR_NAV = '.navbar-nav' const SELECTOR_NAVBAR_NAV = '.navbar-nav'
const SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)' const SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)'
@@ -132,13 +129,9 @@ class Dropdown extends BaseComponent {
return return
} }
const parent = Dropdown.getParentFromElement(this._element) const parent = getElementFromSelector(this._element) || this._element.parentNode
// Totally disable Popper for Dropdowns in Navbar
if (this._inNavbar) {
Manipulator.setDataAttribute(this._menu, 'popper', 'none')
} else {
this._createPopper(parent) this._createPopper(parent)
}
// If this is a touch-enabled device we add extra // If this is a touch-enabled device we add extra
// empty mouseover listeners to the body's immediate children; // empty mouseover listeners to the body's immediate children;
@@ -246,13 +239,7 @@ class Dropdown extends BaseComponent {
} }
const popperConfig = this._getPopperConfig() const popperConfig = this._getPopperConfig()
const isDisplayStatic = popperConfig.modifiers.find(modifier => modifier.name === 'applyStyles' && modifier.enabled === false)
this._popper = Popper.createPopper(referenceElement, this._menu, popperConfig) this._popper = Popper.createPopper(referenceElement, this._menu, popperConfig)
if (isDisplayStatic) {
Manipulator.setDataAttribute(this._menu, 'popper', 'static')
}
} }
_isShown(element = this._element) { _isShown(element = this._element) {
@@ -285,7 +272,7 @@ class Dropdown extends BaseComponent {
} }
_detectNavbar() { _detectNavbar() {
return this._element.closest(`.${CLASS_NAME_NAVBAR}`) !== null return this._element.closest(SELECTOR_NAVBAR) !== null
} }
_getOffset() { _getOffset() {
@@ -319,8 +306,9 @@ class Dropdown extends BaseComponent {
}] }]
} }
// Disable Popper if we have a static display // Disable Popper if we have a static display or Dropdown is in Navbar
if (this._config.display === 'static') { if (this._inNavbar || this._config.display === 'static') {
Manipulator.setDataAttribute(this._menu, 'popper', 'static') // todo:v6 remove
defaultBsPopperConfig.modifiers = [{ defaultBsPopperConfig.modifiers = [{
name: 'applyStyles', name: 'applyStyles',
enabled: false enabled: false
@@ -363,7 +351,7 @@ class Dropdown extends BaseComponent {
} }
static clearMenus(event) { static clearMenus(event) {
if (event && (event.button === RIGHT_MOUSE_BUTTON || (event.type === 'keyup' && event.key !== TAB_KEY))) { if (event.button === RIGHT_MOUSE_BUTTON || (event.type === 'keyup' && event.key !== TAB_KEY)) {
return return
} }
@@ -383,7 +371,6 @@ class Dropdown extends BaseComponent {
relatedTarget: context._element relatedTarget: context._element
} }
if (event) {
const composedPath = event.composedPath() const composedPath = event.composedPath()
const isMenuTarget = composedPath.includes(context._menu) const isMenuTarget = composedPath.includes(context._menu)
if ( if (
@@ -402,35 +389,36 @@ class Dropdown extends BaseComponent {
if (event.type === 'click') { if (event.type === 'click') {
relatedTarget.clickEvent = event relatedTarget.clickEvent = event
} }
}
context._completeHide(relatedTarget) context._completeHide(relatedTarget)
} }
} }
static getParentFromElement(element) {
return getElementFromSelector(element) || element.parentNode
}
static dataApiKeydownHandler(event) { static dataApiKeydownHandler(event) {
// If not input/textarea: // If not input/textarea:
// - And not a key in REGEXP_KEYDOWN => not a dropdown command // - And not a key in UP | DOWN | ESCAPE => not a dropdown command
// If input/textarea: // If input/textarea && If key is other than ESCAPE
// - If space key => not a dropdown command // - If key is not UP or DOWN => not a dropdown command
// - If key is other than escape
// - If key is not up or down => not a dropdown command
// - If trigger inside the menu => not a dropdown command // - If trigger inside the menu => not a dropdown command
if (/input|textarea/i.test(event.target.tagName) ?
event.key === SPACE_KEY || (event.key !== ESCAPE_KEY && const isInput = /input|textarea/i.test(event.target.tagName)
((event.key !== ARROW_DOWN_KEY && event.key !== ARROW_UP_KEY) || const isEscapeEvent = event.key === ESCAPE_KEY
event.target.closest(SELECTOR_MENU))) : const isUpOrDownEvent = [ARROW_UP_KEY, ARROW_DOWN_KEY].includes(event.key)
!REGEXP_KEYDOWN.test(event.key)) {
if (!isInput && !(isUpOrDownEvent || isEscapeEvent)) {
return return
} }
if (isInput && !isEscapeEvent) {
// eslint-disable-next-line unicorn/no-lonely-if
if (!isUpOrDownEvent || event.target.closest(SELECTOR_MENU)) {
return
}
}
const isActive = this.classList.contains(CLASS_NAME_SHOW) const isActive = this.classList.contains(CLASS_NAME_SHOW)
if (!isActive && event.key === ESCAPE_KEY) { if (!isActive && isEscapeEvent) {
return return
} }
@@ -444,22 +432,14 @@ class Dropdown extends BaseComponent {
const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0] const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0]
const instance = Dropdown.getOrCreateInstance(getToggleButton) const instance = Dropdown.getOrCreateInstance(getToggleButton)
if (event.key === ESCAPE_KEY) { if (isEscapeEvent) {
instance.hide() instance.hide()
return return
} }
if (event.key === ARROW_UP_KEY || event.key === ARROW_DOWN_KEY) { if (isUpOrDownEvent) {
if (!isActive) {
instance.show() instance.show()
}
instance._selectMenuItem(event) instance._selectMenuItem(event)
return
}
if (!isActive || event.key === SPACE_KEY) {
Dropdown.clearMenus()
} }
} }
} }
+45 -64
View File
@@ -40,8 +40,6 @@ const EVENT_SHOWN = `shown${EVENT_KEY}`
const EVENT_RESIZE = `resize${EVENT_KEY}` const EVENT_RESIZE = `resize${EVENT_KEY}`
const EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}` const EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}`
const EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}` const EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`
const EVENT_MOUSEUP_DISMISS = `mouseup.dismiss${EVENT_KEY}`
const EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY}`
const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}` const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`
const CLASS_NAME_OPEN = 'modal-open' const CLASS_NAME_OPEN = 'modal-open'
@@ -79,7 +77,6 @@ class Modal extends BaseComponent {
this._backdrop = this._initializeBackDrop() this._backdrop = this._initializeBackDrop()
this._focustrap = this._initializeFocusTrap() this._focustrap = this._initializeFocusTrap()
this._isShown = false this._isShown = false
this._ignoreBackdropClick = false
this._isTransitioning = false this._isTransitioning = false
this._scrollBar = new ScrollBarHelper() this._scrollBar = new ScrollBarHelper()
} }
@@ -112,10 +109,7 @@ class Modal extends BaseComponent {
} }
this._isShown = true this._isShown = true
if (this._isAnimated()) {
this._isTransitioning = true this._isTransitioning = true
}
this._scrollBar.hide() this._scrollBar.hide()
@@ -123,16 +117,8 @@ class Modal extends BaseComponent {
this._adjustDialog() this._adjustDialog()
this._setEscapeEvent() this._toggleEscapeEventListener(true)
this._setResizeEvent() this._toggleResizeEventListener(true)
EventHandler.on(this._dialog, EVENT_MOUSEDOWN_DISMISS, () => {
EventHandler.one(this._element, EVENT_MOUSEUP_DISMISS, event => {
if (event.target === this._element) {
this._ignoreBackdropClick = true
}
})
})
this._showBackdrop(() => this._showElement(relatedTarget)) this._showBackdrop(() => this._showElement(relatedTarget))
} }
@@ -149,23 +135,16 @@ class Modal extends BaseComponent {
} }
this._isShown = false this._isShown = false
const isAnimated = this._isAnimated()
if (isAnimated) {
this._isTransitioning = true this._isTransitioning = true
}
this._setEscapeEvent() this._toggleEscapeEventListener(false)
this._setResizeEvent() this._toggleResizeEventListener(false)
this._focustrap.deactivate() this._focustrap.deactivate()
this._element.classList.remove(CLASS_NAME_SHOW) this._element.classList.remove(CLASS_NAME_SHOW)
EventHandler.off(this._element, EVENT_CLICK_DISMISS) this._queueCallback(() => this._hideModal(), this._element, this._isAnimated())
EventHandler.off(this._dialog, EVENT_MOUSEDOWN_DISMISS)
this._queueCallback(() => this._hideModal(), this._element, isAnimated)
} }
dispose() { dispose() {
@@ -207,11 +186,8 @@ class Modal extends BaseComponent {
} }
_showElement(relatedTarget) { _showElement(relatedTarget) {
const isAnimated = this._isAnimated() // try to append dynamic modal
const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog) if (!document.body.contains(this._element)) {
if (!this._element.parentNode || this._element.parentNode.nodeType !== Node.ELEMENT_NODE) {
// Don't move modal's DOM position
document.body.append(this._element) document.body.append(this._element)
} }
@@ -221,13 +197,12 @@ class Modal extends BaseComponent {
this._element.setAttribute('role', 'dialog') this._element.setAttribute('role', 'dialog')
this._element.scrollTop = 0 this._element.scrollTop = 0
const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog)
if (modalBody) { if (modalBody) {
modalBody.scrollTop = 0 modalBody.scrollTop = 0
} }
if (isAnimated) {
reflow(this._element) reflow(this._element)
}
this._element.classList.add(CLASS_NAME_SHOW) this._element.classList.add(CLASS_NAME_SHOW)
@@ -242,30 +217,37 @@ class Modal extends BaseComponent {
}) })
} }
this._queueCallback(transitionComplete, this._dialog, isAnimated) this._queueCallback(transitionComplete, this._dialog, this._isAnimated())
}
_toggleEscapeEventListener(enable) {
if (!enable) {
EventHandler.off(this._element, EVENT_KEYDOWN_DISMISS)
return
} }
_setEscapeEvent() {
if (this._isShown) {
EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => { EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {
if (this._config.keyboard && event.key === ESCAPE_KEY) { if (event.key !== ESCAPE_KEY) {
return
}
if (this._config.keyboard) {
event.preventDefault() event.preventDefault()
this.hide() this.hide()
} else if (!this._config.keyboard && event.key === ESCAPE_KEY) { return
this._triggerBackdropTransition()
}
})
} else {
EventHandler.off(this._element, EVENT_KEYDOWN_DISMISS)
}
} }
_setResizeEvent() { this._triggerBackdropTransition()
if (this._isShown) { })
EventHandler.on(window, EVENT_RESIZE, () => this._adjustDialog())
} else {
EventHandler.off(window, EVENT_RESIZE)
} }
_toggleResizeEventListener(enable) {
if (enable) {
EventHandler.on(window, EVENT_RESIZE, () => this._adjustDialog())
return
}
EventHandler.off(window, EVENT_RESIZE)
} }
_hideModal() { _hideModal() {
@@ -274,6 +256,7 @@ class Modal extends BaseComponent {
this._element.removeAttribute('aria-modal') this._element.removeAttribute('aria-modal')
this._element.removeAttribute('role') this._element.removeAttribute('role')
this._isTransitioning = false this._isTransitioning = false
this._backdrop.hide(() => { this._backdrop.hide(() => {
document.body.classList.remove(CLASS_NAME_OPEN) document.body.classList.remove(CLASS_NAME_OPEN)
this._resetAdjustments() this._resetAdjustments()
@@ -284,18 +267,16 @@ class Modal extends BaseComponent {
_showBackdrop(callback) { _showBackdrop(callback) {
EventHandler.on(this._element, EVENT_CLICK_DISMISS, event => { EventHandler.on(this._element, EVENT_CLICK_DISMISS, event => {
if (this._ignoreBackdropClick) {
this._ignoreBackdropClick = false
return
}
if (event.target !== event.currentTarget) { if (event.target !== event.currentTarget) {
return return
} }
if (this._config.backdrop === true) { if (this._config.backdrop === true) {
this.hide() this.hide()
} else if (this._config.backdrop === 'static') { return
}
if (this._config.backdrop === 'static') {
this._triggerBackdropTransition() this._triggerBackdropTransition()
} }
}) })
@@ -315,9 +296,9 @@ class Modal extends BaseComponent {
const { classList, scrollHeight, style } = this._element const { classList, scrollHeight, style } = this._element
const isModalOverflowing = scrollHeight > document.documentElement.clientHeight const isModalOverflowing = scrollHeight > document.documentElement.clientHeight
const initialOverflowY = style.overflowY
// return if the following background transition hasn't yet completed // return if the following background transition hasn't yet completed
if ((!isModalOverflowing && style.overflowY === 'hidden') || classList.contains(CLASS_NAME_STATIC)) { if (initialOverflowY === 'hidden' || classList.contains(CLASS_NAME_STATIC)) {
return return
} }
@@ -328,11 +309,9 @@ class Modal extends BaseComponent {
classList.add(CLASS_NAME_STATIC) classList.add(CLASS_NAME_STATIC)
this._queueCallback(() => { this._queueCallback(() => {
classList.remove(CLASS_NAME_STATIC) classList.remove(CLASS_NAME_STATIC)
if (!isModalOverflowing) {
this._queueCallback(() => { this._queueCallback(() => {
style.overflowY = '' style.overflowY = initialOverflowY
}, this._dialog) }, this._dialog)
}
}, this._dialog) }, this._dialog)
this._element.focus() this._element.focus()
@@ -347,12 +326,14 @@ class Modal extends BaseComponent {
const scrollbarWidth = this._scrollBar.getWidth() const scrollbarWidth = this._scrollBar.getWidth()
const isBodyOverflowing = scrollbarWidth > 0 const isBodyOverflowing = scrollbarWidth > 0
if ((!isBodyOverflowing && isModalOverflowing && !isRTL()) || (isBodyOverflowing && !isModalOverflowing && isRTL())) { if (isBodyOverflowing && !isModalOverflowing) {
this._element.style.paddingLeft = `${scrollbarWidth}px` const property = isRTL() ? 'paddingLeft' : 'paddingRight'
this._element.style[property] = `${scrollbarWidth}px`
} }
if ((isBodyOverflowing && !isModalOverflowing && !isRTL()) || (!isBodyOverflowing && isModalOverflowing && isRTL())) { if (!isBodyOverflowing && isModalOverflowing) {
this._element.style.paddingRight = `${scrollbarWidth}px` const property = isRTL() ? 'paddingRight' : 'paddingLeft'
this._element.style[property] = `${scrollbarWidth}px`
} }
} }
+13 -14
View File
@@ -15,7 +15,6 @@ import Tooltip from './tooltip'
const NAME = 'popover' const NAME = 'popover'
const DATA_KEY = 'bs.popover' const DATA_KEY = 'bs.popover'
const EVENT_KEY = `.${DATA_KEY}` const EVENT_KEY = `.${DATA_KEY}`
const CLASS_PREFIX = 'bs-popover'
const SELECTOR_TITLE = '.popover-header' const SELECTOR_TITLE = '.popover-header'
const SELECTOR_CONTENT = '.popover-body' const SELECTOR_CONTENT = '.popover-body'
@@ -74,22 +73,20 @@ class Popover extends Tooltip {
} }
// Overrides // Overrides
isWithContent() { _isWithContent() {
return this.getTitle() || this._getContent() return this._getTitle() || this._getContent()
}
setContent(tip) {
this._sanitizeAndSetContent(tip, this.getTitle(), SELECTOR_TITLE)
this._sanitizeAndSetContent(tip, this._getContent(), SELECTOR_CONTENT)
} }
// Private // Private
_getContent() { _getContentForTemplate() {
return this._resolvePossibleFunction(this._config.content) return {
[SELECTOR_TITLE]: this._getTitle(),
[SELECTOR_CONTENT]: this._getContent()
}
} }
_getBasicClassPrefix() { _getContent() {
return CLASS_PREFIX return this._resolvePossibleFunction(this._config.content)
} }
// Static // Static
@@ -97,13 +94,15 @@ class Popover extends Tooltip {
return this.each(function () { return this.each(function () {
const data = Popover.getOrCreateInstance(this, config) const data = Popover.getOrCreateInstance(this, config)
if (typeof config === 'string') { if (typeof config !== 'string') {
return
}
if (typeof data[config] === 'undefined') { if (typeof data[config] === 'undefined') {
throw new TypeError(`No method named "${config}"`) throw new TypeError(`No method named "${config}"`)
} }
data[config]() data[config]()
}
}) })
} }
} }
+147 -251
View File
@@ -11,17 +11,15 @@ import {
findShadowRoot, findShadowRoot,
getElement, getElement,
getUID, getUID,
isElement,
isRTL, isRTL,
noop, noop,
typeCheckConfig typeCheckConfig
} from './util/index' } from './util/index'
import { DefaultAllowlist, sanitizeHtml } from './util/sanitizer' import { DefaultAllowlist } from './util/sanitizer'
import Data from './dom/data'
import EventHandler from './dom/event-handler' import EventHandler from './dom/event-handler'
import Manipulator from './dom/manipulator' import Manipulator from './dom/manipulator'
import SelectorEngine from './dom/selector-engine'
import BaseComponent from './base-component' import BaseComponent from './base-component'
import TemplateFactory from './util/template-factory'
/** /**
* Constants * Constants
@@ -30,16 +28,12 @@ import BaseComponent from './base-component'
const NAME = 'tooltip' const NAME = 'tooltip'
const DATA_KEY = 'bs.tooltip' const DATA_KEY = 'bs.tooltip'
const EVENT_KEY = `.${DATA_KEY}` const EVENT_KEY = `.${DATA_KEY}`
const CLASS_PREFIX = 'bs-tooltip'
const DISALLOWED_ATTRIBUTES = new Set(['sanitize', 'allowList', 'sanitizeFn']) const DISALLOWED_ATTRIBUTES = new Set(['sanitize', 'allowList', 'sanitizeFn'])
const CLASS_NAME_FADE = 'fade' const CLASS_NAME_FADE = 'fade'
const CLASS_NAME_MODAL = 'modal' const CLASS_NAME_MODAL = 'modal'
const CLASS_NAME_SHOW = 'show' const CLASS_NAME_SHOW = 'show'
const HOVER_STATE_SHOW = 'show'
const HOVER_STATE_OUT = 'out'
const SELECTOR_TOOLTIP_INNER = '.tooltip-inner' const SELECTOR_TOOLTIP_INNER = '.tooltip-inner'
const SELECTOR_MODAL = `.${CLASS_NAME_MODAL}` const SELECTOR_MODAL = `.${CLASS_NAME_MODAL}`
@@ -129,9 +123,10 @@ class Tooltip extends BaseComponent {
// Private // Private
this._isEnabled = true this._isEnabled = true
this._timeout = 0 this._timeout = 0
this._hoverState = '' this._isHovered = false
this._activeTrigger = {} this._activeTrigger = {}
this._popper = null this._popper = null
this._templateFactory = null
// Protected // Protected
this._config = this._getConfig(config) this._config = this._getConfig(config)
@@ -181,17 +176,17 @@ class Tooltip extends BaseComponent {
context._activeTrigger.click = !context._activeTrigger.click context._activeTrigger.click = !context._activeTrigger.click
if (context._isWithActiveTrigger()) { if (context._isWithActiveTrigger()) {
context._enter(null, context) context._enter()
} else { } else {
context._leave(null, context) context._leave()
} }
} else { } else {
if (this.getTipElement().classList.contains(CLASS_NAME_SHOW)) { if (this._getTipElement().classList.contains(CLASS_NAME_SHOW)) {
this._leave(null, this) this._leave()
return return
} }
this._enter(null, this) this._enter()
} }
} }
@@ -213,7 +208,7 @@ class Tooltip extends BaseComponent {
throw new Error('Please use show on visible elements') throw new Error('Please use show on visible elements')
} }
if (!(this.isWithContent() && this._isEnabled)) { if (!(this._isWithContent() && this._isEnabled)) {
return return
} }
@@ -227,33 +222,11 @@ class Tooltip extends BaseComponent {
return return
} }
// A trick to recreate a tooltip in case a new title is given by using the NOT documented `data-bs-original-title` const tip = this._getTipElement()
// This will be removed later in favor of a `setContent` method
if (this.constructor.NAME === 'tooltip' && this.tip && this.getTitle() !== this.tip.querySelector(SELECTOR_TOOLTIP_INNER).innerHTML) {
this._disposePopper()
this.tip.remove()
this.tip = null
}
const tip = this.getTipElement() this._element.setAttribute('aria-describedby', tip.getAttribute('id'))
const tipId = getUID(this.constructor.NAME)
tip.setAttribute('id', tipId)
this._element.setAttribute('aria-describedby', tipId)
if (this._config.animation) {
tip.classList.add(CLASS_NAME_FADE)
}
const placement = typeof this._config.placement === 'function' ?
this._config.placement.call(this, tip, this._element) :
this._config.placement
const attachment = this._getAttachment(placement)
this._addAttachmentClass(attachment)
const { container } = this._config const { container } = this._config
Data.set(tip, this.constructor.DATA_KEY, this)
if (!this._element.ownerDocument.documentElement.contains(this.tip)) { if (!this._element.ownerDocument.documentElement.contains(this.tip)) {
container.append(tip) container.append(tip)
@@ -263,16 +236,15 @@ class Tooltip extends BaseComponent {
if (this._popper) { if (this._popper) {
this._popper.update() this._popper.update()
} else { } else {
const placement = typeof this._config.placement === 'function' ?
this._config.placement.call(this, tip, this._element) :
this._config.placement
const attachment = AttachmentMap[placement.toUpperCase()]
this._popper = Popper.createPopper(this._element, tip, this._getPopperConfig(attachment)) this._popper = Popper.createPopper(this._element, tip, this._getPopperConfig(attachment))
} }
tip.classList.add(CLASS_NAME_SHOW) tip.classList.add(CLASS_NAME_SHOW)
const customClass = this._resolvePossibleFunction(this._config.customClass)
if (customClass) {
tip.classList.add(...customClass.split(' '))
}
// If this is a touch-enabled device we add extra // If this is a touch-enabled device we add extra
// empty mouseover listeners to the body's immediate children; // empty mouseover listeners to the body's immediate children;
// only needed because of broken event delegation on iOS // only needed because of broken event delegation on iOS
@@ -284,18 +256,17 @@ class Tooltip extends BaseComponent {
} }
const complete = () => { const complete = () => {
const prevHoverState = this._hoverState const prevHoverState = this._isHovered
this._hoverState = null this._isHovered = false
EventHandler.trigger(this._element, this.constructor.Event.SHOWN) EventHandler.trigger(this._element, this.constructor.Event.SHOWN)
if (prevHoverState === HOVER_STATE_OUT) { if (prevHoverState) {
this._leave(null, this) this._leave()
} }
} }
const isAnimated = this.tip.classList.contains(CLASS_NAME_FADE) this._queueCallback(complete, this.tip, this._isAnimated())
this._queueCallback(complete, this.tip, isAnimated)
} }
hide() { hide() {
@@ -303,28 +274,12 @@ class Tooltip extends BaseComponent {
return return
} }
const tip = this.getTipElement()
const complete = () => {
if (this._isWithActiveTrigger()) {
return
}
if (this._hoverState !== HOVER_STATE_SHOW) {
tip.remove()
}
this._cleanTipClass()
this._element.removeAttribute('aria-describedby')
EventHandler.trigger(this._element, this.constructor.Event.HIDDEN)
this._disposePopper()
}
const hideEvent = EventHandler.trigger(this._element, this.constructor.Event.HIDE) const hideEvent = EventHandler.trigger(this._element, this.constructor.Event.HIDE)
if (hideEvent.defaultPrevented) { if (hideEvent.defaultPrevented) {
return return
} }
const tip = this._getTipElement()
tip.classList.remove(CLASS_NAME_SHOW) tip.classList.remove(CLASS_NAME_SHOW)
// If this is a touch-enabled device we remove the extra // If this is a touch-enabled device we remove the extra
@@ -339,107 +294,116 @@ class Tooltip extends BaseComponent {
this._activeTrigger[TRIGGER_FOCUS] = false this._activeTrigger[TRIGGER_FOCUS] = false
this._activeTrigger[TRIGGER_HOVER] = false this._activeTrigger[TRIGGER_HOVER] = false
const isAnimated = this.tip.classList.contains(CLASS_NAME_FADE) const complete = () => {
this._queueCallback(complete, this.tip, isAnimated) if (this._isWithActiveTrigger()) {
this._hoverState = '' return
}
if (!this._isHovered) {
tip.remove()
}
this._element.removeAttribute('aria-describedby')
EventHandler.trigger(this._element, this.constructor.Event.HIDDEN)
this._disposePopper()
}
this._queueCallback(complete, this.tip, this._isAnimated())
this._isHovered = false
} }
update() { update() {
if (this._popper !== null) { if (this._popper) {
this._popper.update() this._popper.update()
} }
} }
// Protected // Protected
isWithContent() { _isWithContent() {
return Boolean(this.getTitle()) return Boolean(this._getTitle())
}
_getTipElement() {
if (!this.tip) {
this.tip = this._createTipElement(this._getContentForTemplate())
} }
getTipElement() {
if (this.tip) {
return this.tip return this.tip
} }
const element = document.createElement('div') _createTipElement(content) {
element.innerHTML = this._config.template const tip = this._getTemplateFactory(content).toHtml()
// todo: remove this check on v6
if (!tip) {
return null
}
const tip = element.children[0]
this.setContent(tip)
tip.classList.remove(CLASS_NAME_FADE, CLASS_NAME_SHOW) tip.classList.remove(CLASS_NAME_FADE, CLASS_NAME_SHOW)
// todo: on v6 the following can be achieved with CSS only
tip.classList.add(`bs-${this.constructor.NAME}-auto`)
this.tip = tip const tipId = getUID(this.constructor.NAME).toString()
return this.tip
tip.setAttribute('id', tipId)
if (this._isAnimated()) {
tip.classList.add(CLASS_NAME_FADE)
} }
setContent(tip) { return tip
this._sanitizeAndSetContent(tip, this.getTitle(), SELECTOR_TOOLTIP_INNER)
} }
_sanitizeAndSetContent(template, content, selector) { setContent(content) {
const templateElement = SelectorEngine.findOne(selector, template) let isShown = false
if (this.tip) {
if (!content && templateElement) { isShown = this.tip.classList.contains(CLASS_NAME_SHOW)
templateElement.remove() this.tip.remove()
return this.tip = null
} }
// we use append for html objects to maintain js events this._disposePopper()
this.setElementContent(templateElement, content) this.tip = this._createTipElement(content)
if (isShown) {
this.show()
}
} }
setElementContent(element, content) { _getTemplateFactory(content) {
if (element === null) { if (this._templateFactory) {
return this._templateFactory.changeContent(content)
}
if (isElement(content)) {
content = getElement(content)
// content is a DOM node or a jQuery
if (this._config.html) {
if (content.parentNode !== element) {
element.innerHTML = ''
element.append(content)
}
} else { } else {
element.textContent = content.textContent this._templateFactory = new TemplateFactory({
...this._config,
// the `content` var has to be after `this._config`
// to override config.content in case of popover
content,
extraClass: this._resolvePossibleFunction(this._config.customClass)
})
} }
return return this._templateFactory
} }
if (this._config.html) { _getContentForTemplate() {
if (this._config.sanitize) { return {
content = sanitizeHtml(content, this._config.allowList, this._config.sanitizeFn) [SELECTOR_TOOLTIP_INNER]: this._getTitle()
}
element.innerHTML = content // lgtm [js/xss-through-dom]
} else {
element.textContent = content
} }
} }
getTitle() { _getTitle() {
const title = this._element.getAttribute('data-bs-original-title') || this._config.title return this._config.title
return this._resolvePossibleFunction(title)
}
updateAttachment(attachment) {
if (attachment === 'right') {
return 'end'
}
if (attachment === 'left') {
return 'start'
}
return attachment
} }
// Private // Private
_initializeOnDelegatedTarget(event, context) { _initializeOnDelegatedTarget(event) {
return context || this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig()) return this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig())
}
_isAnimated() {
return this._config.animation || (this.tip && this.tip.classList.contains(CLASS_NAME_FADE))
} }
_getOffset() { _getOffset() {
@@ -456,8 +420,8 @@ class Tooltip extends BaseComponent {
return offset return offset
} }
_resolvePossibleFunction(content) { _resolvePossibleFunction(arg) {
return typeof content === 'function' ? content.call(this._element) : content return typeof arg === 'function' ? arg.call(this._element) : arg
} }
_getPopperConfig(attachment) { _getPopperConfig(attachment) {
@@ -487,19 +451,8 @@ class Tooltip extends BaseComponent {
options: { options: {
element: `.${this.constructor.NAME}-arrow` element: `.${this.constructor.NAME}-arrow`
} }
},
{
name: 'onChange',
enabled: true,
phase: 'afterWrite',
fn: data => this._handlePopperPlacementChange(data)
}
],
onFirstUpdate: data => {
if (data.options.placement !== data.placement) {
this._handlePopperPlacementChange(data)
}
} }
]
} }
return { return {
@@ -508,14 +461,6 @@ class Tooltip extends BaseComponent {
} }
} }
_addAttachmentClass(attachment) {
this.getTipElement().classList.add(`${this._getBasicClassPrefix()}-${this.updateAttachment(attachment)}`)
}
_getAttachment(placement) {
return AttachmentMap[placement.toUpperCase()]
}
_setListeners() { _setListeners() {
const triggers = this._config.trigger.split(' ') const triggers = this._config.trigger.split(' ')
@@ -530,8 +475,18 @@ class Tooltip extends BaseComponent {
this.constructor.Event.MOUSELEAVE : this.constructor.Event.MOUSELEAVE :
this.constructor.Event.FOCUSOUT this.constructor.Event.FOCUSOUT
EventHandler.on(this._element, eventIn, this._config.selector, event => this._enter(event)) EventHandler.on(this._element, eventIn, this._config.selector, event => {
EventHandler.on(this._element, eventOut, this._config.selector, event => this._leave(event)) const context = this._initializeOnDelegatedTarget(event)
context._activeTrigger[event.type === 'focusin' ? TRIGGER_FOCUS : TRIGGER_HOVER] = true
context._enter()
})
EventHandler.on(this._element, eventOut, this._config.selector, event => {
const context = this._initializeOnDelegatedTarget(event)
context._activeTrigger[event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER] =
context._element.contains(event.relatedTarget)
context._leave()
})
} }
} }
@@ -555,86 +510,55 @@ class Tooltip extends BaseComponent {
} }
_fixTitle() { _fixTitle() {
const title = this._element.getAttribute('title') const title = this._config.originalTitle
const originalTitleType = typeof this._element.getAttribute('data-bs-original-title')
if (title || originalTitleType !== 'string') { if (!title) {
this._element.setAttribute('data-bs-original-title', title || '') return
if (title && !this._element.getAttribute('aria-label') && !this._element.textContent) { }
if (!this._element.getAttribute('aria-label') && !this._element.textContent) {
this._element.setAttribute('aria-label', title) this._element.setAttribute('aria-label', title)
} }
this._element.setAttribute('title', '') this._element.removeAttribute('title')
}
} }
_enter(event, context) { _enter() {
context = this._initializeOnDelegatedTarget(event, context) if (this._getTipElement().classList.contains(CLASS_NAME_SHOW) || this._isHovered) {
this._isHovered = true
if (event) {
context._activeTrigger[
event.type === 'focusin' ? TRIGGER_FOCUS : TRIGGER_HOVER
] = true
}
if (context.getTipElement().classList.contains(CLASS_NAME_SHOW) || context._hoverState === HOVER_STATE_SHOW) {
context._hoverState = HOVER_STATE_SHOW
return return
} }
clearTimeout(context._timeout) this._isHovered = true
context._hoverState = HOVER_STATE_SHOW this._setTimeout(() => {
if (this._isHovered) {
this.show()
}
}, this._config.delay.show)
}
if (!context._config.delay || !context._config.delay.show) { _leave() {
context.show() if (this._isWithActiveTrigger()) {
return return
} }
context._timeout = setTimeout(() => { this._isHovered = false
if (context._hoverState === HOVER_STATE_SHOW) {
context.show() this._setTimeout(() => {
if (!this._isHovered) {
this.hide()
} }
}, context._config.delay.show) }, this._config.delay.hide)
} }
_leave(event, context) { _setTimeout(handler, timeout) {
context = this._initializeOnDelegatedTarget(event, context) clearTimeout(this._timeout)
this._timeout = setTimeout(handler, timeout)
if (event) {
context._activeTrigger[
event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER
] = context._element.contains(event.relatedTarget)
}
if (context._isWithActiveTrigger()) {
return
}
clearTimeout(context._timeout)
context._hoverState = HOVER_STATE_OUT
if (!context._config.delay || !context._config.delay.hide) {
context.hide()
return
}
context._timeout = setTimeout(() => {
if (context._hoverState === HOVER_STATE_OUT) {
context.hide()
}
}, context._config.delay.hide)
} }
_isWithActiveTrigger() { _isWithActiveTrigger() {
for (const trigger in this._activeTrigger) { return Object.values(this._activeTrigger).includes(true)
if (this._activeTrigger[trigger]) {
return true
}
}
return false
} }
_getConfig(config) { _getConfig(config) {
@@ -661,6 +585,8 @@ class Tooltip extends BaseComponent {
} }
} }
config.originalTitle = this._element.getAttribute('title') || ''
config.title = this._resolvePossibleFunction(config.title) || config.originalTitle
if (typeof config.title === 'number') { if (typeof config.title === 'number') {
config.title = config.title.toString() config.title = config.title.toString()
} }
@@ -670,11 +596,6 @@ class Tooltip extends BaseComponent {
} }
typeCheckConfig(NAME, config, this.constructor.DefaultType) typeCheckConfig(NAME, config, this.constructor.DefaultType)
if (config.sanitize) {
config.template = sanitizeHtml(config.template, config.allowList, config.sanitizeFn)
}
return config return config
} }
@@ -693,33 +614,6 @@ class Tooltip extends BaseComponent {
return config return config
} }
_cleanTipClass() {
const tip = this.getTipElement()
const basicClassPrefixRegex = new RegExp(`(^|\\s)${this._getBasicClassPrefix()}\\S+`, 'g')
const tabClass = tip.getAttribute('class').match(basicClassPrefixRegex)
if (tabClass !== null && tabClass.length > 0) {
for (const tClass of tabClass.map(token => token.trim())) {
tip.classList.remove(tClass)
}
}
}
_getBasicClassPrefix() {
return CLASS_PREFIX
}
_handlePopperPlacementChange(popperData) {
const { state } = popperData
if (!state) {
return
}
this.tip = state.elements.popper
this._cleanTipClass()
this._addAttachmentClass(this._getAttachment(state.placement))
}
_disposePopper() { _disposePopper() {
if (this._popper) { if (this._popper) {
this._popper.destroy() this._popper.destroy()
@@ -733,13 +627,15 @@ class Tooltip extends BaseComponent {
return this.each(function () { return this.each(function () {
const data = Tooltip.getOrCreateInstance(this, config) const data = Tooltip.getOrCreateInstance(this, config)
if (typeof config === 'string') { if (typeof config !== 'string') {
return
}
if (typeof data[config] === 'undefined') { if (typeof data[config] === 'undefined') {
throw new TypeError(`No method named "${config}"`) throw new TypeError(`No method named "${config}"`)
} }
data[config]() data[config]()
}
}) })
} }
} }
+161
View File
@@ -0,0 +1,161 @@
/**
* --------------------------------------------------------------------------
* Bootstrap (v5.1.3): util/template-factory.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
import { DefaultAllowlist, sanitizeHtml } from './sanitizer'
import { getElement, isElement, typeCheckConfig } from '../util/index'
import SelectorEngine from '../dom/selector-engine'
/**
* Constants
*/
const NAME = 'TemplateFactory'
const Default = {
extraClass: '',
template: '<div></div>',
content: {}, // { selector : text , selector2 : text2 , }
html: false,
sanitize: true,
sanitizeFn: null,
allowList: DefaultAllowlist
}
const DefaultType = {
extraClass: '(string|function)',
template: 'string',
content: 'object',
html: 'boolean',
sanitize: 'boolean',
sanitizeFn: '(null|function)',
allowList: 'object'
}
const DefaultContentType = {
selector: '(string|element)',
entry: '(string|element|function|null)'
}
/**
* Class definition
*/
class TemplateFactory {
constructor(config) {
this._config = this._getConfig(config)
}
// Getters
static get NAME() {
return NAME
}
static get Default() {
return Default
}
// Public
getContent() {
return Object.values(this._config.content)
.map(config => this._resolvePossibleFunction(config))
.filter(Boolean)
}
hasContent() {
return this.getContent().length > 0
}
changeContent(content) {
this._checkContent(content)
this._config.content = { ...this._config.content, ...content }
return this
}
toHtml() {
const templateWrapper = document.createElement('div')
templateWrapper.innerHTML = this._maybeSanitize(this._config.template)
for (const [selector, text] of Object.entries(this._config.content)) {
this._setContent(templateWrapper, text, selector)
}
const template = templateWrapper.children[0]
const extraClass = this._resolvePossibleFunction(this._config.extraClass)
if (extraClass) {
template.classList.add(...extraClass.split(' '))
}
return template
}
// Private
_getConfig(config) {
config = {
...Default,
...(typeof config === 'object' ? config : {})
}
typeCheckConfig(NAME, config, DefaultType)
this._checkContent(config.content)
return config
}
_checkContent(arg) {
for (const [selector, content] of Object.entries(arg)) {
typeCheckConfig(NAME, { selector, entry: content }, DefaultContentType)
}
}
_setContent(template, content, selector) {
const templateElement = SelectorEngine.findOne(selector, template)
if (!templateElement) {
return
}
content = this._resolvePossibleFunction(content)
if (!content) {
templateElement.remove()
return
}
if (isElement(content)) {
this._putElementInTemplate(getElement(content), templateElement)
return
}
if (this._config.html) {
templateElement.innerHTML = this._maybeSanitize(content)
return
}
templateElement.textContent = content
}
_maybeSanitize(arg) {
return this._config.sanitize ? sanitizeHtml(arg, this._config.allowList, this._config.sanitizeFn) : arg
}
_resolvePossibleFunction(arg) {
return typeof arg === 'function' ? arg(this) : arg
}
_putElementInTemplate(element, templateElement) {
if (this._config.html) {
templateElement.innerHTML = ''
templateElement.append(element)
return
}
templateElement.textContent = element.textContent
}
}
export default TemplateFactory
+1 -1
View File
@@ -35,7 +35,7 @@ Currently we're aiming for at least 90% test coverage for our code. To ensure yo
describe('getInstance', () => { describe('getInstance', () => {
it('should return null if there is no instance', () => { it('should return null if there is no instance', () => {
// Make assertion // Make assertion
expect(Tab.getInstance(fixtureEl)).toEqual(null) expect(Tab.getInstance(fixtureEl)).toBeNull()
}) })
it('should return this instance', () => { it('should return this instance', () => {
+1 -4
View File
@@ -74,9 +74,6 @@ const browsers = {
} }
} }
const browsersKeys = Object.keys(browsers)
module.exports = { module.exports = {
browsers, browsers
browsersKeys
} }
+2 -6
View File
@@ -8,11 +8,7 @@ const { babel } = require('@rollup/plugin-babel')
const istanbul = require('rollup-plugin-istanbul') const istanbul = require('rollup-plugin-istanbul')
const { nodeResolve } = require('@rollup/plugin-node-resolve') const { nodeResolve } = require('@rollup/plugin-node-resolve')
const replace = require('@rollup/plugin-replace') const replace = require('@rollup/plugin-replace')
const { browsers } = require('./browsers')
const {
browsers,
browsersKeys
} = require('./browsers')
const ENV = process.env const ENV = process.env
const BROWSERSTACK = Boolean(ENV.BROWSERSTACK) const BROWSERSTACK = Boolean(ENV.BROWSERSTACK)
@@ -115,7 +111,7 @@ if (BROWSERSTACK) {
} }
plugins.push('karma-browserstack-launcher', 'karma-jasmine-html-reporter') plugins.push('karma-browserstack-launcher', 'karma-jasmine-html-reporter')
conf.customLaunchers = browsers conf.customLaunchers = browsers
conf.browsers = browsersKeys conf.browsers = Object.keys(browsers)
reporters.push('BrowserStack', 'kjhtml') reporters.push('BrowserStack', 'kjhtml')
} else if (JQUERY_TEST) { } else if (JQUERY_TEST) {
frameworks.push('detectBrowsers') frameworks.push('detectBrowsers')
+35 -7
View File
@@ -25,7 +25,7 @@ describe('Alert', () => {
}) })
it('should return version', () => { it('should return version', () => {
expect(typeof Alert.VERSION).toEqual('string') expect(Alert.VERSION).toEqual(jasmine.any(String))
}) })
describe('DATA_KEY', () => { describe('DATA_KEY', () => {
@@ -45,7 +45,7 @@ describe('Alert', () => {
const button = document.querySelector('button') const button = document.querySelector('button')
button.click() button.click()
expect(document.querySelectorAll('.alert').length).toEqual(0) expect(document.querySelectorAll('.alert')).toHaveSize(0)
}) })
it('should close an alert without instantiating it manually with the parent selector', () => { it('should close an alert without instantiating it manually with the parent selector', () => {
@@ -58,7 +58,7 @@ describe('Alert', () => {
const button = document.querySelector('button') const button = document.querySelector('button')
button.click() button.click()
expect(document.querySelectorAll('.alert').length).toEqual(0) expect(document.querySelectorAll('.alert')).toHaveSize(0)
}) })
}) })
@@ -71,7 +71,7 @@ describe('Alert', () => {
const alert = new Alert(alertEl) const alert = new Alert(alertEl)
alertEl.addEventListener('closed.bs.alert', () => { alertEl.addEventListener('closed.bs.alert', () => {
expect(document.querySelectorAll('.alert').length).toEqual(0) expect(document.querySelectorAll('.alert')).toHaveSize(0)
expect(spy).not.toHaveBeenCalled() expect(spy).not.toHaveBeenCalled()
done() done()
}) })
@@ -90,7 +90,7 @@ describe('Alert', () => {
}) })
alertEl.addEventListener('closed.bs.alert', () => { alertEl.addEventListener('closed.bs.alert', () => {
expect(document.querySelectorAll('.alert').length).toEqual(0) expect(document.querySelectorAll('.alert')).toHaveSize(0)
done() done()
}) })
@@ -179,6 +179,34 @@ describe('Alert', () => {
expect(Alert.getInstance(alertEl)).not.toBeNull() expect(Alert.getInstance(alertEl)).not.toBeNull()
expect(fixtureEl.querySelector('.alert')).not.toBeNull() expect(fixtureEl.querySelector('.alert')).not.toBeNull()
}) })
it('should throw an error on undefined method', () => {
fixtureEl.innerHTML = '<div></div>'
const div = fixtureEl.querySelector('div')
const action = 'undefinedMethod'
jQueryMock.fn.alert = Alert.jQueryInterface
jQueryMock.elements = [div]
expect(() => {
jQueryMock.fn.alert.call(jQueryMock, action)
}).toThrowError(TypeError, `No method named "${action}"`)
})
it('should throw an error on protected method', () => {
fixtureEl.innerHTML = '<div></div>'
const div = fixtureEl.querySelector('div')
const action = '_getConfig'
jQueryMock.fn.alert = Alert.jQueryInterface
jQueryMock.elements = [div]
expect(() => {
jQueryMock.fn.alert.call(jQueryMock, action)
}).toThrowError(TypeError, `No method named "${action}"`)
})
}) })
describe('getInstance', () => { describe('getInstance', () => {
@@ -197,7 +225,7 @@ describe('Alert', () => {
const div = fixtureEl.querySelector('div') const div = fixtureEl.querySelector('div')
expect(Alert.getInstance(div)).toEqual(null) expect(Alert.getInstance(div)).toBeNull()
}) })
}) })
@@ -218,7 +246,7 @@ describe('Alert', () => {
const div = fixtureEl.querySelector('div') const div = fixtureEl.querySelector('div')
expect(Alert.getInstance(div)).toEqual(null) expect(Alert.getInstance(div)).toBeNull()
expect(Alert.getOrCreateInstance(div)).toBeInstanceOf(Alert) expect(Alert.getOrCreateInstance(div)).toBeInstanceOf(Alert)
}) })
}) })
+24 -3
View File
@@ -37,7 +37,7 @@ describe('Base Component', () => {
describe('Static Methods', () => { describe('Static Methods', () => {
describe('VERSION', () => { describe('VERSION', () => {
it('should return version', () => { it('should return version', () => {
expect(typeof DummyClass.VERSION).toEqual('string') expect(DummyClass.VERSION).toEqual(jasmine.any(String))
}) })
}) })
@@ -48,6 +48,13 @@ describe('Base Component', () => {
}) })
describe('NAME', () => { describe('NAME', () => {
it('should throw an Error if it is not initialized', () => {
expect(() => {
// eslint-disable-next-line no-unused-expressions
BaseComponent.NAME
}).toThrowError(Error)
})
it('should return plugin NAME', () => { it('should return plugin NAME', () => {
expect(DummyClass.NAME).toEqual(name) expect(DummyClass.NAME).toEqual(name)
}) })
@@ -59,6 +66,7 @@ describe('Base Component', () => {
}) })
}) })
}) })
describe('Public Methods', () => { describe('Public Methods', () => {
describe('constructor', () => { describe('constructor', () => {
it('should accept element, either passed as a CSS selector or DOM element', () => { it('should accept element, either passed as a CSS selector or DOM element', () => {
@@ -74,7 +82,19 @@ describe('Base Component', () => {
expect(elInstance._element).toEqual(el) expect(elInstance._element).toEqual(el)
expect(selectorInstance._element).toEqual(fixtureEl.querySelector('#bar')) expect(selectorInstance._element).toEqual(fixtureEl.querySelector('#bar'))
}) })
it('should not initialize and add element record to Data (caching), if argument `element` is not an HTML element', () => {
fixtureEl.innerHTML = ''
const el = fixtureEl.querySelector('#foo')
const elInstance = new DummyClass(el)
const selectorInstance = new DummyClass('#bar')
expect(elInstance._element).not.toBeDefined()
expect(selectorInstance._element).not.toBeDefined()
}) })
})
describe('dispose', () => { describe('dispose', () => {
it('should dispose an component', () => { it('should dispose an component', () => {
createInstance() createInstance()
@@ -123,9 +143,10 @@ describe('Base Component', () => {
const div = fixtureEl.querySelector('div') const div = fixtureEl.querySelector('div')
expect(DummyClass.getInstance(div)).toEqual(null) expect(DummyClass.getInstance(div)).toBeNull()
}) })
}) })
describe('getOrCreateInstance', () => { describe('getOrCreateInstance', () => {
it('should return an instance', () => { it('should return an instance', () => {
createInstance() createInstance()
@@ -139,7 +160,7 @@ describe('Base Component', () => {
fixtureEl.innerHTML = '<div id="foo"></div>' fixtureEl.innerHTML = '<div id="foo"></div>'
element = fixtureEl.querySelector('#foo') element = fixtureEl.querySelector('#foo')
expect(DummyClass.getInstance(element)).toEqual(null) expect(DummyClass.getInstance(element)).toBeNull()
expect(DummyClass.getOrCreateInstance(element)).toBeInstanceOf(DummyClass) expect(DummyClass.getOrCreateInstance(element)).toBeInstanceOf(DummyClass)
}) })
}) })
+10 -10
View File
@@ -45,19 +45,19 @@ describe('Button', () => {
const divTest = fixtureEl.querySelector('.test') const divTest = fixtureEl.querySelector('.test')
const btnTestParent = fixtureEl.querySelector('.testParent') const btnTestParent = fixtureEl.querySelector('.testParent')
expect(btn.classList.contains('active')).toEqual(false) expect(btn).not.toHaveClass('active')
btn.click() btn.click()
expect(btn.classList.contains('active')).toEqual(true) expect(btn).toHaveClass('active')
btn.click() btn.click()
expect(btn.classList.contains('active')).toEqual(false) expect(btn).not.toHaveClass('active')
divTest.click() divTest.click()
expect(btnTestParent.classList.contains('active')).toEqual(true) expect(btnTestParent).toHaveClass('active')
}) })
}) })
@@ -69,12 +69,12 @@ describe('Button', () => {
const button = new Button(btnEl) const button = new Button(btnEl)
expect(btnEl.getAttribute('aria-pressed')).toEqual('false') expect(btnEl.getAttribute('aria-pressed')).toEqual('false')
expect(btnEl.classList.contains('active')).toEqual(false) expect(btnEl).not.toHaveClass('active')
button.toggle() button.toggle()
expect(btnEl.getAttribute('aria-pressed')).toEqual('true') expect(btnEl.getAttribute('aria-pressed')).toEqual('true')
expect(btnEl.classList.contains('active')).toEqual(true) expect(btnEl).toHaveClass('active')
}) })
}) })
@@ -121,7 +121,7 @@ describe('Button', () => {
jQueryMock.fn.button.call(jQueryMock, 'toggle') jQueryMock.fn.button.call(jQueryMock, 'toggle')
expect(Button.getInstance(btnEl)).not.toBeNull() expect(Button.getInstance(btnEl)).not.toBeNull()
expect(btnEl.classList.contains('active')).toEqual(true) expect(btnEl).toHaveClass('active')
}) })
it('should just create a button instance without calling toggle', () => { it('should just create a button instance without calling toggle', () => {
@@ -135,7 +135,7 @@ describe('Button', () => {
jQueryMock.fn.button.call(jQueryMock) jQueryMock.fn.button.call(jQueryMock)
expect(Button.getInstance(btnEl)).not.toBeNull() expect(Button.getInstance(btnEl)).not.toBeNull()
expect(btnEl.classList.contains('active')).toEqual(false) expect(btnEl).not.toHaveClass('active')
}) })
}) })
@@ -155,7 +155,7 @@ describe('Button', () => {
const div = fixtureEl.querySelector('div') const div = fixtureEl.querySelector('div')
expect(Button.getInstance(div)).toEqual(null) expect(Button.getInstance(div)).toBeNull()
}) })
}) })
@@ -176,7 +176,7 @@ describe('Button', () => {
const div = fixtureEl.querySelector('div') const div = fixtureEl.querySelector('div')
expect(Button.getInstance(div)).toEqual(null) expect(Button.getInstance(div)).toBeNull()
expect(Button.getOrCreateInstance(div)).toBeInstanceOf(Button) expect(Button.getOrCreateInstance(div)).toBeInstanceOf(Button)
}) })
}) })
+29 -30
View File
@@ -143,7 +143,7 @@ describe('Carousel', () => {
carouselEl.addEventListener('keydown', event => { carouselEl.addEventListener('keydown', event => {
expect(carousel._keydown).toHaveBeenCalled() expect(carousel._keydown).toHaveBeenCalled()
expect(event.defaultPrevented).toEqual(false) expect(event.defaultPrevented).toBeFalse()
done() done()
}) })
@@ -235,9 +235,7 @@ describe('Carousel', () => {
const carouselEl = fixtureEl.querySelector('#myCarousel') const carouselEl = fixtureEl.querySelector('#myCarousel')
const carousel = new Carousel(carouselEl, { wrap: true }) const carousel = new Carousel(carouselEl, { wrap: true })
const getActiveId = () => { const getActiveId = () => carouselEl.querySelector('.carousel-item.active').getAttribute('id')
return carouselEl.querySelector('.carousel-item.active').getAttribute('id')
}
carouselEl.addEventListener('slid.bs.carousel', event => { carouselEl.addEventListener('slid.bs.carousel', event => {
const activeId = getActiveId() const activeId = getActiveId()
@@ -285,7 +283,7 @@ describe('Carousel', () => {
carousel.prev() carousel.prev()
setTimeout(() => { setTimeout(() => {
expect(firstElement.classList.contains('active')).toEqual(true) expect(firstElement).toHaveClass('active')
done() done()
}, 10) }, 10)
}) })
@@ -368,7 +366,7 @@ describe('Carousel', () => {
spyOn(carousel, '_slide').and.callThrough() spyOn(carousel, '_slide').and.callThrough()
carouselEl.addEventListener('slid.bs.carousel', event => { carouselEl.addEventListener('slid.bs.carousel', event => {
expect(item.classList.contains('active')).toEqual(true) expect(item).toHaveClass('active')
expect(carousel._slide).toHaveBeenCalledWith('right') expect(carousel._slide).toHaveBeenCalledWith('right')
expect(event.direction).toEqual('right') expect(event.direction).toEqual('right')
stylesCarousel.remove() stylesCarousel.remove()
@@ -413,7 +411,7 @@ describe('Carousel', () => {
spyOn(carousel, '_slide').and.callThrough() spyOn(carousel, '_slide').and.callThrough()
carouselEl.addEventListener('slid.bs.carousel', event => { carouselEl.addEventListener('slid.bs.carousel', event => {
expect(item.classList.contains('active')).toEqual(false) expect(item).not.toHaveClass('active')
expect(carousel._slide).toHaveBeenCalledWith('left') expect(carousel._slide).toHaveBeenCalledWith('left')
expect(event.direction).toEqual('left') expect(event.direction).toEqual('left')
stylesCarousel.remove() stylesCarousel.remove()
@@ -453,7 +451,7 @@ describe('Carousel', () => {
spyOn(carousel, '_slide').and.callThrough() spyOn(carousel, '_slide').and.callThrough()
carouselEl.addEventListener('slid.bs.carousel', event => { carouselEl.addEventListener('slid.bs.carousel', event => {
expect(item.classList.contains('active')).toEqual(true) expect(item).toHaveClass('active')
expect(carousel._slide).toHaveBeenCalledWith('right') expect(carousel._slide).toHaveBeenCalledWith('right')
expect(event.direction).toEqual('right') expect(event.direction).toEqual('right')
delete document.documentElement.ontouchstart delete document.documentElement.ontouchstart
@@ -492,7 +490,7 @@ describe('Carousel', () => {
spyOn(carousel, '_slide').and.callThrough() spyOn(carousel, '_slide').and.callThrough()
carouselEl.addEventListener('slid.bs.carousel', event => { carouselEl.addEventListener('slid.bs.carousel', event => {
expect(item.classList.contains('active')).toEqual(false) expect(item).not.toHaveClass('active')
expect(carousel._slide).toHaveBeenCalledWith('left') expect(carousel._slide).toHaveBeenCalledWith('left')
expect(event.direction).toEqual('left') expect(event.direction).toEqual('left')
delete document.documentElement.ontouchstart delete document.documentElement.ontouchstart
@@ -632,7 +630,7 @@ describe('Carousel', () => {
const doneTest = () => { const doneTest = () => {
setTimeout(() => { setTimeout(() => {
expect(slidEvent).toEqual(false) expect(slidEvent).toBeFalse()
done() done()
}, 20) }, 20)
} }
@@ -665,7 +663,7 @@ describe('Carousel', () => {
const onSlide = event => { const onSlide = event => {
expect(event.direction).toEqual('left') expect(event.direction).toEqual('left')
expect(event.relatedTarget.classList.contains('carousel-item')).toEqual(true) expect(event.relatedTarget).toHaveClass('carousel-item')
expect(event.from).toEqual(0) expect(event.from).toEqual(0)
expect(event.to).toEqual(1) expect(event.to).toEqual(1)
@@ -700,7 +698,7 @@ describe('Carousel', () => {
const onSlid = event => { const onSlid = event => {
expect(event.direction).toEqual('left') expect(event.direction).toEqual('left')
expect(event.relatedTarget.classList.contains('carousel-item')).toEqual(true) expect(event.relatedTarget).toHaveClass('carousel-item')
expect(event.from).toEqual(0) expect(event.from).toEqual(0)
expect(event.to).toEqual(1) expect(event.to).toEqual(1)
@@ -761,9 +759,9 @@ describe('Carousel', () => {
const carousel = new Carousel(carouselEl) const carousel = new Carousel(carouselEl)
carouselEl.addEventListener('slid.bs.carousel', () => { carouselEl.addEventListener('slid.bs.carousel', () => {
expect(firstIndicator.classList.contains('active')).toEqual(false) expect(firstIndicator).not.toHaveClass('active')
expect(firstIndicator.hasAttribute('aria-current')).toEqual(false) expect(firstIndicator.hasAttribute('aria-current')).toBeFalse()
expect(secondIndicator.classList.contains('active')).toEqual(true) expect(secondIndicator).toHaveClass('active')
expect(secondIndicator.getAttribute('aria-current')).toEqual('true') expect(secondIndicator.getAttribute('aria-current')).toEqual('true')
done() done()
}) })
@@ -859,7 +857,7 @@ describe('Carousel', () => {
expect(carousel.cycle).toHaveBeenCalledWith(true) expect(carousel.cycle).toHaveBeenCalledWith(true)
expect(window.clearInterval).toHaveBeenCalled() expect(window.clearInterval).toHaveBeenCalled()
expect(carousel._isPaused).toEqual(true) expect(carousel._isPaused).toBeTrue()
}) })
it('should not call cycle if nothing is in transition', () => { it('should not call cycle if nothing is in transition', () => {
@@ -885,7 +883,7 @@ describe('Carousel', () => {
expect(carousel.cycle).not.toHaveBeenCalled() expect(carousel.cycle).not.toHaveBeenCalled()
expect(window.clearInterval).toHaveBeenCalled() expect(window.clearInterval).toHaveBeenCalled()
expect(carousel._isPaused).toEqual(true) expect(carousel._isPaused).toBeTrue()
}) })
it('should not set is paused at true if an event is passed', () => { it('should not set is paused at true if an event is passed', () => {
@@ -910,7 +908,7 @@ describe('Carousel', () => {
carousel.pause(event) carousel.pause(event)
expect(window.clearInterval).toHaveBeenCalled() expect(window.clearInterval).toHaveBeenCalled()
expect(carousel._isPaused).toEqual(false) expect(carousel._isPaused).toBeFalse()
}) })
}) })
@@ -1153,6 +1151,7 @@ describe('Carousel', () => {
}) })
}) })
}) })
describe('rtl function', () => { describe('rtl function', () => {
it('"_directionToOrder" and "_orderToDirection" must return the right results', () => { it('"_directionToOrder" and "_orderToDirection" must return the right results', () => {
fixtureEl.innerHTML = '<div></div>' fixtureEl.innerHTML = '<div></div>'
@@ -1175,7 +1174,7 @@ describe('Carousel', () => {
const carouselEl = fixtureEl.querySelector('div') const carouselEl = fixtureEl.querySelector('div')
const carousel = new Carousel(carouselEl, {}) const carousel = new Carousel(carouselEl, {})
expect(isRTL()).toEqual(true, 'rtl has to be true') expect(isRTL()).toBeTrue()
expect(carousel._directionToOrder('left')).toEqual('prev') expect(carousel._directionToOrder('left')).toEqual('prev')
expect(carousel._directionToOrder('prev')).toEqual('prev') expect(carousel._directionToOrder('prev')).toEqual('prev')
@@ -1292,7 +1291,7 @@ describe('Carousel', () => {
const div = fixtureEl.querySelector('div') const div = fixtureEl.querySelector('div')
expect(Carousel.getInstance(div)).toEqual(null) expect(Carousel.getInstance(div)).toBeNull()
}) })
}) })
@@ -1313,7 +1312,7 @@ describe('Carousel', () => {
const div = fixtureEl.querySelector('div') const div = fixtureEl.querySelector('div')
expect(Carousel.getInstance(div)).toEqual(null) expect(Carousel.getInstance(div)).toBeNull()
expect(Carousel.getOrCreateInstance(div)).toBeInstanceOf(Carousel) expect(Carousel.getOrCreateInstance(div)).toBeInstanceOf(Carousel)
}) })
@@ -1322,7 +1321,7 @@ describe('Carousel', () => {
const div = fixtureEl.querySelector('div') const div = fixtureEl.querySelector('div')
expect(Carousel.getInstance(div)).toEqual(null) expect(Carousel.getInstance(div)).toBeNull()
const carousel = Carousel.getOrCreateInstance(div, { const carousel = Carousel.getOrCreateInstance(div, {
interval: 1 interval: 1
}) })
@@ -1431,7 +1430,7 @@ describe('Carousel', () => {
' <div class="carousel-item">item 3</div>', ' <div class="carousel-item">item 3</div>',
' </div>', ' </div>',
' <button class="carousel-control-prev" data-bs-target="#myCarousel" type="button" data-bs-slide="prev"></button>', ' <button class="carousel-control-prev" data-bs-target="#myCarousel" type="button" data-bs-slide="prev"></button>',
' <button id="next" class="carousel-control-next" data-bs-target="#myCarousel" type="button" data-bs-slide="next"></div>', ' <button id="next" class="carousel-control-next" data-bs-target="#myCarousel" type="button" data-bs-slide="next"></button>',
'</div>' '</div>'
].join('') ].join('')
@@ -1441,7 +1440,7 @@ describe('Carousel', () => {
next.click() next.click()
setTimeout(() => { setTimeout(() => {
expect(item2.classList.contains('active')).toEqual(true) expect(item2).toHaveClass('active')
done() done()
}, 10) }, 10)
}) })
@@ -1454,8 +1453,8 @@ describe('Carousel', () => {
' <div id="item2" class="carousel-item">item 2</div>', ' <div id="item2" class="carousel-item">item 2</div>',
' <div class="carousel-item">item 3</div>', ' <div class="carousel-item">item 3</div>',
' </div>', ' </div>',
' <a class="carousel-control-prev" href="#myCarousel" role="button" data-bs-slide="prev"></button>', ' <a class="carousel-control-prev" href="#myCarousel" role="button" data-bs-slide="prev"></a>',
' <a id="next" class="carousel-control-next" href="#myCarousel" role="button" data-bs-slide="next"></div>', ' <a id="next" class="carousel-control-next" href="#myCarousel" role="button" data-bs-slide="next"></a>',
'</div>' '</div>'
].join('') ].join('')
@@ -1465,7 +1464,7 @@ describe('Carousel', () => {
next.click() next.click()
setTimeout(() => { setTimeout(() => {
expect(item2.classList.contains('active')).toEqual(true) expect(item2).toHaveClass('active')
done() done()
}, 10) }, 10)
}) })
@@ -1488,7 +1487,7 @@ describe('Carousel', () => {
next.click() next.click()
setTimeout(() => { setTimeout(() => {
expect(item2.classList.contains('active')).toEqual(true) expect(item2).toHaveClass('active')
done() done()
}, 10) }, 10)
}) })
@@ -1521,8 +1520,8 @@ describe('Carousel', () => {
' <div id="item2" class="carousel-item">item 2</div>', ' <div id="item2" class="carousel-item">item 2</div>',
' <div class="carousel-item">item 3</div>', ' <div class="carousel-item">item 3</div>',
' </div>', ' </div>',
' <button class="carousel-control-prev" data-bs-target="#myCarousel" type="button" data-bs-slide="prev"></div>', ' <button class="carousel-control-prev" data-bs-target="#myCarousel" type="button" data-bs-slide="prev"></button>',
' <button id="next" class="carousel-control-next" data-bs-target="#myCarousel" type="button" data-bs-slide="next"></div>', ' <button id="next" class="carousel-control-next" data-bs-target="#myCarousel" type="button" data-bs-slide="next"></button>',
'</div>' '</div>'
].join('') ].join('')
+72 -71
View File
@@ -159,8 +159,8 @@ describe('Collapse', () => {
})) }))
collapseEl2.addEventListener('shown.bs.collapse', () => { collapseEl2.addEventListener('shown.bs.collapse', () => {
expect(collapseEl2.classList.contains('show')).toEqual(true) expect(collapseEl2).toHaveClass('show')
expect(collapseEl1.classList.contains('show')).toEqual(false) expect(collapseEl1).not.toHaveClass('show')
done() done()
}) })
@@ -212,7 +212,7 @@ describe('Collapse', () => {
expect(collapseEl.style.height).toEqual('0px') expect(collapseEl.style.height).toEqual('0px')
}) })
collapseEl.addEventListener('shown.bs.collapse', () => { collapseEl.addEventListener('shown.bs.collapse', () => {
expect(collapseEl.classList.contains('show')).toEqual(true) expect(collapseEl).toHaveClass('show')
expect(collapseEl.style.height).toEqual('') expect(collapseEl.style.height).toEqual('')
done() done()
}) })
@@ -232,7 +232,7 @@ describe('Collapse', () => {
expect(collapseEl.style.width).toEqual('0px') expect(collapseEl.style.width).toEqual('0px')
}) })
collapseEl.addEventListener('shown.bs.collapse', () => { collapseEl.addEventListener('shown.bs.collapse', () => {
expect(collapseEl.classList.contains('show')).toEqual(true) expect(collapseEl).toHaveClass('show')
expect(collapseEl.style.width).toEqual('') expect(collapseEl.style.width).toEqual('')
done() done()
}) })
@@ -257,8 +257,8 @@ describe('Collapse', () => {
}) })
el1.addEventListener('shown.bs.collapse', () => { el1.addEventListener('shown.bs.collapse', () => {
expect(el1.classList.contains('show')).toEqual(true) expect(el1).toHaveClass('show')
expect(el2.classList.contains('show')).toEqual(true) expect(el2).toHaveClass('show')
done() done()
}) })
@@ -307,21 +307,22 @@ describe('Collapse', () => {
const childCollapseEl2 = el('#childContent2') const childCollapseEl2 = el('#childContent2')
parentCollapseEl.addEventListener('shown.bs.collapse', () => { parentCollapseEl.addEventListener('shown.bs.collapse', () => {
expect(parentCollapseEl.classList.contains('show')).toEqual(true) expect(parentCollapseEl).toHaveClass('show')
childBtn1.click() childBtn1.click()
}) })
childCollapseEl1.addEventListener('shown.bs.collapse', () => { childCollapseEl1.addEventListener('shown.bs.collapse', () => {
expect(childCollapseEl1.classList.contains('show')).toEqual(true) expect(childCollapseEl1).toHaveClass('show')
childBtn2.click() childBtn2.click()
}) })
childCollapseEl2.addEventListener('shown.bs.collapse', () => { childCollapseEl2.addEventListener('shown.bs.collapse', () => {
expect(childCollapseEl2.classList.contains('show')).toEqual(true) expect(childCollapseEl2).toHaveClass('show')
expect(childCollapseEl1.classList.contains('show')).toEqual(false) expect(childCollapseEl1).not.toHaveClass('show')
done() done()
}) })
parentBtn.click() parentBtn.click()
}) })
it('should not change tab tabpanels descendants on accordion', done => { it('should not change tab tabpanels descendants on accordion', done => {
fixtureEl.innerHTML = [ fixtureEl.innerHTML = [
'<div class="accordion" id="accordionExample">', '<div class="accordion" id="accordionExample">',
@@ -346,7 +347,7 @@ describe('Collapse', () => {
' </div>', ' </div>',
' </div>', ' </div>',
' </div>', ' </div>',
' </div>' '</div>'
].join('') ].join('')
const el = fixtureEl.querySelector('#collapseOne') const el = fixtureEl.querySelector('#collapseOne')
@@ -359,7 +360,7 @@ describe('Collapse', () => {
}) })
el.addEventListener('shown.bs.collapse', () => { el.addEventListener('shown.bs.collapse', () => {
expect(activeTabPane.classList.contains('show')).toEqual(true) expect(activeTabPane).toHaveClass('show')
times++ times++
if (times === 2) { if (times === 2) {
done() done()
@@ -440,7 +441,7 @@ describe('Collapse', () => {
}) })
collapseEl.addEventListener('hidden.bs.collapse', () => { collapseEl.addEventListener('hidden.bs.collapse', () => {
expect(collapseEl.classList.contains('show')).toEqual(false) expect(collapseEl).not.toHaveClass('show')
expect(collapseEl.style.height).toEqual('') expect(collapseEl.style.height).toEqual('')
done() done()
}) })
@@ -489,7 +490,7 @@ describe('Collapse', () => {
collapse.dispose() collapse.dispose()
expect(Collapse.getInstance(collapseEl)).toEqual(null) expect(Collapse.getInstance(collapseEl)).toBeNull()
}) })
}) })
@@ -508,8 +509,8 @@ describe('Collapse', () => {
spyOn(Event.prototype, 'preventDefault').and.callThrough() spyOn(Event.prototype, 'preventDefault').and.callThrough()
triggerEl.addEventListener('click', event => { triggerEl.addEventListener('click', event => {
expect(event.target.isEqualNode(nestedTriggerEl)).toEqual(true) expect(event.target.isEqualNode(nestedTriggerEl)).toBeTrue()
expect(event.delegateTarget.isEqualNode(triggerEl)).toEqual(true) expect(event.delegateTarget.isEqualNode(triggerEl)).toBeTrue()
expect(Event.prototype.preventDefault).toHaveBeenCalled() expect(Event.prototype.preventDefault).toHaveBeenCalled()
done() done()
}) })
@@ -530,9 +531,9 @@ describe('Collapse', () => {
collapse2.addEventListener('shown.bs.collapse', () => { collapse2.addEventListener('shown.bs.collapse', () => {
expect(trigger.getAttribute('aria-expanded')).toEqual('true') expect(trigger.getAttribute('aria-expanded')).toEqual('true')
expect(trigger.classList.contains('collapsed')).toEqual(false) expect(trigger).not.toHaveClass('collapsed')
expect(collapse1.classList.contains('show')).toEqual(true) expect(collapse1).toHaveClass('show')
expect(collapse1.classList.contains('show')).toEqual(true) expect(collapse1).toHaveClass('show')
done() done()
}) })
@@ -552,9 +553,9 @@ describe('Collapse', () => {
collapse2.addEventListener('hidden.bs.collapse', () => { collapse2.addEventListener('hidden.bs.collapse', () => {
expect(trigger.getAttribute('aria-expanded')).toEqual('false') expect(trigger.getAttribute('aria-expanded')).toEqual('false')
expect(trigger.classList.contains('collapsed')).toEqual(true) expect(trigger).toHaveClass('collapsed')
expect(collapse1.classList.contains('show')).toEqual(false) expect(collapse1).not.toHaveClass('show')
expect(collapse1.classList.contains('show')).toEqual(false) expect(collapse1).not.toHaveClass('show')
done() done()
}) })
@@ -575,8 +576,8 @@ describe('Collapse', () => {
collapseTest1.addEventListener('shown.bs.collapse', () => { collapseTest1.addEventListener('shown.bs.collapse', () => {
expect(link1.getAttribute('aria-expanded')).toEqual('true') expect(link1.getAttribute('aria-expanded')).toEqual('true')
expect(link2.getAttribute('aria-expanded')).toEqual('true') expect(link2.getAttribute('aria-expanded')).toEqual('true')
expect(link1.classList.contains('collapsed')).toEqual(false) expect(link1).not.toHaveClass('collapsed')
expect(link2.classList.contains('collapsed')).toEqual(false) expect(link2).not.toHaveClass('collapsed')
done() done()
}) })
@@ -597,8 +598,8 @@ describe('Collapse', () => {
collapseTest1.addEventListener('hidden.bs.collapse', () => { collapseTest1.addEventListener('hidden.bs.collapse', () => {
expect(link1.getAttribute('aria-expanded')).toEqual('false') expect(link1.getAttribute('aria-expanded')).toEqual('false')
expect(link2.getAttribute('aria-expanded')).toEqual('false') expect(link2.getAttribute('aria-expanded')).toEqual('false')
expect(link1.classList.contains('collapsed')).toEqual(true) expect(link1).toHaveClass('collapsed')
expect(link2.classList.contains('collapsed')).toEqual(true) expect(link2).toHaveClass('collapsed')
done() done()
}) })
@@ -625,12 +626,12 @@ describe('Collapse', () => {
const collapseTwo = fixtureEl.querySelector('#collapseTwo') const collapseTwo = fixtureEl.querySelector('#collapseTwo')
collapseOne.addEventListener('shown.bs.collapse', () => { collapseOne.addEventListener('shown.bs.collapse', () => {
expect(collapseOne.classList.contains('show')).toEqual(true) expect(collapseOne).toHaveClass('show')
expect(collapseTwo.classList.contains('show')).toEqual(false) expect(collapseTwo).not.toHaveClass('show')
collapseTwo.addEventListener('shown.bs.collapse', () => { collapseTwo.addEventListener('shown.bs.collapse', () => {
expect(collapseOne.classList.contains('show')).toEqual(false) expect(collapseOne).not.toHaveClass('show')
expect(collapseTwo.classList.contains('show')).toEqual(true) expect(collapseTwo).toHaveClass('show')
done() done()
}) })
@@ -650,8 +651,8 @@ describe('Collapse', () => {
const collapseEl = fixtureEl.querySelector('#collapsediv1') const collapseEl = fixtureEl.querySelector('#collapsediv1')
collapseEl.addEventListener('shown.bs.collapse', () => { collapseEl.addEventListener('shown.bs.collapse', () => {
expect(collapseEl.classList.contains('show')).toEqual(true) expect(collapseEl).toHaveClass('show')
expect(target.checked).toEqual(true) expect(target.checked).toBeTrue()
done() done()
}) })
@@ -684,21 +685,21 @@ describe('Collapse', () => {
const collapseTwoEl = fixtureEl.querySelector('#collapseTwo') const collapseTwoEl = fixtureEl.querySelector('#collapseTwo')
collapseOneEl.addEventListener('shown.bs.collapse', () => { collapseOneEl.addEventListener('shown.bs.collapse', () => {
expect(collapseOneEl.classList.contains('show')).toEqual(true) expect(collapseOneEl).toHaveClass('show')
expect(triggerEl.classList.contains('collapsed')).toEqual(false) expect(triggerEl).not.toHaveClass('collapsed')
expect(triggerEl.getAttribute('aria-expanded')).toEqual('true') expect(triggerEl.getAttribute('aria-expanded')).toEqual('true')
expect(collapseTwoEl.classList.contains('show')).toEqual(false) expect(collapseTwoEl).not.toHaveClass('show')
expect(triggerTwoEl.classList.contains('collapsed')).toEqual(true) expect(triggerTwoEl).toHaveClass('collapsed')
expect(triggerTwoEl.getAttribute('aria-expanded')).toEqual('false') expect(triggerTwoEl.getAttribute('aria-expanded')).toEqual('false')
collapseTwoEl.addEventListener('shown.bs.collapse', () => { collapseTwoEl.addEventListener('shown.bs.collapse', () => {
expect(collapseOneEl.classList.contains('show')).toEqual(false) expect(collapseOneEl).not.toHaveClass('show')
expect(triggerEl.classList.contains('collapsed')).toEqual(true) expect(triggerEl).toHaveClass('collapsed')
expect(triggerEl.getAttribute('aria-expanded')).toEqual('false') expect(triggerEl.getAttribute('aria-expanded')).toEqual('false')
expect(collapseTwoEl.classList.contains('show')).toEqual(true) expect(collapseTwoEl).toHaveClass('show')
expect(triggerTwoEl.classList.contains('collapsed')).toEqual(false) expect(triggerTwoEl).not.toHaveClass('collapsed')
expect(triggerTwoEl.getAttribute('aria-expanded')).toEqual('true') expect(triggerTwoEl.getAttribute('aria-expanded')).toEqual('true')
done() done()
}) })
@@ -733,21 +734,21 @@ describe('Collapse', () => {
} }
function firstTest() { function firstTest() {
expect(collapseOneOne.classList.contains('show')).toEqual(true) expect(collapseOneOne).toHaveClass('show')
expect(collapseOneTwo.classList.contains('show')).toEqual(true) expect(collapseOneTwo).toHaveClass('show')
expect(collapseTwoOne.classList.contains('show')).toEqual(false) expect(collapseTwoOne).not.toHaveClass('show')
expect(collapseTwoTwo.classList.contains('show')).toEqual(false) expect(collapseTwoTwo).not.toHaveClass('show')
triggerTwo.click() triggerTwo.click()
} }
function secondTest() { function secondTest() {
expect(collapseOneOne.classList.contains('show')).toEqual(false) expect(collapseOneOne).not.toHaveClass('show')
expect(collapseOneTwo.classList.contains('show')).toEqual(false) expect(collapseOneTwo).not.toHaveClass('show')
expect(collapseTwoOne.classList.contains('show')).toEqual(true) expect(collapseTwoOne).toHaveClass('show')
expect(collapseTwoTwo.classList.contains('show')).toEqual(true) expect(collapseTwoTwo).toHaveClass('show')
done() done()
} }
@@ -815,9 +816,9 @@ describe('Collapse', () => {
const nestedCollapseOne = fixtureEl.querySelector('#nestedCollapseOne') const nestedCollapseOne = fixtureEl.querySelector('#nestedCollapseOne')
function handlerCollapseOne() { function handlerCollapseOne() {
expect(collapseOne.classList.contains('show')).toEqual(true) expect(collapseOne).toHaveClass('show')
expect(collapseTwo.classList.contains('show')).toEqual(false) expect(collapseTwo).not.toHaveClass('show')
expect(nestedCollapseOne.classList.contains('show')).toEqual(false) expect(nestedCollapseOne).not.toHaveClass('show')
nestedCollapseOne.addEventListener('shown.bs.collapse', handlerNestedCollapseOne) nestedCollapseOne.addEventListener('shown.bs.collapse', handlerNestedCollapseOne)
nestedTrigger.click() nestedTrigger.click()
@@ -825,14 +826,14 @@ describe('Collapse', () => {
} }
function handlerNestedCollapseOne() { function handlerNestedCollapseOne() {
expect(collapseOne.classList.contains('show')).toEqual(true) expect(collapseOne).toHaveClass('show')
expect(collapseTwo.classList.contains('show')).toEqual(false) expect(collapseTwo).not.toHaveClass('show')
expect(nestedCollapseOne.classList.contains('show')).toEqual(true) expect(nestedCollapseOne).toHaveClass('show')
collapseTwo.addEventListener('shown.bs.collapse', () => { collapseTwo.addEventListener('shown.bs.collapse', () => {
expect(collapseOne.classList.contains('show')).toEqual(false) expect(collapseOne).not.toHaveClass('show')
expect(collapseTwo.classList.contains('show')).toEqual(true) expect(collapseTwo).toHaveClass('show')
expect(nestedCollapseOne.classList.contains('show')).toEqual(true) expect(nestedCollapseOne).toHaveClass('show')
done() done()
}) })
@@ -860,33 +861,33 @@ describe('Collapse', () => {
const target2 = fixtureEl.querySelector('#test2') const target2 = fixtureEl.querySelector('#test2')
const target2Shown = () => { const target2Shown = () => {
expect(trigger1.classList.contains('collapsed')).toEqual(false) expect(trigger1).not.toHaveClass('collapsed')
expect(trigger1.getAttribute('aria-expanded')).toEqual('true') expect(trigger1.getAttribute('aria-expanded')).toEqual('true')
expect(trigger2.classList.contains('collapsed')).toEqual(false) expect(trigger2).not.toHaveClass('collapsed')
expect(trigger2.getAttribute('aria-expanded')).toEqual('true') expect(trigger2.getAttribute('aria-expanded')).toEqual('true')
expect(trigger3.classList.contains('collapsed')).toEqual(false) expect(trigger3).not.toHaveClass('collapsed')
expect(trigger3.getAttribute('aria-expanded')).toEqual('true') expect(trigger3.getAttribute('aria-expanded')).toEqual('true')
target2.addEventListener('hidden.bs.collapse', () => { target2.addEventListener('hidden.bs.collapse', () => {
expect(trigger1.classList.contains('collapsed')).toEqual(false) expect(trigger1).not.toHaveClass('collapsed')
expect(trigger1.getAttribute('aria-expanded')).toEqual('true') expect(trigger1.getAttribute('aria-expanded')).toEqual('true')
expect(trigger2.classList.contains('collapsed')).toEqual(true) expect(trigger2).toHaveClass('collapsed')
expect(trigger2.getAttribute('aria-expanded')).toEqual('false') expect(trigger2.getAttribute('aria-expanded')).toEqual('false')
expect(trigger3.classList.contains('collapsed')).toEqual(false) expect(trigger3).not.toHaveClass('collapsed')
expect(trigger3.getAttribute('aria-expanded')).toEqual('true') expect(trigger3.getAttribute('aria-expanded')).toEqual('true')
target1.addEventListener('hidden.bs.collapse', () => { target1.addEventListener('hidden.bs.collapse', () => {
expect(trigger1.classList.contains('collapsed')).toEqual(true) expect(trigger1).toHaveClass('collapsed')
expect(trigger1.getAttribute('aria-expanded')).toEqual('false') expect(trigger1.getAttribute('aria-expanded')).toEqual('false')
expect(trigger2.classList.contains('collapsed')).toEqual(true) expect(trigger2).toHaveClass('collapsed')
expect(trigger2.getAttribute('aria-expanded')).toEqual('false') expect(trigger2.getAttribute('aria-expanded')).toEqual('false')
expect(trigger3.classList.contains('collapsed')).toEqual(true) expect(trigger3).toHaveClass('collapsed')
expect(trigger3.getAttribute('aria-expanded')).toEqual('false') expect(trigger3.getAttribute('aria-expanded')).toEqual('false')
done() done()
}) })
@@ -961,7 +962,7 @@ describe('Collapse', () => {
const div = fixtureEl.querySelector('div') const div = fixtureEl.querySelector('div')
expect(Collapse.getInstance(div)).toEqual(null) expect(Collapse.getInstance(div)).toBeNull()
}) })
}) })
@@ -982,7 +983,7 @@ describe('Collapse', () => {
const div = fixtureEl.querySelector('div') const div = fixtureEl.querySelector('div')
expect(Collapse.getInstance(div)).toEqual(null) expect(Collapse.getInstance(div)).toBeNull()
expect(Collapse.getOrCreateInstance(div)).toBeInstanceOf(Collapse) expect(Collapse.getOrCreateInstance(div)).toBeInstanceOf(Collapse)
}) })
@@ -991,13 +992,13 @@ describe('Collapse', () => {
const div = fixtureEl.querySelector('div') const div = fixtureEl.querySelector('div')
expect(Collapse.getInstance(div)).toEqual(null) expect(Collapse.getInstance(div)).toBeNull()
const collapse = Collapse.getOrCreateInstance(div, { const collapse = Collapse.getOrCreateInstance(div, {
toggle: false toggle: false
}) })
expect(collapse).toBeInstanceOf(Collapse) expect(collapse).toBeInstanceOf(Collapse)
expect(collapse._config.toggle).toEqual(false) expect(collapse._config.toggle).toBeFalse()
}) })
it('should return the instance when exists without given configuration', () => { it('should return the instance when exists without given configuration', () => {
@@ -1015,7 +1016,7 @@ describe('Collapse', () => {
expect(collapse).toBeInstanceOf(Collapse) expect(collapse).toBeInstanceOf(Collapse)
expect(collapse2).toEqual(collapse) expect(collapse2).toEqual(collapse)
expect(collapse2._config.toggle).toEqual(false) expect(collapse2._config.toggle).toBeFalse()
}) })
}) })
}) })
+5 -4
View File
@@ -50,7 +50,7 @@ describe('Data', () => {
Data.set(div, TEST_KEY, data) Data.set(div, TEST_KEY, data)
expect(Data.get(div, TEST_KEY)).toBe(data) expect(Data.get(div, TEST_KEY)).toEqual(data)
}) })
it('should overwrite data if something is already stored', () => { it('should overwrite data if something is already stored', () => {
@@ -60,11 +60,12 @@ describe('Data', () => {
Data.set(div, TEST_KEY, data) Data.set(div, TEST_KEY, data)
Data.set(div, TEST_KEY, copy) Data.set(div, TEST_KEY, copy)
// Using `toBe` since spread creates a shallow copy
expect(Data.get(div, TEST_KEY)).not.toBe(data) expect(Data.get(div, TEST_KEY)).not.toBe(data)
expect(Data.get(div, TEST_KEY)).toBe(copy) expect(Data.get(div, TEST_KEY)).toBe(copy)
}) })
it('should do nothing when an element have nothing stored', () => { it('should do nothing when an element has nothing stored', () => {
Data.remove(div, TEST_KEY) Data.remove(div, TEST_KEY)
expect().nothing() expect().nothing()
@@ -76,7 +77,7 @@ describe('Data', () => {
Data.set(div, TEST_KEY, data) Data.set(div, TEST_KEY, data)
Data.remove(div, UNKNOWN_KEY) Data.remove(div, UNKNOWN_KEY)
expect(Data.get(div, TEST_KEY)).toBe(data) expect(Data.get(div, TEST_KEY)).toEqual(data)
}) })
it('should remove data for a given key', () => { it('should remove data for a given key', () => {
@@ -99,7 +100,7 @@ describe('Data', () => {
Data.set(div, UNKNOWN_KEY, copy) Data.set(div, UNKNOWN_KEY, copy)
expect(console.error).toHaveBeenCalled() expect(console.error).toHaveBeenCalled()
expect(Data.get(div, UNKNOWN_KEY)).toBe(null) expect(Data.get(div, UNKNOWN_KEY)).toBeNull()
}) })
/* eslint-enable no-console */ /* eslint-enable no-console */
}) })
+8 -8
View File
@@ -105,10 +105,10 @@ describe('EventHandler', () => {
EventHandler.on(outer, 'mouseleave', '.inner', delegateLeaveSpy) EventHandler.on(outer, 'mouseleave', '.inner', delegateLeaveSpy)
EventHandler.on(sibling, 'mouseenter', () => { EventHandler.on(sibling, 'mouseenter', () => {
expect(enterSpy.calls.count()).toBe(2) expect(enterSpy.calls.count()).toEqual(2)
expect(leaveSpy.calls.count()).toBe(2) expect(leaveSpy.calls.count()).toEqual(2)
expect(delegateEnterSpy.calls.count()).toBe(2) expect(delegateEnterSpy.calls.count()).toEqual(2)
expect(delegateLeaveSpy.calls.count()).toBe(2) expect(delegateLeaveSpy.calls.count()).toEqual(2)
done() done()
}) })
@@ -133,10 +133,10 @@ describe('EventHandler', () => {
moveMouse(inner, outer) moveMouse(inner, outer)
setTimeout(() => { setTimeout(() => {
expect(enterSpy.calls.count()).toBe(1) expect(enterSpy.calls.count()).toEqual(1)
expect(leaveSpy.calls.count()).toBe(1) expect(leaveSpy.calls.count()).toEqual(1)
expect(delegateEnterSpy.calls.count()).toBe(1) expect(delegateEnterSpy.calls.count()).toEqual(1)
expect(delegateLeaveSpy.calls.count()).toBe(1) expect(delegateLeaveSpy.calls.count()).toEqual(1)
// from outer to inner to sibling (adjacent) // from outer to inner to sibling (adjacent)
moveMouse(outer, inner) moveMouse(outer, inner)
+4 -4
View File
@@ -96,10 +96,10 @@ describe('Manipulator', () => {
const div = fixtureEl.querySelector('div') const div = fixtureEl.querySelector('div')
expect(Manipulator.getDataAttribute(div, 'test')).toEqual(false) expect(Manipulator.getDataAttribute(div, 'test')).toBeFalse()
div.setAttribute('data-bs-test', 'true') div.setAttribute('data-bs-test', 'true')
expect(Manipulator.getDataAttribute(div, 'test')).toEqual(true) expect(Manipulator.getDataAttribute(div, 'test')).toBeTrue()
div.setAttribute('data-bs-test', '1') div.setAttribute('data-bs-test', '1')
expect(Manipulator.getDataAttribute(div, 'test')).toEqual(1) expect(Manipulator.getDataAttribute(div, 'test')).toEqual(1)
@@ -152,8 +152,8 @@ describe('Manipulator', () => {
body.append(forceScrollBars) body.append(forceScrollBars)
const scrollHandler = () => { const scrollHandler = () => {
expect(window.pageYOffset).toBe(scrollY) expect(window.pageYOffset).toEqual(scrollY)
expect(window.pageXOffset).toBe(scrollX) expect(window.pageXOffset).toEqual(scrollX)
const newOffset = Manipulator.offset(div) const newOffset = Manipulator.offset(div)
+5 -13
View File
@@ -73,7 +73,7 @@ describe('SelectorEngine', () => {
describe('parents', () => { describe('parents', () => {
it('should return parents', () => { it('should return parents', () => {
expect(SelectorEngine.parents(fixtureEl, 'body').length).toEqual(1) expect(SelectorEngine.parents(fixtureEl, 'body')).toHaveSize(1)
}) })
}) })
@@ -197,9 +197,7 @@ describe('SelectorEngine', () => {
}) })
it('should return not return elements with negative tab index', () => { it('should return not return elements with negative tab index', () => {
fixtureEl.innerHTML = [ fixtureEl.innerHTML = '<button tabindex="-1">lorem</button>'
'<button tabindex="-1">lorem</button>'
].join('')
const expectedElements = [] const expectedElements = []
@@ -207,9 +205,7 @@ describe('SelectorEngine', () => {
}) })
it('should return contenteditable elements', () => { it('should return contenteditable elements', () => {
fixtureEl.innerHTML = [ fixtureEl.innerHTML = '<div contenteditable="true">lorem</div>'
'<div contenteditable="true">lorem</div>'
].join('')
const expectedElements = [fixtureEl.querySelector('[contenteditable="true"]')] const expectedElements = [fixtureEl.querySelector('[contenteditable="true"]')]
@@ -217,9 +213,7 @@ describe('SelectorEngine', () => {
}) })
it('should not return disabled elements', () => { it('should not return disabled elements', () => {
fixtureEl.innerHTML = [ fixtureEl.innerHTML = '<button disabled="true">lorem</button>'
'<button disabled="true">lorem</button>'
].join('')
const expectedElements = [] const expectedElements = []
@@ -227,9 +221,7 @@ describe('SelectorEngine', () => {
}) })
it('should not return invisible elements', () => { it('should not return invisible elements', () => {
fixtureEl.innerHTML = [ fixtureEl.innerHTML = '<button style="display:none;">lorem</button>'
'<button style="display:none;">lorem</button>'
].join('')
const expectedElements = [] const expectedElements = []
+81 -100
View File
@@ -130,7 +130,7 @@ describe('Dropdown', () => {
it('should allow to pass config to Popper with `popperConfig` as a function', () => { it('should allow to pass config to Popper with `popperConfig` as a function', () => {
fixtureEl.innerHTML = [ fixtureEl.innerHTML = [
'<div class="dropdown">', '<div class="dropdown">',
' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" data-bs-placement="right" >Dropdown</button>', ' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" data-bs-placement="right">Dropdown</button>',
' <div class="dropdown-menu">', ' <div class="dropdown-menu">',
' <a class="dropdown-item" href="#">Secondary link</a>', ' <a class="dropdown-item" href="#">Secondary link</a>',
' </div>', ' </div>',
@@ -165,7 +165,7 @@ describe('Dropdown', () => {
const dropdown = new Dropdown(btnDropdown) const dropdown = new Dropdown(btnDropdown)
btnDropdown.addEventListener('shown.bs.dropdown', () => { btnDropdown.addEventListener('shown.bs.dropdown', () => {
expect(btnDropdown.classList.contains('show')).toEqual(true) expect(btnDropdown).toHaveClass('show')
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true') expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
done() done()
}) })
@@ -196,7 +196,7 @@ describe('Dropdown', () => {
const dropdown1 = new Dropdown(btnDropdown1) const dropdown1 = new Dropdown(btnDropdown1)
firstDropdownEl.addEventListener('shown.bs.dropdown', () => { firstDropdownEl.addEventListener('shown.bs.dropdown', () => {
expect(btnDropdown1.classList.contains('show')).toEqual(true) expect(btnDropdown1).toHaveClass('show')
spyOn(dropdown1._popper, 'destroy') spyOn(dropdown1._popper, 'destroy')
btnDropdown2.click() btnDropdown2.click()
}) })
@@ -228,7 +228,7 @@ describe('Dropdown', () => {
spyOn(EventHandler, 'off') spyOn(EventHandler, 'off')
btnDropdown.addEventListener('shown.bs.dropdown', () => { btnDropdown.addEventListener('shown.bs.dropdown', () => {
expect(btnDropdown.classList.contains('show')).toEqual(true) expect(btnDropdown).toHaveClass('show')
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true') expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
expect(EventHandler.on).toHaveBeenCalledWith(jasmine.any(Object), 'mouseover', noop) expect(EventHandler.on).toHaveBeenCalledWith(jasmine.any(Object), 'mouseover', noop)
@@ -236,7 +236,7 @@ describe('Dropdown', () => {
}) })
btnDropdown.addEventListener('hidden.bs.dropdown', () => { btnDropdown.addEventListener('hidden.bs.dropdown', () => {
expect(btnDropdown.classList.contains('show')).toEqual(false) expect(btnDropdown).not.toHaveClass('show')
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('false') expect(btnDropdown.getAttribute('aria-expanded')).toEqual('false')
expect(EventHandler.off).toHaveBeenCalledWith(jasmine.any(Object), 'mouseover', noop) expect(EventHandler.off).toHaveBeenCalledWith(jasmine.any(Object), 'mouseover', noop)
@@ -261,7 +261,7 @@ describe('Dropdown', () => {
const dropdown = new Dropdown(btnDropdown) const dropdown = new Dropdown(btnDropdown)
btnDropdown.addEventListener('shown.bs.dropdown', () => { btnDropdown.addEventListener('shown.bs.dropdown', () => {
expect(btnDropdown.classList.contains('show')).toEqual(true) expect(btnDropdown).toHaveClass('show')
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true') expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
done() done()
}) })
@@ -284,7 +284,7 @@ describe('Dropdown', () => {
const dropdown = new Dropdown(btnDropdown) const dropdown = new Dropdown(btnDropdown)
dropupEl.addEventListener('shown.bs.dropdown', () => { dropupEl.addEventListener('shown.bs.dropdown', () => {
expect(btnDropdown.classList.contains('show')).toEqual(true) expect(btnDropdown).toHaveClass('show')
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true') expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
done() done()
}) })
@@ -307,7 +307,7 @@ describe('Dropdown', () => {
const dropdown = new Dropdown(btnDropdown) const dropdown = new Dropdown(btnDropdown)
dropupEl.addEventListener('shown.bs.dropdown', () => { dropupEl.addEventListener('shown.bs.dropdown', () => {
expect(btnDropdown.classList.contains('show')).toEqual(true) expect(btnDropdown).toHaveClass('show')
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true') expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
done() done()
}) })
@@ -330,7 +330,7 @@ describe('Dropdown', () => {
const dropdown = new Dropdown(btnDropdown) const dropdown = new Dropdown(btnDropdown)
dropendEl.addEventListener('shown.bs.dropdown', () => { dropendEl.addEventListener('shown.bs.dropdown', () => {
expect(btnDropdown.classList.contains('show')).toEqual(true) expect(btnDropdown).toHaveClass('show')
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true') expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
done() done()
}) })
@@ -353,7 +353,7 @@ describe('Dropdown', () => {
const dropdown = new Dropdown(btnDropdown) const dropdown = new Dropdown(btnDropdown)
dropstartEl.addEventListener('shown.bs.dropdown', () => { dropstartEl.addEventListener('shown.bs.dropdown', () => {
expect(btnDropdown.classList.contains('show')).toEqual(true) expect(btnDropdown).toHaveClass('show')
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true') expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
done() done()
}) })
@@ -377,7 +377,7 @@ describe('Dropdown', () => {
}) })
btnDropdown.addEventListener('shown.bs.dropdown', () => { btnDropdown.addEventListener('shown.bs.dropdown', () => {
expect(btnDropdown.classList.contains('show')).toEqual(true) expect(btnDropdown).toHaveClass('show')
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true') expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
done() done()
}) })
@@ -401,7 +401,7 @@ describe('Dropdown', () => {
}) })
btnDropdown.addEventListener('shown.bs.dropdown', () => { btnDropdown.addEventListener('shown.bs.dropdown', () => {
expect(btnDropdown.classList.contains('show')).toEqual(true) expect(btnDropdown).toHaveClass('show')
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true') expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
done() done()
}) })
@@ -425,7 +425,7 @@ describe('Dropdown', () => {
}) })
btnDropdown.addEventListener('shown.bs.dropdown', () => { btnDropdown.addEventListener('shown.bs.dropdown', () => {
expect(btnDropdown.classList.contains('show')).toEqual(true) expect(btnDropdown).toHaveClass('show')
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true') expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
done() done()
}) })
@@ -474,7 +474,7 @@ describe('Dropdown', () => {
popperConfig: { popperConfig: {
onFirstUpdate() { onFirstUpdate() {
expect(virtualElement.getBoundingClientRect).toHaveBeenCalled() expect(virtualElement.getBoundingClientRect).toHaveBeenCalled()
expect(btnDropdown.classList.contains('show')).toEqual(true) expect(btnDropdown).toHaveClass('show')
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true') expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
done() done()
} }
@@ -606,7 +606,7 @@ describe('Dropdown', () => {
const dropdown = new Dropdown(btnDropdown) const dropdown = new Dropdown(btnDropdown)
btnDropdown.addEventListener('shown.bs.dropdown', () => { btnDropdown.addEventListener('shown.bs.dropdown', () => {
expect(btnDropdown.classList.contains('show')).toEqual(true) expect(btnDropdown).toHaveClass('show')
done() done()
}) })
@@ -734,7 +734,7 @@ describe('Dropdown', () => {
const dropdown = new Dropdown(btnDropdown) const dropdown = new Dropdown(btnDropdown)
btnDropdown.addEventListener('hidden.bs.dropdown', () => { btnDropdown.addEventListener('hidden.bs.dropdown', () => {
expect(dropdownMenu.classList.contains('show')).toEqual(false) expect(dropdownMenu).not.toHaveClass('show')
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('false') expect(btnDropdown.getAttribute('aria-expanded')).toEqual('false')
done() done()
}) })
@@ -789,7 +789,7 @@ describe('Dropdown', () => {
dropdown.hide() dropdown.hide()
setTimeout(() => { setTimeout(() => {
expect(dropdownMenu.classList.contains('show')).toEqual(true) expect(dropdownMenu).toHaveClass('show')
done() done()
}, 10) }, 10)
}) })
@@ -815,7 +815,7 @@ describe('Dropdown', () => {
dropdown.hide() dropdown.hide()
setTimeout(() => { setTimeout(() => {
expect(dropdownMenu.classList.contains('show')).toEqual(true) expect(dropdownMenu).toHaveClass('show')
done() done()
}, 10) }, 10)
}) })
@@ -870,7 +870,7 @@ describe('Dropdown', () => {
dropdown.hide() dropdown.hide()
setTimeout(() => { setTimeout(() => {
expect(dropdownMenu.classList.contains('show')).toEqual(true) expect(dropdownMenu).toHaveClass('show')
done() done()
}) })
}) })
@@ -897,7 +897,7 @@ describe('Dropdown', () => {
}) })
btnDropdown.addEventListener('hidden.bs.dropdown', () => { btnDropdown.addEventListener('hidden.bs.dropdown', () => {
expect(btnDropdown.classList.contains('show')).toEqual(false) expect(btnDropdown).not.toHaveClass('show')
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('false') expect(btnDropdown.getAttribute('aria-expanded')).toEqual('false')
expect(EventHandler.off).toHaveBeenCalled() expect(EventHandler.off).toHaveBeenCalled()
@@ -1032,9 +1032,9 @@ describe('Dropdown', () => {
}) })
btnDropdown.addEventListener('shown.bs.dropdown', event => setTimeout(() => { btnDropdown.addEventListener('shown.bs.dropdown', event => setTimeout(() => {
expect(btnDropdown.classList.contains('show')).toEqual(true) expect(btnDropdown).toHaveClass('show')
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true') expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
expect(showEventTriggered).toEqual(true) expect(showEventTriggered).toBeTrue()
expect(event.relatedTarget).toEqual(btnDropdown) expect(event.relatedTarget).toEqual(btnDropdown)
document.body.click() document.body.click()
})) }))
@@ -1044,9 +1044,9 @@ describe('Dropdown', () => {
}) })
btnDropdown.addEventListener('hidden.bs.dropdown', event => { btnDropdown.addEventListener('hidden.bs.dropdown', event => {
expect(btnDropdown.classList.contains('show')).toEqual(false) expect(btnDropdown).not.toHaveClass('show')
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('false') expect(btnDropdown.getAttribute('aria-expanded')).toEqual('false')
expect(hideEventTriggered).toEqual(true) expect(hideEventTriggered).toBeTrue()
expect(event.relatedTarget).toEqual(btnDropdown) expect(event.relatedTarget).toEqual(btnDropdown)
done() done()
}) })
@@ -1054,7 +1054,7 @@ describe('Dropdown', () => {
btnDropdown.click() btnDropdown.click()
}) })
it('should not use Popper in navbar', done => { it('should not use "static" Popper in navbar', done => {
fixtureEl.innerHTML = [ fixtureEl.innerHTML = [
'<nav class="navbar navbar-expand-md navbar-light bg-light">', '<nav class="navbar navbar-expand-md navbar-light bg-light">',
' <div class="dropdown">', ' <div class="dropdown">',
@@ -1071,8 +1071,8 @@ describe('Dropdown', () => {
const dropdown = new Dropdown(btnDropdown) const dropdown = new Dropdown(btnDropdown)
btnDropdown.addEventListener('shown.bs.dropdown', () => { btnDropdown.addEventListener('shown.bs.dropdown', () => {
expect(dropdown._popper).toBeNull() expect(dropdown._popper).not.toBeNull()
expect(dropdownMenu.getAttribute('style')).toEqual(null, 'no inline style applied by Popper') expect(dropdownMenu.getAttribute('data-bs-popper')).toEqual('static')
done() done()
}) })
@@ -1120,7 +1120,7 @@ describe('Dropdown', () => {
dropdown.show() dropdown.show()
}) })
it('should manage bs attribute `data-bs-popper`="none" when dropdown is in navbar', done => { it('should manage bs attribute `data-bs-popper`="static" when dropdown is in navbar', done => {
fixtureEl.innerHTML = [ fixtureEl.innerHTML = [
'<nav class="navbar navbar-expand-md navbar-light bg-light">', '<nav class="navbar navbar-expand-md navbar-light bg-light">',
' <div class="dropdown">', ' <div class="dropdown">',
@@ -1137,7 +1137,7 @@ describe('Dropdown', () => {
const dropdown = new Dropdown(btnDropdown) const dropdown = new Dropdown(btnDropdown)
btnDropdown.addEventListener('shown.bs.dropdown', () => { btnDropdown.addEventListener('shown.bs.dropdown', () => {
expect(dropdownMenu.getAttribute('data-bs-popper')).toEqual('none') expect(dropdownMenu.getAttribute('data-bs-popper')).toEqual('static')
dropdown.hide() dropdown.hide()
}) })
@@ -1164,7 +1164,7 @@ describe('Dropdown', () => {
btnDropdown.addEventListener('shown.bs.dropdown', () => { btnDropdown.addEventListener('shown.bs.dropdown', () => {
// Popper adds this attribute when we use it // Popper adds this attribute when we use it
expect(dropdownMenu.getAttribute('data-popper-placement')).toEqual(null) expect(dropdownMenu.getAttribute('data-popper-placement')).toBeNull()
done() done()
}) })
@@ -1211,7 +1211,7 @@ describe('Dropdown', () => {
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]') const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
btnDropdown.addEventListener('shown.bs.dropdown', () => { btnDropdown.addEventListener('shown.bs.dropdown', () => {
expect(btnDropdown.classList.contains('show')).toEqual(true) expect(btnDropdown).toHaveClass('show')
const keyup = createEvent('keyup') const keyup = createEvent('keyup')
@@ -1220,7 +1220,7 @@ describe('Dropdown', () => {
}) })
btnDropdown.addEventListener('hidden.bs.dropdown', () => { btnDropdown.addEventListener('hidden.bs.dropdown', () => {
expect(btnDropdown.classList.contains('show')).toEqual(false) expect(btnDropdown).not.toHaveClass('show')
done() done()
}) })
@@ -1248,29 +1248,29 @@ describe('Dropdown', () => {
const triggerDropdownList = fixtureEl.querySelectorAll('[data-bs-toggle="dropdown"]') const triggerDropdownList = fixtureEl.querySelectorAll('[data-bs-toggle="dropdown"]')
expect(triggerDropdownList.length).toEqual(2) expect(triggerDropdownList).toHaveSize(2)
const [triggerDropdownFirst, triggerDropdownLast] = triggerDropdownList const [triggerDropdownFirst, triggerDropdownLast] = triggerDropdownList
triggerDropdownFirst.addEventListener('shown.bs.dropdown', () => { triggerDropdownFirst.addEventListener('shown.bs.dropdown', () => {
expect(triggerDropdownFirst.classList.contains('show')).toEqual(true) expect(triggerDropdownFirst).toHaveClass('show')
expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(1) expect(fixtureEl.querySelectorAll('.dropdown-menu.show')).toHaveSize(1)
document.body.click() document.body.click()
}) })
triggerDropdownFirst.addEventListener('hidden.bs.dropdown', () => { triggerDropdownFirst.addEventListener('hidden.bs.dropdown', () => {
expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(0) expect(fixtureEl.querySelectorAll('.dropdown-menu.show')).toHaveSize(0)
triggerDropdownLast.click() triggerDropdownLast.click()
}) })
triggerDropdownLast.addEventListener('shown.bs.dropdown', () => { triggerDropdownLast.addEventListener('shown.bs.dropdown', () => {
expect(triggerDropdownLast.classList.contains('show')).toEqual(true) expect(triggerDropdownLast).toHaveClass('show')
expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(1) expect(fixtureEl.querySelectorAll('.dropdown-menu.show')).toHaveSize(1)
document.body.click() document.body.click()
}) })
triggerDropdownLast.addEventListener('hidden.bs.dropdown', () => { triggerDropdownLast.addEventListener('hidden.bs.dropdown', () => {
expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(0) expect(fixtureEl.querySelectorAll('.dropdown-menu.show')).toHaveSize(0)
done() done()
}) })
@@ -1296,13 +1296,13 @@ describe('Dropdown', () => {
const triggerDropdownList = fixtureEl.querySelectorAll('[data-bs-toggle="dropdown"]') const triggerDropdownList = fixtureEl.querySelectorAll('[data-bs-toggle="dropdown"]')
expect(triggerDropdownList.length).toEqual(2) expect(triggerDropdownList).toHaveSize(2)
const [triggerDropdownFirst, triggerDropdownLast] = triggerDropdownList const [triggerDropdownFirst, triggerDropdownLast] = triggerDropdownList
triggerDropdownFirst.addEventListener('shown.bs.dropdown', () => { triggerDropdownFirst.addEventListener('shown.bs.dropdown', () => {
expect(triggerDropdownFirst.classList.contains('show')).toEqual(true, '"show" class added on click') expect(triggerDropdownFirst).toHaveClass('show')
expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(1, 'only one dropdown is shown') expect(fixtureEl.querySelectorAll('.dropdown-menu.show')).toHaveSize(1)
const keyup = createEvent('keyup') const keyup = createEvent('keyup')
keyup.key = 'Tab' keyup.key = 'Tab'
@@ -1311,13 +1311,13 @@ describe('Dropdown', () => {
}) })
triggerDropdownFirst.addEventListener('hidden.bs.dropdown', () => { triggerDropdownFirst.addEventListener('hidden.bs.dropdown', () => {
expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(0, '"show" class removed') expect(fixtureEl.querySelectorAll('.dropdown-menu.show')).toHaveSize(0)
triggerDropdownLast.click() triggerDropdownLast.click()
}) })
triggerDropdownLast.addEventListener('shown.bs.dropdown', () => { triggerDropdownLast.addEventListener('shown.bs.dropdown', () => {
expect(triggerDropdownLast.classList.contains('show')).toEqual(true, '"show" class added on click') expect(triggerDropdownLast).toHaveClass('show')
expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(1, 'only one dropdown is shown') expect(fixtureEl.querySelectorAll('.dropdown-menu.show')).toHaveSize(1)
const keyup = createEvent('keyup') const keyup = createEvent('keyup')
keyup.key = 'Tab' keyup.key = 'Tab'
@@ -1326,7 +1326,7 @@ describe('Dropdown', () => {
}) })
triggerDropdownLast.addEventListener('hidden.bs.dropdown', () => { triggerDropdownLast.addEventListener('hidden.bs.dropdown', () => {
expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(0, '"show" class removed') expect(fixtureEl.querySelectorAll('.dropdown-menu.show')).toHaveSize(0)
done() done()
}) })
@@ -1456,8 +1456,8 @@ describe('Dropdown', () => {
triggerDropdown.dispatchEvent(keydown) triggerDropdown.dispatchEvent(keydown)
triggerDropdown.dispatchEvent(keydown) triggerDropdown.dispatchEvent(keydown)
expect(document.activeElement.classList.contains('disabled')).toEqual(false, '.disabled not focused') expect(document.activeElement).not.toHaveClass('disabled')
expect(document.activeElement.hasAttribute('disabled')).toEqual(false, ':disabled not focused') expect(document.activeElement.hasAttribute('disabled')).toBeFalse()
done() done()
}) })
@@ -1490,9 +1490,9 @@ describe('Dropdown', () => {
triggerDropdown.dispatchEvent(keydown) triggerDropdown.dispatchEvent(keydown)
expect(document.activeElement.classList.contains('d-none')).toEqual(false, '.d-none not focused') expect(document.activeElement).not.toHaveClass('d-none')
expect(document.activeElement.style.display).not.toBe('none', '"display: none" not focused') expect(document.activeElement.style.display).not.toEqual('none')
expect(document.activeElement.style.visibility).not.toBe('hidden', '"visibility: hidden" not focused') expect(document.activeElement.style.visibility).not.toEqual('hidden')
done() done()
}) })
@@ -1603,12 +1603,12 @@ describe('Dropdown', () => {
const input = fixtureEl.querySelector('input') const input = fixtureEl.querySelector('input')
input.addEventListener('click', () => { input.addEventListener('click', () => {
expect(triggerDropdown.classList.contains('show')).toEqual(true, 'dropdown menu is shown') expect(triggerDropdown).toHaveClass('show')
done() done()
}) })
triggerDropdown.addEventListener('shown.bs.dropdown', () => { triggerDropdown.addEventListener('shown.bs.dropdown', () => {
expect(triggerDropdown.classList.contains('show')).toEqual(true, 'dropdown menu is shown') expect(triggerDropdown).toHaveClass('show')
input.dispatchEvent(createEvent('click')) input.dispatchEvent(createEvent('click'))
}) })
@@ -1629,12 +1629,12 @@ describe('Dropdown', () => {
const textarea = fixtureEl.querySelector('textarea') const textarea = fixtureEl.querySelector('textarea')
textarea.addEventListener('click', () => { textarea.addEventListener('click', () => {
expect(triggerDropdown.classList.contains('show')).toEqual(true, 'dropdown menu is shown') expect(triggerDropdown).toHaveClass('show')
done() done()
}) })
triggerDropdown.addEventListener('shown.bs.dropdown', () => { triggerDropdown.addEventListener('shown.bs.dropdown', () => {
expect(triggerDropdown.classList.contains('show')).toEqual(true, 'dropdown menu is shown') expect(triggerDropdown).toHaveClass('show')
textarea.dispatchEvent(createEvent('click')) textarea.dispatchEvent(createEvent('click'))
}) })
@@ -1684,57 +1684,38 @@ describe('Dropdown', () => {
const input = fixtureEl.querySelector('input') const input = fixtureEl.querySelector('input')
const textarea = fixtureEl.querySelector('textarea') const textarea = fixtureEl.querySelector('textarea')
const keydownSpace = createEvent('keydown') const test = (eventKey, elementToDispatch) => {
keydownSpace.key = 'Space' const event = createEvent('keydown')
event.key = eventKey
const keydownArrowUp = createEvent('keydown') elementToDispatch.focus()
keydownArrowUp.key = 'ArrowUp' elementToDispatch.dispatchEvent(event)
expect(document.activeElement).toEqual(elementToDispatch, `${elementToDispatch.tagName} still focused`)
const keydownArrowDown = createEvent('keydown') }
keydownArrowDown.key = 'ArrowDown'
const keydownEscape = createEvent('keydown') const keydownEscape = createEvent('keydown')
keydownEscape.key = 'Escape' keydownEscape.key = 'Escape'
triggerDropdown.addEventListener('shown.bs.dropdown', () => { triggerDropdown.addEventListener('shown.bs.dropdown', () => {
// Key Space // Key Space
input.focus() test('Space', input)
input.dispatchEvent(keydownSpace)
expect(document.activeElement).toEqual(input, 'input still focused') test('Space', textarea)
textarea.focus()
textarea.dispatchEvent(keydownSpace)
expect(document.activeElement).toEqual(textarea, 'textarea still focused')
// Key ArrowUp // Key ArrowUp
input.focus() test('ArrowUp', input)
input.dispatchEvent(keydownArrowUp)
expect(document.activeElement).toEqual(input, 'input still focused') test('ArrowUp', textarea)
textarea.focus()
textarea.dispatchEvent(keydownArrowUp)
expect(document.activeElement).toEqual(textarea, 'textarea still focused')
// Key ArrowDown // Key ArrowDown
input.focus() test('ArrowDown', input)
input.dispatchEvent(keydownArrowDown)
expect(document.activeElement).toEqual(input, 'input still focused') test('ArrowDown', textarea)
textarea.focus()
textarea.dispatchEvent(keydownArrowDown)
expect(document.activeElement).toEqual(textarea, 'textarea still focused')
// Key Escape // Key Escape
input.focus() input.focus()
input.dispatchEvent(keydownEscape) input.dispatchEvent(keydownEscape)
expect(triggerDropdown.classList.contains('show')).toEqual(false, 'dropdown menu is not shown') expect(triggerDropdown).not.toHaveClass('show')
done() done()
}) })
@@ -1771,7 +1752,7 @@ describe('Dropdown', () => {
setTimeout(() => { setTimeout(() => {
expect(dropdown.toggle).not.toHaveBeenCalled() expect(dropdown.toggle).not.toHaveBeenCalled()
expect(triggerDropdown.classList.contains('show')).toEqual(false) expect(triggerDropdown).not.toHaveClass('show')
done() done()
}, 20) }, 20)
}) })
@@ -1823,7 +1804,7 @@ describe('Dropdown', () => {
const dropdownMenu = fixtureEl.querySelector('.dropdown-menu') const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
const expectDropdownToBeOpened = () => setTimeout(() => { const expectDropdownToBeOpened = () => setTimeout(() => {
expect(dropdownToggle.classList.contains('show')).toEqual(true) expect(dropdownToggle).toHaveClass('show')
dropdownMenu.click() dropdownMenu.click()
}, 150) }, 150)
@@ -1833,7 +1814,7 @@ describe('Dropdown', () => {
}) })
dropdownToggle.addEventListener('hidden.bs.dropdown', () => setTimeout(() => { dropdownToggle.addEventListener('hidden.bs.dropdown', () => setTimeout(() => {
expect(dropdownToggle.classList.contains('show')).toEqual(false) expect(dropdownToggle).not.toHaveClass('show')
done() done()
})) }))
@@ -1854,7 +1835,7 @@ describe('Dropdown', () => {
const dropdownMenu = fixtureEl.querySelector('.dropdown-menu') const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
const expectDropdownToBeOpened = () => setTimeout(() => { const expectDropdownToBeOpened = () => setTimeout(() => {
expect(dropdownToggle.classList.contains('show')).toEqual(true) expect(dropdownToggle).toHaveClass('show')
document.documentElement.click() document.documentElement.click()
}, 150) }, 150)
@@ -1864,7 +1845,7 @@ describe('Dropdown', () => {
}) })
dropdownToggle.addEventListener('hidden.bs.dropdown', () => { dropdownToggle.addEventListener('hidden.bs.dropdown', () => {
expect(dropdownToggle.classList.contains('show')).toEqual(false) expect(dropdownToggle).not.toHaveClass('show')
done() done()
}) })
@@ -1885,7 +1866,7 @@ describe('Dropdown', () => {
const dropdownMenu = fixtureEl.querySelector('.dropdown-menu') const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
const expectDropdownToBeOpened = (shouldTriggerClick = true) => setTimeout(() => { const expectDropdownToBeOpened = (shouldTriggerClick = true) => setTimeout(() => {
expect(dropdownToggle.classList.contains('show')).toEqual(true) expect(dropdownToggle).toHaveClass('show')
if (shouldTriggerClick) { if (shouldTriggerClick) {
document.documentElement.click() document.documentElement.click()
} else { } else {
@@ -1963,7 +1944,7 @@ describe('Dropdown', () => {
const div = fixtureEl.querySelector('div') const div = fixtureEl.querySelector('div')
expect(Dropdown.getInstance(div)).toEqual(null) expect(Dropdown.getInstance(div)).toBeNull()
}) })
}) })
@@ -1984,7 +1965,7 @@ describe('Dropdown', () => {
const div = fixtureEl.querySelector('div') const div = fixtureEl.querySelector('div')
expect(Dropdown.getInstance(div)).toEqual(null) expect(Dropdown.getInstance(div)).toBeNull()
expect(Dropdown.getOrCreateInstance(div)).toBeInstanceOf(Dropdown) expect(Dropdown.getOrCreateInstance(div)).toBeInstanceOf(Dropdown)
}) })
@@ -1993,7 +1974,7 @@ describe('Dropdown', () => {
const div = fixtureEl.querySelector('div') const div = fixtureEl.querySelector('div')
expect(Dropdown.getInstance(div)).toEqual(null) expect(Dropdown.getInstance(div)).toBeNull()
const dropdown = Dropdown.getOrCreateInstance(div, { const dropdown = Dropdown.getOrCreateInstance(div, {
display: 'dynamic' display: 'dynamic'
}) })
@@ -2043,7 +2024,7 @@ describe('Dropdown', () => {
keyup.key = 'ArrowUp' keyup.key = 'ArrowUp'
const handleArrowDown = () => { const handleArrowDown = () => {
expect(triggerDropdown.classList.contains('show')).toEqual(true) expect(triggerDropdown).toHaveClass('show')
expect(triggerDropdown.getAttribute('aria-expanded')).toEqual('true') expect(triggerDropdown.getAttribute('aria-expanded')).toEqual('true')
setTimeout(() => { setTimeout(() => {
dropdown.hide() dropdown.hide()
@@ -2053,7 +2034,7 @@ describe('Dropdown', () => {
} }
const handleArrowUp = () => { const handleArrowUp = () => {
expect(triggerDropdown.classList.contains('show')).toEqual(true) expect(triggerDropdown).toHaveClass('show')
expect(triggerDropdown.getAttribute('aria-expanded')).toEqual('true') expect(triggerDropdown.getAttribute('aria-expanded')).toEqual('true')
done() done()
} }
@@ -2108,7 +2089,7 @@ describe('Dropdown', () => {
const childElement = fixtureEl.querySelector('#childElement') const childElement = fixtureEl.querySelector('#childElement')
btnDropdown.addEventListener('shown.bs.dropdown', () => setTimeout(() => { btnDropdown.addEventListener('shown.bs.dropdown', () => setTimeout(() => {
expect(btnDropdown.classList.contains('show')).toEqual(true) expect(btnDropdown).toHaveClass('show')
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true') expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
done() done()
})) }))
+1 -1
View File
@@ -49,7 +49,7 @@ describe('jQuery', () => {
$(fixtureEl).find('.alert') $(fixtureEl).find('.alert')
.one('closed.bs.alert', () => { .one('closed.bs.alert', () => {
expect($(fixtureEl).find('.alert').length).toEqual(0) expect($(fixtureEl).find('.alert')).toHaveSize(0)
done() done()
}) })
+48 -20
View File
@@ -57,9 +57,7 @@ describe('Modal', () => {
describe('toggle', () => { describe('toggle', () => {
it('should call ScrollBarHelper to handle scrollBar on body', done => { it('should call ScrollBarHelper to handle scrollBar on body', done => {
fixtureEl.innerHTML = [ fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog"></div></div>'
'<div class="modal"><div class="modal-dialog"></div></div>'
].join('')
spyOn(ScrollBarHelper.prototype, 'hide').and.callThrough() spyOn(ScrollBarHelper.prototype, 'hide').and.callThrough()
spyOn(ScrollBarHelper.prototype, 'reset').and.callThrough() spyOn(ScrollBarHelper.prototype, 'reset').and.callThrough()
@@ -234,12 +232,12 @@ describe('Modal', () => {
modalEl.addEventListener('show.bs.modal', () => { modalEl.addEventListener('show.bs.modal', () => {
setTimeout(() => { setTimeout(() => {
expect(modal._isTransitioning).toEqual(true) expect(modal._isTransitioning).toBeTrue()
}) })
}) })
modalEl.addEventListener('shown.bs.modal', () => { modalEl.addEventListener('shown.bs.modal', () => {
expect(modal._isTransitioning).toEqual(false) expect(modal._isTransitioning).toBeFalse()
done() done()
}) })
@@ -279,8 +277,7 @@ describe('Modal', () => {
fixtureEl.innerHTML = [ fixtureEl.innerHTML = [
'<button type="button" data-bs-dismiss="modal" data-bs-target="#modal1"></button>', '<button type="button" data-bs-dismiss="modal" data-bs-target="#modal1"></button>',
'<div id="modal1" class="modal fade">', '<div id="modal1" class="modal fade">',
' <div class="modal-dialog">', ' <div class="modal-dialog"></div>',
' </div>',
'</div>' '</div>'
].join('') ].join('')
@@ -305,8 +302,7 @@ describe('Modal', () => {
it('should set .modal\'s scroll top to 0', done => { it('should set .modal\'s scroll top to 0', done => {
fixtureEl.innerHTML = [ fixtureEl.innerHTML = [
'<div class="modal fade">', '<div class="modal fade">',
' <div class="modal-dialog">', ' <div class="modal-dialog"></div>',
' </div>',
'</div>' '</div>'
].join('') ].join('')
@@ -432,6 +428,38 @@ describe('Modal', () => {
modal.show() modal.show()
}) })
it('should not close modal when clicking on modal-content', done => {
fixtureEl.innerHTML = [
'<div class="modal">',
' <div class="modal-dialog">',
' <div class="modal-content"></div>',
' </div>',
'</div>'
].join('')
const modalEl = fixtureEl.querySelector('.modal')
const modal = new Modal(modalEl)
const shownCallback = () => {
setTimeout(() => {
expect(modal._isShown).toEqual(true)
done()
}, 10)
}
modalEl.addEventListener('shown.bs.modal', () => {
fixtureEl.querySelector('.modal-dialog').click()
fixtureEl.querySelector('.modal-content').click()
shownCallback()
})
modalEl.addEventListener('hidden.bs.modal', () => {
throw new Error('Should not hide a modal')
})
modal.show()
})
it('should not close modal when clicking outside of modal-content if backdrop = false', done => { it('should not close modal when clicking outside of modal-content if backdrop = false', done => {
fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog"></div></div>' fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog"></div></div>'
@@ -442,7 +470,7 @@ describe('Modal', () => {
const shownCallback = () => { const shownCallback = () => {
setTimeout(() => { setTimeout(() => {
expect(modal._isShown).toEqual(true) expect(modal._isShown).toBeTrue()
done() done()
}, 10) }, 10)
} }
@@ -469,7 +497,7 @@ describe('Modal', () => {
const shownCallback = () => { const shownCallback = () => {
setTimeout(() => { setTimeout(() => {
expect(modal._isShown).toEqual(true) expect(modal._isShown).toBeTrue()
done() done()
}, 10) }, 10)
} }
@@ -497,7 +525,7 @@ describe('Modal', () => {
const shownCallback = () => { const shownCallback = () => {
setTimeout(() => { setTimeout(() => {
expect(modal._isShown).toEqual(false) expect(modal._isShown).toBeFalse()
done() done()
}, 10) }, 10)
} }
@@ -523,7 +551,7 @@ describe('Modal', () => {
const shownCallback = () => { const shownCallback = () => {
setTimeout(() => { setTimeout(() => {
expect(modal._isShown).toEqual(true) expect(modal._isShown).toBeTrue()
done() done()
}, 10) }, 10)
} }
@@ -687,7 +715,7 @@ describe('Modal', () => {
const hideCallback = () => { const hideCallback = () => {
setTimeout(() => { setTimeout(() => {
expect(modal._isShown).toEqual(true) expect(modal._isShown).toBeTrue()
done() done()
}, 10) }, 10)
} }
@@ -994,7 +1022,7 @@ describe('Modal', () => {
}) })
modalEl1.addEventListener('hidden.bs.modal', () => { modalEl1.addEventListener('hidden.bs.modal', () => {
expect(Modal.getInstance(modalEl2)).not.toBeNull() expect(Modal.getInstance(modalEl2)).not.toBeNull()
expect(modalEl2.classList.contains('show')).toBeTrue() expect(modalEl2).toHaveClass('show')
done() done()
}) })
modal1.show() modal1.show()
@@ -1029,7 +1057,7 @@ describe('Modal', () => {
const modal = Modal.getInstance(div) const modal = Modal.getInstance(div)
expect(modal).not.toBeNull() expect(modal).not.toBeNull()
expect(modal._config.keyboard).toBe(false) expect(modal._config.keyboard).toBeFalse()
}) })
it('should not re create a modal', () => { it('should not re create a modal', () => {
@@ -1129,7 +1157,7 @@ describe('Modal', () => {
const div = fixtureEl.querySelector('div') const div = fixtureEl.querySelector('div')
expect(Modal.getInstance(div)).toEqual(null) expect(Modal.getInstance(div)).toBeNull()
expect(Modal.getOrCreateInstance(div)).toBeInstanceOf(Modal) expect(Modal.getOrCreateInstance(div)).toBeInstanceOf(Modal)
}) })
@@ -1138,13 +1166,13 @@ describe('Modal', () => {
const div = fixtureEl.querySelector('div') const div = fixtureEl.querySelector('div')
expect(Modal.getInstance(div)).toEqual(null) expect(Modal.getInstance(div)).toBeNull()
const modal = Modal.getOrCreateInstance(div, { const modal = Modal.getOrCreateInstance(div, {
backdrop: true backdrop: true
}) })
expect(modal).toBeInstanceOf(Modal) expect(modal).toBeInstanceOf(Modal)
expect(modal._config.backdrop).toEqual(true) expect(modal._config.backdrop).toBeTrue()
}) })
it('should return the instance when exists without given configuration', () => { it('should return the instance when exists without given configuration', () => {
@@ -1162,7 +1190,7 @@ describe('Modal', () => {
expect(modal).toBeInstanceOf(Modal) expect(modal).toBeInstanceOf(Modal)
expect(modal2).toEqual(modal) expect(modal2).toEqual(modal)
expect(modal2._config.backdrop).toEqual(true) expect(modal2._config.backdrop).toBeTrue()
}) })
}) })
}) })
+34 -42
View File
@@ -55,7 +55,7 @@ describe('Offcanvas', () => {
closeEl.click() closeEl.click()
expect(offCanvas._config.keyboard).toBe(true) expect(offCanvas._config.keyboard).toBeTrue()
expect(offCanvas.hide).toHaveBeenCalled() expect(offCanvas.hide).toHaveBeenCalled()
}) })
@@ -101,47 +101,38 @@ describe('Offcanvas', () => {
document.dispatchEvent(keyDownEsc) document.dispatchEvent(keyDownEsc)
expect(offCanvas._config.keyboard).toBe(false) expect(offCanvas._config.keyboard).toBeFalse()
expect(offCanvas.hide).not.toHaveBeenCalled() expect(offCanvas.hide).not.toHaveBeenCalled()
}) })
}) })
describe('config', () => { describe('config', () => {
it('should have default values', () => { it('should have default values', () => {
fixtureEl.innerHTML = [ fixtureEl.innerHTML = '<div class="offcanvas"></div>'
'<div class="offcanvas">',
'</div>'
].join('')
const offCanvasEl = fixtureEl.querySelector('.offcanvas') const offCanvasEl = fixtureEl.querySelector('.offcanvas')
const offCanvas = new Offcanvas(offCanvasEl) const offCanvas = new Offcanvas(offCanvasEl)
expect(offCanvas._config.backdrop).toEqual(true) expect(offCanvas._config.backdrop).toBeTrue()
expect(offCanvas._backdrop._config.isVisible).toEqual(true) expect(offCanvas._backdrop._config.isVisible).toBeTrue()
expect(offCanvas._config.keyboard).toEqual(true) expect(offCanvas._config.keyboard).toBeTrue()
expect(offCanvas._config.scroll).toEqual(false) expect(offCanvas._config.scroll).toBeFalse()
}) })
it('should read data attributes and override default config', () => { it('should read data attributes and override default config', () => {
fixtureEl.innerHTML = [ fixtureEl.innerHTML = '<div class="offcanvas" data-bs-scroll="true" data-bs-backdrop="false" data-bs-keyboard="false"></div>'
'<div class="offcanvas" data-bs-scroll="true" data-bs-backdrop="false" data-bs-keyboard="false">',
'</div>'
].join('')
const offCanvasEl = fixtureEl.querySelector('.offcanvas') const offCanvasEl = fixtureEl.querySelector('.offcanvas')
const offCanvas = new Offcanvas(offCanvasEl) const offCanvas = new Offcanvas(offCanvasEl)
expect(offCanvas._config.backdrop).toEqual(false) expect(offCanvas._config.backdrop).toBeFalse()
expect(offCanvas._backdrop._config.isVisible).toEqual(false) expect(offCanvas._backdrop._config.isVisible).toBeFalse()
expect(offCanvas._config.keyboard).toEqual(false) expect(offCanvas._config.keyboard).toBeFalse()
expect(offCanvas._config.scroll).toEqual(true) expect(offCanvas._config.scroll).toBeTrue()
}) })
it('given a config object must override data attributes', () => { it('given a config object must override data attributes', () => {
fixtureEl.innerHTML = [ fixtureEl.innerHTML = '<div class="offcanvas" data-bs-scroll="true" data-bs-backdrop="false" data-bs-keyboard="false"></div>'
'<div class="offcanvas" data-bs-scroll="true" data-bs-backdrop="false" data-bs-keyboard="false">',
'</div>'
].join('')
const offCanvasEl = fixtureEl.querySelector('.offcanvas') const offCanvasEl = fixtureEl.querySelector('.offcanvas')
const offCanvas = new Offcanvas(offCanvasEl, { const offCanvas = new Offcanvas(offCanvasEl, {
@@ -149,11 +140,12 @@ describe('Offcanvas', () => {
keyboard: true, keyboard: true,
scroll: false scroll: false
}) })
expect(offCanvas._config.backdrop).toEqual(true) expect(offCanvas._config.backdrop).toBeTrue()
expect(offCanvas._config.keyboard).toEqual(true) expect(offCanvas._config.keyboard).toBeTrue()
expect(offCanvas._config.scroll).toEqual(false) expect(offCanvas._config.scroll).toBeFalse()
}) })
}) })
describe('options', () => { describe('options', () => {
it('if scroll is enabled, should allow body to scroll while offcanvas is open', done => { it('if scroll is enabled, should allow body to scroll while offcanvas is open', done => {
fixtureEl.innerHTML = '<div class="offcanvas"></div>' fixtureEl.innerHTML = '<div class="offcanvas"></div>'
@@ -204,7 +196,7 @@ describe('Offcanvas', () => {
spyOn(offCanvas._backdrop._config, 'clickCallback').and.callThrough() spyOn(offCanvas._backdrop._config, 'clickCallback').and.callThrough()
offCanvasEl.addEventListener('shown.bs.offcanvas', () => { offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
expect(typeof offCanvas._backdrop._config.clickCallback).toBe('function') expect(offCanvas._backdrop._config.clickCallback).toEqual(jasmine.any(Function))
offCanvas._backdrop._getElement().dispatchEvent(clickEvent) offCanvas._backdrop._getElement().dispatchEvent(clickEvent)
}) })
@@ -256,7 +248,7 @@ describe('Offcanvas', () => {
const offCanvasEl = fixtureEl.querySelector('.offcanvas') const offCanvasEl = fixtureEl.querySelector('.offcanvas')
const offCanvas = new Offcanvas(offCanvasEl) const offCanvas = new Offcanvas(offCanvasEl)
offCanvas.show() offCanvas.show()
expect(offCanvasEl.classList.contains('show')).toBe(true) expect(offCanvasEl).toHaveClass('show')
spyOn(offCanvas, 'hide') spyOn(offCanvas, 'hide')
@@ -274,7 +266,7 @@ describe('Offcanvas', () => {
const offCanvas = new Offcanvas(offCanvasEl) const offCanvas = new Offcanvas(offCanvasEl)
offCanvas.show() offCanvas.show()
expect(offCanvasEl.classList.contains('show')).toBe(true) expect(offCanvasEl).toHaveClass('show')
spyOn(offCanvas._backdrop, 'show').and.callThrough() spyOn(offCanvas._backdrop, 'show').and.callThrough()
spyOn(EventHandler, 'trigger').and.callThrough() spyOn(EventHandler, 'trigger').and.callThrough()
@@ -292,7 +284,7 @@ describe('Offcanvas', () => {
spyOn(offCanvas._backdrop, 'show').and.callThrough() spyOn(offCanvas._backdrop, 'show').and.callThrough()
offCanvasEl.addEventListener('shown.bs.offcanvas', () => { offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
expect(offCanvasEl.classList.contains('show')).toEqual(true) expect(offCanvasEl).toHaveClass('show')
expect(offCanvas._backdrop.show).toHaveBeenCalled() expect(offCanvas._backdrop.show).toHaveBeenCalled()
done() done()
}) })
@@ -384,7 +376,7 @@ describe('Offcanvas', () => {
offCanvas.show() offCanvas.show()
offCanvasEl.addEventListener('hidden.bs.offcanvas', () => { offCanvasEl.addEventListener('hidden.bs.offcanvas', () => {
expect(offCanvasEl.classList.contains('show')).toEqual(false) expect(offCanvasEl).not.toHaveClass('show')
expect(offCanvas._backdrop.hide).toHaveBeenCalled() expect(offCanvas._backdrop.hide).toHaveBeenCalled()
done() done()
}) })
@@ -458,7 +450,7 @@ describe('Offcanvas', () => {
expect(offCanvas._backdrop).toBeNull() expect(offCanvas._backdrop).toBeNull()
expect(focustrap.deactivate).toHaveBeenCalled() expect(focustrap.deactivate).toHaveBeenCalled()
expect(offCanvas._focustrap).toBeNull() expect(offCanvas._focustrap).toBeNull()
expect(Offcanvas.getInstance(offCanvasEl)).toEqual(null) expect(Offcanvas.getInstance(offCanvasEl)).toBeNull()
}) })
}) })
@@ -473,8 +465,8 @@ describe('Offcanvas', () => {
const offCanvasEl = fixtureEl.querySelector('#offcanvasdiv1') const offCanvasEl = fixtureEl.querySelector('#offcanvasdiv1')
offCanvasEl.addEventListener('shown.bs.offcanvas', () => { offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
expect(offCanvasEl.classList.contains('show')).toEqual(true) expect(offCanvasEl).toHaveClass('show')
expect(target.checked).toEqual(true) expect(target.checked).toBeTrue()
done() done()
}) })
@@ -498,7 +490,7 @@ describe('Offcanvas', () => {
it('should call hide first, if another offcanvas is open', done => { it('should call hide first, if another offcanvas is open', done => {
fixtureEl.innerHTML = [ fixtureEl.innerHTML = [
'<button id="btn2" data-bs-toggle="offcanvas" data-bs-target="#offcanvas2" ></button>', '<button id="btn2" data-bs-toggle="offcanvas" data-bs-target="#offcanvas2"></button>',
'<div id="offcanvas1" class="offcanvas"></div>', '<div id="offcanvas1" class="offcanvas"></div>',
'<div id="offcanvas2" class="offcanvas"></div>' '<div id="offcanvas2" class="offcanvas"></div>'
].join('') ].join('')
@@ -520,7 +512,7 @@ describe('Offcanvas', () => {
it('should focus on trigger element after closing offcanvas', done => { it('should focus on trigger element after closing offcanvas', done => {
fixtureEl.innerHTML = [ fixtureEl.innerHTML = [
'<button id="btn" data-bs-toggle="offcanvas" data-bs-target="#offcanvas" ></button>', '<button id="btn" data-bs-toggle="offcanvas" data-bs-target="#offcanvas"></button>',
'<div id="offcanvas" class="offcanvas"></div>' '<div id="offcanvas" class="offcanvas"></div>'
].join('') ].join('')
@@ -544,7 +536,7 @@ describe('Offcanvas', () => {
it('should not focus on trigger element after closing offcanvas, if it is not visible', done => { it('should not focus on trigger element after closing offcanvas, if it is not visible', done => {
fixtureEl.innerHTML = [ fixtureEl.innerHTML = [
'<button id="btn" data-bs-toggle="offcanvas" data-bs-target="#offcanvas" ></button>', '<button id="btn" data-bs-toggle="offcanvas" data-bs-target="#offcanvas"></button>',
'<div id="offcanvas" class="offcanvas"></div>' '<div id="offcanvas" class="offcanvas"></div>'
].join('') ].join('')
@@ -559,7 +551,7 @@ describe('Offcanvas', () => {
}) })
offcanvasEl.addEventListener('hidden.bs.offcanvas', () => { offcanvasEl.addEventListener('hidden.bs.offcanvas', () => {
setTimeout(() => { setTimeout(() => {
expect(isVisible(trigger)).toBe(false) expect(isVisible(trigger)).toBeFalse()
expect(trigger.focus).not.toHaveBeenCalled() expect(trigger.focus).not.toHaveBeenCalled()
done() done()
}, 5) }, 5)
@@ -665,7 +657,7 @@ describe('Offcanvas', () => {
const offcanvas = Offcanvas.getInstance(div) const offcanvas = Offcanvas.getInstance(div)
expect(offcanvas).not.toBeNull() expect(offcanvas).not.toBeNull()
expect(offcanvas._config.scroll).toBe(true) expect(offcanvas._config.scroll).toBeTrue()
}) })
}) })
@@ -706,7 +698,7 @@ describe('Offcanvas', () => {
const div = fixtureEl.querySelector('div') const div = fixtureEl.querySelector('div')
expect(Offcanvas.getInstance(div)).toEqual(null) expect(Offcanvas.getInstance(div)).toBeNull()
expect(Offcanvas.getOrCreateInstance(div)).toBeInstanceOf(Offcanvas) expect(Offcanvas.getOrCreateInstance(div)).toBeInstanceOf(Offcanvas)
}) })
@@ -715,13 +707,13 @@ describe('Offcanvas', () => {
const div = fixtureEl.querySelector('div') const div = fixtureEl.querySelector('div')
expect(Offcanvas.getInstance(div)).toEqual(null) expect(Offcanvas.getInstance(div)).toBeNull()
const offcanvas = Offcanvas.getOrCreateInstance(div, { const offcanvas = Offcanvas.getOrCreateInstance(div, {
scroll: true scroll: true
}) })
expect(offcanvas).toBeInstanceOf(Offcanvas) expect(offcanvas).toBeInstanceOf(Offcanvas)
expect(offcanvas._config.scroll).toEqual(true) expect(offcanvas._config.scroll).toBeTrue()
}) })
it('should return the instance when exists without given configuration', () => { it('should return the instance when exists without given configuration', () => {
@@ -739,7 +731,7 @@ describe('Offcanvas', () => {
expect(offcanvas).toBeInstanceOf(Offcanvas) expect(offcanvas).toBeInstanceOf(Offcanvas)
expect(offcanvas2).toEqual(offcanvas) expect(offcanvas2).toEqual(offcanvas)
expect(offcanvas2._config.scroll).toEqual(true) expect(offcanvas2._config.scroll).toBeTrue()
}) })
}) })
}) })
+24 -7
View File
@@ -155,6 +155,22 @@ describe('Popover', () => {
popover.show() popover.show()
}) })
it('"setContent" should keep the initial template', () => {
fixtureEl.innerHTML = '<a href="#" title="Popover" data-bs-content="https://twitter.com/getbootstrap" data-bs-custom-class="custom-class">BS twitter</a>'
const popoverEl = fixtureEl.querySelector('a')
const popover = new Popover(popoverEl)
popover.setContent({ '.tooltip-inner': 'foo' })
const tip = popover._getTipElement()
expect(tip).toHaveClass('popover')
expect(tip).toHaveClass('bs-popover-auto')
expect(tip.querySelector('.popover-arrow')).not.toBeNull()
expect(tip.querySelector('.popover-header')).not.toBeNull()
expect(tip.querySelector('.popover-body')).not.toBeNull()
})
it('should call setContent once', done => { it('should call setContent once', done => {
fixtureEl.innerHTML = '<a href="#">BS twitter</a>' fixtureEl.innerHTML = '<a href="#">BS twitter</a>'
@@ -162,8 +178,8 @@ describe('Popover', () => {
const popover = new Popover(popoverEl, { const popover = new Popover(popoverEl, {
content: 'Popover content' content: 'Popover content'
}) })
expect(popover._templateFactory).toBeNull()
const spy = spyOn(popover, 'setContent').and.callThrough() let spy = null
let times = 1 let times = 1
popoverEl.addEventListener('hidden.bs.popover', () => { popoverEl.addEventListener('hidden.bs.popover', () => {
@@ -171,11 +187,12 @@ describe('Popover', () => {
}) })
popoverEl.addEventListener('shown.bs.popover', () => { popoverEl.addEventListener('shown.bs.popover', () => {
spy = spy || spyOn(popover._templateFactory, 'constructor').and.callThrough()
const popoverDisplayed = document.querySelector('.popover') const popoverDisplayed = document.querySelector('.popover')
expect(popoverDisplayed).not.toBeNull() expect(popoverDisplayed).not.toBeNull()
expect(popoverDisplayed.querySelector('.popover-body').textContent).toEqual('Popover content') expect(popoverDisplayed.querySelector('.popover-body').textContent).toEqual('Popover content')
expect(spy).toHaveBeenCalledTimes(1) expect(spy).toHaveBeenCalledTimes(0)
if (times > 1) { if (times > 1) {
done() done()
} }
@@ -195,7 +212,7 @@ describe('Popover', () => {
popoverEl.addEventListener('shown.bs.popover', () => { popoverEl.addEventListener('shown.bs.popover', () => {
const tip = document.querySelector('.popover') const tip = document.querySelector('.popover')
expect(tip).not.toBeNull() expect(tip).not.toBeNull()
expect(tip.classList.contains('custom-class')).toBeTrue() expect(tip).toHaveClass('custom-class')
done() done()
}) })
@@ -313,7 +330,7 @@ describe('Popover', () => {
const popoverEl = fixtureEl.querySelector('a') const popoverEl = fixtureEl.querySelector('a')
expect(Popover.getInstance(popoverEl)).toEqual(null) expect(Popover.getInstance(popoverEl)).toBeNull()
}) })
}) })
@@ -334,7 +351,7 @@ describe('Popover', () => {
const div = fixtureEl.querySelector('div') const div = fixtureEl.querySelector('div')
expect(Popover.getInstance(div)).toEqual(null) expect(Popover.getInstance(div)).toBeNull()
expect(Popover.getOrCreateInstance(div)).toBeInstanceOf(Popover) expect(Popover.getOrCreateInstance(div)).toBeInstanceOf(Popover)
}) })
@@ -343,7 +360,7 @@ describe('Popover', () => {
const div = fixtureEl.querySelector('div') const div = fixtureEl.querySelector('div')
expect(Popover.getInstance(div)).toEqual(null) expect(Popover.getInstance(div)).toBeNull()
const popover = Popover.getOrCreateInstance(div, { const popover = Popover.getOrCreateInstance(div, {
placement: 'top' placement: 'top'
}) })
+14 -14
View File
@@ -14,7 +14,7 @@ describe('ScrollSpy', () => {
const scrollHeight = Math.ceil(contentEl.scrollTop + Manipulator.position(target).top) + paddingTop const scrollHeight = Math.ceil(contentEl.scrollTop + Manipulator.position(target).top) + paddingTop
function listener() { function listener() {
expect(element.classList.contains('active')).toEqual(true) expect(element).toHaveClass('active')
contentEl.removeEventListener('scroll', listener) contentEl.removeEventListener('scroll', listener)
expect(scrollSpy._process).toHaveBeenCalled() expect(scrollSpy._process).toHaveBeenCalled()
spy.calls.reset() spy.calls.reset()
@@ -82,7 +82,7 @@ describe('ScrollSpy', () => {
target: '#navigation' target: '#navigation'
}) })
expect(scrollSpy._targets.length).toEqual(2) expect(scrollSpy._targets).toHaveSize(2)
}) })
it('should only switch "active" class on current target', done => { it('should only switch "active" class on current target', done => {
@@ -120,7 +120,7 @@ describe('ScrollSpy', () => {
spyOn(scrollSpy, '_process').and.callThrough() spyOn(scrollSpy, '_process').and.callThrough()
scrollSpyEl.addEventListener('scroll', () => { scrollSpyEl.addEventListener('scroll', () => {
expect(rootEl.classList.contains('active')).toEqual(true) expect(rootEl).toHaveClass('active')
expect(scrollSpy._process).toHaveBeenCalled() expect(scrollSpy._process).toHaveBeenCalled()
done() done()
}) })
@@ -163,7 +163,7 @@ describe('ScrollSpy', () => {
spyOn(scrollSpy, '_process').and.callThrough() spyOn(scrollSpy, '_process').and.callThrough()
scrollSpyEl.addEventListener('scroll', () => { scrollSpyEl.addEventListener('scroll', () => {
expect(rootEl.classList.contains('active')).toEqual(true) expect(rootEl).toHaveClass('active')
expect(scrollSpy._process).toHaveBeenCalled() expect(scrollSpy._process).toHaveBeenCalled()
done() done()
}) })
@@ -197,9 +197,9 @@ describe('ScrollSpy', () => {
spyOn(scrollSpy, '_process').and.callThrough() spyOn(scrollSpy, '_process').and.callThrough()
contentEl.addEventListener('scroll', () => { contentEl.addEventListener('scroll', () => {
expect(fixtureEl.querySelector('#one-link').classList.contains('active')).toEqual(false) expect(fixtureEl.querySelector('#one-link')).not.toHaveClass('active')
expect(fixtureEl.querySelector('#two-link').classList.contains('active')).toEqual(true) expect(fixtureEl.querySelector('#two-link')).toHaveClass('active')
expect(fixtureEl.querySelector('#three-link').classList.contains('active')).toEqual(false) expect(fixtureEl.querySelector('#three-link')).not.toHaveClass('active')
expect(scrollSpy._process).toHaveBeenCalled() expect(scrollSpy._process).toHaveBeenCalled()
done() done()
}) })
@@ -361,7 +361,7 @@ describe('ScrollSpy', () => {
expect(spy).toHaveBeenCalled() expect(spy).toHaveBeenCalled()
spy.calls.reset() spy.calls.reset()
if (firstTime) { if (firstTime) {
expect(fixtureEl.querySelectorAll('.active').length).toEqual(1) expect(fixtureEl.querySelectorAll('.active')).toHaveSize(1)
expect(active.getAttribute('id')).toEqual('two-link') expect(active.getAttribute('id')).toEqual('two-link')
firstTime = false firstTime = false
contentEl.scrollTop = 0 contentEl.scrollTop = 0
@@ -409,12 +409,12 @@ describe('ScrollSpy', () => {
expect(spy).toHaveBeenCalled() expect(spy).toHaveBeenCalled()
spy.calls.reset() spy.calls.reset()
if (firstTime) { if (firstTime) {
expect(fixtureEl.querySelectorAll('.active').length).toEqual(1) expect(fixtureEl.querySelectorAll('.active')).toHaveSize(1)
expect(active.getAttribute('id')).toEqual('two-link') expect(active.getAttribute('id')).toEqual('two-link')
firstTime = false firstTime = false
contentEl.scrollTop = negativeHeight contentEl.scrollTop = negativeHeight
} else { } else {
expect(fixtureEl.querySelectorAll('.active').length).toEqual(1) expect(fixtureEl.querySelectorAll('.active')).toHaveSize(1)
expect(active.getAttribute('id')).toEqual('one-link') expect(active.getAttribute('id')).toEqual('one-link')
done() done()
} }
@@ -602,7 +602,7 @@ describe('ScrollSpy', () => {
const scrollspy = ScrollSpy.getInstance(div) const scrollspy = ScrollSpy.getInstance(div)
expect(scrollspy).not.toBeNull() expect(scrollspy).not.toBeNull()
expect(scrollspy._config.offset).toBe(15) expect(scrollspy._config.offset).toEqual(15)
}) })
it('should not re create a scrollspy', () => { it('should not re create a scrollspy', () => {
@@ -663,7 +663,7 @@ describe('ScrollSpy', () => {
}) })
it('should return null if there is no instance', () => { it('should return null if there is no instance', () => {
expect(ScrollSpy.getInstance(fixtureEl)).toEqual(null) expect(ScrollSpy.getInstance(fixtureEl)).toBeNull()
}) })
}) })
@@ -684,7 +684,7 @@ describe('ScrollSpy', () => {
const div = fixtureEl.querySelector('div') const div = fixtureEl.querySelector('div')
expect(ScrollSpy.getInstance(div)).toEqual(null) expect(ScrollSpy.getInstance(div)).toBeNull()
expect(ScrollSpy.getOrCreateInstance(div)).toBeInstanceOf(ScrollSpy) expect(ScrollSpy.getOrCreateInstance(div)).toBeInstanceOf(ScrollSpy)
}) })
@@ -693,7 +693,7 @@ describe('ScrollSpy', () => {
const div = fixtureEl.querySelector('div') const div = fixtureEl.querySelector('div')
expect(ScrollSpy.getInstance(div)).toEqual(null) expect(ScrollSpy.getInstance(div)).toBeNull()
const scrollspy = ScrollSpy.getOrCreateInstance(div, { const scrollspy = ScrollSpy.getOrCreateInstance(div, {
offset: 1 offset: 1
}) })
+46 -36
View File
@@ -21,8 +21,12 @@ describe('Tab', () => {
describe('constructor', () => { describe('constructor', () => {
it('should take care of element either passed as a CSS selector or DOM element', () => { it('should take care of element either passed as a CSS selector or DOM element', () => {
fixtureEl.innerHTML = [ fixtureEl.innerHTML = [
'<ul class="nav"><li><a href="#home" role="tab">Home</a></li></ul>', '<ul class="nav">',
'<ul><li id="home"></li></ul>' ' <li><a href="#home" role="tab">Home</a></li>',
'</ul>',
'<ul>',
' <li id="home"></li>',
'</ul>'
].join('') ].join('')
const tabEl = fixtureEl.querySelector('[href="#home"]') const tabEl = fixtureEl.querySelector('[href="#home"]')
@@ -51,7 +55,7 @@ describe('Tab', () => {
const tab = new Tab(profileTriggerEl) const tab = new Tab(profileTriggerEl)
profileTriggerEl.addEventListener('shown.bs.tab', () => { profileTriggerEl.addEventListener('shown.bs.tab', () => {
expect(fixtureEl.querySelector('#profile').classList.contains('active')).toEqual(true) expect(fixtureEl.querySelector('#profile')).toHaveClass('active')
expect(profileTriggerEl.getAttribute('aria-selected')).toEqual('true') expect(profileTriggerEl.getAttribute('aria-selected')).toEqual('true')
done() done()
}) })
@@ -75,7 +79,7 @@ describe('Tab', () => {
const tab = new Tab(profileTriggerEl) const tab = new Tab(profileTriggerEl)
profileTriggerEl.addEventListener('shown.bs.tab', () => { profileTriggerEl.addEventListener('shown.bs.tab', () => {
expect(fixtureEl.querySelector('#profile').classList.contains('active')).toEqual(true) expect(fixtureEl.querySelector('#profile')).toHaveClass('active')
expect(profileTriggerEl.getAttribute('aria-selected')).toEqual('true') expect(profileTriggerEl.getAttribute('aria-selected')).toEqual('true')
done() done()
}) })
@@ -99,7 +103,7 @@ describe('Tab', () => {
const tab = new Tab(profileTriggerEl) const tab = new Tab(profileTriggerEl)
profileTriggerEl.addEventListener('shown.bs.tab', () => { profileTriggerEl.addEventListener('shown.bs.tab', () => {
expect(fixtureEl.querySelector('#profile').classList.contains('active')).toEqual(true) expect(fixtureEl.querySelector('#profile')).toHaveClass('active')
done() done()
}) })
@@ -110,16 +114,19 @@ describe('Tab', () => {
fixtureEl.innerHTML = [ fixtureEl.innerHTML = [
'<nav class="nav">', '<nav class="nav">',
' <button type="button" data-bs-target="#home" role="tab">Home</button>', ' <button type="button" data-bs-target="#home" role="tab">Home</button>',
' <button type="button" id="triggerProfile" data-bs-target="#profile" role="tab">Profile</a>', ' <button type="button" id="triggerProfile" data-bs-target="#profile" role="tab">Profile</button>',
'</nav>', '</nav>',
'<div><div id="home" role="tabpanel"></div><div id="profile" role="tabpanel"></div></div>' '<div>',
' <div id="home" role="tabpanel"></div>',
' <div id="profile" role="tabpanel"></div>',
'</div>'
].join('') ].join('')
const profileTriggerEl = fixtureEl.querySelector('#triggerProfile') const profileTriggerEl = fixtureEl.querySelector('#triggerProfile')
const tab = new Tab(profileTriggerEl) const tab = new Tab(profileTriggerEl)
profileTriggerEl.addEventListener('shown.bs.tab', () => { profileTriggerEl.addEventListener('shown.bs.tab', () => {
expect(fixtureEl.querySelector('#profile').classList.contains('active')).toEqual(true) expect(fixtureEl.querySelector('#profile')).toHaveClass('active')
done() done()
}) })
@@ -132,14 +139,17 @@ describe('Tab', () => {
' <button type="button" data-bs-target="#home" role="tab">Home</button>', ' <button type="button" data-bs-target="#home" role="tab">Home</button>',
' <button type="button" id="triggerProfile" data-bs-target="#profile" role="tab">Profile</button>', ' <button type="button" id="triggerProfile" data-bs-target="#profile" role="tab">Profile</button>',
'</div>', '</div>',
'<div><div id="home" role="tabpanel"></div><div id="profile" role="tabpanel"></div></div>' '<div>',
' <div id="home" role="tabpanel"></div>',
' <div id="profile" role="tabpanel"></div>',
'</div>'
].join('') ].join('')
const profileTriggerEl = fixtureEl.querySelector('#triggerProfile') const profileTriggerEl = fixtureEl.querySelector('#triggerProfile')
const tab = new Tab(profileTriggerEl) const tab = new Tab(profileTriggerEl)
profileTriggerEl.addEventListener('shown.bs.tab', () => { profileTriggerEl.addEventListener('shown.bs.tab', () => {
expect(fixtureEl.querySelector('#profile').classList.contains('active')).toEqual(true) expect(fixtureEl.querySelector('#profile')).toHaveClass('active')
done() done()
}) })
@@ -248,7 +258,7 @@ describe('Tab', () => {
}) })
triggerList[0].addEventListener('hidden.bs.tab', ev => { triggerList[0].addEventListener('hidden.bs.tab', ev => {
expect(hideCalled).toEqual(true) expect(hideCalled).toBeTrue()
expect(ev.relatedTarget.getAttribute('data-bs-target')).toEqual('#profile') expect(ev.relatedTarget.getAttribute('data-bs-target')).toEqual('#profile')
done() done()
}) })
@@ -321,7 +331,7 @@ describe('Tab', () => {
const secondNavTab = new Tab(secondNavEl) const secondNavTab = new Tab(secondNavEl)
secondNavEl.addEventListener('shown.bs.tab', () => { secondNavEl.addEventListener('shown.bs.tab', () => {
expect(fixtureEl.querySelectorAll('.nav-tab').length).toEqual(2) expect(fixtureEl.querySelectorAll('.nav-tab')).toHaveSize(2)
done() done()
}) })
@@ -417,7 +427,7 @@ describe('Tab', () => {
describe('getInstance', () => { describe('getInstance', () => {
it('should return null if there is no instance', () => { it('should return null if there is no instance', () => {
expect(Tab.getInstance(fixtureEl)).toEqual(null) expect(Tab.getInstance(fixtureEl)).toBeNull()
}) })
it('should return this instance', () => { it('should return this instance', () => {
@@ -448,7 +458,7 @@ describe('Tab', () => {
const div = fixtureEl.querySelector('div') const div = fixtureEl.querySelector('div')
expect(Tab.getInstance(div)).toEqual(null) expect(Tab.getInstance(div)).toBeNull()
expect(Tab.getOrCreateInstance(div)).toBeInstanceOf(Tab) expect(Tab.getOrCreateInstance(div)).toBeInstanceOf(Tab)
}) })
}) })
@@ -469,8 +479,8 @@ describe('Tab', () => {
const secondTabTrigger = fixtureEl.querySelector('#triggerProfile') const secondTabTrigger = fixtureEl.querySelector('#triggerProfile')
secondTabTrigger.addEventListener('shown.bs.tab', () => { secondTabTrigger.addEventListener('shown.bs.tab', () => {
expect(secondTabTrigger.classList.contains('active')).toEqual(true) expect(secondTabTrigger).toHaveClass('active')
expect(fixtureEl.querySelector('#profile').classList.contains('active')).toEqual(true) expect(fixtureEl.querySelector('#profile')).toHaveClass('active')
done() done()
}) })
@@ -495,9 +505,9 @@ describe('Tab', () => {
const firstLiLinkEl = fixtureEl.querySelector('li:first-child a') const firstLiLinkEl = fixtureEl.querySelector('li:first-child a')
firstLiLinkEl.click() firstLiLinkEl.click()
expect(firstLiLinkEl.classList.contains('active')).toEqual(true) expect(firstLiLinkEl).toHaveClass('active')
expect(fixtureEl.querySelector('li:last-child a').classList.contains('active')).toEqual(false) expect(fixtureEl.querySelector('li:last-child a')).not.toHaveClass('active')
expect(fixtureEl.querySelector('li:last-child .dropdown-menu a:first-child').classList.contains('active')).toEqual(false) expect(fixtureEl.querySelector('li:last-child .dropdown-menu a:first-child')).not.toHaveClass('active')
}) })
it('selecting a dropdown tab does not activate another', () => { it('selecting a dropdown tab does not activate another', () => {
@@ -529,10 +539,10 @@ describe('Tab', () => {
const firstDropItem = fixtureEl.querySelector('#nav1 .dropdown-item') const firstDropItem = fixtureEl.querySelector('#nav1 .dropdown-item')
firstDropItem.click() firstDropItem.click()
expect(firstDropItem.classList.contains('active')).toEqual(true) expect(firstDropItem).toHaveClass('active')
expect(fixtureEl.querySelector('#nav1 .dropdown-toggle').classList.contains('active')).toEqual(true) expect(fixtureEl.querySelector('#nav1 .dropdown-toggle')).toHaveClass('active')
expect(fixtureEl.querySelector('#nav2 .dropdown-toggle').classList.contains('active')).toEqual(false) expect(fixtureEl.querySelector('#nav2 .dropdown-toggle')).not.toHaveClass('active')
expect(fixtureEl.querySelector('#nav2 .dropdown-item').classList.contains('active')).toEqual(false) expect(fixtureEl.querySelector('#nav2 .dropdown-item')).not.toHaveClass('active')
}) })
it('should support li > .dropdown-item', () => { it('should support li > .dropdown-item', () => {
@@ -553,8 +563,8 @@ describe('Tab', () => {
const firstDropItem = fixtureEl.querySelector('.dropdown-item') const firstDropItem = fixtureEl.querySelector('.dropdown-item')
firstDropItem.click() firstDropItem.click()
expect(firstDropItem.classList.contains('active')).toEqual(true) expect(firstDropItem).toHaveClass('active')
expect(fixtureEl.querySelector('.nav-link').classList.contains('active')).toEqual(false) expect(fixtureEl.querySelector('.nav-link')).not.toHaveClass('active')
}) })
it('should handle nested tabs', done => { it('should handle nested tabs', done => {
@@ -585,12 +595,12 @@ describe('Tab', () => {
const xTab1El = fixtureEl.querySelector('#x-tab1') const xTab1El = fixtureEl.querySelector('#x-tab1')
tabNested2El.addEventListener('shown.bs.tab', () => { tabNested2El.addEventListener('shown.bs.tab', () => {
expect(xTab1El.classList.contains('active')).toEqual(true) expect(xTab1El).toHaveClass('active')
done() done()
}) })
tab1El.addEventListener('shown.bs.tab', () => { tab1El.addEventListener('shown.bs.tab', () => {
expect(xTab1El.classList.contains('active')).toEqual(true) expect(xTab1El).toHaveClass('active')
tabNested2El.click() tabNested2El.click()
}) })
@@ -615,15 +625,15 @@ describe('Tab', () => {
const tabHomeEl = fixtureEl.querySelector('#home') const tabHomeEl = fixtureEl.querySelector('#home')
triggerTabProfileEl.addEventListener('shown.bs.tab', () => { triggerTabProfileEl.addEventListener('shown.bs.tab', () => {
expect(tabProfileEl.classList.contains('fade')).toEqual(true) expect(tabProfileEl).toHaveClass('fade')
expect(tabProfileEl.classList.contains('show')).toEqual(true) expect(tabProfileEl).toHaveClass('show')
triggerTabHomeEl.addEventListener('shown.bs.tab', () => { triggerTabHomeEl.addEventListener('shown.bs.tab', () => {
expect(tabProfileEl.classList.contains('fade')).toEqual(true) expect(tabProfileEl).toHaveClass('fade')
expect(tabProfileEl.classList.contains('show')).toEqual(false) expect(tabProfileEl).not.toHaveClass('show')
expect(tabHomeEl.classList.contains('fade')).toEqual(true) expect(tabHomeEl).toHaveClass('fade')
expect(tabHomeEl.classList.contains('show')).toEqual(true) expect(tabHomeEl).toHaveClass('show')
done() done()
}) })
@@ -653,7 +663,7 @@ describe('Tab', () => {
const secondNavEl = fixtureEl.querySelector('#secondNav') const secondNavEl = fixtureEl.querySelector('#secondNav')
secondNavEl.addEventListener('shown.bs.tab', () => { secondNavEl.addEventListener('shown.bs.tab', () => {
expect(fixtureEl.querySelectorAll('.show').length).toEqual(0) expect(fixtureEl.querySelectorAll('.show')).toHaveSize(0)
done() done()
}) })
@@ -679,7 +689,7 @@ describe('Tab', () => {
const secondNavEl = fixtureEl.querySelector('#secondNav') const secondNavEl = fixtureEl.querySelector('#secondNav')
secondNavEl.addEventListener('shown.bs.tab', () => { secondNavEl.addEventListener('shown.bs.tab', () => {
expect(fixtureEl.querySelectorAll('.show').length).toEqual(1) expect(fixtureEl.querySelectorAll('.show')).toHaveSize(1)
done() done()
}) })
@@ -698,7 +708,7 @@ describe('Tab', () => {
spyOn(Event.prototype, 'preventDefault').and.callThrough() spyOn(Event.prototype, 'preventDefault').and.callThrough()
tabEl.addEventListener('shown.bs.tab', () => { tabEl.addEventListener('shown.bs.tab', () => {
expect(tabEl.classList.contains('active')).toEqual(true) expect(tabEl).toHaveClass('active')
expect(Event.prototype.preventDefault).toHaveBeenCalled() expect(Event.prototype.preventDefault).toHaveBeenCalled()
done() done()
}) })
+14 -14
View File
@@ -51,7 +51,7 @@ describe('Toast', () => {
}) })
toastEl.addEventListener('shown.bs.toast', () => { toastEl.addEventListener('shown.bs.toast', () => {
expect(toastEl.classList.contains('show')).toEqual(true) expect(toastEl).toHaveClass('show')
done() done()
}) })
@@ -69,7 +69,7 @@ describe('Toast', () => {
const toast = new Toast(toastEl) const toast = new Toast(toastEl)
toastEl.addEventListener('shown.bs.toast', () => { toastEl.addEventListener('shown.bs.toast', () => {
expect(toastEl.classList.contains('show')).toEqual(true) expect(toastEl).toHaveClass('show')
const button = toastEl.querySelector('.btn-close') const button = toastEl.querySelector('.btn-close')
@@ -77,7 +77,7 @@ describe('Toast', () => {
}) })
toastEl.addEventListener('hidden.bs.toast', () => { toastEl.addEventListener('hidden.bs.toast', () => {
expect(toastEl.classList.contains('show')).toEqual(false) expect(toastEl).not.toHaveClass('show')
done() done()
}) })
@@ -124,7 +124,7 @@ describe('Toast', () => {
const toast = new Toast(toastEl) const toast = new Toast(toastEl)
toastEl.addEventListener('hidden.bs.toast', () => { toastEl.addEventListener('hidden.bs.toast', () => {
expect(toastEl.classList.contains('show')).toEqual(false) expect(toastEl).not.toHaveClass('show')
done() done()
}) })
@@ -144,7 +144,7 @@ describe('Toast', () => {
const toast = new Toast(toastEl) const toast = new Toast(toastEl)
toastEl.addEventListener('shown.bs.toast', () => { toastEl.addEventListener('shown.bs.toast', () => {
expect(toastEl.classList.contains('fade')).toEqual(false) expect(toastEl).not.toHaveClass('fade')
done() done()
}) })
@@ -165,7 +165,7 @@ describe('Toast', () => {
const assertDone = () => { const assertDone = () => {
setTimeout(() => { setTimeout(() => {
expect(toastEl.classList.contains('show')).toEqual(false) expect(toastEl).not.toHaveClass('show')
done() done()
}, 20) }, 20)
} }
@@ -393,7 +393,7 @@ describe('Toast', () => {
' <div class="toast-body">', ' <div class="toast-body">',
' a simple toast', ' a simple toast',
' </div>', ' </div>',
' </div>' '</div>'
].join('') ].join('')
const toastEl = fixtureEl.querySelector('.toast') const toastEl = fixtureEl.querySelector('.toast')
@@ -404,7 +404,7 @@ describe('Toast', () => {
}) })
toastEl.addEventListener('hidden.bs.toast', () => { toastEl.addEventListener('hidden.bs.toast', () => {
expect(toastEl.classList.contains('show')).toEqual(false) expect(toastEl).not.toHaveClass('show')
done() done()
}) })
@@ -438,7 +438,7 @@ describe('Toast', () => {
const assertDone = () => { const assertDone = () => {
setTimeout(() => { setTimeout(() => {
expect(toastEl.classList.contains('show')).toEqual(true) expect(toastEl).toHaveClass('show')
done() done()
}, 20) }, 20)
} }
@@ -487,13 +487,13 @@ describe('Toast', () => {
const toastEl = fixtureEl.querySelector('div') const toastEl = fixtureEl.querySelector('div')
const toast = new Toast(toastEl) const toast = new Toast(toastEl)
const expected = () => { const expected = () => {
expect(toastEl.classList.contains('show')).toEqual(true) expect(toastEl).toHaveClass('show')
expect(Toast.getInstance(toastEl)).not.toBeNull() expect(Toast.getInstance(toastEl)).not.toBeNull()
toast.dispose() toast.dispose()
expect(Toast.getInstance(toastEl)).toBeNull() expect(Toast.getInstance(toastEl)).toBeNull()
expect(toastEl.classList.contains('show')).toEqual(false) expect(toastEl).not.toHaveClass('show')
done() done()
} }
@@ -582,7 +582,7 @@ describe('Toast', () => {
const div = fixtureEl.querySelector('div') const div = fixtureEl.querySelector('div')
expect(Toast.getInstance(div)).toEqual(null) expect(Toast.getInstance(div)).toBeNull()
}) })
}) })
@@ -603,7 +603,7 @@ describe('Toast', () => {
const div = fixtureEl.querySelector('div') const div = fixtureEl.querySelector('div')
expect(Toast.getInstance(div)).toEqual(null) expect(Toast.getInstance(div)).toBeNull()
expect(Toast.getOrCreateInstance(div)).toBeInstanceOf(Toast) expect(Toast.getOrCreateInstance(div)).toBeInstanceOf(Toast)
}) })
@@ -612,7 +612,7 @@ describe('Toast', () => {
const div = fixtureEl.querySelector('div') const div = fixtureEl.querySelector('div')
expect(Toast.getInstance(div)).toEqual(null) expect(Toast.getInstance(div)).toBeNull()
const toast = Toast.getOrCreateInstance(div, { const toast = Toast.getOrCreateInstance(div, {
delay: 1 delay: 1
}) })
+137 -117
View File
@@ -78,7 +78,7 @@ describe('Tooltip', () => {
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl) const tooltip = new Tooltip(tooltipEl)
expect(tooltip._config.sanitize).toEqual(true) expect(tooltip._config.sanitize).toBeTrue()
}) })
it('should convert title and content to string if numbers', () => { it('should convert title and content to string if numbers', () => {
@@ -137,7 +137,7 @@ describe('Tooltip', () => {
const offset = tooltip._getOffset() const offset = tooltip._getOffset()
expect(typeof offset).toEqual('function') expect(offset).toEqual(jasmine.any(Function))
tooltip.show() tooltip.show()
}) })
@@ -180,6 +180,15 @@ describe('Tooltip', () => {
expect(getPopperConfig).toHaveBeenCalled() expect(getPopperConfig).toHaveBeenCalled()
expect(popperConfig.placement).toEqual('left') expect(popperConfig.placement).toEqual('left')
}) })
it('should use original title, if not "data-bs-title" is given', () => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl)
expect(tooltip._config.title).toEqual('Another tooltip')
})
}) })
describe('enable', () => { describe('enable', () => {
@@ -229,11 +238,11 @@ describe('Tooltip', () => {
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl) const tooltip = new Tooltip(tooltipEl)
expect(tooltip._isEnabled).toEqual(true) expect(tooltip._isEnabled).toBeTrue()
tooltip.toggleEnabled() tooltip.toggleEnabled()
expect(tooltip._isEnabled).toEqual(false) expect(tooltip._isEnabled).toBeFalse()
}) })
}) })
@@ -354,7 +363,7 @@ describe('Tooltip', () => {
tooltip.dispose() tooltip.dispose()
expect(Tooltip.getInstance(tooltipEl)).toEqual(null) expect(Tooltip.getInstance(tooltipEl)).toBeNull()
expect(removeEventSpy.calls.allArgs()).toEqual(expectedArgs) expect(removeEventSpy.calls.allArgs()).toEqual(expectedArgs)
}) })
@@ -369,8 +378,8 @@ describe('Tooltip', () => {
}) })
tooltipEl.addEventListener('hidden.bs.tooltip', () => { tooltipEl.addEventListener('hidden.bs.tooltip', () => {
tooltip.dispose() tooltip.dispose()
expect(tooltip.tip).toEqual(null) expect(tooltip.tip).toBeNull()
expect(Tooltip.getInstance(tooltipEl)).toEqual(null) expect(Tooltip.getInstance(tooltipEl)).toBeNull()
done() done()
}) })
@@ -415,14 +424,15 @@ describe('Tooltip', () => {
tooltip.show() tooltip.show()
}) })
it('should show a tooltip when hovering a children element', done => { it('should show a tooltip when hovering a child element', done => {
fixtureEl.innerHTML = fixtureEl.innerHTML = [
'<a href="#" rel="tooltip" title="Another tooltip">' + '<a href="#" rel="tooltip" title="Another tooltip">',
'<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 100 100">' + ' <svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 100 100">',
'<rect width="100%" fill="#563d7c"/>' + ' <rect width="100%" fill="#563d7c"/>',
'<circle cx="50" cy="50" r="30" fill="#fff"/>' + ' <circle cx="50" cy="50" r="30" fill="#fff"/>',
'</svg>' + ' </svg>',
'</a>' '</a>'
].join('')
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl) const tooltip = new Tooltip(tooltipEl)
@@ -465,13 +475,12 @@ describe('Tooltip', () => {
}) })
tooltipEl.addEventListener('inserted.bs.tooltip', () => { tooltipEl.addEventListener('inserted.bs.tooltip', () => {
expect(tooltip.getTipElement().classList.contains('bs-tooltip-bottom')).toEqual(true) expect(tooltip._getTipElement()).toHaveClass('bs-tooltip-auto')
}) })
tooltipEl.addEventListener('shown.bs.tooltip', () => { tooltipEl.addEventListener('shown.bs.tooltip', () => {
const tooltipShown = document.querySelector('.tooltip') expect(tooltip._getTipElement()).toHaveClass('bs-tooltip-auto')
expect(tooltip._getTipElement().getAttribute('data-popper-placement')).toEqual('bottom')
expect(tooltipShown.classList.contains('bs-tooltip-bottom')).toEqual(true)
done() done()
}) })
@@ -586,7 +595,7 @@ describe('Tooltip', () => {
const tip = document.querySelector('.tooltip') const tip = document.querySelector('.tooltip')
expect(tip).not.toBeNull() expect(tip).not.toBeNull()
expect(tip.classList.contains('fade')).toEqual(false) expect(tip).not.toHaveClass('fade')
done() done()
}) })
@@ -670,7 +679,7 @@ describe('Tooltip', () => {
setTimeout(() => { setTimeout(() => {
expect(tooltip.show).toHaveBeenCalled() expect(tooltip.show).toHaveBeenCalled()
expect(document.querySelectorAll('.tooltip').length).toEqual(0) expect(document.querySelectorAll('.tooltip')).toHaveSize(0)
done() done()
}, 200) }, 200)
@@ -689,19 +698,20 @@ describe('Tooltip', () => {
}) })
setTimeout(() => { setTimeout(() => {
expect(tooltip.getTipElement().classList.contains('show')).toEqual(true) expect(tooltip._getTipElement()).toHaveClass('show')
tooltipEl.dispatchEvent(createEvent('mouseout')) tooltipEl.dispatchEvent(createEvent('mouseout'))
setTimeout(() => { setTimeout(() => {
expect(tooltip.getTipElement().classList.contains('show')).toEqual(true) expect(tooltip._getTipElement()).toHaveClass('show')
tooltipEl.dispatchEvent(createEvent('mouseover')) tooltipEl.dispatchEvent(createEvent('mouseover'))
}, 100) }, 100)
setTimeout(() => { setTimeout(() => {
expect(tooltip.getTipElement().classList.contains('show')).toEqual(true) expect(tooltip._getTipElement()).toHaveClass('show')
expect(document.querySelectorAll('.tooltip')).toHaveSize(1)
done() done()
}, 200) }, 200)
}, 0) }, 10)
tooltipEl.dispatchEvent(createEvent('mouseover')) tooltipEl.dispatchEvent(createEvent('mouseover'))
}) })
@@ -751,20 +761,20 @@ describe('Tooltip', () => {
setTimeout(() => { setTimeout(() => {
expect(tooltip._popper).not.toBeNull() expect(tooltip._popper).not.toBeNull()
expect(tooltip.getTipElement().getAttribute('data-popper-placement')).toBe('top') expect(tooltip._getTipElement().getAttribute('data-popper-placement')).toEqual('top')
tooltipEl.dispatchEvent(createEvent('mouseout')) tooltipEl.dispatchEvent(createEvent('mouseout'))
setTimeout(() => { setTimeout(() => {
expect(tooltip.getTipElement().classList.contains('show')).toEqual(false) expect(tooltip._getTipElement()).not.toHaveClass('show')
tooltipEl.dispatchEvent(createEvent('mouseover')) tooltipEl.dispatchEvent(createEvent('mouseover'))
}, 100) }, 100)
setTimeout(() => { setTimeout(() => {
expect(tooltip._popper).not.toBeNull() expect(tooltip._popper).not.toBeNull()
expect(tooltip.getTipElement().getAttribute('data-popper-placement')).toBe('top') expect(tooltip._getTipElement().getAttribute('data-popper-placement')).toEqual('top')
done() done()
}, 200) }, 200)
}, 0) }, 10)
tooltipEl.dispatchEvent(createEvent('mouseover')) tooltipEl.dispatchEvent(createEvent('mouseover'))
}) })
@@ -809,7 +819,7 @@ describe('Tooltip', () => {
tooltipEl.addEventListener('shown.bs.tooltip', () => { tooltipEl.addEventListener('shown.bs.tooltip', () => {
const tip = document.querySelector('.tooltip') const tip = document.querySelector('.tooltip')
expect(tip).not.toBeNull() expect(tip).not.toBeNull()
expect(tip.classList.contains('custom-class')).toBeTrue() expect(tip).toHaveClass('custom-class')
done() done()
}) })
@@ -827,8 +837,8 @@ describe('Tooltip', () => {
tooltipEl.addEventListener('shown.bs.tooltip', () => { tooltipEl.addEventListener('shown.bs.tooltip', () => {
const tip = document.querySelector('.tooltip') const tip = document.querySelector('.tooltip')
expect(tip).not.toBeNull() expect(tip).not.toBeNull()
expect(tip.classList.contains('custom-class')).toBeTrue() expect(tip).toHaveClass('custom-class')
expect(tip.classList.contains('custom-class-2')).toBeTrue() expect(tip).toHaveClass('custom-class-2')
done() done()
}) })
@@ -848,12 +858,25 @@ describe('Tooltip', () => {
const tip = document.querySelector('.tooltip') const tip = document.querySelector('.tooltip')
expect(tip).not.toBeNull() expect(tip).not.toBeNull()
expect(spy).toHaveBeenCalled() expect(spy).toHaveBeenCalled()
expect(tip.classList.contains('custom-class')).toBeTrue() expect(tip).toHaveClass('custom-class')
done() done()
}) })
tooltip.show() tooltip.show()
}) })
it('should remove `title` attribute if exists', done => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl)
tooltipEl.addEventListener('shown.bs.tooltip', () => {
expect(tooltipEl.getAttribute('title')).toBeNull()
done()
})
tooltip.show()
})
}) })
describe('hide', () => { describe('hide', () => {
@@ -985,14 +1008,14 @@ describe('Tooltip', () => {
}) })
}) })
describe('isWithContent', () => { describe('_isWithContent', () => {
it('should return true if there is content', () => { 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">'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl) const tooltip = new Tooltip(tooltipEl)
expect(tooltip.isWithContent()).toEqual(true) expect(tooltip._isWithContent()).toBeTrue()
}) })
it('should return false if there is no content', () => { it('should return false if there is no content', () => {
@@ -1001,11 +1024,11 @@ describe('Tooltip', () => {
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl) const tooltip = new Tooltip(tooltipEl)
expect(tooltip.isWithContent()).toEqual(false) expect(tooltip._isWithContent()).toBeFalse()
}) })
}) })
describe('getTipElement', () => { describe('_getTipElement', () => {
it('should create the tip element and return it', () => { 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">'
@@ -1014,7 +1037,7 @@ describe('Tooltip', () => {
spyOn(document, 'createElement').and.callThrough() spyOn(document, 'createElement').and.callThrough()
expect(tooltip.getTipElement()).toBeDefined() expect(tooltip._getTipElement()).toBeDefined()
expect(document.createElement).toHaveBeenCalled() expect(document.createElement).toHaveBeenCalled()
}) })
@@ -1026,12 +1049,12 @@ describe('Tooltip', () => {
const spy = spyOn(document, 'createElement').and.callThrough() const spy = spyOn(document, 'createElement').and.callThrough()
expect(tooltip.getTipElement()).toBeDefined() expect(tooltip._getTipElement()).toBeDefined()
expect(spy).toHaveBeenCalled() expect(spy).toHaveBeenCalled()
spy.calls.reset() spy.calls.reset()
expect(tooltip.getTipElement()).toBeDefined() expect(tooltip._getTipElement()).toBeDefined()
expect(spy).not.toHaveBeenCalled() expect(spy).not.toHaveBeenCalled()
}) })
}) })
@@ -1041,84 +1064,78 @@ describe('Tooltip', () => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl) const tooltip = new Tooltip(tooltipEl, { animation: false })
const tip = tooltip.getTipElement() const tip = tooltip._getTipElement()
tooltip.setContent(tip) tooltip.setContent(tip)
expect(tip.classList.contains('show')).toEqual(false) expect(tip).not.toHaveClass('show')
expect(tip.classList.contains('fade')).toEqual(false) expect(tip).not.toHaveClass('fade')
expect(tip.querySelector('.tooltip-inner').textContent).toEqual('Another tooltip') expect(tip.querySelector('.tooltip-inner').textContent).toEqual('Another tooltip')
}) })
it('should re-show tip if it was already shown', () => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" data-bs-title="Another tooltip">'
const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl)
tooltip.show()
const tip = () => tooltip._getTipElement()
expect(tip()).toHaveClass('show')
tooltip.setContent({ '.tooltip-inner': 'foo' })
expect(tip()).toHaveClass('show')
expect(tip().querySelector('.tooltip-inner').textContent).toEqual('foo')
}) })
describe('updateAttachment', () => { it('should keep tip hidden, if it was already hidden before', () => {
it('should use end class name when right placement specified', done => { fixtureEl.innerHTML = '<a href="#" rel="tooltip" data-bs-title="Another tooltip">'
const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl)
const tip = () => tooltip._getTipElement()
expect(tip()).not.toHaveClass('show')
tooltip.setContent({ '.tooltip-inner': 'foo' })
expect(tip()).not.toHaveClass('show')
expect(tip().querySelector('.tooltip-inner').textContent).toEqual('foo')
})
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">'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl, { const tooltip = new Tooltip(tooltipEl)
placement: 'right'
})
tooltipEl.addEventListener('inserted.bs.tooltip', () => { tooltip.setContent({ '.tooltip-inner': 'foo' })
expect(tooltip.getTipElement().classList.contains('bs-tooltip-end')).toEqual(true) const tip = tooltip._getTipElement()
done()
})
tooltip.show() expect(tip).toHaveClass('tooltip')
}) expect(tip).toHaveClass('bs-tooltip-auto')
expect(tip.querySelector('.tooltip-arrow')).not.toBeNull()
it('should use start class name when left placement specified', done => { expect(tip.querySelector('.tooltip-inner')).not.toBeNull()
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl, {
placement: 'left'
})
tooltipEl.addEventListener('inserted.bs.tooltip', () => {
expect(tooltip.getTipElement().classList.contains('bs-tooltip-start')).toEqual(true)
done()
})
tooltip.show()
}) })
}) })
describe('setElementContent', () => { describe('setContent', () => {
it('should do nothing if the element is null', () => { 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">'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl) const tooltip = new Tooltip(tooltipEl)
tooltip.setElementContent(null, null) tooltip.setContent({ '.tooltip': null })
expect().nothing() expect().nothing()
}) })
it('should add the content as a child of the element', () => {
fixtureEl.innerHTML = [
'<a href="#" rel="tooltip" title="Another tooltip">',
'<div id="childContent"></div>'
].join('')
const tooltipEl = fixtureEl.querySelector('a')
const childContent = fixtureEl.querySelector('div')
const tooltip = new Tooltip(tooltipEl, {
html: true
})
tooltip.setElementContent(tooltip.getTipElement(), childContent)
expect(childContent.parentNode).toEqual(tooltip.getTipElement())
})
it('should do nothing if the content is a child of the element', () => { it('should do nothing if the content is a child of the element', () => {
fixtureEl.innerHTML = [ fixtureEl.innerHTML = [
'<a href="#" rel="tooltip" title="Another tooltip">', '<a href="#" rel="tooltip" title="Another tooltip">',
'<div id="childContent"></div>' ' <div id="childContent"></div>',
'</a>'
].join('') ].join('')
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
@@ -1127,8 +1144,8 @@ describe('Tooltip', () => {
html: true html: true
}) })
tooltip.getTipElement().append(childContent) tooltip._getTipElement().append(childContent)
tooltip.setElementContent(tooltip.getTipElement(), childContent) tooltip.setContent({ '.tooltip': childContent })
expect().nothing() expect().nothing()
}) })
@@ -1136,7 +1153,8 @@ describe('Tooltip', () => {
it('should add the content as a child of the element for jQuery elements', () => { it('should add the content as a child of the element for jQuery elements', () => {
fixtureEl.innerHTML = [ fixtureEl.innerHTML = [
'<a href="#" rel="tooltip" title="Another tooltip">', '<a href="#" rel="tooltip" title="Another tooltip">',
'<div id="childContent"></div>' ' <div id="childContent"></div>',
'</a>'
].join('') ].join('')
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
@@ -1145,28 +1163,29 @@ describe('Tooltip', () => {
html: true html: true
}) })
tooltip.setElementContent(tooltip.getTipElement(), { 0: childContent, jquery: 'jQuery' }) tooltip.setContent({ '.tooltip': { 0: childContent, jquery: 'jQuery' } })
expect(childContent.parentNode).toEqual(tooltip.getTipElement()) expect(childContent.parentNode).toEqual(tooltip._getTipElement())
}) })
it('should add the child text content in the element', () => { it('should add the child text content in the element', () => {
fixtureEl.innerHTML = [ fixtureEl.innerHTML = [
'<a href="#" rel="tooltip" title="Another tooltip">', '<a href="#" rel="tooltip" title="Another tooltip">',
'<div id="childContent">Tooltip</div>' ' <div id="childContent">Tooltip</div>',
'</a>'
].join('') ].join('')
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const childContent = fixtureEl.querySelector('div') const childContent = fixtureEl.querySelector('div')
const tooltip = new Tooltip(tooltipEl) const tooltip = new Tooltip(tooltipEl)
tooltip.setElementContent(tooltip.getTipElement(), childContent) tooltip.setContent({ '.tooltip': childContent })
expect(childContent.textContent).toEqual(tooltip.getTipElement().textContent) expect(childContent.textContent).toEqual(tooltip._getTipElement().textContent)
}) })
it('should add html without sanitize it', () => { it('should add html without sanitize it', () => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl, { const tooltip = new Tooltip(tooltipEl, {
@@ -1174,49 +1193,50 @@ describe('Tooltip', () => {
html: true html: true
}) })
tooltip.setElementContent(tooltip.getTipElement(), '<div id="childContent">Tooltip</div>') tooltip.setContent({ '.tooltip': '<div id="childContent">Tooltip</div>' })
expect(tooltip.getTipElement().querySelector('div').id).toEqual('childContent') expect(tooltip._getTipElement().querySelector('div').id).toEqual('childContent')
}) })
it('should add html sanitized', () => { it('should add html sanitized', () => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl, { const tooltip = new Tooltip(tooltipEl, {
html: true html: true
}) })
tooltip.setElementContent(tooltip.getTipElement(), [ const content = [
'<div id="childContent">', '<div id="childContent">',
' <button type="button">test btn</button>', ' <button type="button">test btn</button>',
'</div>' '</div>'
].join('')) ].join('')
expect(tooltip.getTipElement().querySelector('div').id).toEqual('childContent') tooltip.setContent({ '.tooltip': content })
expect(tooltip.getTipElement().querySelector('button')).toEqual(null) expect(tooltip._getTipElement().querySelector('div').id).toEqual('childContent')
expect(tooltip._getTipElement().querySelector('button')).toBeNull()
}) })
it('should add text content', () => { it('should add text content', () => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl) const tooltip = new Tooltip(tooltipEl)
tooltip.setElementContent(tooltip.getTipElement(), 'test') tooltip.setContent({ '.tooltip': 'test' })
expect(tooltip.getTipElement().textContent).toEqual('test') expect(tooltip._getTipElement().textContent).toEqual('test')
}) })
}) })
describe('getTitle', () => { describe('_getTitle', () => {
it('should return the title', () => { it('should return the title', () => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl) const tooltip = new Tooltip(tooltipEl)
expect(tooltip.getTitle()).toEqual('Another tooltip') expect(tooltip._getTitle()).toEqual('Another tooltip')
}) })
it('should call title function', () => { it('should call title function', () => {
@@ -1227,7 +1247,7 @@ describe('Tooltip', () => {
title: () => 'test' title: () => 'test'
}) })
expect(tooltip.getTitle()).toEqual('test') expect(tooltip._getTitle()).toEqual('test')
}) })
}) })
@@ -1247,7 +1267,7 @@ describe('Tooltip', () => {
const div = fixtureEl.querySelector('div') const div = fixtureEl.querySelector('div')
expect(Tooltip.getInstance(div)).toEqual(null) expect(Tooltip.getInstance(div)).toBeNull()
}) })
}) })
@@ -1321,7 +1341,7 @@ describe('Tooltip', () => {
const div = fixtureEl.querySelector('div') const div = fixtureEl.querySelector('div')
expect(Tooltip.getInstance(div)).toEqual(null) expect(Tooltip.getInstance(div)).toBeNull()
expect(Tooltip.getOrCreateInstance(div)).toBeInstanceOf(Tooltip) expect(Tooltip.getOrCreateInstance(div)).toBeInstanceOf(Tooltip)
}) })
@@ -1330,13 +1350,13 @@ describe('Tooltip', () => {
const div = fixtureEl.querySelector('div') const div = fixtureEl.querySelector('div')
expect(Tooltip.getInstance(div)).toEqual(null) expect(Tooltip.getInstance(div)).toBeNull()
const tooltip = Tooltip.getOrCreateInstance(div, { const tooltip = Tooltip.getOrCreateInstance(div, {
title: () => 'test' title: () => 'test'
}) })
expect(tooltip).toBeInstanceOf(Tooltip) expect(tooltip).toBeInstanceOf(Tooltip)
expect(tooltip.getTitle()).toEqual('test') expect(tooltip._getTitle()).toEqual('test')
}) })
it('should return the instance when exists without given configuration', () => { it('should return the instance when exists without given configuration', () => {
@@ -1354,7 +1374,7 @@ describe('Tooltip', () => {
expect(tooltip).toBeInstanceOf(Tooltip) expect(tooltip).toBeInstanceOf(Tooltip)
expect(tooltip2).toEqual(tooltip) expect(tooltip2).toEqual(tooltip)
expect(tooltip2.getTitle()).toEqual('nothing') expect(tooltip2._getTitle()).toEqual('nothing')
}) })
}) })
+32 -34
View File
@@ -23,53 +23,53 @@ describe('Backdrop', () => {
}) })
describe('show', () => { describe('show', () => {
it('if it is "shown", should append the backdrop html once, on show, and contain "show" class', done => { it('should append the backdrop html once on show and include the "show" class if it is "shown"', done => {
const instance = new Backdrop({ const instance = new Backdrop({
isVisible: true, isVisible: true,
isAnimated: false isAnimated: false
}) })
const getElements = () => document.querySelectorAll(CLASS_BACKDROP) const getElements = () => document.querySelectorAll(CLASS_BACKDROP)
expect(getElements().length).toEqual(0) expect(getElements()).toHaveSize(0)
instance.show() instance.show()
instance.show(() => { instance.show(() => {
expect(getElements().length).toEqual(1) expect(getElements()).toHaveSize(1)
for (const el of getElements()) { for (const el of getElements()) {
expect(el.classList.contains(CLASS_NAME_SHOW)).toEqual(true) expect(el).toHaveClass(CLASS_NAME_SHOW)
} }
done() done()
}) })
}) })
it('if it is not "shown", should not append the backdrop html', done => { it('should not append the backdrop html if it is not "shown"', done => {
const instance = new Backdrop({ const instance = new Backdrop({
isVisible: false, isVisible: false,
isAnimated: true isAnimated: true
}) })
const getElements = () => document.querySelectorAll(CLASS_BACKDROP) const getElements = () => document.querySelectorAll(CLASS_BACKDROP)
expect(getElements().length).toEqual(0) expect(getElements()).toHaveSize(0)
instance.show(() => { instance.show(() => {
expect(getElements().length).toEqual(0) expect(getElements()).toHaveSize(0)
done() done()
}) })
}) })
it('if it is "shown" and "animated", should append the backdrop html once, and contain "fade" class', done => { it('should append the backdrop html once and include the "fade" class if it is "shown" and "animated"', done => {
const instance = new Backdrop({ const instance = new Backdrop({
isVisible: true, isVisible: true,
isAnimated: true isAnimated: true
}) })
const getElements = () => document.querySelectorAll(CLASS_BACKDROP) const getElements = () => document.querySelectorAll(CLASS_BACKDROP)
expect(getElements().length).toEqual(0) expect(getElements()).toHaveSize(0)
instance.show(() => { instance.show(() => {
expect(getElements().length).toEqual(1) expect(getElements()).toHaveSize(1)
for (const el of getElements()) { for (const el of getElements()) {
expect(el.classList.contains(CLASS_NAME_FADE)).toEqual(true) expect(el).toHaveClass(CLASS_NAME_FADE)
} }
done() done()
@@ -86,17 +86,17 @@ describe('Backdrop', () => {
const getElements = () => document.body.querySelectorAll(CLASS_BACKDROP) const getElements = () => document.body.querySelectorAll(CLASS_BACKDROP)
expect(getElements().length).toEqual(0) expect(getElements()).toHaveSize(0)
instance.show(() => { instance.show(() => {
expect(getElements().length).toEqual(1) expect(getElements()).toHaveSize(1)
instance.hide(() => { instance.hide(() => {
expect(getElements().length).toEqual(0) expect(getElements()).toHaveSize(0)
done() done()
}) })
}) })
}) })
it('should remove "show" class', done => { it('should remove the "show" class', done => {
const instance = new Backdrop({ const instance = new Backdrop({
isVisible: true, isVisible: true,
isAnimated: true isAnimated: true
@@ -105,12 +105,12 @@ describe('Backdrop', () => {
instance.show() instance.show()
instance.hide(() => { instance.hide(() => {
expect(elem.classList.contains(CLASS_NAME_SHOW)).toEqual(false) expect(elem).not.toHaveClass(CLASS_NAME_SHOW)
done() done()
}) })
}) })
it('if it is not "shown", should not try to remove Node on remove method', done => { it('should not try to remove Node on remove method if it is not "shown"', done => {
const instance = new Backdrop({ const instance = new Backdrop({
isVisible: false, isVisible: false,
isAnimated: true isAnimated: true
@@ -118,13 +118,13 @@ describe('Backdrop', () => {
const getElements = () => document.querySelectorAll(CLASS_BACKDROP) const getElements = () => document.querySelectorAll(CLASS_BACKDROP)
const spy = spyOn(instance, 'dispose').and.callThrough() const spy = spyOn(instance, 'dispose').and.callThrough()
expect(getElements().length).toEqual(0) expect(getElements()).toHaveSize(0)
expect(instance._isAppended).toEqual(false) expect(instance._isAppended).toBeFalse()
instance.show(() => { instance.show(() => {
instance.hide(() => { instance.hide(() => {
expect(getElements().length).toEqual(0) expect(getElements()).toHaveSize(0)
expect(spy).not.toHaveBeenCalled() expect(spy).not.toHaveBeenCalled()
expect(instance._isAppended).toEqual(false) expect(instance._isAppended).toBeFalse()
done() done()
}) })
}) })
@@ -145,7 +145,7 @@ describe('Backdrop', () => {
instance.show(() => { instance.show(() => {
wrapper.remove() wrapper.remove()
instance.hide(() => { instance.hide(() => {
expect(getElements().length).toEqual(0) expect(getElements()).toHaveSize(0)
done() done()
}) })
}) })
@@ -153,7 +153,7 @@ describe('Backdrop', () => {
}) })
describe('click callback', () => { describe('click callback', () => {
it('it should execute callback on click', done => { it('should execute callback on click', done => {
const spy = jasmine.createSpy('spy') const spy = jasmine.createSpy('spy')
const instance = new Backdrop({ const instance = new Backdrop({
@@ -178,7 +178,7 @@ describe('Backdrop', () => {
}) })
describe('animation callbacks', () => { describe('animation callbacks', () => {
it('if it is animated, should show and hide backdrop after counting transition duration', done => { it('should show and hide backdrop after counting transition duration if it is animated', done => {
const instance = new Backdrop({ const instance = new Backdrop({
isVisible: true, isVisible: true,
isAnimated: true isAnimated: true
@@ -200,7 +200,7 @@ describe('Backdrop', () => {
expect(spy2).not.toHaveBeenCalled() expect(spy2).not.toHaveBeenCalled()
}) })
it('if it is not animated, should show and hide backdrop without delay', done => { it('should show and hide backdrop without a delay if it is not animated', done => {
const spy = jasmine.createSpy('spy', getTransitionDurationFromElement) const spy = jasmine.createSpy('spy', getTransitionDurationFromElement)
const instance = new Backdrop({ const instance = new Backdrop({
isVisible: true, isVisible: true,
@@ -218,7 +218,7 @@ describe('Backdrop', () => {
}, 10) }, 10)
}) })
it('if it is not "shown", should not call delay callbacks', done => { it('should not call delay callbacks if it is not "shown"', done => {
const instance = new Backdrop({ const instance = new Backdrop({
isVisible: false, isVisible: false,
isAnimated: true isAnimated: true
@@ -232,9 +232,10 @@ describe('Backdrop', () => {
}) })
}) })
}) })
describe('Config', () => { describe('Config', () => {
describe('rootElement initialization', () => { describe('rootElement initialization', () => {
it('Should be appended on "document.body" by default', done => { it('should be appended on "document.body" by default', done => {
const instance = new Backdrop({ const instance = new Backdrop({
isVisible: true isVisible: true
}) })
@@ -245,7 +246,7 @@ describe('Backdrop', () => {
}) })
}) })
it('Should find the rootElement if passed as a string', done => { it('should find the rootElement if passed as a string', done => {
const instance = new Backdrop({ const instance = new Backdrop({
isVisible: true, isVisible: true,
rootElement: 'body' rootElement: 'body'
@@ -257,11 +258,8 @@ describe('Backdrop', () => {
}) })
}) })
it('Should appended on any element given by the proper config', done => { it('should be appended on any element given by the proper config', done => {
fixtureEl.innerHTML = [ fixtureEl.innerHTML = '<div id="wrapper"></div>'
'<div id="wrapper">',
'</div>'
].join('')
const wrapper = fixtureEl.querySelector('#wrapper') const wrapper = fixtureEl.querySelector('#wrapper')
const instance = new Backdrop({ const instance = new Backdrop({
@@ -277,7 +275,7 @@ describe('Backdrop', () => {
}) })
describe('ClassName', () => { describe('ClassName', () => {
it('Should be able to have different classNames than default', done => { it('should allow configuring className', done => {
const instance = new Backdrop({ const instance = new Backdrop({
isVisible: true, isVisible: true,
className: 'foo' className: 'foo'
+2 -2
View File
@@ -182,10 +182,10 @@ describe('FocusTrap', () => {
it('should flag itself as no longer active', () => { it('should flag itself as no longer active', () => {
const focustrap = new FocusTrap({ trapElement: fixtureEl }) const focustrap = new FocusTrap({ trapElement: fixtureEl })
focustrap.activate() focustrap.activate()
expect(focustrap._isActive).toBe(true) expect(focustrap._isActive).toBeTrue()
focustrap.deactivate() focustrap.deactivate()
expect(focustrap._isActive).toBe(false) expect(focustrap._isActive).toBeFalse()
}) })
it('should remove all event listeners', () => { it('should remove all event listeners', () => {
+44 -43
View File
@@ -179,9 +179,9 @@ describe('Util', () => {
const el = fixtureEl.querySelector('#foo') const el = fixtureEl.querySelector('#foo')
expect(Util.isElement(el)).toEqual(true) expect(Util.isElement(el)).toBeTrue()
expect(Util.isElement({})).toEqual(false) expect(Util.isElement({})).toBeFalse()
expect(Util.isElement(fixtureEl.querySelectorAll('.test'))).toEqual(false) expect(Util.isElement(fixtureEl.querySelectorAll('.test'))).toBeFalse()
}) })
it('should detect jQuery element', () => { it('should detect jQuery element', () => {
@@ -193,7 +193,7 @@ describe('Util', () => {
jquery: 'foo' jquery: 'foo'
} }
expect(Util.isElement(fakejQuery)).toEqual(true) expect(Util.isElement(fakejQuery)).toBeTrue()
}) })
}) })
@@ -274,12 +274,12 @@ describe('Util', () => {
describe('isVisible', () => { describe('isVisible', () => {
it('should return false if the element is not defined', () => { it('should return false if the element is not defined', () => {
expect(Util.isVisible(null)).toEqual(false) expect(Util.isVisible(null)).toBeFalse()
expect(Util.isVisible(undefined)).toEqual(false) expect(Util.isVisible(undefined)).toBeFalse()
}) })
it('should return false if the element provided is not a dom element', () => { it('should return false if the element provided is not a dom element', () => {
expect(Util.isVisible({})).toEqual(false) expect(Util.isVisible({})).toBeFalse()
}) })
it('should return false if the element is not visible with display none', () => { it('should return false if the element is not visible with display none', () => {
@@ -287,7 +287,7 @@ describe('Util', () => {
const div = fixtureEl.querySelector('div') const div = fixtureEl.querySelector('div')
expect(Util.isVisible(div)).toEqual(false) expect(Util.isVisible(div)).toBeFalse()
}) })
it('should return false if the element is not visible with visibility hidden', () => { it('should return false if the element is not visible with visibility hidden', () => {
@@ -295,7 +295,7 @@ describe('Util', () => {
const div = fixtureEl.querySelector('div') const div = fixtureEl.querySelector('div')
expect(Util.isVisible(div)).toEqual(false) expect(Util.isVisible(div)).toBeFalse()
}) })
it('should return false if an ancestor element is display none', () => { it('should return false if an ancestor element is display none', () => {
@@ -311,7 +311,7 @@ describe('Util', () => {
const div = fixtureEl.querySelector('.content') const div = fixtureEl.querySelector('.content')
expect(Util.isVisible(div)).toEqual(false) expect(Util.isVisible(div)).toBeFalse()
}) })
it('should return false if an ancestor element is visibility hidden', () => { it('should return false if an ancestor element is visibility hidden', () => {
@@ -327,7 +327,7 @@ describe('Util', () => {
const div = fixtureEl.querySelector('.content') const div = fixtureEl.querySelector('.content')
expect(Util.isVisible(div)).toEqual(false) expect(Util.isVisible(div)).toBeFalse()
}) })
it('should return true if an ancestor element is visibility hidden, but reverted', () => { it('should return true if an ancestor element is visibility hidden, but reverted', () => {
@@ -343,7 +343,7 @@ describe('Util', () => {
const div = fixtureEl.querySelector('.content') const div = fixtureEl.querySelector('.content')
expect(Util.isVisible(div)).toEqual(true) expect(Util.isVisible(div)).toBeTrue()
}) })
it('should return true if the element is visible', () => { it('should return true if the element is visible', () => {
@@ -355,7 +355,7 @@ describe('Util', () => {
const div = fixtureEl.querySelector('#element') const div = fixtureEl.querySelector('#element')
expect(Util.isVisible(div)).toEqual(true) expect(Util.isVisible(div)).toBeTrue()
}) })
it('should return false if the element is hidden, but not via display or visibility', () => { it('should return false if the element is hidden, but not via display or visibility', () => {
@@ -367,20 +367,20 @@ describe('Util', () => {
const div = fixtureEl.querySelector('#element') const div = fixtureEl.querySelector('#element')
expect(Util.isVisible(div)).toEqual(false) expect(Util.isVisible(div)).toBeFalse()
}) })
}) })
describe('isDisabled', () => { describe('isDisabled', () => {
it('should return true if the element is not defined', () => { it('should return true if the element is not defined', () => {
expect(Util.isDisabled(null)).toEqual(true) expect(Util.isDisabled(null)).toBeTrue()
expect(Util.isDisabled(undefined)).toEqual(true) expect(Util.isDisabled(undefined)).toBeTrue()
expect(Util.isDisabled()).toEqual(true) expect(Util.isDisabled()).toBeTrue()
}) })
it('should return true if the element provided is not a dom element', () => { it('should return true if the element provided is not a dom element', () => {
expect(Util.isDisabled({})).toEqual(true) expect(Util.isDisabled({})).toBeTrue()
expect(Util.isDisabled('test')).toEqual(true) expect(Util.isDisabled('test')).toBeTrue()
}) })
it('should return true if the element has disabled attribute', () => { it('should return true if the element has disabled attribute', () => {
@@ -396,9 +396,9 @@ describe('Util', () => {
const div1 = fixtureEl.querySelector('#element1') const div1 = fixtureEl.querySelector('#element1')
const div2 = fixtureEl.querySelector('#element2') const div2 = fixtureEl.querySelector('#element2')
expect(Util.isDisabled(div)).toEqual(true) expect(Util.isDisabled(div)).toBeTrue()
expect(Util.isDisabled(div1)).toEqual(true) expect(Util.isDisabled(div1)).toBeTrue()
expect(Util.isDisabled(div2)).toEqual(true) expect(Util.isDisabled(div2)).toBeTrue()
}) })
it('should return false if the element has disabled attribute with "false" value, or doesn\'t have attribute', () => { it('should return false if the element has disabled attribute with "false" value, or doesn\'t have attribute', () => {
@@ -412,8 +412,8 @@ describe('Util', () => {
const div = fixtureEl.querySelector('#element') const div = fixtureEl.querySelector('#element')
const div1 = fixtureEl.querySelector('#element1') const div1 = fixtureEl.querySelector('#element1')
expect(Util.isDisabled(div)).toEqual(false) expect(Util.isDisabled(div)).toBeFalse()
expect(Util.isDisabled(div1)).toEqual(false) expect(Util.isDisabled(div1)).toBeFalse()
}) })
it('should return false if the element is not disabled ', () => { it('should return false if the element is not disabled ', () => {
@@ -427,10 +427,11 @@ describe('Util', () => {
const el = selector => fixtureEl.querySelector(selector) const el = selector => fixtureEl.querySelector(selector)
expect(Util.isDisabled(el('#button'))).toEqual(false) expect(Util.isDisabled(el('#button'))).toBeFalse()
expect(Util.isDisabled(el('#select'))).toEqual(false) expect(Util.isDisabled(el('#select'))).toBeFalse()
expect(Util.isDisabled(el('#input'))).toEqual(false) expect(Util.isDisabled(el('#input'))).toBeFalse()
}) })
it('should return true if the element has disabled attribute', () => { it('should return true if the element has disabled attribute', () => {
fixtureEl.innerHTML = [ fixtureEl.innerHTML = [
'<div>', '<div>',
@@ -446,12 +447,12 @@ describe('Util', () => {
const el = selector => fixtureEl.querySelector(selector) const el = selector => fixtureEl.querySelector(selector)
expect(Util.isDisabled(el('#input'))).toEqual(true) expect(Util.isDisabled(el('#input'))).toBeTrue()
expect(Util.isDisabled(el('#input1'))).toEqual(true) expect(Util.isDisabled(el('#input1'))).toBeTrue()
expect(Util.isDisabled(el('#button'))).toEqual(true) expect(Util.isDisabled(el('#button'))).toBeTrue()
expect(Util.isDisabled(el('#button1'))).toEqual(true) expect(Util.isDisabled(el('#button1'))).toBeTrue()
expect(Util.isDisabled(el('#button2'))).toEqual(true) expect(Util.isDisabled(el('#button2'))).toBeTrue()
expect(Util.isDisabled(el('#input'))).toEqual(true) expect(Util.isDisabled(el('#input'))).toBeTrue()
}) })
it('should return true if the element has class "disabled"', () => { it('should return true if the element has class "disabled"', () => {
@@ -463,7 +464,7 @@ describe('Util', () => {
const div = fixtureEl.querySelector('#element') const div = fixtureEl.querySelector('#element')
expect(Util.isDisabled(div)).toEqual(true) expect(Util.isDisabled(div)).toBeTrue()
}) })
it('should return true if the element has class "disabled" but disabled attribute is false', () => { it('should return true if the element has class "disabled" but disabled attribute is false', () => {
@@ -475,7 +476,7 @@ describe('Util', () => {
const div = fixtureEl.querySelector('#input') const div = fixtureEl.querySelector('#input')
expect(Util.isDisabled(div)).toEqual(true) expect(Util.isDisabled(div)).toBeTrue()
}) })
}) })
@@ -493,7 +494,7 @@ describe('Util', () => {
spyOn(document.documentElement, 'attachShadow').and.returnValue(null) spyOn(document.documentElement, 'attachShadow').and.returnValue(null)
expect(Util.findShadowRoot(div)).toEqual(null) expect(Util.findShadowRoot(div)).toBeNull()
}) })
it('should return null when we do not find a shadow root', () => { it('should return null when we do not find a shadow root', () => {
@@ -505,7 +506,7 @@ describe('Util', () => {
spyOn(document, 'getRootNode').and.returnValue(undefined) spyOn(document, 'getRootNode').and.returnValue(undefined)
expect(Util.findShadowRoot(document)).toEqual(null) expect(Util.findShadowRoot(document)).toBeNull()
}) })
it('should return the shadow root when found', () => { it('should return the shadow root when found', () => {
@@ -532,7 +533,7 @@ describe('Util', () => {
describe('noop', () => { describe('noop', () => {
it('should be a function', () => { it('should be a function', () => {
expect(typeof Util.noop).toEqual('function') expect(Util.noop).toEqual(jasmine.any(Function))
}) })
}) })
@@ -569,14 +570,14 @@ describe('Util', () => {
document.body.setAttribute('data-bs-no-jquery', '') document.body.setAttribute('data-bs-no-jquery', '')
expect(window.jQuery).toEqual(fakejQuery) expect(window.jQuery).toEqual(fakejQuery)
expect(Util.getjQuery()).toEqual(null) expect(Util.getjQuery()).toBeNull()
document.body.removeAttribute('data-bs-no-jquery') document.body.removeAttribute('data-bs-no-jquery')
}) })
it('should not return jQuery if not present', () => { it('should not return jQuery if not present', () => {
window.jQuery = undefined window.jQuery = undefined
expect(Util.getjQuery()).toEqual(null) expect(Util.getjQuery()).toBeNull()
}) })
}) })
@@ -628,9 +629,9 @@ describe('Util', () => {
pluginMock.jQueryInterface = function () {} pluginMock.jQueryInterface = function () {}
Util.defineJQueryPlugin(pluginMock) Util.defineJQueryPlugin(pluginMock)
expect(fakejQuery.fn.test).toBe(pluginMock.jQueryInterface) expect(fakejQuery.fn.test).toEqual(pluginMock.jQueryInterface)
expect(fakejQuery.fn.test.Constructor).toBe(pluginMock) expect(fakejQuery.fn.test.Constructor).toEqual(pluginMock)
expect(typeof fakejQuery.fn.test.noConflict).toEqual('function') expect(fakejQuery.fn.test.noConflict).toEqual(jasmine.any(Function))
}) })
}) })
+53 -61
View File
@@ -5,9 +5,9 @@ import ScrollBarHelper from '../../../src/util/scrollbar'
describe('ScrollBar', () => { describe('ScrollBar', () => {
let fixtureEl let fixtureEl
const doc = document.documentElement const doc = document.documentElement
const parseInt = arg => Number.parseInt(arg, 10) const parseIntDecimal = arg => Number.parseInt(arg, 10)
const getPaddingX = el => parseInt(window.getComputedStyle(el).paddingRight) const getPaddingX = el => parseIntDecimal(window.getComputedStyle(el).paddingRight)
const getMarginX = el => parseInt(window.getComputedStyle(el).marginRight) const getMarginX = el => parseIntDecimal(window.getComputedStyle(el).marginRight)
const getOverFlow = el => el.style.overflow const getOverFlow = el => el.style.overflow
const getPaddingAttr = el => Manipulator.getDataAttribute(el, 'padding-right') const getPaddingAttr = el => Manipulator.getDataAttribute(el, 'padding-right')
const getMarginAttr = el => Manipulator.getDataAttribute(el, 'margin-right') const getMarginAttr = el => Manipulator.getDataAttribute(el, 'margin-right')
@@ -24,7 +24,9 @@ describe('ScrollBar', () => {
} }
} }
const isScrollBarHidden = () => { // IOS devices, Android devices and Browsers on Mac, hide scrollbar by default and appear it, only while scrolling. So the tests for scrollbar would fail // iOS, Android devices and macOS browsers hide scrollbar by default and show it only while scrolling.
// So the tests for scrollbar would fail
const isScrollBarHidden = () => {
const calc = windowCalculations() const calc = windowCalculations()
return calc.htmlClient === calc.htmlOffset && calc.htmlClient === calc.window return calc.htmlClient === calc.htmlOffset && calc.htmlClient === calc.window
} }
@@ -52,28 +54,24 @@ describe('ScrollBar', () => {
it('should return true if body is overflowing', () => { it('should return true if body is overflowing', () => {
document.documentElement.style.overflowY = 'scroll' document.documentElement.style.overflowY = 'scroll'
document.body.style.overflowY = 'scroll' document.body.style.overflowY = 'scroll'
fixtureEl.innerHTML = [ fixtureEl.innerHTML = '<div style="height: 110vh; width: 100%"></div>'
'<div style="height: 110vh; width: 100%"></div>'
].join('')
const result = new ScrollBarHelper().isOverflowing() const result = new ScrollBarHelper().isOverflowing()
if (isScrollBarHidden()) { if (isScrollBarHidden()) {
expect(result).toEqual(false) expect(result).toBeFalse()
} else { } else {
expect(result).toEqual(true) expect(result).toBeTrue()
} }
}) })
it('should return false if body is not overflowing', () => { it('should return false if body is not overflowing', () => {
doc.style.overflowY = 'hidden' doc.style.overflowY = 'hidden'
document.body.style.overflowY = 'hidden' document.body.style.overflowY = 'hidden'
fixtureEl.innerHTML = [ fixtureEl.innerHTML = '<div style="height: 110vh; width: 100%"></div>'
'<div style="height: 110vh; width: 100%"></div>'
].join('')
const scrollBar = new ScrollBarHelper() const scrollBar = new ScrollBarHelper()
const result = scrollBar.isOverflowing() const result = scrollBar.isOverflowing()
expect(result).toEqual(false) expect(result).toBeFalse()
}) })
}) })
@@ -81,13 +79,11 @@ describe('ScrollBar', () => {
it('should return an integer greater than zero, if body is overflowing', () => { it('should return an integer greater than zero, if body is overflowing', () => {
doc.style.overflowY = 'scroll' doc.style.overflowY = 'scroll'
document.body.style.overflowY = 'scroll' document.body.style.overflowY = 'scroll'
fixtureEl.innerHTML = [ fixtureEl.innerHTML = '<div style="height: 110vh; width: 100%"></div>'
'<div style="height: 110vh; width: 100%"></div>'
].join('')
const result = new ScrollBarHelper().getWidth() const result = new ScrollBarHelper().getWidth()
if (isScrollBarHidden()) { if (isScrollBarHidden()) {
expect(result).toBe(0) expect(result).toEqual(0)
} else { } else {
expect(result).toBeGreaterThan(1) expect(result).toBeGreaterThan(1)
} }
@@ -96,9 +92,7 @@ describe('ScrollBar', () => {
it('should return 0 if body is not overflowing', () => { it('should return 0 if body is not overflowing', () => {
document.documentElement.style.overflowY = 'hidden' document.documentElement.style.overflowY = 'hidden'
document.body.style.overflowY = 'hidden' document.body.style.overflowY = 'hidden'
fixtureEl.innerHTML = [ fixtureEl.innerHTML = '<div style="height: 110vh; width: 100%"></div>'
'<div style="height: 110vh; width: 100%"></div>'
].join('')
const result = new ScrollBarHelper().getWidth() const result = new ScrollBarHelper().getWidth()
@@ -109,9 +103,9 @@ describe('ScrollBar', () => {
describe('hide - reset', () => { describe('hide - reset', () => {
it('should adjust the inline padding of fixed elements which are full-width', done => { it('should adjust the inline padding of fixed elements which are full-width', done => {
fixtureEl.innerHTML = [ fixtureEl.innerHTML = [
'<div style="height: 110vh; width: 100%">' + '<div style="height: 110vh; width: 100%">',
'<div class="fixed-top" id="fixed1" style="padding-right: 0px; width: 100vw"></div>', ' <div class="fixed-top" id="fixed1" style="padding-right: 0px; width: 100vw"></div>',
'<div class="fixed-top" id="fixed2" style="padding-right: 5px; width: 100vw"></div>', ' <div class="fixed-top" id="fixed2" style="padding-right: 5px; width: 100vw"></div>',
'</div>' '</div>'
].join('') ].join('')
doc.style.overflowY = 'scroll' doc.style.overflowY = 'scroll'
@@ -128,25 +122,25 @@ describe('ScrollBar', () => {
let currentPadding = getPaddingX(fixedEl) let currentPadding = getPaddingX(fixedEl)
let currentPadding2 = getPaddingX(fixedEl2) let currentPadding2 = getPaddingX(fixedEl2)
expect(getPaddingAttr(fixedEl)).toEqual(`${originalPadding}px`, 'original fixed element padding should be stored in data-bs-padding-right') expect(getPaddingAttr(fixedEl)).toEqual(`${originalPadding}px`)
expect(getPaddingAttr(fixedEl2)).toEqual(`${originalPadding2}px`, 'original fixed element padding should be stored in data-bs-padding-right') expect(getPaddingAttr(fixedEl2)).toEqual(`${originalPadding2}px`)
expect(currentPadding).toEqual(expectedPadding, 'fixed element padding should be adjusted while opening') expect(currentPadding).toEqual(expectedPadding)
expect(currentPadding2).toEqual(expectedPadding2, 'fixed element padding should be adjusted while opening') expect(currentPadding2).toEqual(expectedPadding2)
scrollBar.reset() scrollBar.reset()
currentPadding = getPaddingX(fixedEl) currentPadding = getPaddingX(fixedEl)
currentPadding2 = getPaddingX(fixedEl2) currentPadding2 = getPaddingX(fixedEl2)
expect(getPaddingAttr(fixedEl)).toEqual(null, 'data-bs-padding-right should be cleared after closing') expect(getPaddingAttr(fixedEl)).toBeNull()
expect(getPaddingAttr(fixedEl2)).toEqual(null, 'data-bs-padding-right should be cleared after closing') expect(getPaddingAttr(fixedEl2)).toBeNull()
expect(currentPadding).toEqual(originalPadding, 'fixed element padding should be reset after closing') expect(currentPadding).toEqual(originalPadding)
expect(currentPadding2).toEqual(originalPadding2, 'fixed element padding should be reset after closing') expect(currentPadding2).toEqual(originalPadding2)
done() done()
}) })
it('should adjust the inline margin and padding of sticky elements', done => { it('should adjust the inline margin and padding of sticky elements', done => {
fixtureEl.innerHTML = [ fixtureEl.innerHTML = [
'<div style="height: 110vh">' + '<div style="height: 110vh">',
'<div class="sticky-top" style="margin-right: 10px; padding-right: 20px; width: 100vw; height: 10px"></div>', ' <div class="sticky-top" style="margin-right: 10px; padding-right: 20px; width: 100vw; height: 10px"></div>',
'</div>' '</div>'
].join('') ].join('')
doc.style.overflowY = 'scroll' doc.style.overflowY = 'scroll'
@@ -159,23 +153,21 @@ describe('ScrollBar', () => {
const expectedPadding = originalPadding + scrollBar.getWidth() const expectedPadding = originalPadding + scrollBar.getWidth()
scrollBar.hide() scrollBar.hide()
expect(getMarginAttr(stickyTopEl)).toEqual(`${originalMargin}px`, 'original sticky element margin should be stored in data-bs-margin-right') expect(getMarginAttr(stickyTopEl)).toEqual(`${originalMargin}px`)
expect(getMarginX(stickyTopEl)).toEqual(expectedMargin, 'sticky element margin should be adjusted while opening') expect(getMarginX(stickyTopEl)).toEqual(expectedMargin)
expect(getPaddingAttr(stickyTopEl)).toEqual(`${originalPadding}px`, 'original sticky element margin should be stored in data-bs-margin-right') expect(getPaddingAttr(stickyTopEl)).toEqual(`${originalPadding}px`)
expect(getPaddingX(stickyTopEl)).toEqual(expectedPadding, 'sticky element margin should be adjusted while opening') expect(getPaddingX(stickyTopEl)).toEqual(expectedPadding)
scrollBar.reset() scrollBar.reset()
expect(getMarginAttr(stickyTopEl)).toEqual(null, 'data-bs-margin-right should be cleared after closing') expect(getMarginAttr(stickyTopEl)).toBeNull()
expect(getMarginX(stickyTopEl)).toEqual(originalMargin, 'sticky element margin should be reset after closing') expect(getMarginX(stickyTopEl)).toEqual(originalMargin)
expect(getPaddingAttr(stickyTopEl)).toEqual(null, 'data-bs-margin-right should be cleared after closing') expect(getPaddingAttr(stickyTopEl)).toBeNull()
expect(getPaddingX(stickyTopEl)).toEqual(originalPadding, 'sticky element margin should be reset after closing') expect(getPaddingX(stickyTopEl)).toEqual(originalPadding)
done() done()
}) })
it('should not adjust the inline margin and padding of sticky and fixed elements when element do not have full width', () => { it('should not adjust the inline margin and padding of sticky and fixed elements when element do not have full width', () => {
fixtureEl.innerHTML = [ fixtureEl.innerHTML = '<div class="sticky-top" style="margin-right: 0px; padding-right: 0px; width: 50vw"></div>'
'<div class="sticky-top" style="margin-right: 0px; padding-right: 0px; width: 50vw"></div>'
].join('')
const stickyTopEl = fixtureEl.querySelector('.sticky-top') const stickyTopEl = fixtureEl.querySelector('.sticky-top')
const originalMargin = getMarginX(stickyTopEl) const originalMargin = getMarginX(stickyTopEl)
@@ -187,16 +179,16 @@ describe('ScrollBar', () => {
const currentMargin = getMarginX(stickyTopEl) const currentMargin = getMarginX(stickyTopEl)
const currentPadding = getPaddingX(stickyTopEl) const currentPadding = getPaddingX(stickyTopEl)
expect(currentMargin).toEqual(originalMargin, 'sticky element\'s margin should not be adjusted while opening') expect(currentMargin).toEqual(originalMargin)
expect(currentPadding).toEqual(originalPadding, 'sticky element\'s padding should not be adjusted while opening') expect(currentPadding).toEqual(originalPadding)
scrollBar.reset() scrollBar.reset()
}) })
it('should not put data-attribute if element doesn\'t have the proper style property, should just remove style property if element didn\'t had one', () => { it('should not put data-attribute if element doesn\'t have the proper style property, should just remove style property if element didn\'t had one', () => {
fixtureEl.innerHTML = [ fixtureEl.innerHTML = [
'<div style="height: 110vh; width: 100%">' + '<div style="height: 110vh; width: 100%">',
'<div class="sticky-top" id="sticky" style="width: 100vw"></div>', ' <div class="sticky-top" id="sticky" style="width: 100vw"></div>',
'</div>' '</div>'
].join('') ].join('')
@@ -232,8 +224,8 @@ describe('ScrollBar', () => {
const scrollBarWidth = scrollBar.getWidth() const scrollBarWidth = scrollBar.getWidth()
scrollBar.hide() scrollBar.hide()
expect(getPaddingX(document.body)).toEqual(scrollBarWidth, 'body does not have inline padding set') expect(getPaddingX(document.body)).toEqual(scrollBarWidth)
expect(document.body.style.color).toEqual('red', 'body still has other inline styles set') expect(document.body.style.color).toEqual('red')
scrollBar.reset() scrollBar.reset()
}) })
@@ -243,7 +235,7 @@ describe('ScrollBar', () => {
fixtureEl.innerHTML = [ fixtureEl.innerHTML = [
'<style>', '<style>',
' body {', ' body {',
` padding-right: ${styleSheetPadding} }`, ` padding-right: ${styleSheetPadding}`,
' }', ' }',
'</style>' '</style>'
].join('') ].join('')
@@ -253,7 +245,7 @@ describe('ScrollBar', () => {
el.style.paddingRight = inlineStylePadding el.style.paddingRight = inlineStylePadding
const originalPadding = getPaddingX(el) const originalPadding = getPaddingX(el)
expect(originalPadding).toEqual(parseInt(inlineStylePadding)) // Respect only the inline style as it has prevails this of css expect(originalPadding).toEqual(parseIntDecimal(inlineStylePadding)) // Respect only the inline style as it has prevails this of css
const originalOverFlow = 'auto' const originalOverFlow = 'auto'
el.style.overflow = originalOverFlow el.style.overflow = originalOverFlow
const scrollBar = new ScrollBarHelper() const scrollBar = new ScrollBarHelper()
@@ -264,7 +256,7 @@ describe('ScrollBar', () => {
const currentPadding = getPaddingX(el) const currentPadding = getPaddingX(el)
expect(currentPadding).toEqual(scrollBarWidth + originalPadding) expect(currentPadding).toEqual(scrollBarWidth + originalPadding)
expect(currentPadding).toEqual(scrollBarWidth + parseInt(inlineStylePadding)) expect(currentPadding).toEqual(scrollBarWidth + parseIntDecimal(inlineStylePadding))
expect(getPaddingAttr(el)).toEqual(inlineStylePadding) expect(getPaddingAttr(el)).toEqual(inlineStylePadding)
expect(getOverFlow(el)).toEqual('hidden') expect(getOverFlow(el)).toEqual('hidden')
expect(getOverFlowAttr(el)).toEqual(originalOverFlow) expect(getOverFlowAttr(el)).toEqual(originalOverFlow)
@@ -273,9 +265,9 @@ describe('ScrollBar', () => {
const currentPadding1 = getPaddingX(el) const currentPadding1 = getPaddingX(el)
expect(currentPadding1).toEqual(originalPadding) expect(currentPadding1).toEqual(originalPadding)
expect(getPaddingAttr(el)).toEqual(null) expect(getPaddingAttr(el)).toBeNull()
expect(getOverFlow(el)).toEqual(originalOverFlow) expect(getOverFlow(el)).toEqual(originalOverFlow)
expect(getOverFlowAttr(el)).toEqual(null) expect(getOverFlowAttr(el)).toBeNull()
}) })
it('should hide scrollbar and reset it to its initial value - respecting css rules', () => { it('should hide scrollbar and reset it to its initial value - respecting css rules', () => {
@@ -283,7 +275,7 @@ describe('ScrollBar', () => {
fixtureEl.innerHTML = [ fixtureEl.innerHTML = [
'<style>', '<style>',
' body {', ' body {',
` padding-right: ${styleSheetPadding} }`, ` padding-right: ${styleSheetPadding}`,
' }', ' }',
'</style>' '</style>'
].join('') ].join('')
@@ -299,7 +291,7 @@ describe('ScrollBar', () => {
const currentPadding = getPaddingX(el) const currentPadding = getPaddingX(el)
expect(currentPadding).toEqual(scrollBarWidth + originalPadding) expect(currentPadding).toEqual(scrollBarWidth + originalPadding)
expect(currentPadding).toEqual(scrollBarWidth + parseInt(styleSheetPadding)) expect(currentPadding).toEqual(scrollBarWidth + parseIntDecimal(styleSheetPadding))
expect(getPaddingAttr(el)).toBeNull() // We do not have to keep css padding expect(getPaddingAttr(el)).toBeNull() // We do not have to keep css padding
expect(getOverFlow(el)).toEqual('hidden') expect(getOverFlow(el)).toEqual('hidden')
expect(getOverFlowAttr(el)).toEqual(originalOverFlow) expect(getOverFlowAttr(el)).toEqual(originalOverFlow)
@@ -308,9 +300,9 @@ describe('ScrollBar', () => {
const currentPadding1 = getPaddingX(el) const currentPadding1 = getPaddingX(el)
expect(currentPadding1).toEqual(originalPadding) expect(currentPadding1).toEqual(originalPadding)
expect(getPaddingAttr(el)).toEqual(null) expect(getPaddingAttr(el)).toBeNull()
expect(getOverFlow(el)).toEqual(originalOverFlow) expect(getOverFlow(el)).toEqual(originalOverFlow)
expect(getOverFlowAttr(el)).toEqual(null) expect(getOverFlowAttr(el)).toBeNull()
}) })
it('should not adjust the inline body padding when it does not overflow', () => { it('should not adjust the inline body padding when it does not overflow', () => {
@@ -324,7 +316,7 @@ describe('ScrollBar', () => {
scrollBar.hide() scrollBar.hide()
const currentPadding = getPaddingX(document.body) const currentPadding = getPaddingX(document.body)
expect(currentPadding).toEqual(originalPadding, 'body padding should not be adjusted') expect(currentPadding).toEqual(originalPadding)
scrollBar.reset() scrollBar.reset()
}) })
@@ -344,7 +336,7 @@ describe('ScrollBar', () => {
const currentPadding = getPaddingX(document.body) const currentPadding = getPaddingX(document.body)
expect(currentPadding).toEqual(originalPadding, 'body padding should not be adjusted') expect(currentPadding).toEqual(originalPadding)
scrollBar.reset() scrollBar.reset()
}) })
+1 -1
View File
@@ -49,7 +49,7 @@ describe('Swipe', () => {
'</style>' '</style>'
].join('') ].join('')
fixtureEl.innerHTML = '<div id="swipeEl"></div>' + cssStyle fixtureEl.innerHTML = `<div id="swipeEl"></div>${cssStyle}`
swipeEl = fixtureEl.querySelector('div') swipeEl = fixtureEl.querySelector('div')
}) })
+306
View File
@@ -0,0 +1,306 @@
import { clearFixture, getFixture } from '../../helpers/fixture'
import TemplateFactory from '../../../src/util/template-factory'
describe('TemplateFactory', () => {
let fixtureEl
beforeAll(() => {
fixtureEl = getFixture()
})
afterEach(() => {
clearFixture()
})
describe('NAME', () => {
it('should return plugin NAME', () => {
expect(TemplateFactory.NAME).toEqual('TemplateFactory')
})
})
describe('Default', () => {
it('should return plugin default config', () => {
expect(TemplateFactory.Default).toEqual(jasmine.any(Object))
})
})
describe('toHtml', () => {
describe('Sanitization', () => {
it('should use "sanitizeHtml" to sanitize template', () => {
const factory = new TemplateFactory({
sanitize: true,
template: '<div><a href="javascript:alert(7)">Click me</a></div>'
})
const spy = spyOn(factory, '_maybeSanitize').and.callThrough()
expect(factory.toHtml().innerHTML).not.toContain('href="javascript:alert(7)')
expect(spy).toHaveBeenCalled()
})
it('should not sanitize template', () => {
const factory = new TemplateFactory({
sanitize: false,
template: '<div><a href="javascript:alert(7)">Click me</a></div>'
})
const spy = spyOn(factory, '_maybeSanitize').and.callThrough()
expect(factory.toHtml().innerHTML).toContain('href="javascript:alert(7)')
expect(spy).toHaveBeenCalled()
})
it('should use "sanitizeHtml" to sanitize content', () => {
const factory = new TemplateFactory({
sanitize: true,
html: true,
template: '<div id="foo"></div>',
content: { '#foo': '<a href="javascript:alert(7)">Click me</a>' }
})
expect(factory.toHtml().innerHTML).not.toContain('href="javascript:alert(7)')
})
it('should not sanitize content', () => {
const factory = new TemplateFactory({
sanitize: false,
html: true,
template: '<div id="foo"></div>',
content: { '#foo': '<a href="javascript:alert(7)">Click me</a>' }
})
expect(factory.toHtml().innerHTML).toContain('href="javascript:alert(7)')
})
it('should sanitize content only if "config.html" is enabled', () => {
const factory = new TemplateFactory({
sanitize: true,
html: false,
template: '<div id="foo"></div>',
content: { '#foo': '<a href="javascript:alert(7)">Click me</a>' }
})
const spy = spyOn(factory, '_maybeSanitize').and.callThrough()
expect(spy).not.toHaveBeenCalled()
})
})
describe('Extra Class', () => {
it('should add extra class', () => {
const factory = new TemplateFactory({
extraClass: 'testClass'
})
expect(factory.toHtml()).toHaveClass('testClass')
})
it('should add extra classes', () => {
const factory = new TemplateFactory({
extraClass: 'testClass testClass2'
})
expect(factory.toHtml()).toHaveClass('testClass')
expect(factory.toHtml()).toHaveClass('testClass2')
})
it('should resolve class if function is given', () => {
const factory = new TemplateFactory({
extraClass: arg => {
expect(arg).toEqual(factory)
return 'testClass'
}
})
expect(factory.toHtml()).toHaveClass('testClass')
})
})
})
describe('Content', () => {
it('add simple text content', () => {
const template = [
'<div>',
' <div class="foo"></div>',
' <div class="foo2"></div>',
'</div>'
].join('')
const factory = new TemplateFactory({
template,
content: {
'.foo': 'bar',
'.foo2': 'bar2'
}
})
const html = factory.toHtml()
expect(html.querySelector('.foo').textContent).toEqual('bar')
expect(html.querySelector('.foo2').textContent).toEqual('bar2')
})
it('should not fill template if selector not exists', () => {
const factory = new TemplateFactory({
sanitize: true,
html: true,
template: '<div id="foo"></div>',
content: { '#bar': 'test' }
})
expect(factory.toHtml().outerHTML).toEqual('<div id="foo"></div>')
})
it('should remove template selector, if content is null', () => {
const factory = new TemplateFactory({
sanitize: true,
html: true,
template: '<div><div id="foo"></div></div>',
content: { '#foo': null }
})
expect(factory.toHtml().outerHTML).toEqual('<div></div>')
})
it('should resolve content if is function', () => {
const factory = new TemplateFactory({
sanitize: true,
html: true,
template: '<div><div id="foo"></div></div>',
content: { '#foo': () => null }
})
expect(factory.toHtml().outerHTML).toEqual('<div></div>')
})
it('if content is element and "config.html=false", should put content\'s textContent', () => {
fixtureEl.innerHTML = '<div>foo<span>bar</span></div>'
const contentElement = fixtureEl.querySelector('div')
const factory = new TemplateFactory({
html: false,
template: '<div><div id="foo"></div></div>',
content: { '#foo': contentElement }
})
const fooEl = factory.toHtml().querySelector('#foo')
expect(fooEl.innerHTML).not.toEqual(contentElement.innerHTML)
expect(fooEl.textContent).toEqual(contentElement.textContent)
expect(fooEl.textContent).toEqual('foobar')
})
it('if content is element and "config.html=true", should put content\'s outerHtml as child', () => {
fixtureEl.innerHTML = '<div>foo<span>bar</span></div>'
const contentElement = fixtureEl.querySelector('div')
const factory = new TemplateFactory({
html: true,
template: '<div><div id="foo"></div></div>',
content: { '#foo': contentElement }
})
const fooEl = factory.toHtml().querySelector('#foo')
expect(fooEl.innerHTML).toEqual(contentElement.outerHTML)
expect(fooEl.textContent).toEqual(contentElement.textContent)
})
})
describe('getContent', () => {
it('should get content as array', () => {
const factory = new TemplateFactory({
content: {
'.foo': 'bar',
'.foo2': 'bar2'
}
})
expect(factory.getContent()).toEqual(['bar', 'bar2'])
})
it('should filter empties', () => {
const factory = new TemplateFactory({
content: {
'.foo': 'bar',
'.foo2': '',
'.foo3': null,
'.foo4': () => 2,
'.foo5': () => null
}
})
expect(factory.getContent()).toEqual(['bar', 2])
})
})
describe('hasContent', () => {
it('should return true, if it has', () => {
const factory = new TemplateFactory({
content: {
'.foo': 'bar',
'.foo2': 'bar2',
'.foo3': ''
}
})
expect(factory.hasContent()).toBeTrue()
})
it('should return false, if filtered content is empty', () => {
const factory = new TemplateFactory({
content: {
'.foo2': '',
'.foo3': null,
'.foo4': () => null
}
})
expect(factory.hasContent()).toBeFalse()
})
})
describe('changeContent', () => {
it('should change Content', () => {
const template = [
'<div>',
' <div class="foo"></div>',
' <div class="foo2"></div>',
'</div>'
].join('')
const factory = new TemplateFactory({
template,
content: {
'.foo': 'bar',
'.foo2': 'bar2'
}
})
const html = selector => factory.toHtml().querySelector(selector).textContent
expect(html('.foo')).toEqual('bar')
expect(html('.foo2')).toEqual('bar2')
factory.changeContent({
'.foo': 'test',
'.foo2': 'test2'
})
expect(html('.foo')).toEqual('test')
expect(html('.foo2')).toEqual('test2')
})
it('should change only the given, content', () => {
const template = [
'<div>',
' <div class="foo"></div>',
' <div class="foo2"></div>',
'</div>'
].join('')
const factory = new TemplateFactory({
template,
content: {
'.foo': 'bar',
'.foo2': 'bar2'
}
})
const html = selector => factory.toHtml().querySelector(selector).textContent
expect(html('.foo')).toEqual('bar')
expect(html('.foo2')).toEqual('bar2')
factory.changeContent({
'.foo': 'test',
'.wrong': 'wrong'
})
expect(html('.foo')).toEqual('test')
expect(html('.foo2')).toEqual('bar2')
})
})
})
+2143 -2190
View File
File diff suppressed because it is too large Load Diff
+26 -26
View File
@@ -64,7 +64,7 @@
"docs-serve": "hugo server --port 9001 --disableFastRender", "docs-serve": "hugo server --port 9001 --disableFastRender",
"docs-serve-only": "npx sirv-cli _site --port 9001", "docs-serve-only": "npx sirv-cli _site --port 9001",
"lockfile-lint": "lockfile-lint --allowed-hosts npm --allowed-schemes https: --empty-hostname false --type npm --path package-lock.json", "lockfile-lint": "lockfile-lint --allowed-hosts npm --allowed-schemes https: --empty-hostname false --type npm --path package-lock.json",
"update-deps": "ncu -u -x globby,karma-browserstack-launcher && echo Manually update site/assets/js/vendor", "update-deps": "ncu -u -x globby,karma-browserstack-launcher,stylelint && echo Manually update site/assets/js/vendor",
"release": "npm-run-all dist release-sri docs-build release-zip*", "release": "npm-run-all dist release-sri docs-build release-zip*",
"release-sri": "node build/generate-sri.js", "release-sri": "node build/generate-sri.js",
"release-version": "node build/change-version.js", "release-version": "node build/change-version.js",
@@ -97,56 +97,56 @@
"url": "https://opencollective.com/bootstrap" "url": "https://opencollective.com/bootstrap"
}, },
"peerDependencies": { "peerDependencies": {
"@popperjs/core": "^2.10.2" "@popperjs/core": "^2.11.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/cli": "^7.15.7", "@babel/cli": "^7.16.0",
"@babel/core": "^7.15.8", "@babel/core": "^7.16.0",
"@babel/preset-env": "^7.15.8", "@babel/preset-env": "^7.16.4",
"@popperjs/core": "^2.10.2", "@popperjs/core": "^2.11.0",
"@rollup/plugin-babel": "^5.3.0", "@rollup/plugin-babel": "^5.3.0",
"@rollup/plugin-commonjs": "^21.0.0", "@rollup/plugin-commonjs": "^21.0.1",
"@rollup/plugin-node-resolve": "^13.0.5", "@rollup/plugin-node-resolve": "^13.0.6",
"@rollup/plugin-replace": "^3.0.0", "@rollup/plugin-replace": "^3.0.0",
"autoprefixer": "^10.3.7", "autoprefixer": "^10.4.0",
"bundlewatch": "^0.3.2", "bundlewatch": "^0.3.2",
"clean-css-cli": "^5.4.1", "clean-css-cli": "^5.4.2",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"cspell": "^5.12.3", "cspell": "^5.13.2",
"eslint": "^8.0.0", "eslint": "^8.4.1",
"eslint-config-xo": "^0.39.0", "eslint-config-xo": "^0.39.0",
"eslint-plugin-import": "^2.25.2", "eslint-plugin-import": "^2.25.3",
"eslint-plugin-unicorn": "^37.0.1", "eslint-plugin-unicorn": "^39.0.0",
"find-unused-sass-variables": "^3.1.0", "find-unused-sass-variables": "^3.1.0",
"glob": "^7.2.0", "glob": "^7.2.0",
"globby": "^11.0.4", "globby": "^11.0.4",
"hammer-simulator": "0.0.1", "hammer-simulator": "0.0.1",
"hugo-bin": "^0.76.1", "hugo-bin": "^0.77.4",
"ip": "^1.1.5", "ip": "^1.1.5",
"jquery": "^3.6.0", "jquery": "^3.6.0",
"karma": "^6.3.4", "karma": "^6.3.9",
"karma-browserstack-launcher": "1.4.0", "karma-browserstack-launcher": "1.4.0",
"karma-chrome-launcher": "^3.1.0", "karma-chrome-launcher": "^3.1.0",
"karma-coverage-istanbul-reporter": "^3.0.3", "karma-coverage-istanbul-reporter": "^3.0.3",
"karma-detect-browsers": "^2.3.3", "karma-detect-browsers": "^2.3.3",
"karma-firefox-launcher": "^2.1.1", "karma-firefox-launcher": "^2.1.2",
"karma-jasmine": "^4.0.1", "karma-jasmine": "^4.0.1",
"karma-jasmine-html-reporter": "^1.7.0", "karma-jasmine-html-reporter": "^1.7.0",
"karma-rollup-preprocessor": "^7.0.7", "karma-rollup-preprocessor": "^7.0.7",
"linkinator": "^2.14.4", "linkinator": "^2.16.2",
"lockfile-lint": "^4.6.2", "lockfile-lint": "^4.6.2",
"nodemon": "^2.0.13", "nodemon": "^2.0.15",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"postcss": "^8.3.9", "postcss": "^8.4.4",
"postcss-cli": "^9.0.1", "postcss-cli": "^9.0.2",
"rollup": "^2.58.0", "rollup": "^2.60.2",
"rollup-plugin-istanbul": "^3.0.0", "rollup-plugin-istanbul": "^3.0.0",
"rtlcss": "^3.3.0", "rtlcss": "^3.5.0",
"sass": "^1.42.1", "sass": "^1.44.0",
"shelljs": "^0.8.4", "shelljs": "^0.8.4",
"stylelint": "^13.13.1", "stylelint": "^13.13.1",
"stylelint-config-twbs-bootstrap": "^2.2.4", "stylelint-config-twbs-bootstrap": "^2.2.4",
"terser": "^5.9.0", "terser": "^5.10.0",
"vnu-jar": "21.10.12" "vnu-jar": "21.10.12"
}, },
"files": [ "files": [
@@ -172,7 +172,7 @@
}, },
"dependencies": {}, "dependencies": {},
"peerDependencies": { "peerDependencies": {
"@popperjs/core": "^2.10.2" "@popperjs/core": "^2.11.0"
} }
} }
} }
+1
View File
@@ -58,6 +58,7 @@
} }
.accordion-item { .accordion-item {
color: color-contrast($accordion-bg);
background-color: $accordion-bg; background-color: $accordion-bg;
border: $accordion-border-width solid $accordion-border-color; border: $accordion-border-width solid $accordion-border-color;
+1 -1
View File
@@ -1,4 +1,4 @@
// transparent background and border properties included for button version. // Transparent background and border properties included for button version.
// iOS requires the button element instead of an anchor tag. // iOS requires the button element instead of an anchor tag.
// If you want the anchor version, it requires `href="#"`. // If you want the anchor version, it requires `href="#"`.
// See https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile // See https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile
+44
View File
@@ -0,0 +1,44 @@
// Re-assigned maps
//
// Placed here so that others can override the default Sass maps and see automatic updates to utilities and more.
// scss-docs-start theme-colors-rgb
$theme-colors-rgb: map-loop($theme-colors, to-rgb, "$value") !default;
// scss-docs-end theme-colors-rgb
// Utilities maps
//
// Extends the default `$theme-colors` maps to help create our utilities.
// Come v6, we'll de-dupe these variables. Until then, for backward compatibility, we keep them to reassign.
// scss-docs-start utilities-colors
$utilities-colors: $theme-colors-rgb !default;
// scss-docs-end utilities-colors
// scss-docs-start utilities-text-colors
$utilities-text: map-merge(
$utilities-colors,
(
"black": to-rgb($black),
"white": to-rgb($white),
"body": to-rgb($body-color)
)
) !default;
$utilities-text-colors: map-loop($utilities-text, rgba-css-var, "$key", "text") !default;
// scss-docs-end utilities-text-colors
// scss-docs-start utilities-bg-colors
$utilities-bg: map-merge(
$utilities-colors,
(
"black": to-rgb($black),
"white": to-rgb($white),
"body": to-rgb($body-bg)
)
) !default;
$utilities-bg-colors: map-loop($utilities-bg, rgba-css-var, "$key", "bg") !default;
// scss-docs-end utilities-bg-colors
$negative-spacers: if($enable-negative-margins, negativify-map($spacers), null) !default;
$gutters: $spacers !default;
+3 -11
View File
@@ -49,7 +49,7 @@
body { body {
margin: 0; // 1 margin: 0; // 1
font-family: var(--#{$variable-prefix}body-font-family); font-family: var(--#{$variable-prefix}body-font-family);
@include font-size(var(--#{$variable-prefix}body-font-size)); font-size: var(--#{$variable-prefix}body-font-size);
font-weight: var(--#{$variable-prefix}body-font-weight); font-weight: var(--#{$variable-prefix}body-font-weight);
line-height: var(--#{$variable-prefix}body-line-height); line-height: var(--#{$variable-prefix}body-line-height);
color: var(--#{$variable-prefix}body-color); color: var(--#{$variable-prefix}body-color);
@@ -279,8 +279,6 @@ kbd,
samp { samp {
font-family: $font-family-code; font-family: $font-family-code;
@include font-size(1em); // Correct the odd `em` font sizing in all browsers. @include font-size(1em); // Correct the odd `em` font sizing in all browsers.
direction: ltr #{"/* rtl:ignore */"};
unicode-bidi: bidi-override;
} }
// 1. Remove browser default top margin // 1. Remove browser default top margin
@@ -571,16 +569,10 @@ legend {
} }
// Inherit font family and line height for file input buttons // 1. Inherit font family and line height for file input buttons
::file-selector-button {
font: inherit;
}
// 1. Change font properties to `inherit`
// 2. Correct the inability to style clickable types in iOS and Safari. // 2. Correct the inability to style clickable types in iOS and Safari.
::-webkit-file-upload-button { ::file-selector-button {
font: inherit; // 1 font: inherit; // 1
-webkit-appearance: button; // 2 -webkit-appearance: button; // 2
} }
+1 -1
View File
@@ -41,7 +41,7 @@
--#{$variable-prefix}root-font-size: #{$font-size-root}; --#{$variable-prefix}root-font-size: #{$font-size-root};
} }
--#{$variable-prefix}body-font-family: #{$font-family-base}; --#{$variable-prefix}body-font-family: #{$font-family-base};
--#{$variable-prefix}body-font-size: #{$font-size-base}; @include rfs($font-size-base, --#{$variable-prefix}body-font-size);
--#{$variable-prefix}body-font-weight: #{$font-weight-base}; --#{$variable-prefix}body-font-weight: #{$font-weight-base};
--#{$variable-prefix}body-line-height: #{$line-height-base}; --#{$variable-prefix}body-line-height: #{$line-height-base};
--#{$variable-prefix}body-color: #{$body-color}; --#{$variable-prefix}body-color: #{$body-color};
+2 -43
View File
@@ -90,10 +90,6 @@ $theme-colors: (
) !default; ) !default;
// scss-docs-end theme-colors-map // scss-docs-end theme-colors-map
// scss-docs-start theme-colors-rgb
$theme-colors-rgb: map-loop($theme-colors, to-rgb, "$value") !default;
// scss-docs-end theme-colors-rgb
// The contrast ratio to reach against white, to determine if color changes from "light" to "dark". Acceptable values for WCAG 2.0 are 3, 4.5 and 7. // The contrast ratio to reach against white, to determine if color changes from "light" to "dark". Acceptable values for WCAG 2.0 are 3, 4.5 and 7.
// See https://www.w3.org/TR/WCAG20/#visual-audio-contrast-contrast // See https://www.w3.org/TR/WCAG20/#visual-audio-contrast-contrast
$min-contrast-ratio: 4.5 !default; $min-contrast-ratio: 4.5 !default;
@@ -228,8 +224,8 @@ $indigos: (
) !default; ) !default;
$purples: ( $purples: (
"purple-100": $purple-200, "purple-100": $purple-100,
"purple-200": $purple-100, "purple-200": $purple-200,
"purple-300": $purple-300, "purple-300": $purple-300,
"purple-400": $purple-400, "purple-400": $purple-400,
"purple-500": $purple-500, "purple-500": $purple-500,
@@ -382,8 +378,6 @@ $spacers: (
4: $spacer * 1.5, 4: $spacer * 1.5,
5: $spacer * 3, 5: $spacer * 3,
) !default; ) !default;
$negative-spacers: if($enable-negative-margins, negativify-map($spacers), null) !default;
// scss-docs-end spacer-variables-maps // scss-docs-end spacer-variables-maps
// Position // Position
@@ -406,39 +400,6 @@ $body-bg: $white !default;
$body-color: $gray-900 !default; $body-color: $gray-900 !default;
$body-text-align: null !default; $body-text-align: null !default;
// Utilities maps
//
// Extends the default `$theme-colors` maps to help create our utilities.
// Come v6, we'll de-dupe these variables. Until then, for backward compatibility, we keep them to reassign.
// scss-docs-start utilities-colors
$utilities-colors: $theme-colors-rgb !default;
// scss-docs-end utilities-colors
// scss-docs-start utilities-text-colors
$utilities-text: map-merge(
$utilities-colors,
(
"black": to-rgb($black),
"white": to-rgb($white),
"body": to-rgb($body-color)
)
) !default;
$utilities-text-colors: map-loop($utilities-text, rgba-css-var, "$key", "text") !default;
// scss-docs-end utilities-text-colors
// scss-docs-start utilities-bg-colors
$utilities-bg: map-merge(
$utilities-colors,
(
"black": to-rgb($black),
"white": to-rgb($white),
"body": to-rgb($body-bg)
)
) !default;
$utilities-bg-colors: map-loop($utilities-bg, rgba-css-var, "$key", "bg") !default;
// scss-docs-end utilities-bg-colors
// Links // Links
// //
// Style anchor elements. // Style anchor elements.
@@ -504,8 +465,6 @@ $grid-columns: 12 !default;
$grid-gutter-width: 1.5rem !default; $grid-gutter-width: 1.5rem !default;
$grid-row-columns: 6 !default; $grid-row-columns: 6 !default;
$gutters: $spacers !default;
// Container padding // Container padding
$container-padding-x: $grid-gutter-width * .5 !default; $container-padding-x: $grid-gutter-width * .5 !default;
+1
View File
@@ -9,6 +9,7 @@ $include-column-box-sizing: true !default;
@import "functions"; @import "functions";
@import "variables"; @import "variables";
@import "maps";
@import "mixins/lists"; @import "mixins/lists";
@import "mixins/breakpoints"; @import "mixins/breakpoints";
+1
View File
@@ -8,6 +8,7 @@
@import "functions"; @import "functions";
@import "variables"; @import "variables";
@import "maps";
@import "mixins"; @import "mixins";
@import "root"; @import "root";
@import "reboot"; @import "reboot";
+1
View File
@@ -8,6 +8,7 @@
// Configuration // Configuration
@import "functions"; @import "functions";
@import "variables"; @import "variables";
@import "maps";
@import "mixins"; @import "mixins";
@import "utilities"; @import "utilities";
+1
View File
@@ -9,6 +9,7 @@
// Configuration // Configuration
@import "functions"; @import "functions";
@import "variables"; @import "variables";
@import "maps";
@import "mixins"; @import "mixins";
@import "utilities"; @import "utilities";
-31
View File
@@ -91,25 +91,6 @@
&:hover:not(:disabled):not([readonly])::file-selector-button { &:hover:not(:disabled):not([readonly])::file-selector-button {
background-color: $form-file-button-hover-bg; background-color: $form-file-button-hover-bg;
} }
&::-webkit-file-upload-button {
padding: $input-padding-y $input-padding-x;
margin: (-$input-padding-y) (-$input-padding-x);
margin-inline-end: $input-padding-x;
color: $form-file-button-color;
@include gradient-bg($form-file-button-bg);
pointer-events: none;
border-color: inherit;
border-style: solid;
border-width: 0;
border-inline-end-width: $input-border-width;
border-radius: 0; // stylelint-disable-line property-disallowed-list
@include transition($btn-transition);
}
&:hover:not(:disabled):not([readonly])::-webkit-file-upload-button {
background-color: $form-file-button-hover-bg;
}
} }
// Readonly controls as plain text // Readonly controls as plain text
@@ -153,12 +134,6 @@
margin: (-$input-padding-y-sm) (-$input-padding-x-sm); margin: (-$input-padding-y-sm) (-$input-padding-x-sm);
margin-inline-end: $input-padding-x-sm; margin-inline-end: $input-padding-x-sm;
} }
&::-webkit-file-upload-button {
padding: $input-padding-y-sm $input-padding-x-sm;
margin: (-$input-padding-y-sm) (-$input-padding-x-sm);
margin-inline-end: $input-padding-x-sm;
}
} }
.form-control-lg { .form-control-lg {
@@ -172,12 +147,6 @@
margin: (-$input-padding-y-lg) (-$input-padding-x-lg); margin: (-$input-padding-y-lg) (-$input-padding-x-lg);
margin-inline-end: $input-padding-x-lg; margin-inline-end: $input-padding-x-lg;
} }
&::-webkit-file-upload-button {
padding: $input-padding-y-lg $input-padding-x-lg;
margin: (-$input-padding-y-lg) (-$input-padding-x-lg);
margin-inline-end: $input-padding-x-lg;
}
} }
// Make sure textareas don't shrink too much when resized // Make sure textareas don't shrink too much when resized
+2
View File
@@ -1,6 +1,8 @@
// Container mixins // Container mixins
@mixin make-container($gutter: $container-padding-x) { @mixin make-container($gutter: $container-padding-x) {
--#{$variable-prefix}gutter-x: #{$gutter};
--#{$variable-prefix}gutter-y: 0;
width: 100%; width: 100%;
padding-right: var(--#{$variable-prefix}gutter-x, #{$gutter}); padding-right: var(--#{$variable-prefix}gutter-x, #{$gutter});
padding-left: var(--#{$variable-prefix}gutter-x, #{$gutter}); padding-left: var(--#{$variable-prefix}gutter-x, #{$gutter});
+1 -1
View File
@@ -13,7 +13,7 @@
margin-left: calc(-.5 * var(--#{$variable-prefix}gutter-x)); // stylelint-disable-line function-disallowed-list margin-left: calc(-.5 * var(--#{$variable-prefix}gutter-x)); // stylelint-disable-line function-disallowed-list
} }
@mixin make-col-ready($gutter: $grid-gutter-width) { @mixin make-col-ready() {
// Add box sizing if only the grid is loaded // Add box sizing if only the grid is loaded
box-sizing: if(variable-exists(include-column-box-sizing) and $include-column-box-sizing, border-box, null); box-sizing: if(variable-exists(include-column-box-sizing) and $include-column-box-sizing, border-box, null);
// Prevent columns from becoming too narrow when at smaller grid tiers by // Prevent columns from becoming too narrow when at smaller grid tiers by
+10 -8
View File
@@ -144,11 +144,12 @@
clipboard.on('success', function (event) { clipboard.on('success', function (event) {
var tooltipBtn = bootstrap.Tooltip.getInstance(event.trigger) var tooltipBtn = bootstrap.Tooltip.getInstance(event.trigger)
var originalTitle = event.trigger.getAttribute('title')
event.trigger.setAttribute('data-bs-original-title', 'Copied!') tooltipBtn.setContent({ '.tooltip-inner': 'Copied!' })
tooltipBtn.show() event.trigger.addEventListener('hidden.bs.tooltip', function () {
tooltipBtn.setContent({ '.tooltip-inner': originalTitle })
event.trigger.setAttribute('data-bs-original-title', 'Copy to clipboard') }, { once: true })
event.clearSelection() event.clearSelection()
}) })
@@ -156,11 +157,12 @@
var modifierKey = /mac/i.test(navigator.userAgent) ? '\u2318' : 'Ctrl-' var modifierKey = /mac/i.test(navigator.userAgent) ? '\u2318' : 'Ctrl-'
var fallbackMsg = 'Press ' + modifierKey + 'C to copy' var fallbackMsg = 'Press ' + modifierKey + 'C to copy'
var tooltipBtn = bootstrap.Tooltip.getInstance(event.trigger) var tooltipBtn = bootstrap.Tooltip.getInstance(event.trigger)
var originalTitle = event.trigger.getAttribute('title')
event.trigger.setAttribute('data-bs-original-title', fallbackMsg) tooltipBtn.setContent({ '.tooltip-inner': fallbackMsg })
tooltipBtn.show() event.trigger.addEventListener('hidden.bs.tooltip', function () {
tooltipBtn.setContent({ '.tooltip-inner': originalTitle })
event.trigger.setAttribute('data-bs-original-title', 'Copy to clipboard') }, { once: true })
}) })
anchors.options = { anchors.options = {
+4 -4
View File
@@ -85,7 +85,7 @@
.bd-example { .bd-example {
position: relative; position: relative;
padding: 1rem; padding: 1rem;
margin: 1rem (-$grid-gutter-width * .5) 0; margin: 1rem ($bd-gutter-x * -1) 0;
border: solid $gray-300; border: solid $gray-300;
border-width: 1px 0 0; border-width: 1px 0 0;
@include clearfix(); @include clearfix();
@@ -313,7 +313,7 @@
// //
.highlight { .highlight {
padding: 1rem; padding: var(--bs-gutter-x) $bd-gutter-x;
margin-bottom: 1rem; margin-bottom: 1rem;
background-color: $gray-100; background-color: $gray-100;
@@ -338,8 +338,8 @@
} }
.bd-content .highlight { .bd-content .highlight {
margin-right: (-$grid-gutter-width * .5); margin-right: $bd-gutter-x * -1;
margin-left: (-$grid-gutter-width * .5); margin-left: $bd-gutter-x * -1;
@include media-breakpoint-up(sm) { @include media-breakpoint-up(sm) {
margin-right: 0; margin-right: 0;
+3
View File
@@ -1,4 +1,7 @@
.bd-layout { .bd-layout {
padding-right: $bd-gutter-x;
padding-left: $bd-gutter-x;
@include media-breakpoint-up(md) { @include media-breakpoint-up(md) {
display: grid; display: grid;
gap: $grid-gutter-width; gap: $grid-gutter-width;
+12
View File
@@ -1,4 +1,7 @@
.bd-navbar { .bd-navbar {
--bs-gutter-x: $bd-gutter-x;
--bs-gutter-y: $bd-gutter-x;
padding: .75rem 0; padding: .75rem 0;
background-color: $bd-purple-bright; background-color: $bd-purple-bright;
@@ -29,4 +32,13 @@
width: 1rem; width: 1rem;
height: 1rem; height: 1rem;
} }
.offcanvas {
background-color: $bd-purple-bright;
border-left: 0;
@include media-breakpoint-down(md) {
box-shadow: $box-shadow-lg;
}
}
} }
+1 -1
View File
@@ -1,6 +1,6 @@
.bd-sidebar { .bd-sidebar {
@include media-breakpoint-down(md) { @include media-breakpoint-down(md) {
margin: 0 -.75rem 1rem; margin: 0 ($bd-gutter-x * -1) 1rem;
} }
} }
+7 -4
View File
@@ -1,4 +1,7 @@
.bd-subnavbar { .bd-subnavbar {
--bs-gutter-x: $bd-gutter-x;
--bs-gutter-y: $bd-gutter-x;
// The position and z-index are needed for the dropdown to stay on top of the content // The position and z-index are needed for the dropdown to stay on top of the content
position: relative; position: relative;
z-index: $zindex-sticky; z-index: $zindex-sticky;
@@ -31,16 +34,16 @@
position: absolute; position: absolute;
top: .4rem; top: .4rem;
right: .4rem; right: .4rem;
bottom: .4rem;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
height: 1.5rem; padding-right: .3125rem;
padding-right: .25rem; padding-left: .3125rem;
padding-left: .25rem;
@include font-size(.75rem); @include font-size(.75rem);
color: $gray-600; color: $gray-600;
content: "Ctrl + /"; content: "Ctrl + /";
border: $border-width solid $border-color; background-color: $gray-100;
@include border-radius(.125rem); @include border-radius(.125rem);
} }
+2
View File
@@ -11,3 +11,5 @@ $bd-warning: #f0ad4e;
$bd-danger: #d9534f; $bd-danger: #d9534f;
$dropdown-active-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'><path fill='#292b2c' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/></svg>"); $dropdown-active-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'><path fill='#292b2c' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/></svg>");
$sidebar-collapse-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'><path fill='none' stroke='rgba(0,0,0,.5)' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M5 14l6-6-6-6'/></svg>"); $sidebar-collapse-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'><path fill='none' stroke='rgba(0,0,0,.5)' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M5 14l6-6-6-6'/></svg>");
$bd-gutter-x: 1.25rem;
+12 -12
View File
@@ -65,7 +65,7 @@ Here's an example of all the sub-components included in a responsive light-theme
<a class="nav-link disabled">Disabled</a> <a class="nav-link disabled">Disabled</a>
</li> </li>
</ul> </ul>
<form class="d-flex"> <form class="d-flex" role="search">
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search"> <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-success" type="submit">Search</button> <button class="btn btn-outline-success" type="submit">Search</button>
</form> </form>
@@ -74,7 +74,7 @@ Here's an example of all the sub-components included in a responsive light-theme
</nav> </nav>
{{< /example >}} {{< /example >}}
This example uses [background]({{< docsref "/utilities/background" >}}) (`bg-light`) and [spacing]({{< docsref "/utilities/spacing" >}}) (`my-2`, `my-lg-0`, `me-sm-0`, `my-sm-0`) utility classes. This example uses [background]({{< docsref "/utilities/background" >}}) (`bg-light`) and [spacing]({{< docsref "/utilities/spacing" >}}) (`me-auto`, `mb-2`, `mb-lg-0`, `me-2`) utility classes.
### Brand ### Brand
@@ -228,7 +228,7 @@ Place various form controls and components within a navbar:
{{< example >}} {{< example >}}
<nav class="navbar navbar-light bg-light"> <nav class="navbar navbar-light bg-light">
<div class="container-fluid"> <div class="container-fluid">
<form class="d-flex"> <form class="d-flex" role="search">
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search"> <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-success" type="submit">Search</button> <button class="btn btn-outline-success" type="submit">Search</button>
</form> </form>
@@ -242,7 +242,7 @@ Immediate child elements of `.navbar` use flex layout and will default to `justi
<nav class="navbar navbar-light bg-light"> <nav class="navbar navbar-light bg-light">
<div class="container-fluid"> <div class="container-fluid">
<a class="navbar-brand">Navbar</a> <a class="navbar-brand">Navbar</a>
<form class="d-flex"> <form class="d-flex" role="search">
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search"> <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-success" type="submit">Search</button> <button class="btn btn-outline-success" type="submit">Search</button>
</form> </form>
@@ -343,7 +343,7 @@ Theming the navbar has never been easier thanks to the combination of theming cl
<a class="nav-link" href="#">About</a> <a class="nav-link" href="#">About</a>
</li> </li>
</ul> </ul>
<form class="d-flex"> <form class="d-flex" role="search">
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search"> <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-light" type="submit">Search</button> <button class="btn btn-outline-light" type="submit">Search</button>
</form> </form>
@@ -372,7 +372,7 @@ Theming the navbar has never been easier thanks to the combination of theming cl
<a class="nav-link" href="#">About</a> <a class="nav-link" href="#">About</a>
</li> </li>
</ul> </ul>
<form class="d-flex"> <form class="d-flex" role="search">
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search"> <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-light" type="submit">Search</button> <button class="btn btn-outline-light" type="submit">Search</button>
</form> </form>
@@ -401,7 +401,7 @@ Theming the navbar has never been easier thanks to the combination of theming cl
<a class="nav-link" href="#">About</a> <a class="nav-link" href="#">About</a>
</li> </li>
</ul> </ul>
<form class="d-flex"> <form class="d-flex" role="search">
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search"> <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-primary" type="submit">Search</button> <button class="btn btn-outline-primary" type="submit">Search</button>
</form> </form>
@@ -524,7 +524,7 @@ Here's an example navbar using `.navbar-nav-scroll` with `style="--bs-scroll-hei
<a class="nav-link disabled">Link</a> <a class="nav-link disabled">Link</a>
</li> </li>
</ul> </ul>
<form class="d-flex"> <form class="d-flex" role="search">
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search"> <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-success" type="submit">Search</button> <button class="btn btn-outline-success" type="submit">Search</button>
</form> </form>
@@ -564,7 +564,7 @@ With no `.navbar-brand` shown at the smallest breakpoint:
<a class="nav-link disabled">Disabled</a> <a class="nav-link disabled">Disabled</a>
</li> </li>
</ul> </ul>
<form class="d-flex"> <form class="d-flex" role="search">
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search"> <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-success" type="submit">Search</button> <button class="btn btn-outline-success" type="submit">Search</button>
</form> </form>
@@ -594,7 +594,7 @@ With a brand name shown on the left and toggler on the right:
<a class="nav-link disabled">Disabled</a> <a class="nav-link disabled">Disabled</a>
</li> </li>
</ul> </ul>
<form class="d-flex"> <form class="d-flex" role="search">
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search"> <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-success" type="submit">Search</button> <button class="btn btn-outline-success" type="submit">Search</button>
</form> </form>
@@ -624,7 +624,7 @@ With a toggler on the left and brand name on the right:
<a class="nav-link disabled">Disabled</a> <a class="nav-link disabled">Disabled</a>
</li> </li>
</ul> </ul>
<form class="d-flex"> <form class="d-flex" role="search">
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search"> <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-success" type="submit">Search</button> <button class="btn btn-outline-success" type="submit">Search</button>
</form> </form>
@@ -695,7 +695,7 @@ In the example below, to create an offcanvas navbar that is always collapsed acr
</ul> </ul>
</li> </li>
</ul> </ul>
<form class="d-flex"> <form class="d-flex" role="search">
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search"> <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-success" type="submit">Search</button> <button class="btn btn-outline-success" type="submit">Search</button>
</form> </form>
@@ -8,7 +8,7 @@ toc: true
## How it works ## How it works
Offcanvas is a sidebar component that can be toggled via JavaScript to appear from the left, right, or bottom edge of the viewport. Buttons or anchors are used as triggers that are attached to specific elements you toggle, and `data` attributes are used to invoke our JavaScript. Offcanvas is a sidebar component that can be toggled via JavaScript to appear from the left, right, top, or bottom edge of the viewport. Buttons or anchors are used as triggers that are attached to specific elements you toggle, and `data` attributes are used to invoke our JavaScript.
- Offcanvas shares some of the same JavaScript code as modals. Conceptually, they are quite similar, but they are separate plugins. - Offcanvas shares some of the same JavaScript code as modals. Conceptually, they are quite similar, but they are separate plugins.
- Similarly, some [source Sass](#sass) variables for offcanvas's styles and dimensions are inherited from the modal's variables. - Similarly, some [source Sass](#sass) variables for offcanvas's styles and dimensions are inherited from the modal's variables.
@@ -95,7 +95,7 @@ Try the top, right, and bottom examples out below.
<div class="offcanvas offcanvas-top" tabindex="-1" id="offcanvasTop" aria-labelledby="offcanvasTopLabel"> <div class="offcanvas offcanvas-top" tabindex="-1" id="offcanvasTop" aria-labelledby="offcanvasTopLabel">
<div class="offcanvas-header"> <div class="offcanvas-header">
<h5 id="offcanvasTopLabel">Offcanvas top</h5> <h5 class="offcanvas-title" id="offcanvasTopLabel">Offcanvas top</h5>
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" aria-label="Close"></button> <button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div> </div>
<div class="offcanvas-body"> <div class="offcanvas-body">
@@ -109,7 +109,7 @@ Try the top, right, and bottom examples out below.
<div class="offcanvas offcanvas-end" tabindex="-1" id="offcanvasRight" aria-labelledby="offcanvasRightLabel"> <div class="offcanvas offcanvas-end" tabindex="-1" id="offcanvasRight" aria-labelledby="offcanvasRightLabel">
<div class="offcanvas-header"> <div class="offcanvas-header">
<h5 id="offcanvasRightLabel">Offcanvas right</h5> <h5 class="offcanvas-title" id="offcanvasRightLabel">Offcanvas right</h5>
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" aria-label="Close"></button> <button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div> </div>
<div class="offcanvas-body"> <div class="offcanvas-body">
@@ -188,6 +188,7 @@ The offcanvas plugin utilizes a few classes and attributes to handle the heavy l
- `.offcanvas.show` shows the content - `.offcanvas.show` shows the content
- `.offcanvas-start` hides the offcanvas on the left - `.offcanvas-start` hides the offcanvas on the left
- `.offcanvas-end` hides the offcanvas on the right - `.offcanvas-end` hides the offcanvas on the right
- `.offcanvas-top` hides the offcanvas on the top
- `.offcanvas-bottom` hides the offcanvas on the bottom - `.offcanvas-bottom` hides the offcanvas on the bottom
Add a dismiss button with the `data-bs-dismiss="offcanvas"` attribute, which triggers the JavaScript functionality. Be sure to use the `<button>` element with it for proper behavior across all devices. Add a dismiss button with the `data-bs-dismiss="offcanvas"` attribute, which triggers the JavaScript functionality. Be sure to use the `<button>` element with it for proper behavior across all devices.
@@ -368,6 +368,21 @@ Removes the ability for an element's popover to be shown. The popover will only
myPopover.disable() myPopover.disable()
``` ```
#### setContent
Gives a way to change the popover's content after its initialization.
```js
myPopover.setContent({
'.popover-header': 'another title',
'.popover-body': 'another content'
})
```
{{< callout info >}}
The `setContent` method accepts an `object` argument, where each property-key is a valid `string` selector within the popover template, and each related property-value can be `string` | `element` | `function` | `null`
{{< /callout >}}
#### toggleEnabled #### toggleEnabled
Toggles the ability for an element's popover to be shown or hidden. Toggles the ability for an element's popover to be shown or hidden.
@@ -392,6 +392,17 @@ Removes the ability for an element's tooltip to be shown. The tooltip will only
tooltip.disable() tooltip.disable()
``` ```
#### setContent
Gives a way to change the tooltip's content after its initialization.
```js
tooltip.setContent({ '.tooltip-inner': 'another title' })
```
{{< callout info >}}
The `setContent` method accepts an `object` argument, where each property-key is a valid `string` selector within the popover template, and each related property-value can be `string` | `element` | `function` | `null`
{{< /callout >}}
#### toggleEnabled #### toggleEnabled
Toggles the ability for an element's tooltip to be shown or hidden. Toggles the ability for an element's tooltip to be shown or hidden.
+1 -1
View File
@@ -48,7 +48,7 @@ Align images with the [helper float classes]({{< docsref "/utilities/float" >}})
If you are using the `<picture>` element to specify multiple `<source>` elements for a specific `<img>`, make sure to add the `.img-*` classes to the `<img>` and not to the `<picture>` tag. If you are using the `<picture>` element to specify multiple `<source>` elements for a specific `<img>`, make sure to add the `.img-*` classes to the `<img>` and not to the `<picture>` tag.
```html ```html
<picture> <picture>
<source srcset="..." type="image/svg+xml"> <source srcset="..." type="image/svg+xml">
<img src="..." class="img-fluid img-thumbnail" alt="..."> <img src="..." class="img-fluid img-thumbnail" alt="...">
</picture> </picture>
+2 -2
View File
@@ -252,7 +252,7 @@ Add `.table-sm` to make any `.table` more compact by cutting all cell `padding`
## Vertical alignment ## Vertical alignment
Table cells of `<thead>` are always vertical aligned to the bottom. Table cells in `<tbody>` inherit their alignment from `<table>` and are aligned to the the top by default. Use the [vertical align]({{< docsref "/utilities/vertical-align" >}}) classes to re-align where needed. Table cells of `<thead>` are always vertical aligned to the bottom. Table cells in `<tbody>` inherit their alignment from `<table>` and are aligned to the top by default. Use the [vertical align]({{< docsref "/utilities/vertical-align" >}}) classes to re-align where needed.
<div class="bd-example"> <div class="bd-example">
<div class="table-responsive"> <div class="table-responsive">
@@ -786,4 +786,4 @@ Use `.table-responsive{-sm|-md|-lg|-xl|-xxl}` as needed to create responsive tab
### Customizing ### Customizing
- The factor variables (`$table-striped-bg-factor`, `$table-active-bg-factor` & `$table-hover-bg-factor`) are used to determine the contrast in table variants. - The factor variables (`$table-striped-bg-factor`, `$table-active-bg-factor` & `$table-hover-bg-factor`) are used to determine the contrast in table variants.
- Apart from the light & dark table variants, theme colors are lightened by the `$table-bg-level` variable. - Apart from the light & dark table variants, theme colors are lightened by the `$table-bg-scale` variable.
+1 -1
View File
@@ -184,7 +184,7 @@ Add `.initialism` to an abbreviation for a slightly smaller font-size.
## Blockquotes ## Blockquotes
For quoting blocks of content from another source within your document. Wrap `<blockquote class="blockquote">` around any <abbr title="HyperText Markup Language">HTML</abbr> as the quote. For quoting blocks of content from another source within your document. Wrap `<blockquote class="blockquote">` around any HTML as the quote.
{{< example >}} {{< example >}}
<blockquote class="blockquote"> <blockquote class="blockquote">
+1
View File
@@ -122,6 +122,7 @@ Here's an example that generates text color utilities (e.g., `.text-purple-500`)
```scss ```scss
@import "bootstrap/scss/functions"; @import "bootstrap/scss/functions";
@import "bootstrap/scss/variables"; @import "bootstrap/scss/variables";
@import "bootstrap/scss/maps";
@import "bootstrap/scss/mixins"; @import "bootstrap/scss/mixins";
@import "bootstrap/scss/utilities"; @import "bootstrap/scss/utilities";
+10 -3
View File
@@ -59,10 +59,15 @@ In your `custom.scss`, you'll import Bootstrap's source Sass files. You have two
// 3. Include remainder of required Bootstrap stylesheets // 3. Include remainder of required Bootstrap stylesheets
@import "../node_modules/bootstrap/scss/variables"; @import "../node_modules/bootstrap/scss/variables";
// 4. Include any default map overrides here
// 5. Include remainder of required parts
@import "../node_modules/bootstrap/scss/maps";
@import "../node_modules/bootstrap/scss/mixins"; @import "../node_modules/bootstrap/scss/mixins";
@import "../node_modules/bootstrap/scss/root"; @import "../node_modules/bootstrap/scss/root";
// 4. Include any optional Bootstrap CSS as needed // 6. Optionally include any other parts as needed
@import "../node_modules/bootstrap/scss/utilities"; @import "../node_modules/bootstrap/scss/utilities";
@import "../node_modules/bootstrap/scss/reboot"; @import "../node_modules/bootstrap/scss/reboot";
@import "../node_modules/bootstrap/scss/type"; @import "../node_modules/bootstrap/scss/type";
@@ -71,10 +76,10 @@ In your `custom.scss`, you'll import Bootstrap's source Sass files. You have two
@import "../node_modules/bootstrap/scss/grid"; @import "../node_modules/bootstrap/scss/grid";
@import "../node_modules/bootstrap/scss/helpers"; @import "../node_modules/bootstrap/scss/helpers";
// 5. Optionally include utilities API last to generate classes based on the Sass map in `_utilities.scss` // 7. Optionally include utilities API last to generate classes based on the Sass map in `_utilities.scss`
@import "../node_modules/bootstrap/scss/utilities/api"; @import "../node_modules/bootstrap/scss/utilities/api";
// 6. Add additional custom code here // 8. Add additional custom code here
``` ```
With that setup in place, you can begin to modify any of the Sass variables and maps in your `custom.scss`. You can also start to add parts of Bootstrap under the `// Optional` section as needed. We suggest using the full import stack from our `bootstrap.scss` file as your starting point. With that setup in place, you can begin to modify any of the Sass variables and maps in your `custom.scss`. You can also start to add parts of Bootstrap under the `// Optional` section as needed. We suggest using the full import stack from our `bootstrap.scss` file as your starting point.
@@ -99,6 +104,7 @@ $body-color: #111;
// Required // Required
@import "../node_modules/bootstrap/scss/variables"; @import "../node_modules/bootstrap/scss/variables";
@import "../node_modules/bootstrap/scss/maps";
@import "../node_modules/bootstrap/scss/mixins"; @import "../node_modules/bootstrap/scss/mixins";
@import "../node_modules/bootstrap/scss/root"; @import "../node_modules/bootstrap/scss/root";
@@ -160,6 +166,7 @@ To remove colors from `$theme-colors`, or any other map, use `map-remove`. Be aw
// Required // Required
@import "../node_modules/bootstrap/scss/functions"; @import "../node_modules/bootstrap/scss/functions";
@import "../node_modules/bootstrap/scss/variables"; @import "../node_modules/bootstrap/scss/variables";
@import "../node_modules/bootstrap/scss/maps";
@import "../node_modules/bootstrap/scss/mixins"; @import "../node_modules/bootstrap/scss/mixins";
@import "../node_modules/bootstrap/scss/root"; @import "../node_modules/bootstrap/scss/root";
@@ -25,7 +25,7 @@ extra_css:
<a class="nav-link disabled">رابط غير مفعل</a> <a class="nav-link disabled">رابط غير مفعل</a>
</li> </li>
</ul> </ul>
<form class="d-flex"> <form class="d-flex" role="search">
<input class="form-control me-2" type="search" placeholder="بحث" aria-label="بحث"> <input class="form-control me-2" type="search" placeholder="بحث" aria-label="بحث">
<button class="btn btn-outline-success" type="submit">بحث</button> <button class="btn btn-outline-success" type="submit">بحث</button>
</form> </form>
@@ -24,7 +24,7 @@ extra_css:
<a class="nav-link disabled">Disabled</a> <a class="nav-link disabled">Disabled</a>
</li> </li>
</ul> </ul>
<form class="d-flex"> <form class="d-flex" role="search">
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search"> <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-success" type="submit">Search</button> <button class="btn btn-outline-success" type="submit">Search</button>
</form> </form>
@@ -1240,7 +1240,7 @@ direction: rtl
<a class="nav-link disabled">معطل</a> <a class="nav-link disabled">معطل</a>
</li> </li>
</ul> </ul>
<form class="d-flex"> <form class="d-flex" role="search">
<input class="form-control me-2" type="search" placeholder="بحث" aria-label="بحث"> <input class="form-control me-2" type="search" placeholder="بحث" aria-label="بحث">
<button class="btn btn-outline-dark" type="submit">بحث</button> <button class="btn btn-outline-dark" type="submit">بحث</button>
</form> </form>
@@ -1279,7 +1279,7 @@ direction: rtl
<a class="nav-link disabled">معطل</a> <a class="nav-link disabled">معطل</a>
</li> </li>
</ul> </ul>
<form class="d-flex"> <form class="d-flex" role="search">
<input class="form-control me-2" type="search" placeholder="بحث" aria-label="بحث"> <input class="form-control me-2" type="search" placeholder="بحث" aria-label="بحث">
<button class="btn btn-outline-light" type="submit">بحث</button> <button class="btn btn-outline-light" type="submit">بحث</button>
</form> </form>
@@ -1497,7 +1497,7 @@ direction: rtl
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="قريب"></button> <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="قريب"></button>
</div> </div>
<div class="toast-body"> <div class="toast-body">
مرحبا بالعالم! هذه رسالة إشعار. مرحبًا بالعالم! هذه رسالة إشعار.
</div> </div>
</div> </div>
{{< /example >}} {{< /example >}}
@@ -1239,7 +1239,7 @@ body_class: "bg-light"
<a class="nav-link disabled">Disabled</a> <a class="nav-link disabled">Disabled</a>
</li> </li>
</ul> </ul>
<form class="d-flex"> <form class="d-flex" role="search">
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search"> <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-dark" type="submit">Search</button> <button class="btn btn-outline-dark" type="submit">Search</button>
</form> </form>
@@ -1278,7 +1278,7 @@ body_class: "bg-light"
<a class="nav-link disabled">Disabled</a> <a class="nav-link disabled">Disabled</a>
</li> </li>
</ul> </ul>
<form class="d-flex"> <form class="d-flex" role="search">
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search"> <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-light" type="submit">Search</button> <button class="btn btn-outline-light" type="submit">Search</button>
</form> </form>
@@ -107,19 +107,19 @@ body_class: ""
</form> </form>
<ul class="list-unstyled mb-0"> <ul class="list-unstyled mb-0">
<li><a class="dropdown-item d-flex align-items-center gap-2 py-2" href="#"> <li><a class="dropdown-item d-flex align-items-center gap-2 py-2" href="#">
<span class="d-inline-block bg-success rounded-circle" style="width: .5em; height: .5em;"></span> <span class="d-inline-block bg-success rounded-circle p-1"></span>
Action Action
</a></li> </a></li>
<li><a class="dropdown-item d-flex align-items-center gap-2 py-2" href="#"> <li><a class="dropdown-item d-flex align-items-center gap-2 py-2" href="#">
<span class="d-inline-block bg-primary rounded-circle" style="width: .5em; height: .5em;"></span> <span class="d-inline-block bg-primary rounded-circle p-1"></span>
Another action Another action
</a></li> </a></li>
<li><a class="dropdown-item d-flex align-items-center gap-2 py-2" href="#"> <li><a class="dropdown-item d-flex align-items-center gap-2 py-2" href="#">
<span class="d-inline-block bg-danger rounded-circle" style="width: .5em; height: .5em;"></span> <span class="d-inline-block bg-danger rounded-circle p-1"></span>
Something else here Something else here
</a></li> </a></li>
<li><a class="dropdown-item d-flex align-items-center gap-2 py-2" href="#"> <li><a class="dropdown-item d-flex align-items-center gap-2 py-2" href="#">
<span class="d-inline-block bg-info rounded-circle" style="width: .5em; height: .5em;"></span> <span class="d-inline-block bg-info rounded-circle p-1"></span>
Separated link Separated link
</a></li> </a></li>
</ul> </ul>
@@ -131,19 +131,19 @@ body_class: ""
</form> </form>
<ul class="list-unstyled mb-0"> <ul class="list-unstyled mb-0">
<li><a class="dropdown-item d-flex align-items-center gap-2 py-2" href="#"> <li><a class="dropdown-item d-flex align-items-center gap-2 py-2" href="#">
<span class="d-inline-block bg-success rounded-circle" style="width: .5em; height: .5em;"></span> <span class="d-inline-block bg-success rounded-circle p-1"></span>
Action Action
</a></li> </a></li>
<li><a class="dropdown-item d-flex align-items-center gap-2 py-2" href="#"> <li><a class="dropdown-item d-flex align-items-center gap-2 py-2" href="#">
<span class="d-inline-block bg-primary rounded-circle" style="width: .5em; height: .5em;"></span> <span class="d-inline-block bg-primary rounded-circle p-1"></span>
Another action Another action
</a></li> </a></li>
<li><a class="dropdown-item d-flex align-items-center gap-2 py-2" href="#"> <li><a class="dropdown-item d-flex align-items-center gap-2 py-2" href="#">
<span class="d-inline-block bg-danger rounded-circle" style="width: .5em; height: .5em;"></span> <span class="d-inline-block bg-danger rounded-circle p-1"></span>
Something else here Something else here
</a></li> </a></li>
<li><a class="dropdown-item d-flex align-items-center gap-2 py-2" href="#"> <li><a class="dropdown-item d-flex align-items-center gap-2 py-2" href="#">
<span class="d-inline-block bg-info rounded-circle" style="width: .5em; height: .5em;"></span> <span class="d-inline-block bg-info rounded-circle p-1"></span>
Separated link Separated link
</a></li> </a></li>
</ul> </ul>
@@ -48,7 +48,7 @@ body_class: ""
<a href="/" class="mb-3 me-2 mb-md-0 text-muted text-decoration-none lh-1"> <a href="/" class="mb-3 me-2 mb-md-0 text-muted text-decoration-none lh-1">
<svg class="bi" width="30" height="24"><use xlink:href="#bootstrap"/></svg> <svg class="bi" width="30" height="24"><use xlink:href="#bootstrap"/></svg>
</a> </a>
<span class="text-muted">&copy; {{< year >}} Company, Inc</span> <span class="mb-3 mb-md-0 text-muted">&copy; {{< year >}} Company, Inc</span>
</div> </div>
<ul class="nav col-md-4 justify-content-end list-unstyled d-flex"> <ul class="nav col-md-4 justify-content-end list-unstyled d-flex">
@@ -77,19 +77,19 @@ body_class: ""
<div class="b-example-divider"></div> <div class="b-example-divider"></div>
<div class="container"> <div class="container">
<footer class="row row-cols-5 py-5 my-5 border-top"> <footer class="row row-cols-1 row-cols-sm-2 row-cols-md-5 py-5 my-5 border-top">
<div class="col"> <div class="col mb-3">
<a href="/" class="d-flex align-items-center mb-3 link-dark text-decoration-none"> <a href="/" class="d-flex align-items-center mb-3 link-dark text-decoration-none">
<svg class="bi me-2" width="40" height="32"><use xlink:href="#bootstrap"/></svg> <svg class="bi me-2" width="40" height="32"><use xlink:href="#bootstrap"/></svg>
</a> </a>
<p class="text-muted">&copy; {{< year >}}</p> <p class="text-muted">&copy; {{< year >}}</p>
</div> </div>
<div class="col"> <div class="col mb-3">
</div> </div>
<div class="col"> <div class="col mb-3">
<h5>Section</h5> <h5>Section</h5>
<ul class="nav flex-column"> <ul class="nav flex-column">
<li class="nav-item mb-2"><a href="#" class="nav-link p-0 text-muted">Home</a></li> <li class="nav-item mb-2"><a href="#" class="nav-link p-0 text-muted">Home</a></li>
@@ -100,7 +100,7 @@ body_class: ""
</ul> </ul>
</div> </div>
<div class="col"> <div class="col mb-3">
<h5>Section</h5> <h5>Section</h5>
<ul class="nav flex-column"> <ul class="nav flex-column">
<li class="nav-item mb-2"><a href="#" class="nav-link p-0 text-muted">Home</a></li> <li class="nav-item mb-2"><a href="#" class="nav-link p-0 text-muted">Home</a></li>
@@ -111,7 +111,7 @@ body_class: ""
</ul> </ul>
</div> </div>
<div class="col"> <div class="col mb-3">
<h5>Section</h5> <h5>Section</h5>
<ul class="nav flex-column"> <ul class="nav flex-column">
<li class="nav-item mb-2"><a href="#" class="nav-link p-0 text-muted">Home</a></li> <li class="nav-item mb-2"><a href="#" class="nav-link p-0 text-muted">Home</a></li>
@@ -130,7 +130,7 @@ body_class: ""
<div class="container"> <div class="container">
<footer class="py-5"> <footer class="py-5">
<div class="row"> <div class="row">
<div class="col-2"> <div class="col-6 col-md-2 mb-3">
<h5>Section</h5> <h5>Section</h5>
<ul class="nav flex-column"> <ul class="nav flex-column">
<li class="nav-item mb-2"><a href="#" class="nav-link p-0 text-muted">Home</a></li> <li class="nav-item mb-2"><a href="#" class="nav-link p-0 text-muted">Home</a></li>
@@ -141,7 +141,7 @@ body_class: ""
</ul> </ul>
</div> </div>
<div class="col-2"> <div class="col-6 col-md-2 mb-3">
<h5>Section</h5> <h5>Section</h5>
<ul class="nav flex-column"> <ul class="nav flex-column">
<li class="nav-item mb-2"><a href="#" class="nav-link p-0 text-muted">Home</a></li> <li class="nav-item mb-2"><a href="#" class="nav-link p-0 text-muted">Home</a></li>
@@ -152,7 +152,7 @@ body_class: ""
</ul> </ul>
</div> </div>
<div class="col-2"> <div class="col-6 col-md-2 mb-3">
<h5>Section</h5> <h5>Section</h5>
<ul class="nav flex-column"> <ul class="nav flex-column">
<li class="nav-item mb-2"><a href="#" class="nav-link p-0 text-muted">Home</a></li> <li class="nav-item mb-2"><a href="#" class="nav-link p-0 text-muted">Home</a></li>
@@ -163,11 +163,11 @@ body_class: ""
</ul> </ul>
</div> </div>
<div class="col-4 offset-1"> <div class="col-md-5 offset-md-1 mb-3">
<form> <form>
<h5>Subscribe to our newsletter</h5> <h5>Subscribe to our newsletter</h5>
<p>Monthly digest of whats new and exciting from us.</p> <p>Monthly digest of whats new and exciting from us.</p>
<div class="d-flex w-100 gap-2"> <div class="d-flex flex-column flex-sm-row w-100 gap-2">
<label for="newsletter1" class="visually-hidden">Email address</label> <label for="newsletter1" class="visually-hidden">Email address</label>
<input id="newsletter1" type="text" class="form-control" placeholder="Email address"> <input id="newsletter1" type="text" class="form-control" placeholder="Email address">
<button class="btn btn-primary" type="button">Subscribe</button> <button class="btn btn-primary" type="button">Subscribe</button>
@@ -176,7 +176,7 @@ body_class: ""
</div> </div>
</div> </div>
<div class="d-flex justify-content-between py-4 my-4 border-top"> <div class="d-flex flex-column flex-sm-row justify-content-between py-4 my-4 border-top">
<p>&copy; {{< year >}} Company, Inc. All rights reserved.</p> <p>&copy; {{< year >}} Company, Inc. All rights reserved.</p>
<ul class="list-unstyled d-flex"> <ul class="list-unstyled d-flex">
<li class="ms-3"><a class="link-dark" href="#"><svg class="bi" width="24" height="24"><use xlink:href="#twitter"/></svg></a></li> <li class="ms-3"><a class="link-dark" href="#"><svg class="bi" width="24" height="24"><use xlink:href="#twitter"/></svg></a></li>
@@ -110,8 +110,8 @@ include_js: false
<hr class="my-4"> <hr class="my-4">
<h2 class="mt-4">Mixed: mobile and desktop</h2> <h2 class="mt-4">Mixed: mobile and desktop</h2>
<p>The Bootstrap v4 grid system has five tiers of classes: xs (extra small, this class infix is not used), sm (small), md (medium), lg (large), and xl (extra large). You can use nearly any combination of these classes to create more dynamic and flexible layouts.</p> <p>The Bootstrap v5 grid system has six tiers of classes: xs (extra small, this class infix is not used), sm (small), md (medium), lg (large), xl (x-large), and xxl (xx-large). You can use nearly any combination of these classes to create more dynamic and flexible layouts.</p>
<p>Each tier of classes scales up, meaning if you plan on setting the same widths for md, lg and xl, you only need to specify md.</p> <p>Each tier of classes scales up, meaning if you plan on setting the same widths for md, lg, xl and xxl, you only need to specify md.</p>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-md-8 themed-grid-col">.col-md-8</div> <div class="col-md-8 themed-grid-col">.col-md-8</div>
<div class="col-6 col-md-4 themed-grid-col">.col-6 .col-md-4</div> <div class="col-6 col-md-4 themed-grid-col">.col-6 .col-md-4</div>
@@ -104,7 +104,7 @@ body_class: ""
<li><a href="#" class="nav-link px-2 text-white">About</a></li> <li><a href="#" class="nav-link px-2 text-white">About</a></li>
</ul> </ul>
<form class="col-12 col-lg-auto mb-3 mb-lg-0 me-lg-3"> <form class="col-12 col-lg-auto mb-3 mb-lg-0 me-lg-3" role="search">
<input type="search" class="form-control form-control-dark" placeholder="Search..." aria-label="Search"> <input type="search" class="form-control form-control-dark" placeholder="Search..." aria-label="Search">
</form> </form>
@@ -132,7 +132,7 @@ body_class: ""
<li><a href="#" class="nav-link px-2 link-dark">Products</a></li> <li><a href="#" class="nav-link px-2 link-dark">Products</a></li>
</ul> </ul>
<form class="col-12 col-lg-auto mb-3 mb-lg-0 me-lg-3"> <form class="col-12 col-lg-auto mb-3 mb-lg-0 me-lg-3" role="search">
<input type="search" class="form-control" placeholder="Search..." aria-label="Search"> <input type="search" class="form-control" placeholder="Search..." aria-label="Search">
</form> </form>
@@ -172,7 +172,7 @@ body_class: ""
</div> </div>
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<form class="w-100 me-3"> <form class="w-100 me-3" role="search">
<input type="search" class="form-control" placeholder="Search..." aria-label="Search"> <input type="search" class="form-control" placeholder="Search..." aria-label="Search">
</form> </form>
@@ -226,7 +226,7 @@ body_class: ""
<svg class="bi me-2" width="40" height="32"><use xlink:href="#bootstrap"/></svg> <svg class="bi me-2" width="40" height="32"><use xlink:href="#bootstrap"/></svg>
<span class="fs-4">Double header</span> <span class="fs-4">Double header</span>
</a> </a>
<form class="col-12 col-lg-auto mb-3 mb-lg-0"> <form class="col-12 col-lg-auto mb-3 mb-lg-0" role="search">
<input type="search" class="form-control" placeholder="Search..." aria-label="Search"> <input type="search" class="form-control" placeholder="Search..." aria-label="Search">
</form> </form>
</div> </div>
@@ -279,7 +279,7 @@ body_class: ""
</div> </div>
<div class="px-3 py-2 border-bottom mb-3"> <div class="px-3 py-2 border-bottom mb-3">
<div class="container d-flex flex-wrap justify-content-center"> <div class="container d-flex flex-wrap justify-content-center">
<form class="col-12 col-lg-auto mb-2 mb-lg-0 me-lg-auto"> <form class="col-12 col-lg-auto mb-2 mb-lg-0 me-lg-auto" role="search">
<input type="search" class="form-control" placeholder="Search..." aria-label="Search"> <input type="search" class="form-control" placeholder="Search..." aria-label="Search">
</form> </form>
@@ -23,7 +23,7 @@ extra_css:
<a class="nav-link disabled">Disabled</a> <a class="nav-link disabled">Disabled</a>
</li> </li>
</ul> </ul>
<form class="d-flex"> <form class="d-flex" role="search">
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search"> <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-success" type="submit">Search</button> <button class="btn btn-outline-success" type="submit">Search</button>
</form> </form>
@@ -23,7 +23,7 @@ extra_css:
<a class="nav-link disabled">Disabled</a> <a class="nav-link disabled">Disabled</a>
</li> </li>
</ul> </ul>
<form class="d-flex"> <form class="d-flex" role="search">
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search"> <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-success" type="submit">Search</button> <button class="btn btn-outline-success" type="submit">Search</button>
</form> </form>
@@ -33,8 +33,8 @@ extra_css:
</ul> </ul>
</li> </li>
</ul> </ul>
<form> <form role="search">
<input class="form-control" type="text" placeholder="Search" aria-label="Search"> <input class="form-control" type="search" placeholder="Search" aria-label="Search">
</form> </form>
</div> </div>
</div> </div>
@@ -56,8 +56,8 @@ extra_css:
<a class="nav-link" href="#">Link</a> <a class="nav-link" href="#">Link</a>
</li> </li>
</ul> </ul>
<form> <form role="search">
<input class="form-control" type="text" placeholder="Search" aria-label="Search"> <input class="form-control" type="search" placeholder="Search" aria-label="Search">
</form> </form>
</div> </div>
</div> </div>
@@ -90,8 +90,8 @@ extra_css:
</ul> </ul>
</li> </li>
</ul> </ul>
<form> <form role="search">
<input class="form-control" type="text" placeholder="Search" aria-label="Search"> <input class="form-control" type="search" placeholder="Search" aria-label="Search">
</form> </form>
</div> </div>
</div> </div>
@@ -124,8 +124,8 @@ extra_css:
</ul> </ul>
</li> </li>
</ul> </ul>
<form> <form role="search">
<input class="form-control" type="text" placeholder="Search" aria-label="Search"> <input class="form-control" type="search" placeholder="Search" aria-label="Search">
</form> </form>
</div> </div>
</div> </div>
@@ -158,8 +158,8 @@ extra_css:
</ul> </ul>
</li> </li>
</ul> </ul>
<form> <form role="search">
<input class="form-control" type="text" placeholder="Search" aria-label="Search"> <input class="form-control" type="search" placeholder="Search" aria-label="Search">
</form> </form>
</div> </div>
</div> </div>
@@ -192,8 +192,8 @@ extra_css:
</ul> </ul>
</li> </li>
</ul> </ul>
<form> <form role="search">
<input class="form-control" type="text" placeholder="Search" aria-label="Search"> <input class="form-control" type="search" placeholder="Search" aria-label="Search">
</form> </form>
</div> </div>
</div> </div>
@@ -226,8 +226,8 @@ extra_css:
</ul> </ul>
</li> </li>
</ul> </ul>
<form> <form role="search">
<input class="form-control" type="text" placeholder="Search" aria-label="Search"> <input class="form-control" type="search" placeholder="Search" aria-label="Search">
</form> </form>
</div> </div>
</div> </div>
@@ -260,8 +260,8 @@ extra_css:
</ul> </ul>
</li> </li>
</ul> </ul>
<form> <form role="search">
<input class="form-control" type="text" placeholder="Search" aria-label="Search"> <input class="form-control" type="search" placeholder="Search" aria-label="Search">
</form> </form>
</div> </div>
</div> </div>
@@ -294,8 +294,8 @@ extra_css:
</ul> </ul>
</li> </li>
</ul> </ul>
<form> <form role="search">
<input class="form-control" type="text" placeholder="Search" aria-label="Search"> <input class="form-control" type="search" placeholder="Search" aria-label="Search">
</form> </form>
</div> </div>
</div> </div>
@@ -363,8 +363,8 @@ extra_css:
</ul> </ul>
</li> </li>
</ul> </ul>
<form> <form role="search">
<input class="form-control" type="text" placeholder="Search" aria-label="Search"> <input class="form-control" type="search" placeholder="Search" aria-label="Search">
</form> </form>
</div> </div>
</div> </div>
@@ -39,7 +39,7 @@ aliases: "/docs/5.1/examples/offcanvas/"
</ul> </ul>
</li> </li>
</ul> </ul>
<form class="d-flex"> <form class="d-flex" role="search">
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search"> <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-success" type="submit">Search</button> <button class="btn btn-outline-success" type="submit">Search</button>
</form> </form>
@@ -27,7 +27,7 @@ body_class: "d-flex flex-column h-100"
<a class="nav-link disabled">Disabled</a> <a class="nav-link disabled">Disabled</a>
</li> </li>
</ul> </ul>
<form class="d-flex"> <form class="d-flex" role="search">
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search"> <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-success" type="submit">Search</button> <button class="btn btn-outline-success" type="submit">Search</button>
</form> </form>
@@ -89,7 +89,7 @@ When working with the Bootstrap grid system, be sure to place form elements with
</div> </div>
<div class="col-md"> <div class="col-md">
<div class="form-floating"> <div class="form-floating">
<select class="form-select" id="floatingSelectGrid" aria-label="Floating label select example"> <select class="form-select" id="floatingSelectGrid">
<option selected>Open this select menu</option> <option selected>Open this select menu</option>
<option value="1">One</option> <option value="1">One</option>
<option value="2">Two</option> <option value="2">Two</option>
+2 -2
View File
@@ -145,10 +145,10 @@ While using visually hidden content (`.visually-hidden`, `aria-label`, and even
## Sass ## Sass
Many form variables are set at a general level to be re-used and extended by individual form components. You'll see these most often as `$btn-input-*` and `$input-*` variables. Many form variables are set at a general level to be re-used and extended by individual form components. You'll see these most often as `$input-btn-*` and `$input-*` variables.
### Variables ### Variables
`$btn-input-*` variables are shared global variables between our [buttons]({{< docsref "/components/buttons" >}}) and our form components. You'll find these frequently reassigned as values to other component-specific variables. `$input-btn-*` variables are shared global variables between our [buttons]({{< docsref "/components/buttons" >}}) and our form components. You'll find these frequently reassigned as values to other component-specific variables.
{{< scss-docs name="input-btn-variables" file="scss/_variables.scss" >}} {{< scss-docs name="input-btn-variables" file="scss/_variables.scss" >}}
+2 -2
View File
@@ -46,10 +46,10 @@ You can see the above requirements reflected in this modified RTL starter templa
<!-- Bootstrap CSS --> <!-- Bootstrap CSS -->
<link rel="stylesheet" href="{{< param "cdn.css_rtl" >}}" integrity="{{< param "cdn.css_rtl_hash" >}}" crossorigin="anonymous"> <link rel="stylesheet" href="{{< param "cdn.css_rtl" >}}" integrity="{{< param "cdn.css_rtl_hash" >}}" crossorigin="anonymous">
<title>مرحبا بالعالم!</title> <title>مرحبًا بالعالم!</title>
</head> </head>
<body> <body>
<h1>مرحبا بالعالم!</h1> <h1>مرحبًا بالعالم!</h1>
<!-- Optional JavaScript; choose one of the two! --> <!-- Optional JavaScript; choose one of the two! -->
+1 -1
View File
@@ -137,7 +137,7 @@ Gutter classes can also be added to [row columns]({{< docsref "/layout/grid#row-
The gutters between columns in our predefined grid classes can be removed with `.g-0`. This removes the negative `margin`s from `.row` and the horizontal `padding` from all immediate children columns. The gutters between columns in our predefined grid classes can be removed with `.g-0`. This removes the negative `margin`s from `.row` and the horizontal `padding` from all immediate children columns.
**Need an edge-to-edge design?** Drop the parent `.container` or `.container-fluid`. **Need an edge-to-edge design?** Drop the parent `.container` or `.container-fluid` and add `.mx-0` to the `.row` to prevent overflow.
In practice, here's how it looks. Note you can continue to use this with all other predefined grid classes (including column widths, responsive tiers, reorders, and more). In practice, here's how it looks. Note you can continue to use this with all other predefined grid classes (including column widths, responsive tiers, reorders, and more).
+47 -1
View File
@@ -9,6 +9,52 @@ toc: true
## v5.2.0 ## v5.2.0
### New `_maps.scss`
Bootstrap v5.2.0 introduced a new Sass file, `_maps.scss`, that pulled out several Sass maps from `_variables.scss` to fix an issue where updates to an original map were not applied to secondary maps that extend them. For example, updates to `$theme-colors` were not being applied to other theme maps that relied on `$theme-colors`, breaking key customization workflows. In short, Sass has a limitation where once a default variable or map has been _used_, it cannot be updated.
This is why variable customizations in Bootstrap have to come after `@import "functions"`, but before `@import "variables"` and the rest of our import stack. The same applies to Sass maps—you must override the defaults before the defaults get used. The following maps have been moved to the new `_maps.scss`:
- `$theme-colors-rgb`
- `$utilities-colors`
- `$utilities-text`
- `$utilities-text-colors`
- `$utilities-bg`
- `$utilities-bg-colors`
- `$negative-spacers`
- `$gutters`
Your custom Bootstrap CSS builds should now look something like this with a separate maps import.
```diff
// Functions come first
@import "functions";
// Optional variable overrides here
+ $custom-color: #df711b;
+ $custom-theme-colors: (
+ "custom": $custom-color
+ );
// Variables come next
@import "variables";
+ // Optional Sass map overrides here
+ $theme-colors: map-merge($theme-colors, $custom-theme-colors);
+
+ // Followed by our default maps
+ @import "maps";
+
// Rest of our imports
@import "mixins";
@import "utilities";
@import "root";
@import "reboot";
// etc
```
### Key changes
- **Introduced new `$enable-container-classes` option.** Now when opting into the experimental CSS Grid layout, `.container-*` classes will still be compiled, unless this option is set to `false`. - **Introduced new `$enable-container-classes` option.** Now when opting into the experimental CSS Grid layout, `.container-*` classes will still be compiled, unless this option is set to `false`.
## Dependencies ## Dependencies
@@ -251,7 +297,7 @@ toc: true
- <span class="badge bg-danger">Breaking</span> All the events for the dropdown are now triggered on the dropdown toggle button and then bubbled up to the parent element. - <span class="badge bg-danger">Breaking</span> All the events for the dropdown are now triggered on the dropdown toggle button and then bubbled up to the parent element.
- Dropdown menus now have a `data-bs-popper="static"` attribute set when the positioning of the dropdown is static and `data-bs-popper="none"` when dropdown is in the navbar. This is added by our JavaScript and helps us use custom position styles without interfering with Popper's positioning. - Dropdown menus now have a `data-bs-popper="static"` attribute set when the positioning of the dropdown is static, or dropdown is in the navbar. This is added by our JavaScript and helps us use custom position styles without interfering with Popper's positioning.
- <span class="badge bg-danger">Breaking</span> Dropped `flip` option for dropdown plugin in favor of native Popper configuration. You can now disable the flipping behavior by passing an empty array for [`fallbackPlacements`](https://popper.js.org/docs/v2/modifiers/flip/#fallbackplacements) option in [flip](https://popper.js.org/docs/v2/modifiers/flip/) modifier. - <span class="badge bg-danger">Breaking</span> Dropped `flip` option for dropdown plugin in favor of native Popper configuration. You can now disable the flipping behavior by passing an empty array for [`fallbackPlacements`](https://popper.js.org/docs/v2/modifiers/flip/#fallbackplacements) option in [flip](https://popper.js.org/docs/v2/modifiers/flip/) modifier.
+15
View File
@@ -363,7 +363,10 @@ New utilities can be added to the default `$utilities` map with a `map-merge`. M
```scss ```scss
@import "bootstrap/scss/functions"; @import "bootstrap/scss/functions";
@import "bootstrap/scss/variables"; @import "bootstrap/scss/variables";
@import "bootstrap/scss/maps";
@import "bootstrap/scss/mixins";
@import "bootstrap/scss/utilities"; @import "bootstrap/scss/utilities";
@import "bootstrap/scss/utilities/api";
$utilities: map-merge( $utilities: map-merge(
$utilities, $utilities,
@@ -385,7 +388,10 @@ Modify existing utilities in the default `$utilities` map with `map-get` and `ma
```scss ```scss
@import "bootstrap/scss/functions"; @import "bootstrap/scss/functions";
@import "bootstrap/scss/variables"; @import "bootstrap/scss/variables";
@import "bootstrap/scss/maps";
@import "bootstrap/scss/mixins";
@import "bootstrap/scss/utilities"; @import "bootstrap/scss/utilities";
@import "bootstrap/scss/utilities/api";
$utilities: map-merge( $utilities: map-merge(
$utilities, $utilities,
@@ -410,7 +416,10 @@ You can enable responsive classes for an existing set of utilities that are not
```scss ```scss
@import "bootstrap/scss/functions"; @import "bootstrap/scss/functions";
@import "bootstrap/scss/variables"; @import "bootstrap/scss/variables";
@import "bootstrap/scss/maps";
@import "bootstrap/scss/mixins";
@import "bootstrap/scss/utilities"; @import "bootstrap/scss/utilities";
@import "bootstrap/scss/utilities/api";
$utilities: map-merge( $utilities: map-merge(
$utilities, ( $utilities, (
@@ -461,7 +470,10 @@ Missing v4 utilities, or used to another naming convention? The utilities API ca
```scss ```scss
@import "bootstrap/scss/functions"; @import "bootstrap/scss/functions";
@import "bootstrap/scss/variables"; @import "bootstrap/scss/variables";
@import "bootstrap/scss/maps";
@import "bootstrap/scss/mixins";
@import "bootstrap/scss/utilities"; @import "bootstrap/scss/utilities";
@import "bootstrap/scss/utilities/api";
$utilities: map-merge( $utilities: map-merge(
$utilities, ( $utilities, (
@@ -480,7 +492,10 @@ Remove any of the default utilities by setting the group key to `null`. For exam
```scss ```scss
@import "bootstrap/scss/functions"; @import "bootstrap/scss/functions";
@import "bootstrap/scss/variables"; @import "bootstrap/scss/variables";
@import "bootstrap/scss/maps";
@import "bootstrap/scss/mixins";
@import "bootstrap/scss/utilities"; @import "bootstrap/scss/utilities";
@import "bootstrap/scss/utilities/api";
$utilities: map-merge( $utilities: map-merge(
$utilities, $utilities,

Some files were not shown because too many files have changed in this diff Show More